diff --git a/BuildTools/update-assemblyinfo.ps1 b/BuildTools/update-assemblyinfo.ps1 index b07199c82..f4096cb74 100644 --- a/BuildTools/update-assemblyinfo.ps1 +++ b/BuildTools/update-assemblyinfo.ps1 @@ -69,6 +69,17 @@ function gitCommitHash() { } } +function gitShortCommitHash() { + if (No-Git) { + return "00000000"; + } + try { + return (git rev-parse --short=8 (git rev-list --max-count 1 HEAD)); + } catch { + return "00000000"; + } +} + function gitBranch() { if (No-Git) { return "no-branch"; @@ -117,6 +128,7 @@ try { $revision = gitVersion; $branchName = gitBranch; $gitCommitHash = gitCommitHash; + $gitShortCommitHash = gitShortCommitHash; if ($branchName -match $masterBranches) { $postfixBranchName = ""; @@ -150,7 +162,7 @@ try { $out = $out.Replace('$INSERTMAJORVERSION$', $major); $out = $out.Replace('$INSERTREVISION$', $revision); $out = $out.Replace('$INSERTCOMMITHASH$', $gitCommitHash); - $out = $out.Replace('$INSERTSHORTCOMMITHASH$', $gitCommitHash.Substring(0, 8)); + $out = $out.Replace('$INSERTSHORTCOMMITHASH$', $gitShortCommitHash); $out = $out.Replace('$INSERTDATE$', [System.DateTime]::Now.ToString("MM/dd/yyyy")); $out = $out.Replace('$INSERTYEAR$', [System.DateTime]::Now.Year.ToString()); $out = $out.Replace('$INSERTBRANCHNAME$', $branchName); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/VariableNaming.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/VariableNaming.cs index 497a36c06..3b5bc08ec 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/VariableNaming.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/VariableNaming.cs @@ -88,5 +88,80 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } #endif } + + private static void NestedForLoopTest2() + { + for (int i = 0; i < 10; i++) + { + Nop(i); + } +#if EXPECTED_OUTPUT && !(LEGACY_CSC && !OPT) + for (int j = 0; j < 10; j++) + { + Nop(j); + } + + for (int k = 0; k < 10; k++) + { + Nop(k); + } + + for (int l = 0; l < 10; l++) + { + Nop(l); + } + + for (int m = 0; m < 10; m++) + { + for (int n = 0; n < 10; n++) + { + Nop(n); + } + } + + for (int num = 0; num < 10; num++) + { + for (int num2 = 0; num2 < 10; num2++) + { + Nop(num2); + } + } +#else + for (int i = 0; i < 10; i++) + { + Nop(i); + } + + for (int i = 0; i < 10; i++) + { + Nop(i); + } + + for (int i = 0; i < 10; i++) + { + Nop(i); + } + + for (int i = 0; i < 10; i++) + { + for (int j = 0; j < 10; j++) + { + Nop(j); + } + } + + for (int i = 0; i < 10; i++) + { + for (int j = 0; j < 10; j++) + { + Nop(j); + } + } +#endif + } + + private static void Nop(int v) + { + } } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index ecc219400..c30339837 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -289,7 +289,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms string nameWithoutNumber = SplitName(newName, out int newIndex); if (reservedVariableNames.TryGetValue(nameWithoutNumber, out int lastUsedIndex)) { - // name without number was already used if (v.Type.IsKnownType(KnownTypeCode.Int32) && loopCounters.Contains(v)) { // special case for loop counters, @@ -298,20 +297,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms nameWithoutNumber = newName; newIndex = 1; } + } + if (reservedVariableNames.TryGetValue(nameWithoutNumber, out lastUsedIndex)) + { + // name without number was already used + if (newIndex > lastUsedIndex) + { + // new index is larger than last, so we can use it + } else { - if (newIndex > lastUsedIndex) - { - // new index is larger than last, so we can use it - } - else - { - // new index is smaller or equal, so we use the next value - newIndex = lastUsedIndex + 1; - } - // resolve conflicts by appending the index to the new name: - newName = nameWithoutNumber + newIndex.ToString(); + // new index is smaller or equal, so we use the next value + newIndex = lastUsedIndex + 1; } + // resolve conflicts by appending the index to the new name: + newName = nameWithoutNumber + newIndex.ToString(); } // update the last used index reservedVariableNames[nameWithoutNumber] = newIndex; diff --git a/ICSharpCode.Decompiler/Properties/AssemblyInfo.cs b/ICSharpCode.Decompiler/Properties/AssemblyInfo.cs index 3f6d671f7..aeac8ac43 100644 --- a/ICSharpCode.Decompiler/Properties/AssemblyInfo.cs +++ b/ICSharpCode.Decompiler/Properties/AssemblyInfo.cs @@ -15,7 +15,7 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] [assembly: AssemblyVersion(DecompilerVersionInfo.Major + "." + DecompilerVersionInfo.Minor + "." + DecompilerVersionInfo.Build + "." + DecompilerVersionInfo.Revision)] -[assembly: AssemblyInformationalVersion(DecompilerVersionInfo.FullVersionWithShortCommitHash)] +[assembly: AssemblyInformationalVersion(DecompilerVersionInfo.FullVersionWithCommitHash)] [assembly: InternalsVisibleTo("ICSharpCode.Decompiler.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001004dcf3979c4e902efa4dd2163a039701ed5822e6f1134d77737296abbb97bf0803083cfb2117b4f5446a217782f5c7c634f9fe1fc60b4c11d62c5b3d33545036706296d31903ddcf750875db38a8ac379512f51620bb948c94d0831125fbc5fe63707cbb93f48c1459c4d1749eb7ac5e681a2f0d6d7c60fa527a3c0b8f92b02bf")] diff --git a/ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs b/ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs index ee12ad9b8..6d29f56fb 100644 --- a/ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs +++ b/ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs @@ -7,5 +7,6 @@ public const string VersionName = "preview2"; public const string FullVersion = Major + "." + Minor + "." + Build + ".$INSERTREVISION$$INSERTBRANCHPOSTFIX$$INSERTVERSIONNAMEPOSTFIX$"; - public const string FullVersionWithShortCommitHash = FullVersion + "-$INSERTSHORTCOMMITHASH$"; + public const string FullVersionWithShortCommitHash = FullVersion + "+$INSERTSHORTCOMMITHASH$"; + public const string FullVersionWithCommitHash = FullVersion + "+$INSERTCOMMITHASH$"; } diff --git a/ICSharpCode.ILSpyCmd/Properties/AssemblyInfo.cs b/ICSharpCode.ILSpyCmd/Properties/AssemblyInfo.cs index f89527a81..0a60d6bf6 100644 --- a/ICSharpCode.ILSpyCmd/Properties/AssemblyInfo.cs +++ b/ICSharpCode.ILSpyCmd/Properties/AssemblyInfo.cs @@ -1,12 +1,8 @@ #region Using directives -using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Resources; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Versioning; #endregion @@ -15,7 +11,7 @@ using System.Runtime.Versioning; [assembly: ComVisible(false)] [assembly: AssemblyVersion(DecompilerVersionInfo.Major + "." + DecompilerVersionInfo.Minor + "." + DecompilerVersionInfo.Build + "." + DecompilerVersionInfo.Revision)] -[assembly: AssemblyInformationalVersion(DecompilerVersionInfo.FullVersionWithShortCommitHash)] +[assembly: AssemblyInformationalVersion(DecompilerVersionInfo.FullVersionWithCommitHash)] [assembly: SuppressMessage("Microsoft.Usage", "CA2243:AttributeStringLiteralsShouldParseCorrectly", Justification = "AssemblyInformationalVersion does not need to be a parsable version")] diff --git a/ICSharpCode.ILSpyX/AssemblyList.cs b/ICSharpCode.ILSpyX/AssemblyList.cs index b52e79117..190f45e52 100644 --- a/ICSharpCode.ILSpyX/AssemblyList.cs +++ b/ICSharpCode.ILSpyX/AssemblyList.cs @@ -186,7 +186,7 @@ namespace ICSharpCode.ILSpyX get { return listName; } } - internal void Move(LoadedAssembly[] assembliesToMove, int index) + public void Move(LoadedAssembly[] assembliesToMove, int index) { VerifyAccess(); lock (lockObj) @@ -230,7 +230,7 @@ namespace ICSharpCode.ILSpyX } } - internal void RefreshSave() + public void RefreshSave() { // Whenever the assembly list is modified, mark it as dirty // and enqueue a task that saves it once the UI has finished modifying the assembly list. @@ -281,8 +281,9 @@ namespace ICSharpCode.ILSpyX { file = Path.GetFullPath(file); return OpenAssembly(file, () => { - var newAsm = new LoadedAssembly(this, file, fileLoaders: manager?.LoaderRegistry, applyWinRTProjections: ApplyWinRTProjections, useDebugSymbols: UseDebugSymbols); - newAsm.IsAutoLoaded = isAutoLoaded; + var newAsm = new LoadedAssembly(this, file, fileLoaders: manager?.LoaderRegistry, applyWinRTProjections: ApplyWinRTProjections, useDebugSymbols: UseDebugSymbols) { + IsAutoLoaded = isAutoLoaded + }; return newAsm; }); } diff --git a/ICSharpCode.ILSpyX/AssemblyListManager.cs b/ICSharpCode.ILSpyX/AssemblyListManager.cs index 8a886df5f..35e1021ce 100644 --- a/ICSharpCode.ILSpyX/AssemblyListManager.cs +++ b/ICSharpCode.ILSpyX/AssemblyListManager.cs @@ -39,7 +39,7 @@ namespace ICSharpCode.ILSpyX public const string DotNet35List = ".NET 3.5"; public const string ASPDotNetMVC3List = "ASP.NET (MVC3)"; - private ISettingsProvider settingsProvider; + private readonly ISettingsProvider settingsProvider; public AssemblyListManager(ISettingsProvider settingsProvider) { @@ -59,9 +59,9 @@ namespace ICSharpCode.ILSpyX public bool UseDebugSymbols { get; set; } - public ObservableCollection<string> AssemblyLists { get; } = new ObservableCollection<string>(); + public ObservableCollection<string> AssemblyLists { get; } = []; - public FileLoaderRegistry LoaderRegistry { get; } = new FileLoaderRegistry(); + public FileLoaderRegistry LoaderRegistry { get; } = new(); /// <summary> /// Loads an assembly list from the ILSpySettings. @@ -69,14 +69,13 @@ namespace ICSharpCode.ILSpyX /// </summary> public AssemblyList LoadList(string listName) { - this.settingsProvider = this.settingsProvider.Load(); AssemblyList list = DoLoadList(listName); if (!AssemblyLists.Contains(list.ListName)) AssemblyLists.Add(list.ListName); return list; } - AssemblyList DoLoadList(string listName) + AssemblyList DoLoadList(string? listName) { XElement doc = this.settingsProvider["AssemblyLists"]; if (listName != null) diff --git a/ICSharpCode.ILSpyX/Extensions/CollectionExtensions.cs b/ICSharpCode.ILSpyX/Extensions/CollectionExtensions.cs index 772c46476..1d4f359e1 100644 --- a/ICSharpCode.ILSpyX/Extensions/CollectionExtensions.cs +++ b/ICSharpCode.ILSpyX/Extensions/CollectionExtensions.cs @@ -27,7 +27,7 @@ namespace ICSharpCode.ILSpyX.Extensions { public static class CollectionExtensions { - public static void AddRange<T>(this ICollection<T> list, IEnumerable<T> items) + internal static void AddRange<T>(this ICollection<T> list, IEnumerable<T> items) { foreach (T item in items) if (!list.Contains(item)) diff --git a/ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj b/ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj index 41e03236d..e0614d694 100644 --- a/ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj +++ b/ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj @@ -40,7 +40,6 @@ </PropertyGroup> <ItemGroup> - <InternalsVisibleTo Include="ILSpy" Key="00240000048000009400000006020000002400005253413100040000010001004dcf3979c4e902efa4dd2163a039701ed5822e6f1134d77737296abbb97bf0803083cfb2117b4f5446a217782f5c7c634f9fe1fc60b4c11d62c5b3d33545036706296d31903ddcf750875db38a8ac379512f51620bb948c94d0831125fbc5fe63707cbb93f48c1459c4d1749eb7ac5e681a2f0d6d7c60fa527a3c0b8f92b02bf" /> <InternalsVisibleTo Include="ILSpy.Tests" Key="00240000048000009400000006020000002400005253413100040000010001004dcf3979c4e902efa4dd2163a039701ed5822e6f1134d77737296abbb97bf0803083cfb2117b4f5446a217782f5c7c634f9fe1fc60b4c11d62c5b3d33545036706296d31903ddcf750875db38a8ac379512f51620bb948c94d0831125fbc5fe63707cbb93f48c1459c4d1749eb7ac5e681a2f0d6d7c60fa527a3c0b8f92b02bf" /> </ItemGroup> diff --git a/ICSharpCode.ILSpyX/LoadedPackage.cs b/ICSharpCode.ILSpyX/LoadedPackage.cs index 47e80b962..78f80b349 100644 --- a/ICSharpCode.ILSpyX/LoadedPackage.cs +++ b/ICSharpCode.ILSpyX/LoadedPackage.cs @@ -51,14 +51,14 @@ namespace ICSharpCode.ILSpyX public PackageKind Kind { get; } - internal SingleFileBundle.Header BundleHeader { get; set; } + public SingleFileBundle.Header BundleHeader { get; set; } /// <summary> /// List of all entries, including those in sub-directories within the package. /// </summary> public IReadOnlyList<PackageEntry> Entries { get; } - internal PackageFolder RootFolder { get; } + public PackageFolder RootFolder { get; } public LoadedPackage(PackageKind kind, IEnumerable<PackageEntry> entries) { @@ -256,7 +256,7 @@ namespace ICSharpCode.ILSpyX public abstract string FullName { get; } } - sealed class PackageFolder : IAssemblyResolver + public sealed class PackageFolder : IAssemblyResolver { /// <summary> /// Gets the short name of the folder. @@ -326,7 +326,7 @@ namespace ICSharpCode.ILSpyX readonly Dictionary<string, LoadedAssembly?> assemblies = new Dictionary<string, LoadedAssembly?>(StringComparer.OrdinalIgnoreCase); - internal LoadedAssembly? ResolveFileName(string name) + public LoadedAssembly? ResolveFileName(string name) { if (package.LoadedAssembly == null) return null; diff --git a/ICSharpCode.ILSpyX/PdbProvider/PortableDebugInfoProvider.cs b/ICSharpCode.ILSpyX/PdbProvider/PortableDebugInfoProvider.cs index ec4cb8faf..a5432f47c 100644 --- a/ICSharpCode.ILSpyX/PdbProvider/PortableDebugInfoProvider.cs +++ b/ICSharpCode.ILSpyX/PdbProvider/PortableDebugInfoProvider.cs @@ -32,7 +32,7 @@ using static ICSharpCode.Decompiler.Metadata.MetadataFile; namespace ICSharpCode.ILSpyX.PdbProvider { - class PortableDebugInfoProvider : IDebugInfoProvider + public class PortableDebugInfoProvider : IDebugInfoProvider { string? pdbFileName; string moduleFileName; @@ -40,7 +40,7 @@ namespace ICSharpCode.ILSpyX.PdbProvider MetadataReaderOptions options; bool hasError; - internal bool IsEmbedded => pdbFileName == null; + public bool IsEmbedded => pdbFileName == null; public PortableDebugInfoProvider(string moduleFileName, MetadataReaderProvider provider, MetadataReaderOptions options = MetadataReaderOptions.Default, @@ -69,7 +69,7 @@ namespace ICSharpCode.ILSpyX.PdbProvider } } - internal MetadataReader? GetMetadataReader() + public MetadataReader? GetMetadataReader() { try { diff --git a/ICSharpCode.ILSpyX/Properties/AssemblyInfo.cs b/ICSharpCode.ILSpyX/Properties/AssemblyInfo.cs index 34d6f6fa1..f4c52a6e3 100644 --- a/ICSharpCode.ILSpyX/Properties/AssemblyInfo.cs +++ b/ICSharpCode.ILSpyX/Properties/AssemblyInfo.cs @@ -1,12 +1,8 @@ #region Using directives -using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Resources; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Versioning; #endregion @@ -15,7 +11,7 @@ using System.Runtime.Versioning; [assembly: ComVisible(false)] [assembly: AssemblyVersion(DecompilerVersionInfo.Major + "." + DecompilerVersionInfo.Minor + "." + DecompilerVersionInfo.Build + "." + DecompilerVersionInfo.Revision)] -[assembly: AssemblyInformationalVersion(DecompilerVersionInfo.FullVersionWithShortCommitHash)] +[assembly: AssemblyInformationalVersion(DecompilerVersionInfo.FullVersionWithCommitHash)] [assembly: SuppressMessage("Microsoft.Usage", "CA2243:AttributeStringLiteralsShouldParseCorrectly", Justification = "AssemblyInformationalVersion does not need to be a parsable version")] diff --git a/ICSharpCode.ILSpyX/Settings/ILSpySettings.cs b/ICSharpCode.ILSpyX/Settings/ILSpySettings.cs index 63e94299b..d19998587 100644 --- a/ICSharpCode.ILSpyX/Settings/ILSpySettings.cs +++ b/ICSharpCode.ILSpyX/Settings/ILSpySettings.cs @@ -34,16 +34,11 @@ namespace ICSharpCode.ILSpyX.Settings /// </summary> public static ISettingsFilePathProvider? SettingsFilePathProvider { get; set; } - readonly XElement root; + XElement root; - ILSpySettings() + ILSpySettings(XElement? root = null) { - this.root = new XElement("ILSpy"); - } - - ILSpySettings(XElement root) - { - this.root = root; + this.root = root ?? new XElement("ILSpy"); } public XElement this[XName section] { @@ -64,13 +59,7 @@ namespace ICSharpCode.ILSpyX.Settings { try { - XDocument doc = LoadWithoutCheckingCharacters(GetConfigFile()); - if (null == doc.Root) - { - return new ILSpySettings(); - } - - return new ILSpySettings(doc.Root); + return new ILSpySettings(LoadFile(GetConfigFile()).Root); } catch (IOException) { @@ -83,7 +72,7 @@ namespace ICSharpCode.ILSpyX.Settings } } - static XDocument LoadWithoutCheckingCharacters(string fileName) + static XDocument LoadFile(string fileName) { return XDocument.Load(fileName, LoadOptions.None); } @@ -91,16 +80,15 @@ namespace ICSharpCode.ILSpyX.Settings /// <summary> /// Saves a setting section. /// </summary> - public static void SaveSettings(XElement section) + public void SaveSettings(XElement section) { - Update( - delegate (XElement root) { - XElement? existingElement = root.Element(section.Name); - if (existingElement != null) - existingElement.ReplaceWith(section); - else - root.Add(section); - }); + Update(rootElement => { + XElement? existingElement = rootElement.Element(section.Name); + if (existingElement != null) + existingElement.ReplaceWith(section); + else + rootElement.Add(section); + }); } /// <summary> @@ -108,7 +96,7 @@ namespace ICSharpCode.ILSpyX.Settings /// We always reload the file on updates to ensure we aren't overwriting unrelated changes performed /// by another ILSpy instance. /// </summary> - public static void Update(Action<XElement> action) + public void Update(Action<XElement> action) { using (new MutexProtector(ConfigFileMutex)) { @@ -116,7 +104,7 @@ namespace ICSharpCode.ILSpyX.Settings XDocument doc; try { - doc = LoadWithoutCheckingCharacters(config); + doc = LoadFile(config); } catch (IOException) { @@ -131,14 +119,10 @@ namespace ICSharpCode.ILSpyX.Settings doc.Root!.SetAttributeValue("version", DecompilerVersionInfo.Major + "." + DecompilerVersionInfo.Minor + "." + DecompilerVersionInfo.Build + "." + DecompilerVersionInfo.Revision); action(doc.Root); doc.Save(config, SaveOptions.None); + this.root = doc.Root; } } - void ISettingsProvider.Update(Action<XElement> action) - { - Update(action); - } - static string GetConfigFile() { if (null != SettingsFilePathProvider) @@ -148,11 +132,6 @@ namespace ICSharpCode.ILSpyX.Settings // return "ILSpy.xml"; } - ISettingsProvider ISettingsProvider.Load() - { - return Load(); - } - const string ConfigFileMutex = "01A91708-49D1-410D-B8EB-4DE2662B3971"; /// <summary> @@ -164,17 +143,16 @@ namespace ICSharpCode.ILSpyX.Settings public MutexProtector(string name) { - bool createdNew; - this.mutex = new Mutex(true, name, out createdNew); - if (!createdNew) + this.mutex = new Mutex(true, name, out bool createdNew); + if (createdNew) + return; + + try + { + mutex.WaitOne(); + } + catch (AbandonedMutexException) { - try - { - mutex.WaitOne(); - } - catch (AbandonedMutexException) - { - } } } diff --git a/ICSharpCode.ILSpyX/Settings/IMiscSettings.cs b/ICSharpCode.ILSpyX/Settings/IMiscSettings.cs deleted file mode 100644 index 363dba66b..000000000 --- a/ICSharpCode.ILSpyX/Settings/IMiscSettings.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2022 AlphaSierraPapa for the SharpDevelop Team -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this -// software and associated documentation files (the "Software"), to deal in the Software -// without restriction, including without limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -// to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Xml.Linq; - -namespace ICSharpCode.ILSpyX.Settings -{ - public interface IMiscSettings - { - public bool AllowMultipleInstances { get; set; } - public bool LoadPreviousAssemblies { get; set; } - - public static void Save(XElement root, IMiscSettings miscSettings) - { - var section = new XElement("MiscSettings"); - section.SetAttributeValue(nameof(miscSettings.AllowMultipleInstances), miscSettings.AllowMultipleInstances); - section.SetAttributeValue(nameof(miscSettings.LoadPreviousAssemblies), miscSettings.LoadPreviousAssemblies); - - XElement? existingElement = root.Element("MiscSettings"); - if (existingElement != null) - existingElement.ReplaceWith(section); - else - root.Add(section); - } - } -} diff --git a/ICSharpCode.ILSpyX/Settings/ISettingsFilePathProvider.cs b/ICSharpCode.ILSpyX/Settings/ISettingsFilePathProvider.cs index 15b16a09b..5e3ffba9b 100644 --- a/ICSharpCode.ILSpyX/Settings/ISettingsFilePathProvider.cs +++ b/ICSharpCode.ILSpyX/Settings/ISettingsFilePathProvider.cs @@ -16,8 +16,6 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; - namespace ICSharpCode.ILSpyX.Settings { public interface ISettingsFilePathProvider diff --git a/ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs b/ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs index 3f62dba76..1ddcec131 100644 --- a/ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs +++ b/ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs @@ -17,13 +17,8 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.ComponentModel; -using System.Linq; -using System.Reflection; using System.Xml.Linq; -using static System.Collections.Specialized.BitVector32; - namespace ICSharpCode.ILSpyX.Settings { public interface ISettingsProvider @@ -31,39 +26,7 @@ namespace ICSharpCode.ILSpyX.Settings XElement this[XName section] { get; } void Update(Action<XElement> action); - ISettingsProvider Load(); - - public static ICSharpCode.Decompiler.DecompilerSettings LoadDecompilerSettings(ISettingsProvider settingsProvider) - { - XElement e = settingsProvider["DecompilerSettings"]; - var newSettings = new Decompiler.DecompilerSettings(); - var properties = typeof(Decompiler.DecompilerSettings).GetProperties() - .Where(p => p.GetCustomAttribute<BrowsableAttribute>()?.Browsable != false); - foreach (var p in properties) - { - var value = (bool?)e.Attribute(p.Name); - if (value.HasValue) - p.SetValue(newSettings, value.Value); - } - return newSettings; - } - - public static void SaveDecompilerSettings(XElement root, ICSharpCode.Decompiler.DecompilerSettings newSettings) - { - var properties = typeof(Decompiler.DecompilerSettings).GetProperties() - .Where(p => p.GetCustomAttribute<BrowsableAttribute>()?.Browsable != false); - - XElement section = new XElement("DecompilerSettings"); - foreach (var p in properties) - { - section.SetAttributeValue(p.Name, p.GetValue(newSettings)); - } - XElement? existingElement = root.Element("DecompilerSettings"); - if (existingElement != null) - existingElement.ReplaceWith(section); - else - root.Add(section); - } + void SaveSettings(XElement section); } } diff --git a/ICSharpCode.ILSpyX/Settings/ISettingsSection.cs b/ICSharpCode.ILSpyX/Settings/ISettingsSection.cs deleted file mode 100644 index 1735289ef..000000000 --- a/ICSharpCode.ILSpyX/Settings/ISettingsSection.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2022 AlphaSierraPapa for the SharpDevelop Team -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this -// software and associated documentation files (the "Software"), to deal in the Software -// without restriction, including without limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -// to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; - -namespace ICSharpCode.ILSpyX.Settings -{ - public interface ISettingsSection<TSelf> - { - // This should be abstract, but that needs C# 11.0 (see IParseable<TSelf>) - // Keep it to be enabled in the future - public static TSelf Load(ISettingsProvider settingsProvider) - { - throw new NotImplementedException(); - } - } -} diff --git a/ICSharpCode.ILSpyX/TreeView/SharpTreeNode.cs b/ICSharpCode.ILSpyX/TreeView/SharpTreeNode.cs index e34675fc3..5f8e1cc31 100644 --- a/ICSharpCode.ILSpyX/TreeView/SharpTreeNode.cs +++ b/ICSharpCode.ILSpyX/TreeView/SharpTreeNode.cs @@ -182,7 +182,7 @@ namespace ICSharpCode.ILSpyX.TreeView #endregion #region OnChildrenChanged - protected internal virtual void OnChildrenChanged(NotifyCollectionChangedEventArgs e) + public virtual void OnChildrenChanged(NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { @@ -359,7 +359,7 @@ namespace ICSharpCode.ILSpyX.TreeView return TreeTraversal.PreOrder(this.Children.Where(c => c.isVisible), n => n.Children.Where(c => c.isVisible)); } - internal IEnumerable<SharpTreeNode> VisibleDescendantsAndSelf() + public IEnumerable<SharpTreeNode> VisibleDescendantsAndSelf() { return TreeTraversal.PreOrder(this, n => n.Children.Where(c => c.isVisible)); } @@ -637,7 +637,7 @@ namespace ICSharpCode.ILSpyX.TreeView return false; } - internal void InternalDrop(IPlatformDragEventArgs e, int index) + public void InternalDrop(IPlatformDragEventArgs e, int index) { if (LazyLoading) { @@ -658,8 +658,9 @@ namespace ICSharpCode.ILSpyX.TreeView public bool IsLast { get { - return Parent == null || - Parent.Children[Parent.Children.Count - 1] == this; + return Parent == null + || Parent.Children.Count == 0 + || Parent.Children[^1] == this; } } diff --git a/ICSharpCode.ILSpyX/TreeView/TreeFlattener.cs b/ICSharpCode.ILSpyX/TreeView/TreeFlattener.cs index 46cc5cafd..80803c194 100644 --- a/ICSharpCode.ILSpyX/TreeView/TreeFlattener.cs +++ b/ICSharpCode.ILSpyX/TreeView/TreeFlattener.cs @@ -26,7 +26,7 @@ using System.Diagnostics; namespace ICSharpCode.ILSpyX.TreeView { - sealed class TreeFlattener : IList, INotifyCollectionChanged + public sealed class TreeFlattener : IList, INotifyCollectionChanged { /// <summary> /// The root node of the flat list tree. diff --git a/ILSpy.ReadyToRun/ReadyToRunDisassembler.cs b/ILSpy.ReadyToRun/ReadyToRunDisassembler.cs index 564922d1b..7f4fd0817 100644 --- a/ILSpy.ReadyToRun/ReadyToRunDisassembler.cs +++ b/ILSpy.ReadyToRun/ReadyToRunDisassembler.cs @@ -26,6 +26,7 @@ using Iced.Intel; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.ILSpy.Util; using ILCompiler.Reflection.ReadyToRun; using ILCompiler.Reflection.ReadyToRun.Amd64; @@ -50,11 +51,13 @@ namespace ICSharpCode.ILSpy.ReadyToRun ReadyToRunMethod readyToRunMethod = runtimeFunction.Method; WriteCommentLine(readyToRunMethod.SignatureString); - if (ReadyToRunOptions.GetIsShowGCInfo(null)) + var options = SettingsService.Instance.GetSettings<ReadyToRunOptions>(); + + if (options.IsShowGCInfo) { if (readyToRunMethod.GcInfo != null) { - string[] lines = readyToRunMethod.GcInfo.ToString().Split(Environment.NewLine); + string[] lines = readyToRunMethod.GcInfo.ToString()?.Split(Environment.NewLine) ?? []; WriteCommentLine("GC info:"); foreach (string line in lines) { @@ -68,12 +71,12 @@ namespace ICSharpCode.ILSpy.ReadyToRun } Dictionary<ulong, UnwindCode>? unwindInfo = null; - if (ReadyToRunOptions.GetIsShowUnwindInfo(null) && bitness == 64) + if (options.IsShowUnwindInfo && bitness == 64) { unwindInfo = WriteUnwindInfo(); } - bool isShowDebugInfo = ReadyToRunOptions.GetIsShowDebugInfo(null); + bool isShowDebugInfo = options.IsShowDebugInfo; DebugInfoHelper? debugInfo = null; if (isShowDebugInfo) { @@ -97,7 +100,7 @@ namespace ICSharpCode.ILSpy.ReadyToRun decoder.Decode(out instructions.AllocUninitializedElement()); } - string disassemblyFormat = ReadyToRunOptions.GetDisassemblyFormat(null); + string disassemblyFormat = options.DisassemblyFormat; Formatter? formatter = null; if (disassemblyFormat.Equals(ReadyToRunOptions.intel)) { @@ -144,7 +147,7 @@ namespace ICSharpCode.ILSpy.ReadyToRun } } } - if (ReadyToRunOptions.GetIsShowGCInfo(null)) + if (options.IsShowGCInfo) { DecorateGCInfo(instr, baseInstrIP, readyToRunMethod.GcInfo); } diff --git a/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml b/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml index 137e0a6a1..388923678 100644 --- a/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml +++ b/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml @@ -1,7 +1,11 @@ <UserControl x:Class="ICSharpCode.ILSpy.ReadyToRun.ReadyToRunOptionPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:properties="clr-namespace:ILSpy.ReadyToRun.Properties" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d:DesignHeight="500" d:DesignWidth="500" mc:Ignorable="d" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:readyToRun="clr-namespace:ICSharpCode.ILSpy.ReadyToRun" + d:DataContext="{d:DesignInstance readyToRun:ReadyToRunOptionsViewModel}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> @@ -14,12 +18,12 @@ <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBlock Margin="3" Text="{x:Static properties:Resources.DisassemblyFormat}" /> - <ComboBox Grid.Column="1" Margin="3" ItemsSource="{Binding DisassemblyFormats}" SelectedItem="{Binding DisassemblyFormat}" /> - <TextBlock Grid.Row="1" Margin="3" Text="{x:Static properties:Resources.ShowStackUnwindInfo}"/> - <CheckBox Grid.Row="1" Grid.Column="1" Margin="3" IsChecked="{Binding IsShowUnwindInfo}" /> + <ComboBox Grid.Row="0" Grid.Column="1" Margin="3" ItemsSource="{Binding Options.DisassemblyFormats}" SelectedItem="{Binding Options.DisassemblyFormat}" /> + <TextBlock Grid.Row="1" Grid.Column="0" Margin="3" Text="{x:Static properties:Resources.ShowStackUnwindInfo}"/> + <CheckBox Grid.Row="1" Grid.Column="1" Margin="3" IsChecked="{Binding Options.IsShowUnwindInfo}" /> <TextBlock Grid.Row="2" Margin="3" Text="{x:Static properties:Resources.ShowDebugInfo}"/> - <CheckBox Grid.Row="2" Grid.Column="1" Margin="3" IsChecked="{Binding IsShowDebugInfo}" /> + <CheckBox Grid.Row="2" Grid.Column="1" Margin="3" IsChecked="{Binding Options.IsShowDebugInfo}" /> <TextBlock Grid.Row="3" Margin="3" Text="{x:Static properties:Resources.ShowGCInfo}"/> - <CheckBox Grid.Row="3" Grid.Column="1" Margin="3" IsChecked="{Binding IsShowGCInfo}" /> + <CheckBox Grid.Row="3" Grid.Column="1" Margin="3" IsChecked="{Binding Options.IsShowGCInfo}" /> </Grid> </UserControl> \ No newline at end of file diff --git a/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs b/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs index 8b8ae256e..d48d55ec1 100644 --- a/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs +++ b/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs @@ -16,112 +16,47 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System.ComponentModel; using System.ComponentModel.Composition; -using System.Windows.Controls; -using System.Xml.Linq; using ICSharpCode.ILSpy.Options; -using ICSharpCode.ILSpyX.Settings; +using ICSharpCode.ILSpy.Util; + +using TomsToolbox.Wpf; +using TomsToolbox.Wpf.Composition.Mef; namespace ICSharpCode.ILSpy.ReadyToRun { - [ExportOptionPage(Title = nameof(global::ILSpy.ReadyToRun.Properties.Resources.ReadyToRun), Order = 40)] + [DataTemplate(typeof(ReadyToRunOptionsViewModel))] [PartCreationPolicy(CreationPolicy.NonShared)] - partial class ReadyToRunOptionPage : UserControl, IOptionPage + partial class ReadyToRunOptionPage { public ReadyToRunOptionPage() { InitializeComponent(); } - - public void Load(ILSpySettings settings) - { - Options s = new Options(); - s.DisassemblyFormat = ReadyToRunOptions.GetDisassemblyFormat(settings); - s.IsShowUnwindInfo = ReadyToRunOptions.GetIsShowUnwindInfo(settings); - s.IsShowDebugInfo = ReadyToRunOptions.GetIsShowDebugInfo(settings); - s.IsShowGCInfo = ReadyToRunOptions.GetIsShowGCInfo(settings); - - this.DataContext = s; - } - - public void LoadDefaults() - { - this.DataContext = new Options(); - } - - public void Save(XElement root) - { - Options s = (Options)this.DataContext; - ReadyToRunOptions.SetDisassemblyOptions(root, s.DisassemblyFormat, s.IsShowUnwindInfo, s.IsShowDebugInfo, s.IsShowGCInfo); - } } - internal class Options : INotifyPropertyChanged + [ExportOptionPage(Order = 40)] + [PartCreationPolicy(CreationPolicy.NonShared)] + class ReadyToRunOptionsViewModel : ObservableObject, IOptionPage { - public string[] DisassemblyFormats { - get { - return ReadyToRunOptions.disassemblyFormats; - } - } - - private bool isShowUnwindInfo; - public bool IsShowUnwindInfo { - get { - return isShowUnwindInfo; - } - set { - isShowUnwindInfo = value; - OnPropertyChanged(nameof(IsShowUnwindInfo)); - } - } - - private bool isShowDebugInfo; + private ReadyToRunOptions options; - public bool IsShowDebugInfo { - get { - return isShowDebugInfo; - } - set { - isShowDebugInfo = value; - OnPropertyChanged(nameof(IsShowDebugInfo)); - } + public ReadyToRunOptions Options { + get => options; + set => SetProperty(ref options, value); } - private bool isShowGCInfo; + public string Title => global::ILSpy.ReadyToRun.Properties.Resources.ReadyToRun; - public bool IsShowGCInfo { - get { - return isShowGCInfo; - } - set { - isShowGCInfo = value; - OnPropertyChanged(nameof(IsShowGCInfo)); - } - } - - private string disassemblyFormat; - - public string DisassemblyFormat { - get { return disassemblyFormat; } - set { - if (disassemblyFormat != value) - { - disassemblyFormat = value; - OnPropertyChanged(nameof(DisassemblyFormat)); - } - } + public void Load(SettingsSnapshot snapshot) + { + Options = snapshot.GetSettings<ReadyToRunOptions>(); } - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged(string? propertyName) + public void LoadDefaults() { - if (PropertyChanged != null) - { - PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); - } + Options.LoadFromXml(new("empty")); } } } \ No newline at end of file diff --git a/ILSpy.ReadyToRun/ReadyToRunOptions.cs b/ILSpy.ReadyToRun/ReadyToRunOptions.cs index 5e65b574a..e6b47e69f 100644 --- a/ILSpy.ReadyToRun/ReadyToRunOptions.cs +++ b/ILSpy.ReadyToRun/ReadyToRunOptions.cs @@ -18,110 +18,77 @@ using System.Xml.Linq; -using ICSharpCode.ILSpyX; -using ICSharpCode.ILSpyX.Settings; +using ICSharpCode.ILSpy.Util; + +using TomsToolbox.Wpf; namespace ICSharpCode.ILSpy.ReadyToRun { - internal class ReadyToRunOptions + internal partial class ReadyToRunOptions : ObservableObject, ISettingsSection { private static readonly XNamespace ns = "http://www.ilspy.net/ready-to-run"; internal static string intel = "Intel"; internal static string gas = "AT & T"; - internal static string[] disassemblyFormats = new string[] { intel, gas }; + internal static string[] disassemblyFormats = [intel, gas]; - public static string GetDisassemblyFormat(ILSpySettings? settings) - { - if (settings == null) - { - settings = ILSpySettings.Load(); - } - XElement e = settings[ns + "ReadyToRunOptions"]; - XAttribute? a = e.Attribute("DisassemblyFormat"); - if (a == null) - { - return ReadyToRunOptions.intel; - } - else - { - return (string)a; + public string[] DisassemblyFormats { + get { + return disassemblyFormats; } } - public static bool GetIsShowUnwindInfo(ILSpySettings? settings) - { - if (settings == null) - { - settings = ILSpySettings.Load(); - } - XElement e = settings[ns + "ReadyToRunOptions"]; - XAttribute? a = e.Attribute("IsShowUnwindInfo"); + private bool isShowUnwindInfo; + public bool IsShowUnwindInfo { + get => isShowUnwindInfo; + set => SetProperty(ref isShowUnwindInfo, value); + } - if (a == null) - { - return false; - } - else - { - return (bool)a; - } + private bool isShowDebugInfo; + public bool IsShowDebugInfo { + get => isShowDebugInfo; + set => SetProperty(ref isShowDebugInfo, value); } - public static bool GetIsShowDebugInfo(ILSpySettings? settings) - { - if (settings == null) - { - settings = ILSpySettings.Load(); - } - XElement e = settings[ns + "ReadyToRunOptions"]; - XAttribute? a = e.Attribute("IsShowDebugInfo"); + private bool isShowGCInfo; + public bool IsShowGCInfo { + get => isShowGCInfo; + set => SetProperty(ref isShowGCInfo, value); + } - if (a == null) - { - return true; - } - else - { - return (bool)a; - } + private string disassemblyFormat; + public string DisassemblyFormat { + get => disassemblyFormat; + set => SetProperty(ref disassemblyFormat, value); } - public static bool GetIsShowGCInfo(ILSpySettings? settings) + public XName SectionName { get; } = ns + "ReadyToRunOptions"; + + public void LoadFromXml(XElement e) { - if (settings == null) - { - settings = ILSpySettings.Load(); - } - XElement e = settings[ns + "ReadyToRunOptions"]; - XAttribute? a = e.Attribute("IsShowGCInfo"); + XAttribute? format = e.Attribute("DisassemblyFormat"); + DisassemblyFormat = format == null ? intel : (string)format; - if (a == null) - { - return false; - } - else - { - return (bool)a; - } + XAttribute? unwind = e.Attribute("IsShowUnwindInfo"); + IsShowUnwindInfo = unwind != null && (bool)unwind; + + XAttribute? debug = e.Attribute("IsShowDebugInfo"); + IsShowDebugInfo = debug == null || (bool)debug; + + XAttribute? showGc = e.Attribute("IsShowGCInfo"); + IsShowGCInfo = showGc != null && (bool)showGc; } - public static void SetDisassemblyOptions(XElement root, string disassemblyFormat, bool isShowUnwindInfo, bool isShowDebugInfo, bool isShowGCInfo) + public XElement SaveToXml() { - XElement section = new XElement(ns + "ReadyToRunOptions"); + var section = new XElement(SectionName); + section.SetAttributeValue("DisassemblyFormat", disassemblyFormat); section.SetAttributeValue("IsShowUnwindInfo", isShowUnwindInfo); section.SetAttributeValue("IsShowDebugInfo", isShowDebugInfo); section.SetAttributeValue("IsShowGCInfo", isShowGCInfo); - XElement? existingElement = root.Element(ns + "ReadyToRunOptions"); - if (existingElement != null) - { - existingElement.ReplaceWith(section); - } - else - { - root.Add(section); - } + + return section; } } } diff --git a/ILSpy/AboutPage.cs b/ILSpy/AboutPage.cs index f9f969eda..4bbe5769d 100644 --- a/ILSpy/AboutPage.cs +++ b/ILSpy/AboutPage.cs @@ -42,7 +42,7 @@ namespace ICSharpCode.ILSpy { public override void Execute(object? parameter) { - MainWindow.Instance.NavigateTo( + MainWindow.Instance.AssemblyTreeModel.NavigateTo( new RequestNavigateEventArgs(new Uri("resource://aboutpage"), null), inNewTabPage: true ); @@ -54,7 +54,7 @@ namespace ICSharpCode.ILSpy Title = Resources.About, EnableHyperlinks = true }; - output.WriteLine(Resources.ILSpyVersion + DecompilerVersionInfo.FullVersion); + output.WriteLine(Resources.ILSpyVersion + DecompilerVersionInfo.FullVersionWithCommitHash); string prodVersion = GetDotnetProductVersion(); output.WriteLine(Resources.NETFrameworkVersion + prodVersion); @@ -76,7 +76,7 @@ namespace ICSharpCode.ILSpy CheckBox checkBox = new CheckBox(); checkBox.Margin = new Thickness(4); checkBox.Content = Resources.AutomaticallyCheckUpdatesEveryWeek; - UpdateSettings settings = new UpdateSettings(ILSpySettings.Load()); + UpdateSettings settings = new UpdateSettings(SettingsService.Instance.SpySettings); checkBox.SetBinding(CheckBox.IsCheckedProperty, new Binding("AutomaticUpdateCheckEnabled") { Source = settings }); return new StackPanel { Margin = new Thickness(0, 4, 0, 0), diff --git a/ILSpy/Analyzers/AnalyzeCommand.cs b/ILSpy/Analyzers/AnalyzeCommand.cs index 65e2f3095..f8230fad7 100644 --- a/ILSpy/Analyzers/AnalyzeCommand.cs +++ b/ILSpy/Analyzers/AnalyzeCommand.cs @@ -78,12 +78,12 @@ namespace ICSharpCode.ILSpy.Analyzers public override bool CanExecute(object? parameter) { - return MainWindow.Instance.SelectedNodes.All(n => n is IMemberTreeNode); + return MainWindow.Instance.AssemblyTreeModel.SelectedNodes.All(n => n is IMemberTreeNode); } public override void Execute(object? parameter) { - foreach (var node in MainWindow.Instance.SelectedNodes.OfType<IMemberTreeNode>()) + foreach (var node in MainWindow.Instance.AssemblyTreeModel.SelectedNodes.OfType<IMemberTreeNode>()) { AnalyzerTreeView.Analyze(node.Member); } diff --git a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs index 3dfab8c06..42451c80f 100644 --- a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs +++ b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs @@ -42,7 +42,8 @@ namespace ICSharpCode.ILSpy.Analyzers MessageBox.Show(Properties.Resources.CannotAnalyzeMissingRef, "ILSpy"); return; } - MainWindow.Instance.JumpToReference(new EntityReference(this.Member.ParentModule.MetadataFile, this.Member.MetadataToken)); + + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(this.Member.ParentModule?.MetadataFile, this.Member.MetadataToken))); } public override bool HandleAssemblyListChanged(ICollection<LoadedAssembly> removedAssemblies, ICollection<LoadedAssembly> addedAssemblies) diff --git a/ILSpy/Analyzers/AnalyzerRootNode.cs b/ILSpy/Analyzers/AnalyzerRootNode.cs index c47fbe27c..93d641554 100644 --- a/ILSpy/Analyzers/AnalyzerRootNode.cs +++ b/ILSpy/Analyzers/AnalyzerRootNode.cs @@ -2,7 +2,6 @@ using System.Collections.Specialized; using System.Linq; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.TreeView; diff --git a/ILSpy/Analyzers/AnalyzerSearchTreeNode.cs b/ILSpy/Analyzers/AnalyzerSearchTreeNode.cs index 0a7bbb95c..7c3d484bc 100644 --- a/ILSpy/Analyzers/AnalyzerSearchTreeNode.cs +++ b/ILSpy/Analyzers/AnalyzerSearchTreeNode.cs @@ -61,7 +61,7 @@ namespace ICSharpCode.ILSpy.Analyzers var context = new AnalyzerContext() { CancellationToken = ct, Language = Language, - AssemblyList = MainWindow.Instance.CurrentAssemblyList + AssemblyList = MainWindow.Instance.AssemblyTreeModel.AssemblyList }; var results = analyzer.Analyze(symbol, context).Select(SymbolTreeNodeFactory); if (context.SortResults) diff --git a/ILSpy/Analyzers/AnalyzerTreeNode.cs b/ILSpy/Analyzers/AnalyzerTreeNode.cs index 8972926a7..fb052303e 100644 --- a/ILSpy/Analyzers/AnalyzerTreeNode.cs +++ b/ILSpy/Analyzers/AnalyzerTreeNode.cs @@ -18,7 +18,6 @@ using System.Collections.Generic; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.TreeView; diff --git a/ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs b/ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs index b54f31f2e..ad8395d31 100644 --- a/ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs +++ b/ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs @@ -61,7 +61,7 @@ namespace ICSharpCode.ILSpy.Analyzers.TreeNodes MessageBox.Show(Properties.Resources.CannotAnalyzeMissingRef, "ILSpy"); return; } - MainWindow.Instance.JumpToReference(analyzedModule.MetadataFile); + MessageBus.Send(this, new NavigateToReferenceEventArgs(analyzedModule.MetadataFile)); } public override IEntity? Member => null; diff --git a/ILSpy/App.xaml b/ILSpy/App.xaml index 0cfda48ce..ce2e14086 100644 --- a/ILSpy/App.xaml +++ b/ILSpy/App.xaml @@ -1,15 +1,14 @@ <Application x:Class="ICSharpCode.ILSpy.App" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:styles="urn:TomsToolbox.Wpf.Styles" - xmlns:toms="urn:TomsToolbox" - xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes" - StartupUri="MainWindow.xaml"> + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:styles="urn:TomsToolbox.Wpf.Styles" + xmlns:toms="urn:TomsToolbox" + xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes"> <Application.Resources> <Style x:Key="DialogWindow" TargetType="{x:Type Window}"> - <Setter Property="ShowInTaskbar" Value="False"/> - <Setter Property="UseLayoutRounding" Value="True"/> - <Setter Property="TextOptions.TextFormattingMode" Value="Display"/> + <Setter Property="ShowInTaskbar" Value="False" /> + <Setter Property="UseLayoutRounding" Value="True" /> + <Setter Property="TextOptions.TextFormattingMode" Value="Display" /> <Setter Property="toms:StyleBindings.Behaviors"> <Setter.Value> <toms:BehaviorCollection> diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs index fa45bd234..a5027f08e 100644 --- a/ILSpy/App.xaml.cs +++ b/ILSpy/App.xaml.cs @@ -32,7 +32,7 @@ using System.Windows.Navigation; using System.Windows.Threading; using ICSharpCode.ILSpy.AppEnv; -using ICSharpCode.ILSpy.Options; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpyX.Analyzers; using ICSharpCode.ILSpyX.Settings; using ICSharpCode.ILSpyX.TreeView; @@ -43,6 +43,9 @@ using Microsoft.VisualStudio.Composition; using TomsToolbox.Composition; using TomsToolbox.Wpf.Composition; +using ICSharpCode.ILSpy.Themes; +using System.Globalization; +using System.Threading; using TomsToolbox.Wpf.Styles; namespace ICSharpCode.ILSpy @@ -65,31 +68,31 @@ namespace ICSharpCode.ILSpy public App() { - ILSpySettings.SettingsFilePathProvider = new ILSpySettingsFilePathProvider(); - var cmdArgs = Environment.GetCommandLineArgs().Skip(1); - App.CommandLineArguments = CommandLineArguments.Create(cmdArgs); + CommandLineArguments = CommandLineArguments.Create(cmdArgs); - bool forceSingleInstance = (App.CommandLineArguments.SingleInstance ?? true) - && !MiscSettingsPanel.CurrentMiscSettings.AllowMultipleInstances; + bool forceSingleInstance = (CommandLineArguments.SingleInstance ?? true) + && !SettingsService.Instance.MiscSettings.AllowMultipleInstances; if (forceSingleInstance) { SingleInstance.Attach(); // will auto-exit for second instance SingleInstance.NewInstanceDetected += SingleInstance_NewInstanceDetected; } - SharpTreeNode.SetImagesProvider(new WpfWindowsTreeNodeImagesProvider()); - InitializeComponent(); - Resources.RegisterDefaultStyles(); - - if (!System.Diagnostics.Debugger.IsAttached) + if (!Debugger.IsAttached) { AppDomain.CurrentDomain.UnhandledException += ShowErrorBox; Dispatcher.CurrentDispatcher.UnhandledException += Dispatcher_UnhandledException; } + TaskScheduler.UnobservedTaskException += DotNet40_UnobservedTaskException; + + SharpTreeNode.SetImagesProvider(new WpfWindowsTreeNodeImagesProvider()); + + Resources.RegisterDefaultStyles(); + InitializeMef().GetAwaiter().GetResult(); // Register the export provider so that it can be accessed from WPF/XAML components. @@ -97,30 +100,34 @@ namespace ICSharpCode.ILSpy // Add data templates registered via MEF. Resources.MergedDictionaries.Add(DataTemplateManager.CreateDynamicDataTemplates(ExportProvider)); + var sessionSettings = SettingsService.Instance.SessionSettings; + ThemeManager.Current.Theme = sessionSettings.Theme; + if (!string.IsNullOrEmpty(sessionSettings.CurrentCulture)) + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo(sessionSettings.CurrentCulture); + } + EventManager.RegisterClassHandler(typeof(Window), Hyperlink.RequestNavigateEvent, new RequestNavigateEventHandler(Window_RequestNavigate)); ILSpyTraceListener.Install(); - if (App.CommandLineArguments.ArgumentsParser.IsShowingInformation) + if (CommandLineArguments.ArgumentsParser.IsShowingInformation) { - MessageBox.Show(App.CommandLineArguments.ArgumentsParser.GetHelpText(), "ILSpy Command Line Arguments"); + MessageBox.Show(CommandLineArguments.ArgumentsParser.GetHelpText(), "ILSpy Command Line Arguments"); } - if (App.CommandLineArguments.ArgumentsParser.RemainingArguments.Any()) + if (CommandLineArguments.ArgumentsParser.RemainingArguments.Any()) { - string unknownArguments = string.Join(", ", App.CommandLineArguments.ArgumentsParser.RemainingArguments); + string unknownArguments = string.Join(", ", CommandLineArguments.ArgumentsParser.RemainingArguments); MessageBox.Show(unknownArguments, "ILSpy Unknown Command Line Arguments Passed"); } - } - private static void SingleInstance_NewInstanceDetected(object? sender, NewInstanceEventArgs e) - { -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - ICSharpCode.ILSpy.MainWindow.Instance.HandleSingleInstanceCommandLineArguments(e.Args); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + SettingsService.Instance.AssemblyListManager.CreateDefaultAssemblyLists(); } + private static void SingleInstance_NewInstanceDetected(object? sender, NewInstanceEventArgs e) => ExportProvider.GetExportedValue<AssemblyTreeModel>().HandleSingleInstanceCommandLineArguments(e.Args).HandleExceptions(); + static Assembly? ResolvePluginDependencies(AssemblyLoadContext context, AssemblyName assemblyName) { var rootPath = Path.GetDirectoryName(typeof(App).Assembly.Location); @@ -199,6 +206,8 @@ namespace ICSharpCode.ILSpy protected override void OnStartup(StartupEventArgs e) { + base.OnStartup(e); + var output = new StringBuilder(); if (StartupExceptions.FormatExceptions(output)) @@ -206,7 +215,12 @@ namespace ICSharpCode.ILSpy MessageBox.Show(output.ToString(), "Sorry we crashed!"); Environment.Exit(1); } - base.OnStartup(e); + + MainWindow = new MainWindow(); + MainWindow.Loaded += (sender, args) => { + ExportProvider.GetExportedValue<AssemblyTreeModel>().Initialize(); + }; + MainWindow.Show(); } void DotNet40_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e) @@ -267,7 +281,7 @@ namespace ICSharpCode.ILSpy void Window_RequestNavigate(object sender, RequestNavigateEventArgs e) { - ILSpy.MainWindow.Instance.NavigateTo(e); + ExportProvider.GetExportedValue<AssemblyTreeModel>().NavigateTo(e); } } } \ No newline at end of file diff --git a/ILSpy/AssemblyTree/AssemblyListPane.xaml b/ILSpy/AssemblyTree/AssemblyListPane.xaml new file mode 100644 index 000000000..7222fdabf --- /dev/null +++ b/ILSpy/AssemblyTree/AssemblyListPane.xaml @@ -0,0 +1,54 @@ +<treeView:SharpTreeView x:Class="ICSharpCode.ILSpy.AssemblyTree.AssemblyListPane" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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:treeView="clr-namespace:ICSharpCode.ILSpy.Controls.TreeView" + xmlns:treeNodes="clr-namespace:ICSharpCode.ILSpy.TreeNodes" + xmlns:assemblyTree="clr-namespace:ICSharpCode.ILSpy.AssemblyTree" + xmlns:toms="urn:TomsToolbox" + mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" + d:DataContext="{d:DesignInstance assemblyTree:AssemblyTreeModel}" + AutomationProperties.Name="Assemblies and Classes" + ShowRoot="False" + AllowDropOrder="True" + AllowDrop="True" + BorderThickness="0" Visibility="Visible" + Root="{Binding Root}" + SelectedItem="{Binding SelectedItem, Mode=TwoWay}" + toms:MultiSelectorExtensions.SelectionBinding="{Binding SelectedItems}"> + <treeView:SharpTreeView.ItemContainerStyle> + <Style TargetType="treeView:SharpTreeViewItem"> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type treeView:SharpTreeViewItem}" + d:DataContext="{d:DesignInstance treeNodes:ILSpyTreeNode}"> + <Border Background="Transparent"> + <Border Background="{TemplateBinding Background}"> + <treeView:SharpTreeNodeView x:Name="nodeView" HorizontalAlignment="Left" /> + </Border> + </Border> + <ControlTemplate.Triggers> + <DataTrigger Binding="{Binding IsAutoLoaded}" Value="True"> + <Setter Property="Foreground" Value="SteelBlue" /> + </DataTrigger> + <DataTrigger Binding="{Binding IsPublicAPI}" Value="False"> + <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" /> + </DataTrigger> + <Trigger Property="IsSelected" Value="True"> + <Setter TargetName="nodeView" Property="TextBackground" + Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" /> + <Setter TargetName="nodeView" Property="Foreground" + Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" /> + </Trigger> + <Trigger Property="IsEnabled" Value="False"> + <Setter TargetName="nodeView" Property="Foreground" + Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" /> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + </treeView:SharpTreeView.ItemContainerStyle> +</treeView:SharpTreeView> \ No newline at end of file diff --git a/ILSpy/AssemblyTree/AssemblyListPane.xaml.cs b/ILSpy/AssemblyTree/AssemblyListPane.xaml.cs new file mode 100644 index 000000000..9b6e7fb79 --- /dev/null +++ b/ILSpy/AssemblyTree/AssemblyListPane.xaml.cs @@ -0,0 +1,44 @@ +using System.ComponentModel.Composition; +using System.Windows; + +using ICSharpCode.ILSpyX.TreeView; + +using TomsToolbox.Wpf.Composition.Mef; + +namespace ICSharpCode.ILSpy.AssemblyTree +{ + /// <summary> + /// Interaction logic for AssemblyListPane.xaml + /// </summary> + [DataTemplate(typeof(AssemblyTreeModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class AssemblyListPane + { + public AssemblyListPane() + { + InitializeComponent(); + + ContextMenuProvider.Add(this); + } + + protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) + { + base.OnPropertyChanged(e); + + if (e.Property == DataContextProperty) + { + if (e.NewValue is not AssemblyTreeModel model) + return; + + model.SetActiveView(this); + } + else if (e.Property == SelectedItemProperty) + { + if (e.NewValue is not SharpTreeNode treeNode) + return; + + FocusNode(treeNode); + } + } + } +} diff --git a/ILSpy/AssemblyTree/AssemblyTreeModel.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.cs new file mode 100644 index 000000000..1c6903a55 --- /dev/null +++ b/ILSpy/AssemblyTree/AssemblyTreeModel.cs @@ -0,0 +1,922 @@ +// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System; +using System.Collections.ObjectModel; +using System.ComponentModel.Composition; +using System.IO; +using System.Threading.Tasks; +using System.Windows.Input; +using System.Windows.Threading; + +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.ILSpy.Docking; +using ICSharpCode.ILSpy.Properties; +using ICSharpCode.ILSpy.TextView; +using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.ILSpy.ViewModels; +using ICSharpCode.ILSpyX; +using ICSharpCode.ILSpyX.Settings; +using ICSharpCode.ILSpyX.TreeView; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using ICSharpCode.Decompiler.Metadata; + +using System.Reflection.Metadata.Ecma335; +using System.Windows; +using ICSharpCode.Decompiler.Documentation; +using ICSharpCode.Decompiler.TypeSystem.Implementation; +using System.Reflection.Metadata; +using System.Text; +using System.Windows.Navigation; + +using ICSharpCode.ILSpy.AppEnv; +using ICSharpCode.ILSpy.Search; +using ICSharpCode.Decompiler; + +using TomsToolbox.Essentials; +using TomsToolbox.Wpf; + +namespace ICSharpCode.ILSpy.AssemblyTree +{ + [ExportToolPane] + [PartCreationPolicy(CreationPolicy.Shared)] + [Export] + public class AssemblyTreeModel : ToolPaneModel + { + public const string PaneContentId = "assemblyListPane"; + + AssemblyListPane activeView; + AssemblyListTreeNode assemblyListTreeNode; + + readonly NavigationHistory<NavigationState> history = new(); + private bool isNavigatingHistory; + + public AssemblyTreeModel() + { + Title = Resources.Assemblies; + ContentId = PaneContentId; + IsCloseable = false; + ShortcutKey = new KeyGesture(Key.F6); + + MessageBus<NavigateToReferenceEventArgs>.Subscribers += JumpToReference; + MessageBus<SettingsChangedEventArgs>.Subscribers += (sender, e) => Settings_PropertyChanged(sender, e); + + var selectionChangeThrottle = new DispatcherThrottle(DispatcherPriority.Input, TreeView_SelectionChanged); + SelectedItems.CollectionChanged += (_, _) => selectionChangeThrottle.Tick(); + } + + private void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (sender is SessionSettings sessionSettings) + { + switch (e.PropertyName) + { + case nameof(SessionSettings.ActiveAssemblyList): + ShowAssemblyList(sessionSettings.ActiveAssemblyList); + break; + case nameof(SessionSettings.Theme): + // update syntax highlighting and force reload (AvalonEdit does not automatically refresh on highlighting change) + DecompilerTextView.RegisterHighlighting(); + DecompileSelectedNodes(DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState); + break; + case nameof(SessionSettings.CurrentCulture): + MessageBox.Show(Properties.Resources.SettingsChangeRestartRequired, "ILSpy"); + break; + } + } + else if (sender is LanguageSettings) + { + switch (e.PropertyName) + { + case nameof(LanguageSettings.Language) or nameof(LanguageSettings.LanguageVersion): + DecompileSelectedNodes(); + break; + } + } + } + + public AssemblyList AssemblyList { get; private set; } + + private SharpTreeNode root; + public SharpTreeNode Root { + get => root; + set => SetProperty(ref root, value); + } + + private SharpTreeNode selectedItem; + public SharpTreeNode SelectedItem { + get => selectedItem; + set => SetProperty(ref selectedItem, value); + } + + public ObservableCollection<SharpTreeNode> SelectedItems { get; } = []; + + public string[] SelectedPath => GetPathForNode(SelectedItem); + + readonly List<LoadedAssembly> commandLineLoadedAssemblies = []; + + public bool HandleCommandLineArguments(CommandLineArguments args) + { + LoadAssemblies(args.AssembliesToLoad, commandLineLoadedAssemblies, focusNode: false); + if (args.Language != null) + SettingsService.Instance.SessionSettings.LanguageSettings.Language = Languages.GetLanguage(args.Language); + return true; + } + + /// <summary> + /// Called on startup or when passed arguments via WndProc from a second instance. + /// In the format case, spySettings is non-null; in the latter it is null. + /// </summary> + public void HandleCommandLineArgumentsAfterShowList(CommandLineArguments args, ISettingsProvider spySettings = null) + { + var sessionSettings = SettingsService.Instance.SessionSettings; + + var relevantAssemblies = commandLineLoadedAssemblies.ToList(); + commandLineLoadedAssemblies.Clear(); // clear references once we don't need them anymore + NavigateOnLaunch(args.NavigateTo, sessionSettings.ActiveTreeViewPath, spySettings, relevantAssemblies); + if (args.Search != null) + { + var searchPane = App.ExportProvider.GetExportedValue<SearchPaneModel>(); + + searchPane.SearchTerm = args.Search; + searchPane.Show(); + } + } + + public async Task HandleSingleInstanceCommandLineArguments(string[] args) + { + var cmdArgs = CommandLineArguments.Create(args); + + await Dispatcher.InvokeAsync(() => { + + if (!HandleCommandLineArguments(cmdArgs)) + return; + + var window = Application.Current.MainWindow; + + if (!cmdArgs.NoActivate && window is { WindowState: WindowState.Minimized }) + { + window.WindowState = WindowState.Normal; + } + + HandleCommandLineArgumentsAfterShowList(cmdArgs); + }); + } + + public async void NavigateOnLaunch(string navigateTo, string[] activeTreeViewPath, ISettingsProvider spySettings, List<LoadedAssembly> relevantAssemblies) + { + var initialSelection = SelectedItem; + if (navigateTo != null) + { + bool found = false; + if (navigateTo.StartsWith("N:", StringComparison.Ordinal)) + { + string namespaceName = navigateTo.Substring(2); + foreach (LoadedAssembly asm in relevantAssemblies) + { + AssemblyTreeNode asmNode = assemblyListTreeNode.FindAssemblyNode(asm); + if (asmNode != null) + { + // FindNamespaceNode() blocks the UI if the assembly is not yet loaded, + // so use an async wait instead. + await asm.GetMetadataFileAsync().Catch<Exception>(ex => { }); + NamespaceTreeNode nsNode = asmNode.FindNamespaceNode(namespaceName); + if (nsNode != null) + { + found = true; + if (SelectedItem == initialSelection) + { + SelectNode(nsNode); + } + break; + } + } + } + } + else if (navigateTo == "none") + { + // Don't navigate anywhere; start empty. + // Used by ILSpy VS addin, it'll send us the real location to navigate to via IPC. + found = true; + } + else + { + IEntity mr = await Task.Run(() => FindEntityInRelevantAssemblies(navigateTo, relevantAssemblies)); + // Make sure we wait for assemblies being loaded... + // BeginInvoke in LoadedAssembly.LookupReferencedAssemblyInternal + await Dispatcher.InvokeAsync(delegate { }, DispatcherPriority.Normal); + if (mr != null && mr.ParentModule?.MetadataFile != null) + { + found = true; + if (SelectedItem == initialSelection) + { + await JumpToReferenceAsync(mr); + } + } + } + if (!found && SelectedItem == initialSelection) + { + AvalonEditTextOutput output = new AvalonEditTextOutput(); + output.Write($"Cannot find '{navigateTo}' in command line specified assemblies."); + DockWorkspace.Instance.ShowText(output); + } + } + else if (relevantAssemblies.Count == 1) + { + // NavigateTo == null and an assembly was given on the command-line: + // Select the newly loaded assembly + AssemblyTreeNode asmNode = assemblyListTreeNode.FindAssemblyNode(relevantAssemblies[0]); + if (asmNode != null && SelectedItem == initialSelection) + { + SelectNode(asmNode); + } + } + else if (spySettings != null) + { + SharpTreeNode node = null; + if (activeTreeViewPath?.Length > 0) + { + foreach (var asm in AssemblyList.GetAssemblies()) + { + if (asm.FileName == activeTreeViewPath[0]) + { + // FindNodeByPath() blocks the UI if the assembly is not yet loaded, + // so use an async wait instead. + await asm.GetMetadataFileAsync().Catch<Exception>(ex => { }); + } + } + node = FindNodeByPath(activeTreeViewPath, true); + } + if (SelectedItem == initialSelection) + { + if (node != null) + { + SelectNode(node); + + // only if not showing the about page, perform the update check: + await MainWindow.Instance.ShowMessageIfUpdatesAvailableAsync(spySettings); + } + else + { + DockWorkspace.Instance.ActiveTabPage.ShowTextView(AboutPage.Display); + } + } + } + } + + public static IEntity FindEntityInRelevantAssemblies(string navigateTo, IEnumerable<LoadedAssembly> relevantAssemblies) + { + ITypeReference typeRef; + IMemberReference memberRef = null; + if (navigateTo.StartsWith("T:", StringComparison.Ordinal)) + { + typeRef = IdStringProvider.ParseTypeName(navigateTo); + } + else + { + memberRef = IdStringProvider.ParseMemberIdString(navigateTo); + typeRef = memberRef.DeclaringTypeReference; + } + foreach (LoadedAssembly asm in relevantAssemblies.ToList()) + { + var module = asm.GetMetadataFileOrNull(); + if (CanResolveTypeInPEFile(module, typeRef, out var typeHandle)) + { + ICompilation compilation = typeHandle.Kind == HandleKind.ExportedType + ? new DecompilerTypeSystem(module, module.GetAssemblyResolver()) + : new SimpleCompilation((PEFile)module, MinimalCorlib.Instance); + return memberRef == null + ? typeRef.Resolve(new SimpleTypeResolveContext(compilation)) as ITypeDefinition + : memberRef.Resolve(new SimpleTypeResolveContext(compilation)); + } + } + return null; + } + + static bool CanResolveTypeInPEFile(MetadataFile module, ITypeReference typeRef, out EntityHandle typeHandle) + { + // We intentionally ignore reference assemblies, so that the loop continues looking for another assembly that might have a usable definition. + if (module.IsReferenceAssembly()) + { + typeHandle = default; + return false; + } + + switch (typeRef) + { + case GetPotentiallyNestedClassTypeReference topLevelType: + typeHandle = topLevelType.ResolveInPEFile(module); + return !typeHandle.IsNil; + case NestedTypeReference nestedType: + if (!CanResolveTypeInPEFile(module, nestedType.DeclaringTypeReference, out typeHandle)) + return false; + if (typeHandle.Kind == HandleKind.ExportedType) + return true; + var typeDef = module.Metadata.GetTypeDefinition((TypeDefinitionHandle)typeHandle); + typeHandle = typeDef.GetNestedTypes().FirstOrDefault(t => { + var td = module.Metadata.GetTypeDefinition(t); + var typeName = ReflectionHelper.SplitTypeParameterCountFromReflectionName(module.Metadata.GetString(td.Name), out int typeParameterCount); + return nestedType.AdditionalTypeParameterCount == typeParameterCount && nestedType.Name == typeName; + }); + return !typeHandle.IsNil; + default: + typeHandle = default; + return false; + } + } + + public void Initialize() + { + this.AssemblyList = SettingsService.Instance.LoadInitialAssemblyList(); + + HandleCommandLineArguments(App.CommandLineArguments); + + var loadPreviousAssemblies = SettingsService.Instance.MiscSettings.LoadPreviousAssemblies; + if (AssemblyList.GetAssemblies().Length == 0 + && AssemblyList.ListName == AssemblyListManager.DefaultListName + && loadPreviousAssemblies) + { + LoadInitialAssemblies(); + } + + ShowAssemblyList(this.AssemblyList); + + var sessionSettings = SettingsService.Instance.SessionSettings; + if (sessionSettings.ActiveAutoLoadedAssembly != null + && File.Exists(sessionSettings.ActiveAutoLoadedAssembly)) + { + this.AssemblyList.Open(sessionSettings.ActiveAutoLoadedAssembly, true); + } + + Dispatcher.BeginInvoke(DispatcherPriority.Loaded, OpenAssemblies); + } + + void OpenAssemblies() + { + HandleCommandLineArgumentsAfterShowList(App.CommandLineArguments, SettingsService.Instance.SpySettings); + + AvalonEditTextOutput output = new(); + if (FormatExceptions(App.StartupExceptions.ToArray(), output)) + DockWorkspace.Instance.ShowText(output); + } + + static bool FormatExceptions(App.ExceptionData[] exceptions, ITextOutput output) + { + var stringBuilder = new StringBuilder(); + var result = exceptions.FormatExceptions(stringBuilder); + if (result) + { + output.Write(stringBuilder.ToString()); + } + return result; + } + + public void ShowAssemblyList(string name) + { + AssemblyList list = SettingsService.Instance.AssemblyListManager.LoadList(name); + //Only load a new list when it is a different one + if (list.ListName != AssemblyList.ListName) + { + ShowAssemblyList(list); + SelectNode(Root); + } + } + + void ShowAssemblyList(AssemblyList assemblyList) + { + history.Clear(); + if (this.AssemblyList != null) + { + this.AssemblyList.CollectionChanged -= assemblyList_CollectionChanged; + } + + this.AssemblyList = assemblyList; + + assemblyList.CollectionChanged += assemblyList_CollectionChanged; + + assemblyListTreeNode = new(assemblyList) { + Select = x => SelectNode(x) + }; + + Root = assemblyListTreeNode; + + if (assemblyList.ListName == AssemblyListManager.DefaultListName) +#if DEBUG + this.Title = $"ILSpy {DecompilerVersionInfo.FullVersion}"; +#else + this.Title = "ILSpy"; +#endif + else +#if DEBUG + this.Title = $"ILSpy {DecompilerVersionInfo.FullVersion} - " + assemblyList.ListName; +#else + this.Title = "ILSpy - " + assemblyList.ListName; +#endif + } + + void assemblyList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Reset) + { + history.RemoveAll(_ => true); + } + if (e.OldItems != null) + { + var oldAssemblies = new HashSet<LoadedAssembly>(e.OldItems.Cast<LoadedAssembly>()); + history.RemoveAll(n => n.TreeNodes.Any( + nd => nd.AncestorsAndSelf().OfType<AssemblyTreeNode>().Any( + a => oldAssemblies.Contains(a.LoadedAssembly)))); + } + + MessageBus.Send(this, new CurrentAssemblyListChangedEventArgs(e)); + } + + void LoadInitialAssemblies() + { + // Called when loading an empty assembly list; so that + // the user can see something initially. + System.Reflection.Assembly[] initialAssemblies = { + typeof(object).Assembly, + typeof(Uri).Assembly, + typeof(System.Linq.Enumerable).Assembly, + typeof(System.Xml.XmlDocument).Assembly, + typeof(System.Windows.Markup.MarkupExtension).Assembly, + typeof(System.Windows.Rect).Assembly, + typeof(System.Windows.UIElement).Assembly, + typeof(System.Windows.FrameworkElement).Assembly + }; + foreach (System.Reflection.Assembly asm in initialAssemblies) + AssemblyList.OpenAssembly(asm.Location); + } + + public AssemblyTreeNode FindAssemblyNode(LoadedAssembly asm) + { + return assemblyListTreeNode.FindAssemblyNode(asm); + } + + #region Node Selection + + public void SelectNode(SharpTreeNode node, bool inNewTabPage = false) + { + if (node == null) + return; + + if (node.AncestorsAndSelf().Any(item => item.IsHidden)) + { + MessageBox.Show(Properties.Resources.NavigationFailed, "ILSpy", MessageBoxButton.OK, MessageBoxImage.Exclamation); + return; + } + + if (inNewTabPage) + { + DockWorkspace.Instance.AddTabPage(); + SelectedItem = null; + } + + if (SelectedItem == node) + { + Dispatcher.BeginInvoke(RefreshDecompiledView); + } + else + { + activeView?.ScrollIntoView(node); + SelectedItem = node; + } + } + + internal void SelectNodes(IEnumerable<SharpTreeNode> nodes) + { + // Ensure nodes exist + var nodesList = nodes.Select(n => FindNodeByPath(GetPathForNode(n), true)) + .Where(n => n != null) + .ToArray(); + + if (!nodesList.Any() || nodesList.Any(n => n.AncestorsAndSelf().Any(a => a.IsHidden))) + { + return; + } + + if (SelectedItems.SequenceEqual(nodesList)) + { + Dispatcher.BeginInvoke(RefreshDecompiledView); + return; + } + + SelectedItems.Clear(); + SelectedItems.AddRange(nodesList); + } + + /// <summary> + /// Retrieves a node using the .ToString() representations of its ancestors. + /// </summary> + public SharpTreeNode FindNodeByPath(string[] path, bool returnBestMatch) + { + if (path == null) + return null; + SharpTreeNode node = Root; + SharpTreeNode bestMatch = node; + foreach (var element in path) + { + if (node == null) + break; + bestMatch = node; + node.EnsureLazyChildren(); + if (node is ILSpyTreeNode ilSpyTreeNode) + ilSpyTreeNode.EnsureChildrenFiltered(); + node = node.Children.FirstOrDefault(c => c.ToString() == element); + } + if (returnBestMatch) + return node ?? bestMatch; + else + return node; + } + + /// <summary> + /// Gets the .ToString() representation of the node's ancestors. + /// </summary> + public static string[] GetPathForNode(SharpTreeNode node) + { + if (node == null) + return null; + List<string> path = new List<string>(); + while (node.Parent != null) + { + path.Add(node.ToString()); + node = node.Parent; + } + path.Reverse(); + return path.ToArray(); + } + + public ILSpyTreeNode FindTreeNode(object reference) + { + switch (reference) + { + case LoadedAssembly lasm: + return assemblyListTreeNode.FindAssemblyNode(lasm); + case MetadataFile asm: + return assemblyListTreeNode.FindAssemblyNode(asm); + case Resource res: + return assemblyListTreeNode.FindResourceNode(res); + case ValueTuple<Resource, string> resName: + return assemblyListTreeNode.FindResourceNode(resName.Item1, resName.Item2); + case ITypeDefinition type: + return assemblyListTreeNode.FindTypeNode(type); + case IField fd: + return assemblyListTreeNode.FindFieldNode(fd); + case IMethod md: + return assemblyListTreeNode.FindMethodNode(md); + case IProperty pd: + return assemblyListTreeNode.FindPropertyNode(pd); + case IEvent ed: + return assemblyListTreeNode.FindEventNode(ed); + case INamespace nd: + return assemblyListTreeNode.FindNamespaceNode(nd); + default: + return null; + } + } + + private void JumpToReference(object sender, NavigateToReferenceEventArgs e) + { + JumpToReferenceAsync(e.Reference, e.InNewTabPage).HandleExceptions(); + } + + /// <summary> + /// Jumps to the specified reference. + /// </summary> + /// <returns> + /// Returns a task that will signal completion when the decompilation of the jump target has finished. + /// The task will be marked as canceled if the decompilation is canceled. + /// </returns> + private Task JumpToReferenceAsync(object reference, bool inNewTabPage = false) + { + var decompilationTask = Task.CompletedTask; + + switch (reference) + { + case Decompiler.Disassembler.OpCodeInfo opCode: + MainWindow.OpenLink(opCode.Link); + break; + case EntityReference unresolvedEntity: + string protocol = unresolvedEntity.Protocol; + var file = unresolvedEntity.ResolveAssembly(AssemblyList); + if (file == null) + { + break; + } + if (protocol != "decompile") + { + var protocolHandlers = App.ExportProvider.GetExportedValues<IProtocolHandler>(); + foreach (var handler in protocolHandlers) + { + var node = handler.Resolve(protocol, file, unresolvedEntity.Handle, out bool newTabPage); + if (node != null) + { + SelectNode(node, newTabPage || inNewTabPage); + return decompilationTask; + } + } + } + var possibleToken = MetadataTokenHelpers.TryAsEntityHandle(MetadataTokens.GetToken(unresolvedEntity.Handle)); + if (possibleToken != null) + { + var typeSystem = new DecompilerTypeSystem(file, file.GetAssemblyResolver(), TypeSystemOptions.Default | TypeSystemOptions.Uncached); + reference = typeSystem.MainModule.ResolveEntity(possibleToken.Value); + goto default; + } + break; + default: + ILSpyTreeNode treeNode = FindTreeNode(reference); + if (treeNode != null) + SelectNode(treeNode, inNewTabPage); + break; + } + return decompilationTask; + } + + #endregion + + public void LoadAssemblies(IEnumerable<string> fileNames, List<LoadedAssembly> loadedAssemblies = null, bool focusNode = true) + { + using (Keyboard.FocusedElement.PreserveFocus(!focusNode)) + { + AssemblyTreeNode lastNode = null; + + foreach (string file in fileNames) + { + var assembly = AssemblyList.OpenAssembly(file); + + if (loadedAssemblies != null) + { + loadedAssemblies.Add(assembly); + } + else + { + var node = assemblyListTreeNode.FindAssemblyNode(assembly); + if (node != null && focusNode) + { + lastNode = node; + SelectedItems.Add(node); + } + } + } + if (focusNode && lastNode != null) + { + activeView?.FocusNode(lastNode); + } + } + } + + #region Decompile (TreeView_SelectionChanged) + + void TreeView_SelectionChanged() + { + if (SelectedItems.Count > 0) + { + if (!isNavigatingHistory) + { + var activeTabPage = DockWorkspace.Instance.ActiveTabPage; + var currentState = activeTabPage.GetState(); + if (currentState != null) + history.UpdateCurrent(new NavigationState(activeTabPage, currentState)); + history.Record(new NavigationState(activeTabPage, SelectedItems)); + } + + var delayDecompilationRequestDueToContextMenu = Mouse.RightButton == MouseButtonState.Pressed; + + if (!delayDecompilationRequestDueToContextMenu) + { + DecompileSelectedNodes(); + } + else + { + ContextMenuProvider.ContextMenuClosed += ContextMenuClosed; + } + } + + MessageBus.Send(this, new AssemblyTreeSelectionChangedEventArgs()); + + return; + + void ContextMenuClosed(object sender, EventArgs e) + { + ContextMenuProvider.ContextMenuClosed -= ContextMenuClosed; + + Dispatcher.BeginInvoke(DispatcherPriority.Background, () => { + if (Mouse.RightButton != MouseButtonState.Pressed) + { + DecompileSelectedNodes(DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState); + } + }); + } + } + + private void DecompileSelectedNodes(DecompilerTextViewState newState = null) + { + var activeTabPage = DockWorkspace.Instance.ActiveTabPage; + + activeTabPage.SupportsLanguageSwitching = true; + + if (SelectedItems.Count == 1) + { + if (SelectedItem is ILSpyTreeNode node && node.View(activeTabPage)) + return; + } + if (newState?.ViewedUri != null) + { + NavigateTo(new(newState.ViewedUri, null), recordHistory: false); + return; + } + + var options = SettingsService.Instance.CreateDecompilationOptions(activeTabPage); + options.TextViewState = newState; + activeTabPage.ShowTextViewAsync(textView => textView.DecompileAsync(this.CurrentLanguage, this.SelectedNodes, options)); + } + + public void RefreshDecompiledView() + { + DecompileSelectedNodes(); + } + + public Language CurrentLanguage => SettingsService.Instance.SessionSettings.LanguageSettings.Language; + + public LanguageVersion CurrentLanguageVersion => SettingsService.Instance.SessionSettings.LanguageSettings.LanguageVersion; + + public IEnumerable<ILSpyTreeNode> SelectedNodes { + get { + return GetTopLevelSelection().OfType<ILSpyTreeNode>(); + } + } + + #endregion + + public void NavigateHistory(bool forward) + { + isNavigatingHistory = true; + this.Dispatcher.BeginInvoke(DispatcherPriority.Background, () => isNavigatingHistory = false); + + TabPageModel tabPage = DockWorkspace.Instance.ActiveTabPage; + var state = tabPage.GetState(); + if (state != null) + history.UpdateCurrent(new NavigationState(tabPage, state)); + var newState = forward ? history.GoForward() : history.GoBack(); + + DockWorkspace.Instance.ActiveTabPage = newState.TabPage; + + SelectNodes(newState.TreeNodes); + } + + public bool CanNavigateBack => history.CanNavigateBack; + + public bool CanNavigateForward => history.CanNavigateForward; + + internal void NavigateTo(RequestNavigateEventArgs e, bool recordHistory = true, bool inNewTabPage = false) + { + if (e.Uri.Scheme == "resource") + { + if (inNewTabPage) + { + DockWorkspace.Instance.AddTabPage(); + } + + if (e.Uri.Host == "aboutpage") + { + RecordHistory(); + DockWorkspace.Instance.ActiveTabPage.ShowTextView(AboutPage.Display); + e.Handled = true; + return; + } + + AvalonEditTextOutput output = new AvalonEditTextOutput { + Address = e.Uri, + Title = e.Uri.AbsolutePath, + EnableHyperlinks = true + }; + using (Stream s = typeof(App).Assembly.GetManifestResourceStream(typeof(App), e.Uri.AbsolutePath)) + { + using (StreamReader r = new StreamReader(s)) + { + string line; + while ((line = r.ReadLine()) != null) + { + output.Write(line); + output.WriteLine(); + } + } + } + RecordHistory(); + DockWorkspace.Instance.ShowText(output); + e.Handled = true; + } + + void RecordHistory() + { + if (!recordHistory) + return; + TabPageModel tabPage = DockWorkspace.Instance.ActiveTabPage; + var currentState = tabPage.GetState(); + if (currentState != null) + history.UpdateCurrent(new NavigationState(tabPage, currentState)); + + UnselectAll(); + + history.Record(new NavigationState(tabPage, new ViewState { ViewedUri = e.Uri })); + } + } + + public void Refresh() + { + using (Keyboard.FocusedElement.PreserveFocus()) + { + var path = GetPathForNode(SelectedItem); + ShowAssemblyList(SettingsService.Instance.AssemblyListManager.LoadList(AssemblyList.ListName)); + SelectNode(FindNodeByPath(path, true), inNewTabPage: false); + } + } + + private void UnselectAll() + { + SelectedItems.Clear(); + } + + public IEnumerable<SharpTreeNode> GetTopLevelSelection() + { + var selection = this.SelectedItems; + var selectionHash = new HashSet<SharpTreeNode>(selection); + + return selection.Where(item => item.Ancestors().All(a => !selectionHash.Contains(a))); + } + + public void SetActiveView(AssemblyListPane activeView) + { + this.activeView = activeView; + } + + public void SortAssemblyList() + { + using (activeView?.LockUpdates()) + { + AssemblyList.Sort(AssemblyComparer.Instance); + } + } + + private class AssemblyComparer : IComparer<LoadedAssembly> + { + public static readonly AssemblyComparer Instance = new(); + int IComparer<LoadedAssembly>.Compare(LoadedAssembly x, LoadedAssembly y) + { + return string.Compare(x?.ShortName, y?.ShortName, StringComparison.CurrentCulture); + } + } + + public void CollapseAll() + { + using (activeView.LockUpdates()) + { + CollapseChildren(Root); + } + } + + static void CollapseChildren(SharpTreeNode node) + { + foreach (var child in node.Children) + { + if (!child.IsExpanded) + continue; + + CollapseChildren(child); + child.IsExpanded = false; + } + } + + public void OpenFiles(string[] fileNames, bool focusNode = true) + { + if (fileNames == null) + throw new ArgumentNullException(nameof(fileNames)); + + if (focusNode) + UnselectAll(); + + LoadAssemblies(fileNames, focusNode: focusNode); + } + } +} diff --git a/ILSpy/Commands/BrowseBackCommand.cs b/ILSpy/Commands/BrowseBackCommand.cs index cf424cbd4..ae53e19ef 100644 --- a/ILSpy/Commands/BrowseBackCommand.cs +++ b/ILSpy/Commands/BrowseBackCommand.cs @@ -19,6 +19,7 @@ using System.ComponentModel.Composition; using System.Windows.Input; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Properties; namespace ICSharpCode.ILSpy @@ -27,9 +28,30 @@ namespace ICSharpCode.ILSpy [PartCreationPolicy(CreationPolicy.Shared)] sealed class BrowseBackCommand : CommandWrapper { - public BrowseBackCommand() + readonly AssemblyTreeModel assemblyTreeModel; + + [ImportingConstructor] + public BrowseBackCommand(AssemblyTreeModel assemblyTreeModel) : base(NavigationCommands.BrowseBack) { + this.assemblyTreeModel = assemblyTreeModel; + } + + protected override void OnCanExecute(object sender, CanExecuteRoutedEventArgs e) + { + base.OnCanExecute(sender, e); + + e.Handled = true; + e.CanExecute = assemblyTreeModel.CanNavigateBack; + } + + protected override void OnExecute(object sender, ExecutedRoutedEventArgs e) + { + if (assemblyTreeModel.CanNavigateBack) + { + e.Handled = true; + assemblyTreeModel.NavigateHistory(false); + } } } } diff --git a/ILSpy/Commands/BrowseForwardCommand.cs b/ILSpy/Commands/BrowseForwardCommand.cs index 3a8276457..ea86ebc11 100644 --- a/ILSpy/Commands/BrowseForwardCommand.cs +++ b/ILSpy/Commands/BrowseForwardCommand.cs @@ -19,6 +19,7 @@ using System.ComponentModel.Composition; using System.Windows.Input; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Properties; namespace ICSharpCode.ILSpy @@ -27,9 +28,31 @@ namespace ICSharpCode.ILSpy [PartCreationPolicy(CreationPolicy.Shared)] sealed class BrowseForwardCommand : CommandWrapper { - public BrowseForwardCommand() + private readonly AssemblyTreeModel assemblyTreeModel; + + [ImportingConstructor] + public BrowseForwardCommand(AssemblyTreeModel assemblyTreeModel) : base(NavigationCommands.BrowseForward) { + this.assemblyTreeModel = assemblyTreeModel; + } + + protected override void OnCanExecute(object sender, CanExecuteRoutedEventArgs e) + { + base.OnCanExecute(sender, e); + + e.Handled = true; + e.CanExecute = assemblyTreeModel.CanNavigateForward; } + + protected override void OnExecute(object sender, ExecutedRoutedEventArgs e) + { + if (assemblyTreeModel.CanNavigateForward) + { + e.Handled = true; + assemblyTreeModel.NavigateHistory(true); + } + } + } } diff --git a/ILSpy/Commands/CheckForUpdatesCommand.cs b/ILSpy/Commands/CheckForUpdatesCommand.cs index c5d1ee0bd..f783933dd 100644 --- a/ILSpy/Commands/CheckForUpdatesCommand.cs +++ b/ILSpy/Commands/CheckForUpdatesCommand.cs @@ -20,7 +20,6 @@ using System.ComponentModel.Composition; using ICSharpCode.ILSpy.Properties; -using ICSharpCode.ILSpyX.Settings; namespace ICSharpCode.ILSpy { @@ -35,7 +34,7 @@ namespace ICSharpCode.ILSpy public override async void Execute(object? parameter) { - await MainWindow.Instance.ShowMessageIfUpdatesAvailableAsync(ILSpySettings.Load(), forceCheck: true); + await MainWindow.Instance.ShowMessageIfUpdatesAvailableAsync(SettingsService.Instance.SpySettings, forceCheck: true); } } } diff --git a/ILSpy/Commands/CommandWrapper.cs b/ILSpy/Commands/CommandWrapper.cs index 30bd37599..ad68ac75b 100644 --- a/ILSpy/Commands/CommandWrapper.cs +++ b/ILSpy/Commands/CommandWrapper.cs @@ -17,26 +17,28 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Windows; using System.Windows.Input; namespace ICSharpCode.ILSpy { - class CommandWrapper : ICommand + abstract class CommandWrapper : ICommand { private readonly ICommand wrappedCommand; - public CommandWrapper(ICommand wrappedCommand) + protected CommandWrapper(ICommand wrappedCommand) { this.wrappedCommand = wrappedCommand; + + Application.Current.MainWindow?.CommandBindings.Add(new CommandBinding(wrappedCommand, OnExecute, OnCanExecute)); } public static ICommand Unwrap(ICommand command) { - CommandWrapper? w = command as CommandWrapper; - if (w != null) + if (command is CommandWrapper w) return w.wrappedCommand; - else - return command; + + return command; } public event EventHandler? CanExecuteChanged { @@ -53,5 +55,12 @@ namespace ICSharpCode.ILSpy { return wrappedCommand.CanExecute(parameter); } + + protected abstract void OnExecute(object sender, ExecutedRoutedEventArgs e); + + protected virtual void OnCanExecute(object sender, CanExecuteRoutedEventArgs e) + { + e.CanExecute = true; + } } } diff --git a/ILSpy/Commands/DecompileAllCommand.cs b/ILSpy/Commands/DecompileAllCommand.cs index b86693760..4f147b06b 100644 --- a/ILSpy/Commands/DecompileAllCommand.cs +++ b/ILSpy/Commands/DecompileAllCommand.cs @@ -26,9 +26,9 @@ using System.Linq; using System.Threading.Tasks; using ICSharpCode.Decompiler; +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; using TomsToolbox.Essentials; @@ -49,7 +49,7 @@ namespace ICSharpCode.ILSpy Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => { AvalonEditTextOutput output = new AvalonEditTextOutput(); Parallel.ForEach( - Partitioner.Create(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), loadBalance: true), + Partitioner.Create(MainWindow.Instance.AssemblyTreeModel.AssemblyList.GetAssemblies(), loadBalance: true), new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, delegate (LoadedAssembly asm) { if (!asm.HasLoadError) @@ -60,7 +60,7 @@ namespace ICSharpCode.ILSpy { try { - var options = MainWindow.Instance.CreateDecompilationOptions(); + var options = SettingsService.Instance.CreateDecompilationOptions(DockWorkspace.Instance.ActiveTabPage); options.CancellationToken = ct; options.FullDecompilation = true; new CSharpLanguage().DecompileAssembly(asm, new PlainTextOutput(writer), options); @@ -96,9 +96,10 @@ namespace ICSharpCode.ILSpy { const int numRuns = 100; var language = SettingsService.Instance.SessionSettings.LanguageSettings.Language; - var nodes = MainWindow.Instance.SelectedNodes.ToArray(); - var options = MainWindow.Instance.CreateDecompilationOptions(); - Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => { + var nodes = MainWindow.Instance.AssemblyTreeModel.SelectedNodes.ToArray(); + DockWorkspace dockWorkspace = DockWorkspace.Instance; + var options = SettingsService.Instance.CreateDecompilationOptions(dockWorkspace.ActiveTabPage); + dockWorkspace.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => { options.CancellationToken = ct; Stopwatch w = Stopwatch.StartNew(); for (int i = 0; i < numRuns; ++i) @@ -113,7 +114,7 @@ namespace ICSharpCode.ILSpy double msPerRun = w.Elapsed.TotalMilliseconds / numRuns; output.Write($"Average time: {msPerRun.ToString("f1")}ms\n"); return output; - }, ct)).Then(output => Docking.DockWorkspace.Instance.ShowText(output)).HandleExceptions(); + }, ct)).Then(output => dockWorkspace.ShowText(output)).HandleExceptions(); } } } diff --git a/ILSpy/Commands/DecompileCommand.cs b/ILSpy/Commands/DecompileCommand.cs index 07ce981fb..152e7cd52 100644 --- a/ILSpy/Commands/DecompileCommand.cs +++ b/ILSpy/Commands/DecompileCommand.cs @@ -66,7 +66,7 @@ namespace ICSharpCode.ILSpy.Commands selection = entity; } if (selection != null) - MainWindow.Instance.JumpToReference(selection); + MessageBus.Send(this, new NavigateToReferenceEventArgs(selection)); } } } diff --git a/ILSpy/Commands/DecompileInNewViewCommand.cs b/ILSpy/Commands/DecompileInNewViewCommand.cs index 07e9bebcb..fd83244eb 100644 --- a/ILSpy/Commands/DecompileInNewViewCommand.cs +++ b/ILSpy/Commands/DecompileInNewViewCommand.cs @@ -25,9 +25,10 @@ using System.Windows.Threading; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Properties; -using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; +using TomsToolbox.Essentials; + namespace ICSharpCode.ILSpy.Commands { [ExportContextMenuEntry(Header = nameof(Resources.DecompileToNewPanel), InputGestureText = "MMB", Icon = "images/Search", Category = nameof(Resources.Analyze), Order = 90)] @@ -53,18 +54,18 @@ namespace ICSharpCode.ILSpy.Commands { if (context.SelectedTreeNodes != null) { - if (context.TreeView != MainWindow.Instance.AssemblyTreeView) + if (context.TreeView.DataContext != MainWindow.Instance.AssemblyTreeModel) { - return context.SelectedTreeNodes.OfType<IMemberTreeNode>().Select(FindTreeNode).Where(n => n != null); + return context.SelectedTreeNodes.OfType<IMemberTreeNode>().Select(FindTreeNode).ExceptNullItems(); } else { - return context.SelectedTreeNodes.OfType<ILSpyTreeNode>().Where(n => n != null); + return context.SelectedTreeNodes.OfType<ILSpyTreeNode>(); } } else if (context.Reference?.Reference is IEntity entity) { - if (MainWindow.Instance.FindTreeNode(entity) is ILSpyTreeNode node) + if (MainWindow.Instance.AssemblyTreeModel.FindTreeNode(entity) is { } node) { return new[] { node }; } @@ -75,7 +76,7 @@ namespace ICSharpCode.ILSpy.Commands { if (node is ILSpyTreeNode ilspyNode) return ilspyNode; - return MainWindow.Instance.FindTreeNode(node.Member); + return MainWindow.Instance.AssemblyTreeModel.FindTreeNode(node.Member); } } @@ -84,8 +85,9 @@ namespace ICSharpCode.ILSpy.Commands if (nodes.Length == 0) return; - MainWindow.Instance.SelectNodes(nodes, inNewTabPage: true); - MainWindow.Instance.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)MainWindow.Instance.RefreshDecompiledView); + DockWorkspace.Instance.AddTabPage(); + + MainWindow.Instance.AssemblyTreeModel.SelectNodes(nodes); } } } diff --git a/ILSpy/Commands/DisassembleAllCommand.cs b/ILSpy/Commands/DisassembleAllCommand.cs index 8907dba99..1686f4bcc 100644 --- a/ILSpy/Commands/DisassembleAllCommand.cs +++ b/ILSpy/Commands/DisassembleAllCommand.cs @@ -26,7 +26,6 @@ using System.Threading.Tasks; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; -using ICSharpCode.ILSpyX; namespace ICSharpCode.ILSpy { @@ -41,12 +40,14 @@ namespace ICSharpCode.ILSpy public override void Execute(object? parameter) { - Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => { - AvalonEditTextOutput output = new AvalonEditTextOutput(); + var dockWorkspace = Docking.DockWorkspace.Instance; + + dockWorkspace.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => { + AvalonEditTextOutput output = new(); Parallel.ForEach( - Partitioner.Create(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), loadBalance: true), - new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, - delegate (LoadedAssembly asm) { + Partitioner.Create(MainWindow.Instance.AssemblyTreeModel.AssemblyList.GetAssemblies(), loadBalance: true), + new() { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, + asm => { if (!asm.HasLoadError) { Stopwatch w = Stopwatch.StartNew(); @@ -55,7 +56,7 @@ namespace ICSharpCode.ILSpy { try { - var options = MainWindow.Instance.CreateDecompilationOptions(); + var options = SettingsService.Instance.CreateDecompilationOptions(dockWorkspace.ActiveTabPage); options.FullDecompilation = true; options.CancellationToken = ct; new ILLanguage().DecompileAssembly(asm, new Decompiler.PlainTextOutput(writer), options); @@ -79,7 +80,7 @@ namespace ICSharpCode.ILSpy } }); return output; - }, ct)).Then(output => Docking.DockWorkspace.Instance.ShowText(output)).HandleExceptions(); + }, ct)).Then(dockWorkspace.ShowText).HandleExceptions(); } } } diff --git a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs index 245fc7663..1c82faf12 100644 --- a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs +++ b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs @@ -29,6 +29,7 @@ using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.Decompiler.DebugInfo; using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; @@ -73,9 +74,10 @@ namespace ICSharpCode.ILSpy dlg.InitialDirectory = Path.GetDirectoryName(assembly.FileName); if (dlg.ShowDialog() != true) return; - DecompilationOptions options = MainWindow.Instance.CreateDecompilationOptions(); + DockWorkspace dockWorkspace = DockWorkspace.Instance; + DecompilationOptions options = SettingsService.Instance.CreateDecompilationOptions(dockWorkspace.ActiveTabPage); string fileName = dlg.FileName; - Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => { + dockWorkspace.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => { AvalonEditTextOutput output = new AvalonEditTextOutput(); Stopwatch stopwatch = Stopwatch.StartNew(); options.CancellationToken = ct; @@ -110,14 +112,14 @@ namespace ICSharpCode.ILSpy { public override bool CanExecute(object? parameter) { - return MainWindow.Instance.SelectedNodes?.Count() == 1 - && MainWindow.Instance.SelectedNodes?.FirstOrDefault() is AssemblyTreeNode tn + return MainWindow.Instance.AssemblyTreeModel.SelectedNodes?.Count() == 1 + && MainWindow.Instance.AssemblyTreeModel.SelectedNodes?.FirstOrDefault() is AssemblyTreeNode tn && !tn.LoadedAssembly.HasLoadError; } public override void Execute(object? parameter) { - var assembly = (MainWindow.Instance.SelectedNodes?.FirstOrDefault() as AssemblyTreeNode)?.LoadedAssembly; + var assembly = (MainWindow.Instance.AssemblyTreeModel.SelectedNodes?.FirstOrDefault() as AssemblyTreeNode)?.LoadedAssembly; if (assembly == null) return; GeneratePdbContextMenuEntry.GeneratePdbForAssembly(assembly); diff --git a/ILSpy/Commands/OpenCommand.cs b/ILSpy/Commands/OpenCommand.cs index 7c66ac43a..06ac419e9 100644 --- a/ILSpy/Commands/OpenCommand.cs +++ b/ILSpy/Commands/OpenCommand.cs @@ -19,8 +19,11 @@ using System.ComponentModel.Composition; using System.Windows.Input; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Properties; +using Microsoft.Win32; + namespace ICSharpCode.ILSpy { [ExportToolbarCommand(ToolTip = nameof(Resources.Open), ToolbarIcon = "Images/Open", ToolbarCategory = nameof(Resources.Open), ToolbarOrder = 0)] @@ -28,9 +31,28 @@ namespace ICSharpCode.ILSpy [PartCreationPolicy(CreationPolicy.Shared)] sealed class OpenCommand : CommandWrapper { - public OpenCommand() + private readonly AssemblyTreeModel assemblyTreeModel; + + [ImportingConstructor] + public OpenCommand(AssemblyTreeModel assemblyTreeModel) : base(ApplicationCommands.Open) { + this.assemblyTreeModel = assemblyTreeModel; + } + + protected override void OnExecute(object sender, ExecutedRoutedEventArgs e) + { + e.Handled = true; + OpenFileDialog dlg = new OpenFileDialog { + Filter = ".NET assemblies|*.dll;*.exe;*.winmd;*.wasm|Nuget Packages (*.nupkg)|*.nupkg|Portable Program Database (*.pdb)|*.pdb|All files|*.*", + Multiselect = true, + RestoreDirectory = true + }; + + if (dlg.ShowDialog() == true) + { + assemblyTreeModel.OpenFiles(dlg.FileNames); + } } } } diff --git a/ILSpy/Commands/OpenFromGacCommand.cs b/ILSpy/Commands/OpenFromGacCommand.cs index a25fa14c0..991e28fe5 100644 --- a/ILSpy/Commands/OpenFromGacCommand.cs +++ b/ILSpy/Commands/OpenFromGacCommand.cs @@ -19,6 +19,7 @@ using System.ComponentModel.Composition; using ICSharpCode.ILSpy.AppEnv; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Properties; namespace ICSharpCode.ILSpy @@ -27,6 +28,14 @@ namespace ICSharpCode.ILSpy [PartCreationPolicy(CreationPolicy.Shared)] sealed class OpenFromGacCommand : SimpleCommand { + private readonly AssemblyTreeModel assemblyTreeModel; + + [ImportingConstructor] + public OpenFromGacCommand(AssemblyTreeModel assemblyTreeModel) + { + this.assemblyTreeModel = assemblyTreeModel; + } + public override bool CanExecute(object? parameter) { return AppEnvironment.IsWindows; @@ -34,10 +43,14 @@ namespace ICSharpCode.ILSpy public override void Execute(object? parameter) { - OpenFromGacDialog dlg = new OpenFromGacDialog(); - dlg.Owner = MainWindow.Instance; + OpenFromGacDialog dlg = new OpenFromGacDialog { + Owner = MainWindow.Instance + }; + if (dlg.ShowDialog() == true) - MainWindow.Instance.OpenFiles(dlg.SelectedFileNames); + { + assemblyTreeModel.OpenFiles(dlg.SelectedFileNames); + } } } } diff --git a/ILSpy/Commands/Pdb2XmlCommand.cs b/ILSpy/Commands/Pdb2XmlCommand.cs index 2ddf9b384..10873c58a 100644 --- a/ILSpy/Commands/Pdb2XmlCommand.cs +++ b/ILSpy/Commands/Pdb2XmlCommand.cs @@ -40,14 +40,14 @@ namespace ICSharpCode.ILSpy { public override bool CanExecute(object? parameter) { - var selectedNodes = MainWindow.Instance.SelectedNodes; + var selectedNodes = MainWindow.Instance.AssemblyTreeModel.SelectedNodes; return selectedNodes?.Any() == true && selectedNodes.All(n => n is AssemblyTreeNode asm && !asm.LoadedAssembly.HasLoadError); } public override void Execute(object? parameter) { - Execute(MainWindow.Instance.SelectedNodes.OfType<AssemblyTreeNode>()); + Execute(MainWindow.Instance.AssemblyTreeModel.SelectedNodes.OfType<AssemblyTreeNode>()); } internal static void Execute(IEnumerable<AssemblyTreeNode> nodes) diff --git a/ILSpy/Commands/RefreshCommand.cs b/ILSpy/Commands/RefreshCommand.cs index cfb03bf34..e01e70f0e 100644 --- a/ILSpy/Commands/RefreshCommand.cs +++ b/ILSpy/Commands/RefreshCommand.cs @@ -19,6 +19,7 @@ using System.ComponentModel.Composition; using System.Windows.Input; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Properties; namespace ICSharpCode.ILSpy @@ -28,9 +29,18 @@ namespace ICSharpCode.ILSpy [PartCreationPolicy(CreationPolicy.Shared)] sealed class RefreshCommand : CommandWrapper { - public RefreshCommand() + private readonly AssemblyTreeModel assemblyTreeModel; + + [ImportingConstructor] + public RefreshCommand(AssemblyTreeModel assemblyTreeModel) : base(NavigationCommands.Refresh) { + this.assemblyTreeModel = assemblyTreeModel; + } + + protected override void OnExecute(object sender, ExecutedRoutedEventArgs e) + { + assemblyTreeModel.Refresh(); } } } diff --git a/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs b/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs index 282c99098..f5e4b93d5 100644 --- a/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs +++ b/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs @@ -29,16 +29,16 @@ namespace ICSharpCode.ILSpy { public override bool CanExecute(object? parameter) { - return MainWindow.Instance.CurrentAssemblyList?.GetAssemblies().Any(l => l.HasLoadError) == true; + return MainWindow.Instance.AssemblyTreeModel.AssemblyList?.GetAssemblies().Any(l => l.HasLoadError) == true; } public override void Execute(object? parameter) { - foreach (var asm in MainWindow.Instance.CurrentAssemblyList.GetAssemblies()) + foreach (var asm in MainWindow.Instance.AssemblyTreeModel.AssemblyList.GetAssemblies()) { if (!asm.HasLoadError) continue; - var node = MainWindow.Instance.AssemblyListTreeNode.FindAssemblyNode(asm); + var node = MainWindow.Instance.AssemblyTreeModel.FindAssemblyNode(asm); if (node != null && node.CanDelete()) node.Delete(); } @@ -51,12 +51,12 @@ namespace ICSharpCode.ILSpy { public override bool CanExecute(object? parameter) { - return MainWindow.Instance.CurrentAssemblyList?.Count > 0; + return MainWindow.Instance.AssemblyTreeModel.AssemblyList?.Count > 0; } public override void Execute(object? parameter) { - MainWindow.Instance.CurrentAssemblyList?.Clear(); + MainWindow.Instance.AssemblyTreeModel.AssemblyList?.Clear(); } } } diff --git a/ILSpy/Commands/SaveCodeContextMenuEntry.cs b/ILSpy/Commands/SaveCodeContextMenuEntry.cs index d68ad1c7f..5759c3abd 100644 --- a/ILSpy/Commands/SaveCodeContextMenuEntry.cs +++ b/ILSpy/Commands/SaveCodeContextMenuEntry.cs @@ -33,6 +33,9 @@ using ICSharpCode.ILSpyX.TreeView; using Microsoft.Win32; +using ICSharpCode.ILSpy.Docking; + + namespace ICSharpCode.ILSpy.TextView { [ExportContextMenuEntry(Header = nameof(Resources._SaveCode), Category = nameof(Resources.Save), Icon = "Images/Save")] @@ -61,8 +64,11 @@ namespace ICSharpCode.ILSpy.TextView public static void Execute(IReadOnlyList<SharpTreeNode> selectedNodes) { - var currentLanguage = SettingsService.Instance.SessionSettings.LanguageSettings.Language; - var tabPage = Docking.DockWorkspace.Instance.ActiveTabPage; + var settingsService = SettingsService.Instance; + var dockWorkspace = Docking.DockWorkspace.Instance; + + var currentLanguage = settingsService.SessionSettings.LanguageSettings.Language; + var tabPage = dockWorkspace.ActiveTabPage; tabPage.ShowTextView(textView => { if (selectedNodes.Count == 1 && selectedNodes[0] is ILSpyTreeNode singleSelection) { @@ -87,7 +93,7 @@ namespace ICSharpCode.ILSpy.TextView // Fallback: if nobody was able to handle the request, use default behavior. // try to save all nodes to disk. - var options = MainWindow.Instance.CreateDecompilationOptions(); + var options = settingsService.CreateDecompilationOptions(dockWorkspace.ActiveTabPage); options.FullDecompilation = true; textView.SaveToDisk(currentLanguage, selectedNodes.OfType<ILSpyTreeNode>(), options); }); diff --git a/ILSpy/Commands/SaveCommand.cs b/ILSpy/Commands/SaveCommand.cs index 8b8871e0a..9e43df69b 100644 --- a/ILSpy/Commands/SaveCommand.cs +++ b/ILSpy/Commands/SaveCommand.cs @@ -17,9 +17,12 @@ // DEALINGS IN THE SOFTWARE. using System.ComponentModel.Composition; +using System.Linq; using System.Windows.Input; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Properties; +using ICSharpCode.ILSpy.TextView; namespace ICSharpCode.ILSpy { @@ -27,9 +30,24 @@ namespace ICSharpCode.ILSpy [PartCreationPolicy(CreationPolicy.Shared)] sealed class SaveCommand : CommandWrapper { - public SaveCommand() + private AssemblyTreeModel assemblyTreeModel; + + [ImportingConstructor] + public SaveCommand(AssemblyTreeModel assemblyTreeModel) : base(ApplicationCommands.Save) { + this.assemblyTreeModel = assemblyTreeModel; + } + + protected override void OnCanExecute(object sender, CanExecuteRoutedEventArgs e) + { + e.Handled = true; + e.CanExecute = SaveCodeContextMenuEntry.CanExecute(assemblyTreeModel.SelectedNodes.ToList()); + } + + protected override void OnExecute(object sender, ExecutedRoutedEventArgs e) + { + SaveCodeContextMenuEntry.Execute(assemblyTreeModel.SelectedNodes.ToList()); } } } diff --git a/ILSpy/Commands/SelectPdbContextMenuEntry.cs b/ILSpy/Commands/SelectPdbContextMenuEntry.cs index 46fa62625..db4fa7373 100644 --- a/ILSpy/Commands/SelectPdbContextMenuEntry.cs +++ b/ILSpy/Commands/SelectPdbContextMenuEntry.cs @@ -48,10 +48,10 @@ namespace ICSharpCode.ILSpy await assembly.LoadDebugInfo(dlg.FileName); } - var node = (AssemblyTreeNode?)MainWindow.Instance.FindNodeByPath(new[] { assembly.FileName }, true); + var node = (AssemblyTreeNode)MainWindow.Instance.AssemblyTreeModel.FindNodeByPath(new[] { assembly.FileName }, true); node.UpdateToolTip(); - MainWindow.Instance.SelectNode(node); - MainWindow.Instance.RefreshDecompiledView(); + MainWindow.Instance.AssemblyTreeModel.SelectNode(node); + MainWindow.Instance.AssemblyTreeModel.RefreshDecompiledView(); } public bool IsEnabled(TextViewContext context) => true; diff --git a/ILSpy/Commands/SetThemeCommand.cs b/ILSpy/Commands/SetThemeCommand.cs index 8822ed67a..ee415cc0d 100644 --- a/ILSpy/Commands/SetThemeCommand.cs +++ b/ILSpy/Commands/SetThemeCommand.cs @@ -1,5 +1,4 @@ -using ICSharpCode.ILSpy.Util; - + namespace ICSharpCode.ILSpy.Commands { public class SetThemeCommand : SimpleCommand @@ -7,7 +6,9 @@ namespace ICSharpCode.ILSpy.Commands public override void Execute(object? parameter) { if (parameter is string theme) + { SettingsService.Instance.SessionSettings.Theme = theme; + } } } } diff --git a/ILSpy/Commands/ShowCFGContextMenuEntry.cs b/ILSpy/Commands/ShowCFGContextMenuEntry.cs index 9ff313da7..a1a124192 100644 --- a/ILSpy/Commands/ShowCFGContextMenuEntry.cs +++ b/ILSpy/Commands/ShowCFGContextMenuEntry.cs @@ -6,7 +6,6 @@ using System.Windows; using ICSharpCode.Decompiler.FlowAnalysis; using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.IL.ControlFlow; -using ICSharpCode.ILSpy.Util; namespace ICSharpCode.ILSpy.Commands { diff --git a/ILSpy/Commands/SortAssemblyListCommand.cs b/ILSpy/Commands/SortAssemblyListCommand.cs index 3e5bce4ae..8f744dadf 100644 --- a/ILSpy/Commands/SortAssemblyListCommand.cs +++ b/ILSpy/Commands/SortAssemblyListCommand.cs @@ -29,17 +29,11 @@ namespace ICSharpCode.ILSpy [ExportMainMenuCommand(ParentMenuID = nameof(Resources._View), Header = nameof(Resources.SortAssembly_listName), MenuIcon = "Images/Sort", MenuCategory = nameof(Resources.View))] [ExportToolbarCommand(ToolTip = nameof(Resources.SortAssemblyListName), ToolbarIcon = "Images/Sort", ToolbarCategory = nameof(Resources.View))] [PartCreationPolicy(CreationPolicy.Shared)] - sealed class SortAssemblyListCommand : SimpleCommand, IComparer<LoadedAssembly> + sealed class SortAssemblyListCommand : SimpleCommand { public override void Execute(object? parameter) { - using (MainWindow.Instance.AssemblyTreeView.LockUpdates()) - MainWindow.Instance.CurrentAssemblyList.Sort(this); - } - - int IComparer<LoadedAssembly>.Compare(LoadedAssembly? x, LoadedAssembly? y) - { - return string.Compare(x.ShortName, y.ShortName, StringComparison.CurrentCulture); + MainWindow.Instance.AssemblyTreeModel.SortAssemblyList(); } } @@ -50,19 +44,8 @@ namespace ICSharpCode.ILSpy { public override void Execute(object? parameter) { - using (MainWindow.Instance.AssemblyTreeView.LockUpdates()) - CollapseChildren(MainWindow.Instance.AssemblyTreeView.Root); + MainWindow.Instance.AssemblyTreeModel.CollapseAll(); - void CollapseChildren(SharpTreeNode node) - { - foreach (var child in node.Children) - { - if (!child.IsExpanded) - continue; - CollapseChildren(child); - child.IsExpanded = false; - } - } } } } diff --git a/ILSpy/ContextMenuEntry.cs b/ILSpy/ContextMenuEntry.cs index 27c7c1b24..f4a96d187 100644 --- a/ILSpy/ContextMenuEntry.cs +++ b/ILSpy/ContextMenuEntry.cs @@ -22,8 +22,6 @@ using System.ComponentModel.Composition; using System.Linq; using System.Windows; using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; using ICSharpCode.AvalonEdit; using ICSharpCode.ILSpy.Controls.TreeView; @@ -32,6 +30,7 @@ using ICSharpCode.ILSpyX.Search; using ICSharpCode.ILSpyX.TreeView; using TomsToolbox.Composition; +using TomsToolbox.Essentials; namespace ICSharpCode.ILSpy { @@ -86,36 +85,40 @@ namespace ICSharpCode.ILSpy /// </summary> public TextViewPosition? Position { get; private set; } - public Point MousePosition { get; private set; } + /// <summary> + /// Returns the original source of the context menu event. + /// </summary> + public DependencyObject OriginalSource { get; private set; } - public static TextViewContext Create(SharpTreeView? treeView = null, DecompilerTextView? textView = null, ListBox? listBox = null, DataGrid? dataGrid = null) + public static TextViewContext Create(ContextMenuEventArgs eventArgs, SharpTreeView?? treeView = null, DecompilerTextView textView = null, ListBox? listBox = null, DataGrid? dataGrid = null) { ReferenceSegment? reference; - if (textView != null) + + if (textView is not null) + { reference = textView.GetReferenceSegmentAtMousePosition(); - else if (listBox?.SelectedItem is SearchResult result) - reference = new ReferenceSegment { Reference = result.Reference }; - else if (listBox?.SelectedItem is TreeNodes.IMemberTreeNode provider) - reference = new ReferenceSegment { Reference = provider.Member }; - else if (listBox?.SelectedItem != null) - reference = new ReferenceSegment { Reference = listBox.SelectedItem }; - else if (dataGrid?.SelectedItem is TreeNodes.IMemberTreeNode provider2) - reference = new ReferenceSegment { Reference = provider2.Member }; - else if (dataGrid?.SelectedItem != null) - reference = new ReferenceSegment { Reference = dataGrid.SelectedItem }; + } else - reference = null; - var position = textView != null ? textView.GetPositionFromMousePosition() : null; - var selectedTreeNodes = treeView != null ? treeView.GetTopLevelSelection().ToArray() : null; - return new TextViewContext { + { + reference = (listBox?.SelectedItem ?? dataGrid?.SelectedItem) switch { + SearchResult searchResult => new() { Reference = searchResult.Reference }, + TreeNodes.IMemberTreeNode treeNode => new() { Reference = treeNode.Member }, { } value => new() { Reference = value }, + _ => null + }; + } + + var position = textView?.GetPositionFromMousePosition(); + var selectedTreeNodes = treeView?.GetTopLevelSelection().ToArray(); + + return new() { ListBox = listBox, DataGrid = dataGrid, TreeView = treeView, - SelectedTreeNodes = selectedTreeNodes, TextView = textView, + SelectedTreeNodes = selectedTreeNodes, Reference = reference, Position = position, - MousePosition = ((Visual?)textView ?? treeView ?? (Visual?)listBox ?? dataGrid).PointToScreen(Mouse.GetPosition((IInputElement?)textView ?? treeView ?? (IInputElement?)listBox ?? dataGrid)) + OriginalSource = eventArgs.OriginalSource as DependencyObject }; } } @@ -168,6 +171,13 @@ namespace ICSharpCode.ILSpy internal class ContextMenuProvider { + private static readonly WeakEventSource<EventArgs> ContextMenuClosedEventSource = new(); + + public static event EventHandler<EventArgs> ContextMenuClosed { + add => ContextMenuClosedEventSource.Subscribe(value); + remove => ContextMenuClosedEventSource.Unsubscribe(value); + } + /// <summary> /// Enables extensible context menu support for the specified tree view. /// </summary> @@ -203,51 +213,54 @@ namespace ICSharpCode.ILSpy dataGrid.ContextMenu = new ContextMenu(); } + readonly Control control; readonly SharpTreeView treeView; readonly DecompilerTextView textView; readonly ListBox listBox; readonly DataGrid dataGrid; readonly IExport<IContextMenuEntry, IContextMenuEntryMetadata>[] entries; - private ContextMenuProvider() + private ContextMenuProvider(Control control) { entries = App.ExportProvider.GetExports<IContextMenuEntry, IContextMenuEntryMetadata>().ToArray(); + + this.control = control; } ContextMenuProvider(DecompilerTextView textView) - : this() + : this((Control)textView) { this.textView = textView ?? throw new ArgumentNullException(nameof(textView)); } ContextMenuProvider(SharpTreeView treeView) - : this() + : this((Control)treeView) { this.treeView = treeView ?? throw new ArgumentNullException(nameof(treeView)); } ContextMenuProvider(ListBox listBox) - : this() + : this((Control)listBox) { this.listBox = listBox ?? throw new ArgumentNullException(nameof(listBox)); } ContextMenuProvider(DataGrid dataGrid) - : this() + : this((Control)dataGrid) { this.dataGrid = dataGrid ?? throw new ArgumentNullException(nameof(dataGrid)); } void treeView_ContextMenuOpening(object sender, ContextMenuEventArgs e) { - TextViewContext context = TextViewContext.Create(treeView); + var context = TextViewContext.Create(e, treeView: treeView); if (context.SelectedTreeNodes.Length == 0) { e.Handled = true; // don't show the menu return; } - ContextMenu menu; - if (ShowContextMenu(context, out menu)) + + if (ShowContextMenu(context, out var menu)) treeView.ContextMenu = menu; else // hide the context menu. @@ -256,9 +269,8 @@ namespace ICSharpCode.ILSpy void textView_ContextMenuOpening(object sender, ContextMenuEventArgs e) { - TextViewContext context = TextViewContext.Create(textView: textView); - ContextMenu menu; - if (ShowContextMenu(context, out menu)) + var context = TextViewContext.Create(e, textView: textView); + if (ShowContextMenu(context, out var menu)) textView.ContextMenu = menu; else // hide the context menu. @@ -267,9 +279,8 @@ namespace ICSharpCode.ILSpy void listBox_ContextMenuOpening(object sender, ContextMenuEventArgs e) { - TextViewContext context = TextViewContext.Create(listBox: listBox); - ContextMenu menu; - if (ShowContextMenu(context, out menu)) + var context = TextViewContext.Create(e, listBox: listBox); + if (ShowContextMenu(context, out var menu)) listBox.ContextMenu = menu; else // hide the context menu. @@ -278,9 +289,8 @@ namespace ICSharpCode.ILSpy void dataGrid_ContextMenuOpening(object sender, ContextMenuEventArgs e) { - TextViewContext context = TextViewContext.Create(dataGrid: dataGrid); - ContextMenu menu; - if (ShowContextMenu(context, out menu)) + var context = TextViewContext.Create(e, dataGrid: dataGrid); + if (ShowContextMenu(context, out var menu)) dataGrid.ContextMenu = menu; else // hide the context menu. @@ -289,7 +299,18 @@ namespace ICSharpCode.ILSpy bool ShowContextMenu(TextViewContext context, out ContextMenu menu) { + // Closing event is raised on the control where mouse is clicked, not on the control that opened the menu, so we hook on the global window event. + var window = Window.GetWindow(control)!; + window.ContextMenuClosing += ContextMenu_Closing; + + void ContextMenu_Closing(object sender, EventArgs e) + { + window.ContextMenuClosing -= ContextMenu_Closing; + ContextMenuClosedEventSource.Raise(this, EventArgs.Empty); + } + menu = new ContextMenu(); + var menuGroups = new Dictionary<string, IExport<IContextMenuEntry, IContextMenuEntryMetadata>[]>(); IExport<IContextMenuEntry, IContextMenuEntryMetadata>[]? topLevelGroup = null; foreach (var group in entries.OrderBy(c => c.Metadata.Order).GroupBy(c => c.Metadata.ParentMenuID)) @@ -310,10 +331,10 @@ namespace ICSharpCode.ILSpy { foreach (var category in menuGroup.GroupBy(c => c.Metadata.Category)) { - bool needSeparatorForCategory = parent.Count > 0; + var needSeparatorForCategory = parent.Count > 0; foreach (var entryPair in category) { - IContextMenuEntry? entry = entryPair.Value; + var entry = entryPair.Value; if (entry.IsVisible(context)) { if (needSeparatorForCategory) @@ -321,8 +342,8 @@ namespace ICSharpCode.ILSpy parent.Add(new Separator()); needSeparatorForCategory = false; } - MenuItem menuItem = new MenuItem(); - menuItem.Header = MainWindow.GetResourceString(entryPair.Metadata.Header); + var menuItem = new MenuItem(); + menuItem.Header = ResourceHelper.GetString(entryPair.Metadata.Header); menuItem.InputGestureText = entryPair.Metadata.InputGestureText; if (!string.IsNullOrEmpty(entryPair.Metadata.Icon)) { diff --git a/ILSpy/Controls/TreeView/SharpTreeView.cs b/ILSpy/Controls/TreeView/SharpTreeView.cs index 7ca753759..12a68bf04 100644 --- a/ILSpy/Controls/TreeView/SharpTreeView.cs +++ b/ILSpy/Controls/TreeView/SharpTreeView.cs @@ -158,6 +158,7 @@ namespace ICSharpCode.ILSpy.Controls.TreeView if (flattener != null) { flattener.Stop(); + flattener.CollectionChanged -= flattener_CollectionChanged; } if (Root != null) { @@ -818,9 +819,9 @@ namespace ICSharpCode.ILSpy.Controls.TreeView /// </summary> public IEnumerable<SharpTreeNode> GetTopLevelSelection() { - var selection = this.SelectedItems.OfType<SharpTreeNode>(); - var selectionHash = new HashSet<SharpTreeNode>(selection); - return selection.Where(item => item.Ancestors().All(a => !selectionHash.Contains(a))); + var selection = this.SelectedItems.OfType<SharpTreeNode>().ToHashSet(); + + return selection.Where(item => item.Ancestors().All(a => !selection.Contains(a))); } #endregion diff --git a/ILSpy/DecompilationOptions.cs b/ILSpy/DecompilationOptions.cs index f07026732..b8b328b6f 100644 --- a/ILSpy/DecompilationOptions.cs +++ b/ILSpy/DecompilationOptions.cs @@ -23,6 +23,8 @@ using ICSharpCode.Decompiler; using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpyX; +using DecompilerSettings = ICSharpCode.ILSpy.Options.DecompilerSettings; + namespace ICSharpCode.ILSpy { /// <summary> @@ -87,7 +89,9 @@ namespace ICSharpCode.ILSpy { if (!Enum.TryParse(version?.Version, out Decompiler.CSharp.LanguageVersion languageVersion)) languageVersion = Decompiler.CSharp.LanguageVersion.Latest; + var newSettings = this.DecompilerSettings = settings.Clone(); + newSettings.SetLanguageVersion(languageVersion); newSettings.ExpandMemberDefinitions = displaySettings.ExpandMemberDefinitions; newSettings.ExpandUsingDeclarations = displaySettings.ExpandUsingDeclarations; diff --git a/ILSpy/Docking/DockLayoutSettings.cs b/ILSpy/Docking/DockLayoutSettings.cs index dc3926500..42533f41a 100644 --- a/ILSpy/Docking/DockLayoutSettings.cs +++ b/ILSpy/Docking/DockLayoutSettings.cs @@ -19,7 +19,6 @@ using System; using System.IO; using System.Linq; -using System.Xml; using System.Xml.Linq; using AvalonDock.Layout.Serialization; diff --git a/ILSpy/Docking/DockWorkspace.cs b/ILSpy/Docking/DockWorkspace.cs index f3de5959f..2b1765b3d 100644 --- a/ILSpy/Docking/DockWorkspace.cs +++ b/ILSpy/Docking/DockWorkspace.cs @@ -33,9 +33,11 @@ using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.ILSpy.Analyzers; using ICSharpCode.ILSpy.Search; using ICSharpCode.ILSpy.TextView; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpy.ViewModels; +using ICSharpCode.ILSpyX.Extensions; +using TomsToolbox.Composition; +using TomsToolbox.Essentials; using TomsToolbox.Wpf; namespace ICSharpCode.ILSpy.Docking @@ -44,11 +46,22 @@ namespace ICSharpCode.ILSpy.Docking { private static SessionSettings SessionSettings => SettingsService.Instance.SessionSettings; + private readonly IExportProvider exportProvider = App.ExportProvider; + public static readonly DockWorkspace Instance = new(); + private readonly ObservableCollection<TabPageModel> tabPages = []; + private readonly ObservableCollection<ToolPaneModel> toolPanes = []; + private DockWorkspace() { - this.TabPages.CollectionChanged += Documents_CollectionChanged; + this.tabPages.CollectionChanged += TabPages_CollectionChanged; + TabPages = new(tabPages); + ToolPanes = new(toolPanes); + + // Make sure there is at least one tab open + AddTabPage(); + MessageBus<CurrentAssemblyListChangedEventArgs>.Subscribers += (sender, e) => CurrentAssemblyList_Changed(sender, e); } @@ -58,52 +71,57 @@ namespace ICSharpCode.ILSpy.Docking { return; } - foreach (var tab in TabPages.ToArray()) + foreach (var tab in tabPages.ToArray()) { var state = tab.GetState(); - if (state == null || state.DecompiledNodes == null) - { + var decompiledNodes = state?.DecompiledNodes; + if (decompiledNodes == null) continue; - } - bool found = false; - foreach (var node in state.DecompiledNodes) - { - var assemblyNode = node.Ancestors().OfType<TreeNodes.AssemblyTreeNode>().LastOrDefault(); - if (assemblyNode != null && !e.OldItems.Contains(assemblyNode.LoadedAssembly)) - { - found = true; - break; - } - } - if (!found && TabPages.Count > 1) + + bool found = decompiledNodes + .Select(node => node.Ancestors().OfType<TreeNodes.AssemblyTreeNode>().LastOrDefault()) + .ExceptNullItems() + .Any(assemblyNode => !e.OldItems.Contains(assemblyNode.LoadedAssembly)); + + if (!found && tabPages.Count > 1) { - TabPages.Remove(tab); + tabPages.Remove(tab); } } } - private void Documents_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + private void TabPages_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { - var collection = (PaneCollection<TabPageModel>?)sender; if (e.Action == NotifyCollectionChangedAction.Add) { - ActiveTabPage = e.NewItems?[0] as TabPageModel; + if (e.NewItems?[0] is TabPageModel model) + { + ActiveTabPage = model; + model.IsActive = true; + model.IsVisible = true; + } } - bool canClose = collection.Count > 1; - foreach (var item in collection) + bool canClose = tabPages.Count > 1; + + foreach (var item in tabPages) { item.IsCloseable = canClose; } } - public PaneCollection<TabPageModel> TabPages { get; } = new PaneCollection<TabPageModel>(); + public void AddTabPage(TabPageModel tabPage = null) + { + tabPages.Add(tabPage ?? new TabPageModel()); + } + + public ReadOnlyObservableCollection<TabPageModel> TabPages { get; } - public ObservableCollection<ToolPaneModel> ToolPanes { get; } = new ObservableCollection<ToolPaneModel>(); + public ReadOnlyObservableCollection<ToolPaneModel> ToolPanes { get; } public bool ShowToolPane(string contentId) { - var pane = ToolPanes.FirstOrDefault(p => p.ContentId == contentId); + var pane = toolPanes.FirstOrDefault(p => p.ContentId == contentId); if (pane != null) { pane.Show(); @@ -115,7 +133,7 @@ namespace ICSharpCode.ILSpy.Docking public void Remove(PaneModel model) { if (model is TabPageModel document) - TabPages.Remove(document); + tabPages.Remove(document); if (model is ToolPaneModel tool) tool.IsVisible = false; } @@ -136,20 +154,22 @@ namespace ICSharpCode.ILSpy.Docking { if (state.DecompiledNodes != null) { - MainWindow.Instance.SelectNodes(state.DecompiledNodes, - inNewTabPage: false, setFocus: true, changingActiveTab: true); + MainWindow.Instance.AssemblyTreeModel.SelectNodes(state.DecompiledNodes); } else { - MainWindow.Instance.NavigateTo(new(state.ViewedUri, null)); + MainWindow.Instance.AssemblyTreeModel.NavigateTo(new(state.ViewedUri, null)); } } - MessageBus.Send(this, new DockWorkspaceActiveTabPageChangedEventArgs()); } } public void InitializeLayout(DockingManager manager) { + var panes = exportProvider.GetExportedValues<ToolPaneModel>("ToolPane").OrderBy(item => item.Title); + + this.toolPanes.AddRange(panes); + manager.LayoutUpdateStrategy = this; XmlLayoutSerializer serializer = new XmlLayoutSerializer(manager); serializer.LayoutSerializationCallback += LayoutSerializationCallback; @@ -168,7 +188,7 @@ namespace ICSharpCode.ILSpy.Docking switch (e.Model) { case LayoutAnchorable la: - e.Content = ToolPanes.FirstOrDefault(p => p.ContentId == la.ContentId); + e.Content = this.toolPanes.FirstOrDefault(p => p.ContentId == la.ContentId); e.Cancel = e.Content == null; la.CanDockAsTabbedDocument = false; if (!e.Cancel) @@ -200,23 +220,23 @@ namespace ICSharpCode.ILSpy.Docking internal void CloseAllTabs() { - foreach (var doc in TabPages.ToArray()) + foreach (var doc in tabPages.ToArray()) { if (doc.IsCloseable) - TabPages.Remove(doc); + tabPages.Remove(doc); } } internal void ResetLayout() { - foreach (var pane in ToolPanes) + foreach (var pane in toolPanes) { pane.IsVisible = false; } CloseAllTabs(); SessionSettings.DockLayout.Reset(); InitializeLayout(MainWindow.Instance.dockManager); - MainWindow.Instance.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)MainWindow.Instance.RefreshDecompiledView); + MainWindow.Instance.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)MainWindow.Instance.AssemblyTreeModel.RefreshDecompiledView); } static readonly PropertyInfo previousContainerProperty = typeof(LayoutContent).GetProperty("PreviousContainer", BindingFlags.NonPublic | BindingFlags.Instance); diff --git a/ILSpy/Docking/PaneCollection.cs b/ILSpy/Docking/PaneCollection.cs deleted file mode 100644 index 0afa5f316..000000000 --- a/ILSpy/Docking/PaneCollection.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this -// software and associated documentation files (the "Software"), to deal in the Software -// without restriction, including without limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -// to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; - -using ICSharpCode.ILSpy.ViewModels; - -namespace ICSharpCode.ILSpy.Docking -{ - public class PaneCollection<T> : INotifyCollectionChanged, ICollection<T> - where T : PaneModel, new() - { - private ObservableCollection<T> observableCollection = new ObservableCollection<T>(); - - public event NotifyCollectionChangedEventHandler? CollectionChanged; - - public PaneCollection() - { - observableCollection.CollectionChanged += (sender, e) => CollectionChanged?.Invoke(this, e); - } - - public void Add(T? item = null) - { - item ??= new T(); - - observableCollection.Add(item); - - item.IsVisible = true; - item.IsActive = true; - } - - public int Count => observableCollection.Count; - public bool IsReadOnly => false; - public void Clear() => observableCollection.Clear(); - public bool Contains(T item) => observableCollection.Contains(item); - public void CopyTo(T[] array, int arrayIndex) => observableCollection.CopyTo(array, arrayIndex); - public bool Remove(T item) => observableCollection.Remove(item); - public IEnumerator<T> GetEnumerator() => observableCollection.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => observableCollection.GetEnumerator(); - } -} diff --git a/ILSpy/ExtensionMethods.cs b/ILSpy/ExtensionMethods.cs index 374ad026e..39d623fd9 100644 --- a/ILSpy/ExtensionMethods.cs +++ b/ILSpy/ExtensionMethods.cs @@ -27,9 +27,10 @@ using System.Windows.Media; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; +using TomsToolbox.Essentials; + namespace ICSharpCode.ILSpy { /// <summary> @@ -125,9 +126,9 @@ namespace ICSharpCode.ILSpy for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++) { DependencyObject child = VisualTreeHelper.GetChild(depObj, i); - if (child != null && child is T) + if (child is T dependencyObject) { - return (T)child; + return dependencyObject; } T? childItem = FindVisualChild<T>(child); @@ -178,20 +179,37 @@ namespace ICSharpCode.ILSpy else output.AppendLine("-------------------------------------------------"); output.AppendLine("Error(s) loading plugin: " + item.PluginName); - if (item.Exception is System.Reflection.ReflectionTypeLoadException) + if (item.Exception is System.Reflection.ReflectionTypeLoadException exception) { - var e = (System.Reflection.ReflectionTypeLoadException)item.Exception; - foreach (var ex in e.LoaderExceptions) + foreach (var ex in exception.LoaderExceptions.ExceptNullItems()) { output.AppendLine(ex.ToString()); output.AppendLine(); } } else + { output.AppendLine(item.Exception.ToString()); + } } return true; } + + public static IDisposable PreserveFocus(this IInputElement? inputElement, bool preserve = true) + { + return new RestoreFocusHelper(inputElement, preserve); + } + + private sealed class RestoreFocusHelper(IInputElement? inputElement, bool preserve) : IDisposable + { + public void Dispose() + { + if (preserve) + { + inputElement?.Focus(); + } + } + } } } diff --git a/ILSpy/GlobalUsings.cs b/ILSpy/GlobalUsings.cs new file mode 100644 index 000000000..12310c612 --- /dev/null +++ b/ILSpy/GlobalUsings.cs @@ -0,0 +1 @@ +global using ICSharpCode.ILSpy.Util; \ No newline at end of file diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 7bed5bc81..ee4bc3253 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -1,5 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>WinExe</OutputType> diff --git a/ILSpy/LanguageSettings.cs b/ILSpy/LanguageSettings.cs index c4a6da66d..e38d50da0 100644 --- a/ILSpy/LanguageSettings.cs +++ b/ILSpy/LanguageSettings.cs @@ -16,22 +16,20 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; -using System.Runtime.CompilerServices; using System.Xml.Linq; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; +using TomsToolbox.Wpf; + namespace ICSharpCode.ILSpy { /// <summary> /// Represents the filters applied to the tree view. /// </summary> - public class LanguageSettings : INotifyPropertyChanged + public class LanguageSettings : ObservableObject, IChildSettings { /// <summary> /// This dictionary is necessary to remember language versions across language changes. For example, @@ -40,14 +38,17 @@ namespace ICSharpCode.ILSpy /// </summary> private readonly Dictionary<Language, LanguageVersion?> languageVersionHistory = new Dictionary<Language, LanguageVersion?>(); - public LanguageSettings(XElement element) + public LanguageSettings(XElement element, ISettingsSection parent) { + Parent = parent; this.ShowApiLevel = (ApiVisibility?)(int?)element.Element("ShowAPILevel") ?? ApiVisibility.PublicAndInternal; this.Language = Languages.GetLanguage((string)element.Element("Language")) ?? Languages.AllLanguages.First(); this.LanguageVersion = Language.LanguageVersions.FirstOrDefault(v => v.Version == (string)element.Element("LanguageVersion")) ?? Language.LanguageVersions.LastOrDefault(); } + public ISettingsSection Parent { get; } + public XElement SaveAsXml() { return new XElement( @@ -173,20 +174,6 @@ namespace ICSharpCode.ILSpy } } - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - if (PropertyChanged != null) - { - var args = new PropertyChangedEventArgs(propertyName); - - PropertyChanged(this, args); - MessageBus.Send(this, new LanguageSettingsChangedEventArgs(args)); - } - } - - // This class has been initially called FilterSettings, but then has been Hijacked to store language settings as well. // While the filter settings were some sort of local, the language settings are global. This is a bit of a mess. // There has been a lot of workarounds cloning the FilterSettings to pass them down to the tree nodes, without messing up the global language settings. diff --git a/ILSpy/Languages/CSharpILMixedLanguage.cs b/ILSpy/Languages/CSharpILMixedLanguage.cs index ebed5d1c6..6007cc18f 100644 --- a/ILSpy/Languages/CSharpILMixedLanguage.cs +++ b/ILSpy/Languages/CSharpILMixedLanguage.cs @@ -34,7 +34,6 @@ using ICSharpCode.Decompiler.Disassembler; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.Extensions; diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index 4ec839456..d261f181e 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -41,7 +41,6 @@ using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; using LanguageVersion = ICSharpCode.ILSpyX.LanguageVersion; @@ -359,15 +358,15 @@ namespace ICSharpCode.ILSpy void AddReferenceWarningMessage(MetadataFile module, ITextOutput output) { - var loadedAssembly = MainWindow.Instance.CurrentAssemblyList.GetAssemblies().FirstOrDefault(la => la.GetMetadataFileOrNull() == module); + var loadedAssembly = MainWindow.Instance.AssemblyTreeModel.AssemblyList.GetAssemblies().FirstOrDefault(la => la.GetMetadataFileOrNull() == module); if (loadedAssembly == null || !loadedAssembly.LoadedAssemblyReferencesInfo.HasErrors) return; string line1 = Properties.Resources.WarningSomeAssemblyReference; string line2 = Properties.Resources.PropertyManuallyMissingReferencesListLoadedAssemblies; AddWarningMessage(module, output, line1, line2, Properties.Resources.ShowAssemblyLoad, Images.ViewCode, delegate { - ILSpyTreeNode assemblyNode = MainWindow.Instance.FindTreeNode(module); + ILSpyTreeNode assemblyNode = MainWindow.Instance.AssemblyTreeModel.FindTreeNode(module); assemblyNode.EnsureLazyChildren(); - MainWindow.Instance.SelectNode(assemblyNode.Children.OfType<ReferenceFolderTreeNode>().Single()); + MainWindow.Instance.AssemblyTreeModel.SelectNode(assemblyNode.Children.OfType<ReferenceFolderTreeNode>().Single()); }); } diff --git a/ILSpy/Languages/ILLanguage.cs b/ILSpy/Languages/ILLanguage.cs index 01d032495..b484406d4 100644 --- a/ILSpy/Languages/ILLanguage.cs +++ b/ILSpy/Languages/ILLanguage.cs @@ -29,8 +29,8 @@ using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Solution; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.TextView; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; namespace ICSharpCode.ILSpy @@ -198,7 +198,10 @@ namespace ICSharpCode.ILSpy public override RichText? GetRichTextTooltip(IEntity entity) { var output = new AvalonEditTextOutput() { IgnoreNewLineAndIndent = true }; - var disasm = CreateDisassembler(output, MainWindow.Instance.CreateDecompilationOptions()); + var settingsService = SettingsService.Instance; + var dockWorkspace = DockWorkspace.Instance; + + var disasm = CreateDisassembler(output, settingsService.CreateDecompilationOptions(dockWorkspace.ActiveTabPage)); MetadataFile? module = entity.ParentModule?.MetadataFile; if (module == null) { diff --git a/ILSpy/Languages/Languages.cs b/ILSpy/Languages/Languages.cs index a58df78da..980f90acc 100644 --- a/ILSpy/Languages/Languages.cs +++ b/ILSpy/Languages/Languages.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.Collections.ObjectModel; using System.Linq; @@ -31,9 +33,9 @@ namespace ICSharpCode.ILSpy /// </summary> public static ReadOnlyCollection<Language> AllLanguages { get; } = Initialize(App.ExportProvider); - static ReadOnlyCollection<Language> Initialize(IExportProvider ep) + static ReadOnlyCollection<Language> Initialize(IExportProvider exportProvider) { - var languages = ep.GetExportedValues<Language>().ToList(); + var languages = exportProvider.GetExportedValues<Language>().ToList(); languages.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal)); #if DEBUG @@ -52,7 +54,8 @@ namespace ICSharpCode.ILSpy return AllLanguages.FirstOrDefault(l => l.Name == name) ?? AllLanguages.First(); } - static ILLanguage ilLanguage; + static ILLanguage? ilLanguage; + public static ILLanguage ILLanguage { get { if (ilLanguage == null) diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index 39407e92e..dcc13af3b 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -1,72 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> - -<Window - x:Class="ICSharpCode.ILSpy.MainWindow" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:tv="clr-namespace:ICSharpCode.ILSpy.Controls.TreeView" - xmlns:local="clr-namespace:ICSharpCode.ILSpy" - xmlns:avalondock="https://github.com/Dirkster99/AvalonDock" - xmlns:controls="clr-namespace:ICSharpCode.ILSpy.Controls" - xmlns:docking="clr-namespace:ICSharpCode.ILSpy.Docking" - xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties" - Title="ILSpy" - MinWidth="250" - MinHeight="200" - UseLayoutRounding="True" - TextOptions.TextFormattingMode="Display" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d:DesignHeight="500" d:DesignWidth="500" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" - xmlns:b="http://schemas.microsoft.com/xaml/behaviors" - xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes" - xmlns:toms="urn:TomsToolbox" - xmlns:viewModels="clr-namespace:ICSharpCode.ILSpy.ViewModels" - xmlns:treeNodes="clr-namespace:ICSharpCode.ILSpy.TreeNodes" - xmlns:util="clr-namespace:ICSharpCode.ILSpy.Util" - d:DataContext="{d:DesignInstance local:MainWindowViewModel}"> +<Window x:Class="ICSharpCode.ILSpy.MainWindow" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:local="clr-namespace:ICSharpCode.ILSpy" + xmlns:avalondock="https://github.com/Dirkster99/AvalonDock" + xmlns:controls="clr-namespace:ICSharpCode.ILSpy.Controls" + xmlns:docking="clr-namespace:ICSharpCode.ILSpy.Docking" + xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties" + Title="ILSpy" + MinWidth="250" + MinHeight="200" + UseLayoutRounding="True" + TextOptions.TextFormattingMode="Display" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d:DesignHeight="500" d:DesignWidth="500" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" + xmlns:b="http://schemas.microsoft.com/xaml/behaviors" + xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes" + xmlns:toms="urn:TomsToolbox" + xmlns:viewModels="clr-namespace:ICSharpCode.ILSpy.ViewModels" + d:DataContext="{d:DesignInstance local:MainWindowViewModel}"> <Window.Resources> - <tv:SharpTreeView x:Key="AssemblyTreeView" - AutomationProperties.Name="Assemblies and Classes" - SelectionChanged="TreeView_SelectionChanged" - ShowRoot="False" - AllowDropOrder="True" - AllowDrop="True" - BorderThickness="0" Visibility="Visible"> - <tv:SharpTreeView.ItemContainerStyle> - <Style TargetType="tv:SharpTreeViewItem"> - <Setter Property="Template"> - <Setter.Value> - <ControlTemplate TargetType="{x:Type tv:SharpTreeViewItem}" d:DataContext="{d:DesignInstance treeNodes:ILSpyTreeNode}"> - <Border Background="Transparent"> - <Border Background="{TemplateBinding Background}"> - <tv:SharpTreeNodeView x:Name="nodeView" HorizontalAlignment="Left" /> - </Border> - </Border> - <ControlTemplate.Triggers> - <DataTrigger Binding="{Binding IsAutoLoaded}" Value="True"> - <Setter Property="Foreground" Value="SteelBlue" /> - </DataTrigger> - <DataTrigger Binding="{Binding IsPublicAPI}" Value="False"> - <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" /> - </DataTrigger> - <Trigger Property="IsSelected" Value="True"> - <Setter TargetName="nodeView" Property="TextBackground" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" /> - <Setter TargetName="nodeView" Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" /> - </Trigger> - <Trigger Property="IsEnabled" Value="False"> - <Setter TargetName="nodeView" Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" /> - </Trigger> - </ControlTemplate.Triggers> - </ControlTemplate> - </Setter.Value> - </Setter> - </Style> - </tv:SharpTreeView.ItemContainerStyle> - </tv:SharpTreeView> - - <DataTemplate DataType="{x:Type viewModels:AssemblyListPaneModel}"> - <ContentControl Content="{StaticResource AssemblyTreeView}" /> - </DataTemplate> <DataTemplate DataType="{x:Type viewModels:TabPageModel}"> <ContentPresenter Content="{Binding Content}" /> @@ -76,40 +29,15 @@ <ContentPresenter Content="{Binding Content}" /> </DataTemplate> - <toms:BindingRelay x:Key="WindowBinding" DataContext="{Binding}" /> - </Window.Resources> <b:Interaction.Behaviors> <themes:WindowStyleManagerBehavior /> </b:Interaction.Behaviors> - <Window.CommandBindings> - <CommandBinding - Command="Open" - Executed="OpenCommandExecuted" /> - <CommandBinding - Command="Refresh" - Executed="RefreshCommandExecuted" /> - <CommandBinding - Command="Save" - CanExecute="SaveCommandCanExecute" - Executed="SaveCommandExecuted" /> - <CommandBinding - Command="BrowseBack" - CanExecute="BackCommandCanExecute" - Executed="BackCommandExecuted" /> - <CommandBinding - Command="BrowseForward" - CanExecute="ForwardCommandCanExecute" - Executed="ForwardCommandExecuted" /> - <CommandBinding - Command="Search" - Executed="SearchCommandExecuted" /> - </Window.CommandBindings> - <Window.InputBindings> <KeyBinding Key="R" Modifiers="Control" Command="{x:Static local:ILSpyCommands.Analyze}" /> + <KeyBinding Key="Z" Modifiers="Control" Command="{x:Static NavigationCommands.BrowseBack}" /> </Window.InputBindings> <Window.TaskbarItemInfo> @@ -122,9 +50,12 @@ <MenuItem Header="{x:Static properties:Resources._File}" Tag="_File" /> <!-- contents of file menu are added using MEF --> <MenuItem Header="{x:Static properties:Resources._View}" Tag="_View"> - <MenuItem Header="{x:Static properties:Resources.Show_publiconlyTypesMembers}" IsCheckable="True" IsChecked="{Binding SessionSettings.LanguageSettings.ApiVisPublicOnly}" /> - <MenuItem Header="{x:Static properties:Resources.Show_internalTypesMembers}" IsCheckable="True" IsChecked="{Binding SessionSettings.LanguageSettings.ApiVisPublicAndInternal}" /> - <MenuItem Header="{x:Static properties:Resources.Show_allTypesAndMembers}" IsCheckable="True" IsChecked="{Binding SessionSettings.LanguageSettings.ApiVisAll}" /> + <MenuItem Header="{x:Static properties:Resources.Show_publiconlyTypesMembers}" IsCheckable="True" + IsChecked="{Binding SessionSettings.LanguageSettings.ApiVisPublicOnly}" /> + <MenuItem Header="{x:Static properties:Resources.Show_internalTypesMembers}" IsCheckable="True" + IsChecked="{Binding SessionSettings.LanguageSettings.ApiVisPublicAndInternal}" /> + <MenuItem Header="{x:Static properties:Resources.Show_allTypesAndMembers}" IsCheckable="True" + IsChecked="{Binding SessionSettings.LanguageSettings.ApiVisAll}" /> <Separator /> <MenuItem Header="{x:Static properties:Resources.Theme}" ItemsSource="{x:Static themes:ThemeManager.AllThemes}"> <MenuItem.ItemContainerStyle> @@ -137,7 +68,7 @@ <Setter.Value> <MultiBinding Converter="{x:Static toms:BinaryOperationConverter.Equality}" Mode="OneWay"> <Binding /> - <Binding Source="{StaticResource WindowBinding}" Path="DataContext.SessionSettings.Theme" /> + <Binding Path="DataContext.SessionSettings.Theme" RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}"/> </MultiBinding> </Setter.Value> </Setter> @@ -145,9 +76,12 @@ </MenuItem.ItemContainerStyle> </MenuItem> <MenuItem Header="{x:Static properties:Resources.UILanguage}"> - <MenuItem Header="{x:Static properties:Resources.UILanguage_System}" IsCheckable="True" IsChecked="{Binding SessionSettings.CurrentCulture, Converter={controls:CultureSelectionConverter}, ConverterParameter={x:Null}}" /> - <MenuItem Header="English" IsCheckable="True" IsChecked="{Binding SessionSettings.CurrentCulture, Converter={controls:CultureSelectionConverter}, ConverterParameter=en-US}" /> - <MenuItem Header="中文" IsCheckable="True" IsChecked="{Binding SessionSettings.CurrentCulture, Converter={controls:CultureSelectionConverter}, ConverterParameter=zh-Hans}" /> + <MenuItem Header="{x:Static properties:Resources.UILanguage_System}" IsCheckable="True" + IsChecked="{Binding SessionSettings.CurrentCulture, Converter={controls:CultureSelectionConverter}, ConverterParameter={x:Null}}" /> + <MenuItem Header="English" IsCheckable="True" + IsChecked="{Binding SessionSettings.CurrentCulture, Converter={controls:CultureSelectionConverter}, ConverterParameter=en-US}" /> + <MenuItem Header="中文" IsCheckable="True" + IsChecked="{Binding SessionSettings.CurrentCulture, Converter={controls:CultureSelectionConverter}, ConverterParameter=zh-Hans}" /> </MenuItem> </MenuItem> <MenuItem Header="{x:Static properties:Resources._Window}" Tag="_Window" /> @@ -183,52 +117,52 @@ <!-- 'Open' toolbar category is inserted here --> <Separator /> <ComboBox Name="assemblyListComboBox" Width="100" MaxDropDownHeight="Auto" - ItemsSource="{Binding AssemblyListManager.AssemblyLists}" - ToolTip="{x:Static properties:Resources.SelectAssemblyListDropdownTooltip}" - SelectedItem="{Binding SessionSettings.ActiveAssemblyList}" /> + ItemsSource="{Binding AssemblyListManager.AssemblyLists}" + ToolTip="{x:Static properties:Resources.SelectAssemblyListDropdownTooltip}" + SelectedItem="{Binding SessionSettings.ActiveAssemblyList}" /> <Button Command="{x:Static local:ILSpyCommands.ManageAssemblyListsCommand}" - ToolTip="{x:Static properties:Resources.ManageAssemblyLists}"> + ToolTip="{x:Static properties:Resources.ManageAssemblyLists}"> <Image Width="16" Height="16" Source="{controls:XamlResource Images/AssemblyList}" - Style="{StaticResource DarkModeAwareImageStyle}" /> + Style="{StaticResource DarkModeAwareImageStyle}" /> </Button> <Separator /> <CheckBox IsChecked="{Binding SessionSettings.LanguageSettings.ApiVisPublicOnly}" - ToolTip="{x:Static properties:Resources.ShowPublicOnlyTypesMembers}"> + ToolTip="{x:Static properties:Resources.ShowPublicOnlyTypesMembers}"> <Image Width="16" Height="16" Source="{controls:XamlResource Images/ShowPublicOnly}" - Style="{StaticResource DarkModeAwareImageStyle}" /> + Style="{StaticResource DarkModeAwareImageStyle}" /> </CheckBox> <CheckBox IsChecked="{Binding SessionSettings.LanguageSettings.ApiVisPublicAndInternal}" - ToolTip="{x:Static properties:Resources.ShowInternalTypesMembers}"> + ToolTip="{x:Static properties:Resources.ShowInternalTypesMembers}"> <Image Width="16" Height="16" Source="{controls:XamlResource Images/ShowPrivateInternal}" - Style="{StaticResource DarkModeAwareImageStyle}" /> + Style="{StaticResource DarkModeAwareImageStyle}" /> </CheckBox> <CheckBox IsChecked="{Binding SessionSettings.LanguageSettings.ApiVisAll}" - ToolTip="{x:Static properties:Resources.ShowAllTypesAndMembers}"> + ToolTip="{x:Static properties:Resources.ShowAllTypesAndMembers}"> <Image Width="16" Height="16" Source="{controls:XamlResource Images/ShowAll}" - Style="{StaticResource DarkModeAwareImageStyle}" /> + Style="{StaticResource DarkModeAwareImageStyle}" /> </CheckBox> <Separator /> <ComboBox Name="languageComboBox" DisplayMemberPath="Name" Width="100" MaxDropDownHeight="Auto" - IsEnabled="{Binding Workspace.ActiveTabPage.SupportsLanguageSwitching}" - ItemsSource="{x:Static local:Languages.AllLanguages}" - ToolTip="{x:Static properties:Resources.SelectLanguageDropdownTooltip}" - SelectedItem="{Binding SessionSettings.LanguageSettings.Language}" /> + IsEnabled="{Binding Workspace.ActiveTabPage.SupportsLanguageSwitching}" + ItemsSource="{x:Static local:Languages.AllLanguages}" + ToolTip="{x:Static properties:Resources.SelectLanguageDropdownTooltip}" + SelectedItem="{Binding SessionSettings.LanguageSettings.Language}" /> <ComboBox Name="languageVersionComboBox" DisplayMemberPath="DisplayName" Width="120" MaxDropDownHeight="Auto" - ToolTip="{x:Static properties:Resources.SelectVersionDropdownTooltip}" - Visibility="{Binding SelectedItem.HasLanguageVersions, ElementName=languageComboBox, Converter={toms:BooleanToVisibilityConverter}}" - IsEnabled="{Binding Workspace.ActiveTabPage.SupportsLanguageSwitching}" - ItemsSource="{Binding SelectedItem.LanguageVersions, ElementName=languageComboBox, UpdateSourceTrigger=PropertyChanged}" - SelectedItem="{Binding SessionSettings.LanguageSettings.LanguageVersion, UpdateSourceTrigger=PropertyChanged}" /> + ToolTip="{x:Static properties:Resources.SelectVersionDropdownTooltip}" + Visibility="{Binding SelectedItem.HasLanguageVersions, ElementName=languageComboBox, Converter={toms:BooleanToVisibilityConverter}}" + IsEnabled="{Binding Workspace.ActiveTabPage.SupportsLanguageSwitching}" + ItemsSource="{Binding SelectedItem.LanguageVersions, ElementName=languageComboBox, UpdateSourceTrigger=PropertyChanged}" + SelectedItem="{Binding SessionSettings.LanguageSettings.LanguageVersion, UpdateSourceTrigger=PropertyChanged}" /> </ToolBar> <!-- Update panel --> <Border DockPanel.Dock="Top" BorderBrush="Black" BorderThickness="1" Name="updatePanel" Visibility="Collapsed"> <DockPanel KeyboardNavigation.TabNavigation="Contained"> - <Button DockPanel.Dock="Right" Click="updatePanelCloseButtonClick" MinWidth="0">X</Button> + <Button DockPanel.Dock="Right" Click="UpdatePanelCloseButtonClick" MinWidth="0">X</Button> <StackPanel Orientation="Horizontal"> <TextBlock Name="updatePanelMessage" Margin="4,0" VerticalAlignment="Center" - Text="{x:Static properties:Resources.ILSpyVersionAvailable}" /> - <Button Name="downloadOrCheckUpdateButton" Click="downloadOrCheckUpdateButtonClick" - Content="{x:Static properties:Resources.Download}" /> + Text="{x:Static properties:Resources.ILSpyVersionAvailable}" /> + <Button Name="downloadOrCheckUpdateButton" Click="DownloadOrCheckUpdateButtonClick" + Content="{x:Static properties:Resources.Download}" /> </StackPanel> </DockPanel> </Border> @@ -236,19 +170,19 @@ <StatusBar x:Name="statusBar" DockPanel.Dock="Bottom" Height="26" Visibility="Collapsed"> <StatusBarItem DockPanel.Dock="Right"> <TextBlock VerticalAlignment="Center" - HorizontalAlignment="Right" - x:Name="statusLabel" - ToolTip="{x:Static properties:Resources.Status}" - Text="{x:Static properties:Resources.StandBy}" /> + HorizontalAlignment="Right" + x:Name="statusLabel" + ToolTip="{x:Static properties:Resources.Status}" + Text="{x:Static properties:Resources.StandBy}" /> </StatusBarItem> </StatusBar> <avalondock:DockingManager x:Name="dockManager" - DataContext="{Binding Workspace}" - AnchorablesSource="{Binding ToolPanes}" - DocumentsSource="{Binding TabPages}" - ActiveContent="{Binding ActiveTabPage, Mode=TwoWay, Converter={docking:TabPageGuardConverter}}" - AllowMixedOrientation="True"> + DataContext="{Binding Workspace}" + AnchorablesSource="{Binding ToolPanes}" + DocumentsSource="{Binding TabPages}" + ActiveContent="{Binding ActiveTabPage, Mode=TwoWay, Converter={docking:TabPageGuardConverter}}" + AllowMixedOrientation="True"> <avalondock:DockingManager.DocumentHeaderTemplate> <DataTemplate DataType="{x:Type viewModels:PaneModel}"> diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index cdd8cb429..f3668f6f0 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -17,52 +17,26 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Collections.Generic; -using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; -using System.IO; +using System.Drawing; using System.Linq; -using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; -using System.Text; using System.Threading.Tasks; using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; -using System.Windows.Navigation; -using System.Windows.Threading; using AvalonDock.Layout.Serialization; -using ICSharpCode.Decompiler; -using ICSharpCode.Decompiler.Documentation; -using ICSharpCode.Decompiler.Metadata; -using ICSharpCode.Decompiler.TypeSystem; -using ICSharpCode.Decompiler.TypeSystem.Implementation; -using ICSharpCode.ILSpy.AppEnv; -using ICSharpCode.ILSpy.Commands; -using ICSharpCode.ILSpy.Controls.TreeView; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Docking; -using ICSharpCode.ILSpy.Options; -using ICSharpCode.ILSpy.Search; -using ICSharpCode.ILSpy.TextView; -using ICSharpCode.ILSpy.Themes; using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.Updates; -using ICSharpCode.ILSpy.Util; -using ICSharpCode.ILSpy.ViewModels; -using ICSharpCode.ILSpyX; -using ICSharpCode.ILSpyX.Extensions; using ICSharpCode.ILSpyX.FileLoaders; using ICSharpCode.ILSpyX.Settings; using ICSharpCode.ILSpyX.TreeView; -using Microsoft.Win32; - -using TomsToolbox.Composition; +using Screen = System.Windows.Forms.Screen; namespace ICSharpCode.ILSpy { @@ -71,99 +45,36 @@ namespace ICSharpCode.ILSpy /// </summary> partial class MainWindow : Window { - bool refreshInProgress, changingActiveTab; - readonly NavigationHistory<NavigationState> history = new NavigationHistory<NavigationState>(); - - AssemblyList assemblyList; - AssemblyListTreeNode assemblyListTreeNode; - static MainWindow instance; + private readonly MainWindowViewModel mainWindowViewModel = new(); + public static MainWindow Instance { get { return instance; } } - public SharpTreeView? AssemblyTreeView { + public AssemblyTreeModel AssemblyTreeModel { get { - return FindResource("AssemblyTreeView") as SharpTreeView; + return App.ExportProvider.GetExportedValue<AssemblyTreeModel>(); } } - public DecompilationOptions CreateDecompilationOptions() - { - var decompilerView = DockWorkspace.Instance.ActiveTabPage.Content as IProgress<DecompilationProgress>; - return new DecompilationOptions(CurrentLanguageVersion, SettingsService.Instance.DecompilerSettings, SettingsService.Instance.DisplaySettings) { Progress = decompilerView }; - } - public MainWindow() { instance = this; - var sessionSettings = SettingsService.Instance.SessionSettings; - // Make sure Images are initialized on the UI thread. this.Icon = Images.ILSpyIcon; - this.DataContext = new MainWindowViewModel { - Workspace = DockWorkspace.Instance, - SessionSettings = sessionSettings, - AssemblyListManager = SettingsService.Instance.AssemblyListManager - }; + this.DataContext = mainWindowViewModel; - SettingsService.Instance.AssemblyListManager.CreateDefaultAssemblyLists(); - if (!string.IsNullOrEmpty(sessionSettings.CurrentCulture)) - { - System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(sessionSettings.CurrentCulture); - } InitializeComponent(); - InitToolPanes(); - DockWorkspace.Instance.InitializeLayout(dockManager); - - MessageBus<SessionSettingsChangedEventArgs>.Subscribers += (sender, e) => SessionSettings_PropertyChanged(sender, e); - MessageBus<LanguageSettingsChangedEventArgs>.Subscribers += (sender, e) => LanguageSettings_PropertyChanged(sender, e); - MessageBus<DockWorkspaceActiveTabPageChangedEventArgs>.Subscribers += DockWorkspace_ActiveTabPageChanged; - - InitMainMenu(); - InitWindowMenu(); - InitToolbar(); - InitFileLoaders(); - ContextMenuProvider.Add(AssemblyTreeView); - - this.Loaded += MainWindow_Loaded; - } - private void DockWorkspace_ActiveTabPageChanged(object? sender, EventArgs e) - { - DockWorkspace dock = DockWorkspace.Instance; + mainWindowViewModel.Workspace.InitializeLayout(dockManager); - var windowMenuItem = mainMenu.Items.OfType<MenuItem>().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); - foreach (MenuItem menuItem in windowMenuItem.Items.OfType<MenuItem>()) - { - if (menuItem.IsCheckable && menuItem.Tag is TabPageModel) - { - menuItem.IsChecked = menuItem.Tag == dock.ActiveTabPage; - } - } - } + MenuService.Instance.Init(mainMenu, toolBar, InputBindings); - private void SessionSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) - { - var sessionSettings = SettingsService.Instance.SessionSettings; - - switch (e.PropertyName) - { - case nameof(SessionSettings.ActiveAssemblyList): - ShowAssemblyList(sessionSettings.ActiveAssemblyList); - break; - case nameof(SessionSettings.Theme): - // update syntax highlighting and force reload (AvalonEdit does not automatically refresh on highlighting change) - DecompilerTextView.RegisterHighlighting(); - DecompileSelectedNodes(DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState); - break; - case nameof(SessionSettings.CurrentCulture): - MessageBox.Show(Properties.Resources.SettingsChangeRestartRequired, "ILSpy"); - break; - } + InitFileLoaders(); } void SetWindowBounds(Rect bounds) @@ -174,367 +85,6 @@ namespace ICSharpCode.ILSpy this.Height = bounds.Height; } - #region Toolbar extensibility - - void InitToolbar() - { - int navigationPos = 0; - int openPos = 1; - var toolbarCommands = App.ExportProvider.GetExports<ICommand, IToolbarCommandMetadata>("ToolbarCommand"); - foreach (var commandGroup in toolbarCommands.OrderBy(c => c.Metadata.ToolbarOrder).GroupBy(c => Properties.Resources.ResourceManager.GetString(c.Metadata.ToolbarCategory))) - { - if (commandGroup.Key == Properties.Resources.ResourceManager.GetString("Navigation")) - { - foreach (var command in commandGroup) - { - toolBar.Items.Insert(navigationPos++, MakeToolbarItem(command)); - openPos++; - } - } - else if (commandGroup.Key == Properties.Resources.ResourceManager.GetString("Open")) - { - foreach (var command in commandGroup) - { - toolBar.Items.Insert(openPos++, MakeToolbarItem(command)); - } - } - else - { - toolBar.Items.Add(new Separator()); - foreach (var command in commandGroup) - { - toolBar.Items.Add(MakeToolbarItem(command)); - } - } - } - - } - - Button MakeToolbarItem(IExport<ICommand, IToolbarCommandMetadata> command) - { - return new Button { - Style = ThemeManager.Current.CreateToolBarButtonStyle(), - Command = CommandWrapper.Unwrap(command.Value), - ToolTip = Properties.Resources.ResourceManager.GetString(command.Metadata.ToolTip), - Tag = command.Metadata.Tag, - Content = new Image { - Width = 16, - Height = 16, - Source = Images.Load(command.Value, command.Metadata.ToolbarIcon) - } - }; - } - #endregion - - #region Main Menu extensibility - - void InitMainMenu() - { - var mainMenuCommands = App.ExportProvider.GetExports<ICommand, IMainMenuCommandMetadata>("MainMenuCommand"); - // Start by constructing the individual flat menus - var parentMenuItems = new Dictionary<string, MenuItem>(); - var menuGroups = mainMenuCommands.OrderBy(c => c.Metadata.MenuOrder).GroupBy(c => c.Metadata.ParentMenuID); - foreach (var menu in menuGroups) - { - // Get or add the target menu item and add all items grouped by menu category - var parentMenuItem = GetOrAddParentMenuItem(menu.Key, menu.Key); - foreach (var category in menu.GroupBy(c => c.Metadata.MenuCategory)) - { - if (parentMenuItem.Items.Count > 0) - { - parentMenuItem.Items.Add(new Separator() { Tag = category.Key }); - } - foreach (var entry in category) - { - if (menuGroups.Any(g => g.Key == entry.Metadata.MenuID)) - { - var menuItem = GetOrAddParentMenuItem(entry.Metadata.MenuID, entry.Metadata.Header); - // replace potential dummy text with real name - menuItem.Header = GetResourceString(entry.Metadata.Header); - parentMenuItem.Items.Add(menuItem); - } - else - { - MenuItem menuItem = new MenuItem(); - menuItem.Command = CommandWrapper.Unwrap(entry.Value); - menuItem.Tag = entry.Metadata.MenuID; - menuItem.Header = GetResourceString(entry.Metadata.Header); - if (!string.IsNullOrEmpty(entry.Metadata.MenuIcon)) - { - menuItem.Icon = new Image { - Width = 16, - Height = 16, - Source = Images.Load(entry.Value, entry.Metadata.MenuIcon) - }; - } - - menuItem.IsEnabled = entry.Metadata.IsEnabled; - if (entry.Value is ToggleableCommand toggle) - { - menuItem.IsCheckable = true; - menuItem.SetBinding(MenuItem.IsCheckedProperty, new Binding("IsChecked") { Source = entry.Value, Mode = BindingMode.OneWay }); - } - - menuItem.InputGestureText = entry.Metadata.InputGestureText; - parentMenuItem.Items.Add(menuItem); - } - } - } - } - - foreach (var (key, item) in parentMenuItems) - { - if (item.Parent == null) - { - mainMenu.Items.Add(item); - } - } - - MenuItem GetOrAddParentMenuItem(string menuID, string resourceKey) - { - if (!parentMenuItems.TryGetValue(menuID, out var parentMenuItem)) - { - var topLevelMenuItem = mainMenu.Items.OfType<MenuItem>().FirstOrDefault(m => (string)m.Tag == menuID); - if (topLevelMenuItem == null) - { - parentMenuItem = new MenuItem(); - parentMenuItem.Header = GetResourceString(resourceKey); - parentMenuItem.Tag = menuID; - parentMenuItems.Add(menuID, parentMenuItem); - } - else - { - parentMenuItems.Add(menuID, topLevelMenuItem); - parentMenuItem = topLevelMenuItem; - } - } - return parentMenuItem; - } - } - - internal static string? GetResourceString(string key) - { - if (string.IsNullOrEmpty(key)) - { - return null; - } - string? value = Properties.Resources.ResourceManager.GetString(key); - if (!string.IsNullOrEmpty(value)) - { - return value; - } - return key; - } - #endregion - - #region Tool Pane extensibility - - private void InitToolPanes() - { - var toolPanes = App.ExportProvider.GetExportedValues<ToolPaneModel>("ToolPane").OrderBy(item => item.Title); - - DockWorkspace.Instance.ToolPanes.AddRange(toolPanes); - } - - private void InitWindowMenu() - { - var windowMenuItem = mainMenu.Items.OfType<MenuItem>().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); - Separator separatorBeforeTools, separatorBeforeDocuments; - windowMenuItem.Items.Add(separatorBeforeTools = new Separator()); - windowMenuItem.Items.Add(separatorBeforeDocuments = new Separator()); - - var dock = DockWorkspace.Instance; - dock.ToolPanes.CollectionChanged += ToolsChanged; - dock.TabPages.CollectionChanged += TabsChanged; - - ToolsChanged(dock.ToolPanes, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - TabsChanged(dock.TabPages, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - - void ToolsChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - int endIndex = windowMenuItem.Items.IndexOf(separatorBeforeDocuments); - int startIndex = windowMenuItem.Items.IndexOf(separatorBeforeTools) + 1; - int insertionIndex; - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - insertionIndex = Math.Min(endIndex, startIndex + e.NewStartingIndex); - foreach (ToolPaneModel pane in e.NewItems) - { - MenuItem menuItem = CreateMenuItem(pane); - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - case NotifyCollectionChangedAction.Remove: - foreach (ToolPaneModel pane in e.OldItems) - { - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[i]; - if (pane == item.Tag) - { - windowMenuItem.Items.RemoveAt(i); - item.Tag = null; - endIndex--; - break; - } - } - } - break; - case NotifyCollectionChangedAction.Replace: - break; - case NotifyCollectionChangedAction.Move: - break; - case NotifyCollectionChangedAction.Reset: - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[0]; - item.Tag = null; - windowMenuItem.Items.RemoveAt(i); - endIndex--; - } - insertionIndex = endIndex; - foreach (ToolPaneModel pane in dock.ToolPanes) - { - MenuItem menuItem = CreateMenuItem(pane); - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - } - - MenuItem CreateMenuItem(ToolPaneModel pane) - { - MenuItem menuItem = new MenuItem(); - menuItem.Command = pane.AssociatedCommand ?? new ToolPaneCommand(pane.ContentId); - menuItem.Header = pane.Title; - menuItem.Tag = pane; - var shortcutKey = pane.ShortcutKey; - if (shortcutKey != null) - { - InputBindings.Add(new InputBinding(menuItem.Command, shortcutKey)); - menuItem.InputGestureText = shortcutKey.GetDisplayStringForCulture(System.Globalization.CultureInfo.CurrentUICulture); - } - if (!string.IsNullOrEmpty(pane.Icon)) - { - menuItem.Icon = new Image { - Width = 16, - Height = 16, - Source = Images.Load(pane, pane.Icon) - }; - } - - return menuItem; - } - } - - void TabsChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - int endIndex = windowMenuItem.Items.Count; - int startIndex = windowMenuItem.Items.IndexOf(separatorBeforeDocuments) + 1; - int insertionIndex; - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - insertionIndex = Math.Min(endIndex, startIndex + e.NewStartingIndex); - foreach (TabPageModel pane in e.NewItems) - { - MenuItem menuItem = CreateMenuItem(pane); - pane.PropertyChanged += TabPageChanged; - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - case NotifyCollectionChangedAction.Remove: - foreach (TabPageModel pane in e.OldItems) - { - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[i]; - if (pane == item.Tag) - { - windowMenuItem.Items.RemoveAt(i); - pane.PropertyChanged -= TabPageChanged; - item.Tag = null; - endIndex--; - break; - } - } - } - break; - case NotifyCollectionChangedAction.Replace: - break; - case NotifyCollectionChangedAction.Move: - break; - case NotifyCollectionChangedAction.Reset: - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[i]; - windowMenuItem.Items.RemoveAt(i); - ((TabPageModel)item.Tag).PropertyChanged -= TabPageChanged; - endIndex--; - } - insertionIndex = endIndex; - foreach (TabPageModel pane in dock.TabPages) - { - MenuItem menuItem = CreateMenuItem(pane); - pane.PropertyChanged += TabPageChanged; - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - } - - MenuItem CreateMenuItem(TabPageModel pane) - { - MenuItem menuItem = new MenuItem(); - menuItem.Command = new TabPageCommand(pane); - menuItem.Header = pane.Title.Length > 20 ? pane.Title.Substring(20) + "..." : pane.Title; - menuItem.Tag = pane; - menuItem.IsCheckable = true; - - return menuItem; - } - } - - static void TabPageChanged(object? sender, PropertyChangedEventArgs e) - { - var windowMenuItem = Instance.mainMenu.Items.OfType<MenuItem>().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); - foreach (MenuItem menuItem in windowMenuItem.Items.OfType<MenuItem>()) - { - if (menuItem.IsCheckable && menuItem.Tag == sender) - { - string title = ((TabPageModel)sender).Title; - menuItem.Header = title.Length > 20 ? title.Substring(0, 20) + "..." : title; - } - } - } - } - - public void ShowInTopPane(string title, object content) - { - var model = DockWorkspace.Instance.ToolPanes.OfType<LegacyToolPaneModel>().FirstOrDefault(p => p.Content == content); - if (model == null) - { - model = new LegacyToolPaneModel(title, content, LegacyToolPaneLocation.Top); - DockWorkspace.Instance.ToolPanes.Add(model); - } - model.Show(); - } - - public void ShowInBottomPane(string title, object content) - { - var model = DockWorkspace.Instance.ToolPanes.OfType<LegacyToolPaneModel>().FirstOrDefault(p => p.Content == content); - if (model == null) - { - model = new LegacyToolPaneModel(title, content, LegacyToolPaneLocation.Bottom); - DockWorkspace.Instance.ToolPanes.Add(model); - } - model.Show(); - } - #endregion - #region File Loader extensibility void InitFileLoaders() @@ -549,27 +99,22 @@ namespace ICSharpCode.ILSpy #endregion #region Message Hook + protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); - PresentationSource source = PresentationSource.FromVisual(this); + + var source = PresentationSource.FromVisual(this); var sessionSettings = SettingsService.Instance.SessionSettings; // Validate and Set Window Bounds - Rect bounds = Rect.Transform(sessionSettings.WindowBounds, source.CompositionTarget.TransformToDevice); - var boundsRect = new System.Drawing.Rectangle((int)bounds.Left, (int)bounds.Top, (int)bounds.Width, (int)bounds.Height); - bool boundsOK = false; - foreach (var screen in System.Windows.Forms.Screen.AllScreens) - { - var intersection = System.Drawing.Rectangle.Intersect(boundsRect, screen.WorkingArea); - if (intersection.Width > 10 && intersection.Height > 10) - boundsOK = true; - } - if (boundsOK) - SetWindowBounds(sessionSettings.WindowBounds); - else - SetWindowBounds(SessionSettings.DefaultWindowBounds); + var windowBounds = Rect.Transform(sessionSettings.WindowBounds, source?.CompositionTarget?.TransformToDevice ?? Matrix.Identity); + var boundsRect = new Rectangle((int)windowBounds.Left, (int)windowBounds.Top, (int)windowBounds.Width, (int)windowBounds.Height); + + bool areBoundsValid = Screen.AllScreens.Any(screen => Rectangle.Intersect(boundsRect, screen.WorkingArea) is { Width: > 10, Height: > 10 }); + + SetWindowBounds(areBoundsValid ? sessionSettings.WindowBounds : SessionSettings.DefaultWindowBounds); this.WindowState = sessionSettings.WindowState; } @@ -599,281 +144,11 @@ namespace ICSharpCode.ILSpy } } - public AssemblyList CurrentAssemblyList { - get { return assemblyList; } - } - - List<LoadedAssembly> commandLineLoadedAssemblies = new List<LoadedAssembly>(); - - internal async Task HandleSingleInstanceCommandLineArguments(string[] args) - { - var cmdArgs = CommandLineArguments.Create(args); - - await Dispatcher.InvokeAsync(() => { - if (HandleCommandLineArguments(cmdArgs)) - { - if (!cmdArgs.NoActivate && WindowState == WindowState.Minimized) - WindowState = WindowState.Normal; - - HandleCommandLineArgumentsAfterShowList(cmdArgs); - } - }); - } - - bool HandleCommandLineArguments(CommandLineArguments args) - { - LoadAssemblies(args.AssembliesToLoad, commandLineLoadedAssemblies, focusNode: false); - if (args.Language != null) - SettingsService.Instance.SessionSettings.LanguageSettings.Language = Languages.GetLanguage(args.Language); - return true; - } - - /// <summary> - /// Called on startup or when passed arguments via WndProc from a second instance. - /// In the format case, spySettings is non-null; in the latter it is null. - /// </summary> - void HandleCommandLineArgumentsAfterShowList(CommandLineArguments args, ILSpySettings? spySettings = null) - { - var sessionSettings = SettingsService.Instance.SessionSettings; - - var relevantAssemblies = commandLineLoadedAssemblies.ToList(); - commandLineLoadedAssemblies.Clear(); // clear references once we don't need them anymore - NavigateOnLaunch(args.NavigateTo, sessionSettings.ActiveTreeViewPath, spySettings, relevantAssemblies); - if (args.Search != null) - { - var searchPane = App.ExportProvider.GetExportedValue<SearchPaneModel>(); - - searchPane.SearchTerm = args.Search; - searchPane.Show(); - } - } - - async void NavigateOnLaunch(string navigateTo, string[] activeTreeViewPath, ILSpySettings? spySettings, List<LoadedAssembly> relevantAssemblies) - { - var initialSelection = AssemblyTreeView.SelectedItem; - if (navigateTo != null) - { - bool found = false; - if (navigateTo.StartsWith("N:", StringComparison.Ordinal)) - { - string namespaceName = navigateTo.Substring(2); - foreach (LoadedAssembly asm in relevantAssemblies) - { - AssemblyTreeNode? asmNode = assemblyListTreeNode.FindAssemblyNode(asm); - if (asmNode != null) - { - // FindNamespaceNode() blocks the UI if the assembly is not yet loaded, - // so use an async wait instead. - await asm.GetMetadataFileAsync().Catch<Exception>(ex => { }); - NamespaceTreeNode? nsNode = asmNode.FindNamespaceNode(namespaceName); - if (nsNode != null) - { - found = true; - if (AssemblyTreeView.SelectedItem == initialSelection) - { - SelectNode(nsNode); - } - break; - } - } - } - } - else if (navigateTo == "none") - { - // Don't navigate anywhere; start empty. - // Used by ILSpy VS addin, it'll send us the real location to navigate to via IPC. - found = true; - } - else - { - IEntity? mr = await Task.Run(() => FindEntityInRelevantAssemblies(navigateTo, relevantAssemblies)); - // Make sure we wait for assemblies being loaded... - // BeginInvoke in LoadedAssembly.LookupReferencedAssemblyInternal - await Dispatcher.InvokeAsync(delegate { }, DispatcherPriority.Normal); - if (mr != null && mr.ParentModule.MetadataFile != null) - { - found = true; - if (AssemblyTreeView.SelectedItem == initialSelection) - { - JumpToReference(mr); - } - } - } - if (!found && AssemblyTreeView.SelectedItem == initialSelection) - { - AvalonEditTextOutput output = new AvalonEditTextOutput(); - output.Write(string.Format("Cannot find '{0}' in command line specified assemblies.", navigateTo)); - DockWorkspace.Instance.ShowText(output); - } - } - else if (relevantAssemblies.Count == 1) - { - // NavigateTo == null and an assembly was given on the command-line: - // Select the newly loaded assembly - AssemblyTreeNode? asmNode = assemblyListTreeNode.FindAssemblyNode(relevantAssemblies[0]); - if (asmNode != null && AssemblyTreeView.SelectedItem == initialSelection) - { - SelectNode(asmNode); - } - } - else if (spySettings != null) - { - SharpTreeNode? node = null; - if (activeTreeViewPath?.Length > 0) - { - foreach (var asm in CurrentAssemblyList.GetAssemblies()) - { - if (asm.FileName == activeTreeViewPath[0]) - { - // FindNodeByPath() blocks the UI if the assembly is not yet loaded, - // so use an async wait instead. - await asm.GetMetadataFileAsync().Catch<Exception>(ex => { }); - } - } - node = FindNodeByPath(activeTreeViewPath, true); - } - if (AssemblyTreeView.SelectedItem == initialSelection) - { - if (node != null) - { - SelectNode(node); - - // only if not showing the about page, perform the update check: - await ShowMessageIfUpdatesAvailableAsync(spySettings); - } - else - { - DockWorkspace.Instance.ActiveTabPage.ShowTextView(AboutPage.Display); - } - } - } - } - - internal static IEntity? FindEntityInRelevantAssemblies(string navigateTo, IEnumerable<LoadedAssembly> relevantAssemblies) - { - ITypeReference? typeRef = null; - IMemberReference? memberRef = null; - if (navigateTo.StartsWith("T:", StringComparison.Ordinal)) - { - typeRef = IdStringProvider.ParseTypeName(navigateTo); - } - else - { - memberRef = IdStringProvider.ParseMemberIdString(navigateTo); - typeRef = memberRef.DeclaringTypeReference; - } - foreach (LoadedAssembly asm in relevantAssemblies.ToList()) - { - var module = asm.GetMetadataFileOrNull(); - if (CanResolveTypeInPEFile(module, typeRef, out var typeHandle)) - { - ICompilation compilation = typeHandle.Kind == HandleKind.ExportedType - ? new DecompilerTypeSystem(module, module.GetAssemblyResolver()) - : new SimpleCompilation((PEFile)module, MinimalCorlib.Instance); - return memberRef == null - ? typeRef.Resolve(new SimpleTypeResolveContext(compilation)) as ITypeDefinition - : (IEntity?)memberRef.Resolve(new SimpleTypeResolveContext(compilation)); - } - } - return null; - } - - static bool CanResolveTypeInPEFile(MetadataFile module, ITypeReference typeRef, out EntityHandle typeHandle) - { - // We intentionally ignore reference assemblies, so that the loop continues looking for another assembly that might have a usable definition. - if (module.IsReferenceAssembly()) - { - typeHandle = default; - return false; - } - - switch (typeRef) - { - case GetPotentiallyNestedClassTypeReference topLevelType: - typeHandle = topLevelType.ResolveInPEFile(module); - return !typeHandle.IsNil; - case NestedTypeReference nestedType: - if (!CanResolveTypeInPEFile(module, nestedType.DeclaringTypeReference, out typeHandle)) - return false; - if (typeHandle.Kind == HandleKind.ExportedType) - return true; - var typeDef = module.Metadata.GetTypeDefinition((TypeDefinitionHandle)typeHandle); - typeHandle = typeDef.GetNestedTypes().FirstOrDefault(t => { - var td = module.Metadata.GetTypeDefinition(t); - var typeName = ReflectionHelper.SplitTypeParameterCountFromReflectionName(module.Metadata.GetString(td.Name), out int typeParameterCount); - return nestedType.AdditionalTypeParameterCount == typeParameterCount && nestedType.Name == typeName; - }); - return !typeHandle.IsNil; - default: - typeHandle = default; - return false; - } - } - - void MainWindow_Loaded(object sender, RoutedEventArgs e) - { - DockWorkspace.Instance.TabPages.Add(); - - var loadPreviousAssemblies = Options.MiscSettingsPanel.CurrentMiscSettings.LoadPreviousAssemblies; - - var sessionSettings = SettingsService.Instance.SessionSettings; - - if (loadPreviousAssemblies) - { - // Load AssemblyList only in Loaded event so that WPF is initialized before we start the CPU-heavy stuff. - // This makes the UI come up a bit faster. - this.assemblyList = SettingsService.Instance.AssemblyListManager.LoadList(sessionSettings.ActiveAssemblyList); - } - else - { - SettingsService.Instance.AssemblyListManager.ClearAll(); - this.assemblyList = SettingsService.Instance.AssemblyListManager.CreateList(AssemblyListManager.DefaultListName); - } - - HandleCommandLineArguments(App.CommandLineArguments); - - if (assemblyList.GetAssemblies().Length == 0 - && assemblyList.ListName == AssemblyListManager.DefaultListName - && loadPreviousAssemblies) - { - LoadInitialAssemblies(); - } - - ShowAssemblyList(this.assemblyList); - - if (sessionSettings.ActiveAutoLoadedAssembly != null - && File.Exists(sessionSettings.ActiveAutoLoadedAssembly)) - { - this.assemblyList.Open(sessionSettings.ActiveAutoLoadedAssembly, true); - } - - Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => OpenAssemblies(SettingsService.Instance.SpySettings))); - } - - void OpenAssemblies(ILSpySettings spySettings) - { - HandleCommandLineArgumentsAfterShowList(App.CommandLineArguments, spySettings); - - AvalonEditTextOutput output = new AvalonEditTextOutput(); - if (FormatExceptions(App.StartupExceptions.ToArray(), output)) - DockWorkspace.Instance.ShowText(output); - } - - static bool FormatExceptions(App.ExceptionData[] exceptions, ITextOutput output) - { - var stringBuilder = new StringBuilder(); - var result = exceptions.FormatExceptions(stringBuilder); - if (result) - { - output.Write(stringBuilder.ToString()); - } - return result; - } - #region Update Check + string updateAvailableDownloadUrl; - public async Task ShowMessageIfUpdatesAvailableAsync(ILSpySettings spySettings, bool forceCheck = false) + public async Task ShowMessageIfUpdatesAvailableAsync(ISettingsProvider spySettings, bool forceCheck = false) { string? downloadUrl; if (forceCheck) @@ -889,21 +164,21 @@ namespace ICSharpCode.ILSpy AdjustUpdateUIAfterCheck(downloadUrl, forceCheck); } - void updatePanelCloseButtonClick(object sender, RoutedEventArgs e) + void UpdatePanelCloseButtonClick(object sender, RoutedEventArgs e) { updatePanel.Visibility = Visibility.Collapsed; } - async void downloadOrCheckUpdateButtonClick(object sender, RoutedEventArgs e) + async void DownloadOrCheckUpdateButtonClick(object sender, RoutedEventArgs e) { if (updateAvailableDownloadUrl != null) { - MainWindow.OpenLink(updateAvailableDownloadUrl); + OpenLink(updateAvailableDownloadUrl); } else { updatePanel.Visibility = Visibility.Collapsed; - string? downloadUrl = await NotifyOfUpdatesStrategy.CheckForUpdatesAsync(ILSpySettings.Load()); + string? downloadUrl = await NotifyOfUpdatesStrategy.CheckForUpdatesAsync(SettingsService.Instance.SpySettings); AdjustUpdateUIAfterCheck(downloadUrl, true); } } @@ -923,327 +198,8 @@ namespace ICSharpCode.ILSpy downloadOrCheckUpdateButton.Content = Properties.Resources.CheckAgain; } } - #endregion - - public void ShowAssemblyList(string name) - { - AssemblyList list = SettingsService.Instance.AssemblyListManager.LoadList(name); - //Only load a new list when it is a different one - if (list.ListName != CurrentAssemblyList.ListName) - { - ShowAssemblyList(list); - SelectNode(AssemblyTreeView.Root); - } - } - void ShowAssemblyList(AssemblyList assemblyList) - { - history.Clear(); - if (this.assemblyList != null) - { - this.assemblyList.CollectionChanged -= assemblyList_Assemblies_CollectionChanged; - } - - this.assemblyList = assemblyList; - assemblyList.CollectionChanged += assemblyList_Assemblies_CollectionChanged; - - assemblyListTreeNode = new AssemblyListTreeNode(assemblyList); - assemblyListTreeNode.Select = x => SelectNode(x, inNewTabPage: false); - AssemblyTreeView.Root = assemblyListTreeNode; - - if (assemblyList.ListName == AssemblyListManager.DefaultListName) -#if DEBUG - this.Title = $"ILSpy {DecompilerVersionInfo.FullVersion}"; -#else - this.Title = "ILSpy"; -#endif - else -#if DEBUG - this.Title = $"ILSpy {DecompilerVersionInfo.FullVersion} - " + assemblyList.ListName; -#else - this.Title = "ILSpy - " + assemblyList.ListName; -#endif - } - - void assemblyList_Assemblies_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - if (e.Action == NotifyCollectionChangedAction.Reset) - { - history.RemoveAll(_ => true); - } - if (e.OldItems != null) - { - var oldAssemblies = new HashSet<LoadedAssembly>(e.OldItems.Cast<LoadedAssembly>()); - history.RemoveAll(n => n.TreeNodes.Any( - nd => nd.AncestorsAndSelf().OfType<AssemblyTreeNode>().Any( - a => oldAssemblies.Contains(a.LoadedAssembly)))); - } - - MessageBus.Send(this, new CurrentAssemblyListChangedEventArgs(e)); - } - - void LoadInitialAssemblies() - { - // Called when loading an empty assembly list; so that - // the user can see something initially. - System.Reflection.Assembly[] initialAssemblies = { - typeof(object).Assembly, - typeof(Uri).Assembly, - typeof(System.Linq.Enumerable).Assembly, - typeof(System.Xml.XmlDocument).Assembly, - typeof(System.Windows.Markup.MarkupExtension).Assembly, - typeof(System.Windows.Rect).Assembly, - typeof(System.Windows.UIElement).Assembly, - typeof(System.Windows.FrameworkElement).Assembly - }; - foreach (System.Reflection.Assembly asm in initialAssemblies) - assemblyList.OpenAssembly(asm.Location); - } - - void LanguageSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == "Language" || e.PropertyName == "LanguageVersion") - { - DecompileSelectedNodes(recordHistory: false); - } - } - - internal AssemblyListTreeNode AssemblyListTreeNode { - get { return assemblyListTreeNode; } - } - - #region Node Selection - - public void SelectNode(SharpTreeNode obj) - { - SelectNode(obj, false); - } - - public void SelectNode(SharpTreeNode obj, bool inNewTabPage) - { - SelectNode(obj, inNewTabPage, true); - } - - public void SelectNode(SharpTreeNode obj, bool inNewTabPage, bool setFocus) - { - if (obj != null) - { - if (!obj.AncestorsAndSelf().Any(node => node.IsHidden)) - { - if (inNewTabPage) - { - DockWorkspace.Instance.TabPages.Add(); - AssemblyTreeView.SelectedItem = null; - } - - // Set both the selection and focus to ensure that keyboard navigation works as expected. - if (setFocus) - { - AssemblyTreeView.FocusNode(obj); - } - else - { - AssemblyTreeView.ScrollIntoView(obj); - } - AssemblyTreeView.SelectedItem = obj; - } - else - { - MessageBox.Show(Properties.Resources.NavigationFailed, "ILSpy", MessageBoxButton.OK, MessageBoxImage.Exclamation); - } - } - } - - public void SelectNodes(IEnumerable<SharpTreeNode> nodes) - { - SelectNodes(nodes, false); - } - - public void SelectNodes(IEnumerable<SharpTreeNode> nodes, bool inNewTabPage) - { - SelectNodes(nodes, inNewTabPage, true); - } - - public void SelectNodes(IEnumerable<SharpTreeNode> nodes, bool inNewTabPage, bool setFocus) - { - SelectNodes(nodes, inNewTabPage, setFocus, false); - } - - internal void SelectNodes(IEnumerable<SharpTreeNode> nodes, bool inNewTabPage, - bool setFocus, bool changingActiveTab) - { - if (inNewTabPage) - { - DockWorkspace.Instance.TabPages.Add(); - } - - // Ensure nodes exist - var nodesList = nodes.Select(n => FindNodeByPath(GetPathForNode(n), true)) - .Where(n => n != null).ToArray(); - - if (!nodesList.Any() || !nodesList.All(n => !n.AncestorsAndSelf().Any(a => a.IsHidden))) - { - return; - } - - this.changingActiveTab = changingActiveTab || inNewTabPage; - try - { - if (setFocus) - { - AssemblyTreeView.FocusNode(nodesList[0]); - } - else - { - AssemblyTreeView.ScrollIntoView(nodesList[0]); - } - - AssemblyTreeView.SetSelectedNodes(nodesList); - } - finally - { - this.changingActiveTab = false; - } - } - - /// <summary> - /// Retrieves a node using the .ToString() representations of its ancestors. - /// </summary> - public SharpTreeNode? FindNodeByPath(string[] path, bool returnBestMatch) - { - if (path == null) - return null; - SharpTreeNode? node = AssemblyTreeView.Root; - SharpTreeNode? bestMatch = node; - foreach (var element in path) - { - if (node == null) - break; - bestMatch = node; - node.EnsureLazyChildren(); - var ilSpyTreeNode = node as ILSpyTreeNode; - if (ilSpyTreeNode != null) - ilSpyTreeNode.EnsureChildrenFiltered(); - node = node.Children.FirstOrDefault(c => c.ToString() == element); - } - if (returnBestMatch) - return node ?? bestMatch; - else - return node; - } - - /// <summary> - /// Gets the .ToString() representation of the node's ancestors. - /// </summary> - public static string[]? GetPathForNode(SharpTreeNode node) - { - if (node == null) - return null; - List<string> path = new List<string>(); - while (node.Parent != null) - { - path.Add(node.ToString()); - node = node.Parent; - } - path.Reverse(); - return path.ToArray(); - } - - public ILSpyTreeNode? FindTreeNode(object reference) - { - switch (reference) - { - case LoadedAssembly lasm: - return assemblyListTreeNode.FindAssemblyNode(lasm); - case MetadataFile asm: - return assemblyListTreeNode.FindAssemblyNode(asm); - case Resource res: - return assemblyListTreeNode.FindResourceNode(res); - case ValueTuple<Resource, string> resName: - return assemblyListTreeNode.FindResourceNode(resName.Item1, resName.Item2); - case ITypeDefinition type: - return assemblyListTreeNode.FindTypeNode(type); - case IField fd: - return assemblyListTreeNode.FindFieldNode(fd); - case IMethod md: - return assemblyListTreeNode.FindMethodNode(md); - case IProperty pd: - return assemblyListTreeNode.FindPropertyNode(pd); - case IEvent ed: - return assemblyListTreeNode.FindEventNode(ed); - case INamespace nd: - return AssemblyListTreeNode.FindNamespaceNode(nd); - default: - return null; - } - } - - public void JumpToReference(object reference) - { - JumpToReference(reference, inNewTabPage: false); - } - - public void JumpToReference(object reference, bool inNewTabPage) - { - JumpToReferenceAsync(reference, inNewTabPage).HandleExceptions(); - } - - /// <summary> - /// Jumps to the specified reference. - /// </summary> - /// <returns> - /// Returns a task that will signal completion when the decompilation of the jump target has finished. - /// The task will be marked as canceled if the decompilation is canceled. - /// </returns> - public Task JumpToReferenceAsync(object reference) - { - return JumpToReferenceAsync(reference, inNewTabPage: false); - } - - public Task JumpToReferenceAsync(object reference, bool inNewTabPage) - { - decompilationTask = TaskHelper.CompletedTask; - switch (reference) - { - case Decompiler.Disassembler.OpCodeInfo opCode: - OpenLink(opCode.Link); - break; - case EntityReference unresolvedEntity: - string protocol = unresolvedEntity.Protocol ?? "decompile"; - var file = unresolvedEntity.ResolveAssembly(assemblyList); - if (file == null) - { - break; - } - if (protocol != "decompile") - { - var protocolHandlers = App.ExportProvider.GetExports<IProtocolHandler>(); - foreach (var handler in protocolHandlers) - { - var node = handler.Value.Resolve(protocol, file, unresolvedEntity.Handle, out bool newTabPage); - if (node != null) - { - SelectNode(node, newTabPage || inNewTabPage); - return decompilationTask; - } - } - } - var possibleToken = MetadataTokenHelpers.TryAsEntityHandle(MetadataTokens.GetToken(unresolvedEntity.Handle)); - if (possibleToken != null) - { - var typeSystem = new DecompilerTypeSystem(file, file.GetAssemblyResolver(), TypeSystemOptions.Default | TypeSystemOptions.Uncached); - reference = typeSystem.MainModule.ResolveEntity(possibleToken.Value); - goto default; - } - break; - default: - ILSpyTreeNode? treeNode = FindTreeNode(reference); - if (treeNode != null) - SelectNode(treeNode, inNewTabPage); - break; - } - return decompilationTask; - } + #endregion public static void OpenLink(string link) { @@ -1274,356 +230,44 @@ namespace ICSharpCode.ILSpy // just ignore all of them. } } - #endregion - - #region Open/Refresh - void OpenCommandExecuted(object sender, ExecutedRoutedEventArgs e) - { - e.Handled = true; - OpenFileDialog dlg = new OpenFileDialog(); - dlg.Filter = ".NET assemblies|*.dll;*.exe;*.winmd;*.wasm|Nuget Packages (*.nupkg)|*.nupkg|Portable Program Database (*.pdb)|*.pdb|All files|*.*"; - dlg.Multiselect = true; - dlg.RestoreDirectory = true; - if (dlg.ShowDialog() == true) - { - OpenFiles(dlg.FileNames); - } - } - - public void OpenFiles(string[] fileNames, bool focusNode = true) - { - if (fileNames == null) - throw new ArgumentNullException(nameof(fileNames)); - - if (focusNode) - AssemblyTreeView.UnselectAll(); - - LoadAssemblies(fileNames, focusNode: focusNode); - } - - void LoadAssemblies(IEnumerable<string> fileNames, List<LoadedAssembly>? loadedAssemblies = null, bool focusNode = true) - { - SharpTreeNode? lastNode = null; - foreach (string file in fileNames) - { - var asm = assemblyList.OpenAssembly(file); - if (asm != null) - { - if (loadedAssemblies != null) - { - loadedAssemblies.Add(asm); - } - else - { - var node = assemblyListTreeNode.FindAssemblyNode(asm); - if (node != null && focusNode) - { - AssemblyTreeView.SelectedItems.Add(node); - lastNode = node; - } - } - } - - if (lastNode != null && focusNode) - AssemblyTreeView.FocusNode(lastNode); - } - } - - void RefreshCommandExecuted(object sender, ExecutedRoutedEventArgs e) - { - try - { - refreshInProgress = true; - var path = GetPathForNode(AssemblyTreeView.SelectedItem as SharpTreeNode); - ShowAssemblyList(SettingsService.Instance.AssemblyListManager.LoadList(assemblyList.ListName)); - SelectNode(FindNodeByPath(path, true), inNewTabPage: false, AssemblyTreeView.IsFocused); - } - finally - { - refreshInProgress = false; - } - } - - void SearchCommandExecuted(object sender, ExecutedRoutedEventArgs e) - { - DockWorkspace.Instance.ShowToolPane(SearchPaneModel.PaneContentId); - } - #endregion - - #region Decompile (TreeView_SelectionChanged) - bool delayDecompilationRequestDueToContextMenu; - - protected override void OnContextMenuClosing(ContextMenuEventArgs e) - { - base.OnContextMenuClosing(e); - - if (delayDecompilationRequestDueToContextMenu) - { - delayDecompilationRequestDueToContextMenu = false; - var state = DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState; - DecompileSelectedNodes(state); - } - } - - void TreeView_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - DecompilerTextViewState? state = null; - if (refreshInProgress || changingActiveTab) - { - state = DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState; - } - - this.delayDecompilationRequestDueToContextMenu = Mouse.RightButton == MouseButtonState.Pressed; - if (!changingActiveTab && !delayDecompilationRequestDueToContextMenu) - { - DecompileSelectedNodes(state); - } - - SelectionChanged?.Invoke(sender, e); - } - - Task decompilationTask; - bool ignoreDecompilationRequests; - - void DecompileSelectedNodes(DecompilerTextViewState? newState = null, bool recordHistory = true) - { - if (ignoreDecompilationRequests) - return; - - if (AssemblyTreeView.SelectedItems.Count == 0 && refreshInProgress) - return; - - if (recordHistory) - { - var tabPage = DockWorkspace.Instance.ActiveTabPage; - var currentState = tabPage.GetState(); - if (currentState != null) - history.UpdateCurrent(new NavigationState(tabPage, currentState)); - history.Record(new NavigationState(tabPage, AssemblyTreeView.SelectedItems.OfType<SharpTreeNode>())); - } - - DockWorkspace.Instance.ActiveTabPage.SupportsLanguageSwitching = true; - - if (AssemblyTreeView.SelectedItems.Count == 1) - { - ILSpyTreeNode? node = AssemblyTreeView.SelectedItem as ILSpyTreeNode; - if (node != null && node.View(DockWorkspace.Instance.ActiveTabPage)) - return; - } - if (newState?.ViewedUri != null) - { - NavigateTo(new RequestNavigateEventArgs(newState.ViewedUri, null), recordHistory: false); - return; - } - var options = MainWindow.Instance.CreateDecompilationOptions(); - options.TextViewState = newState; - decompilationTask = DockWorkspace.Instance.ActiveTabPage.ShowTextViewAsync( - textView => textView.DecompileAsync(this.CurrentLanguage, this.SelectedNodes, options) - ); - } - - void SaveCommandCanExecute(object sender, CanExecuteRoutedEventArgs e) - { - e.Handled = true; - e.CanExecute = SaveCodeContextMenuEntry.CanExecute(SelectedNodes.ToList()); - } - - void SaveCommandExecuted(object sender, ExecutedRoutedEventArgs e) - { - SaveCodeContextMenuEntry.Execute(SelectedNodes.ToList()); - } - - public void RefreshDecompiledView() - { - try - { - refreshInProgress = true; - DecompileSelectedNodes(); - } - finally - { - refreshInProgress = false; - } - } - - public Language CurrentLanguage => SettingsService.Instance.SessionSettings.LanguageSettings.Language; - - public LanguageVersion? CurrentLanguageVersion => SettingsService.Instance.SessionSettings.LanguageSettings.LanguageVersion; - - public event SelectionChangedEventHandler SelectionChanged; - - public IEnumerable<ILSpyTreeNode> SelectedNodes { - get { - return AssemblyTreeView.GetTopLevelSelection().OfType<ILSpyTreeNode>(); - } - } - #endregion - - #region Back/Forward navigation - void BackCommandCanExecute(object sender, CanExecuteRoutedEventArgs e) - { - e.Handled = true; - e.CanExecute = history.CanNavigateBack; - } - - void BackCommandExecuted(object sender, ExecutedRoutedEventArgs e) - { - if (history.CanNavigateBack) - { - e.Handled = true; - NavigateHistory(false); - } - } - - void ForwardCommandCanExecute(object sender, CanExecuteRoutedEventArgs e) - { - e.Handled = true; - e.CanExecute = history.CanNavigateForward; - } - - void ForwardCommandExecuted(object sender, ExecutedRoutedEventArgs e) - { - if (history.CanNavigateForward) - { - e.Handled = true; - NavigateHistory(true); - } - } - - void NavigateHistory(bool forward) - { - TabPageModel tabPage = DockWorkspace.Instance.ActiveTabPage; - var state = tabPage.GetState(); - if (state != null) - history.UpdateCurrent(new NavigationState(tabPage, state)); - var newState = forward ? history.GoForward() : history.GoBack(); - - ignoreDecompilationRequests = true; - AssemblyTreeView.SelectedItems.Clear(); - DockWorkspace.Instance.ActiveTabPage = newState.TabPage; - foreach (var node in newState.TreeNodes) - { - AssemblyTreeView.SelectedItems.Add(node); - } - if (newState.TreeNodes.Any()) - AssemblyTreeView.FocusNode(newState.TreeNodes.First()); - ignoreDecompilationRequests = false; - DecompileSelectedNodes(newState.ViewState as DecompilerTextViewState, false); - } - #endregion - - internal void NavigateTo(RequestNavigateEventArgs e, bool recordHistory = true, bool inNewTabPage = false) - { - if (e.Uri.Scheme == "resource") - { - if (inNewTabPage) - { - DockWorkspace.Instance.TabPages.Add(); - } - - if (e.Uri.Host == "aboutpage") - { - RecordHistory(); - DockWorkspace.Instance.ActiveTabPage.ShowTextView(AboutPage.Display); - e.Handled = true; - return; - } - AvalonEditTextOutput output = new AvalonEditTextOutput { - Address = e.Uri, - Title = e.Uri.AbsolutePath, - EnableHyperlinks = true - }; - using (Stream? s = typeof(App).Assembly.GetManifestResourceStream(typeof(App), e.Uri.AbsolutePath)) - { - using (StreamReader r = new StreamReader(s)) - { - string? line; - while ((line = r.ReadLine()) != null) - { - output.Write(line); - output.WriteLine(); - } - } - } - RecordHistory(); - DockWorkspace.Instance.ShowText(output); - e.Handled = true; - } - - void RecordHistory() - { - if (!recordHistory) - return; - TabPageModel tabPage = DockWorkspace.Instance.ActiveTabPage; - var currentState = tabPage.GetState(); - if (currentState != null) - history.UpdateCurrent(new NavigationState(tabPage, currentState)); - ignoreDecompilationRequests = true; - UnselectAll(); - ignoreDecompilationRequests = false; - history.Record(new NavigationState(tabPage, new ViewState { ViewedUri = e.Uri })); - } - } protected override void OnStateChanged(EventArgs e) { base.OnStateChanged(e); // store window state in settings only if it's not minimized - if (this.WindowState != System.Windows.WindowState.Minimized) + if (this.WindowState != WindowState.Minimized) SettingsService.Instance.SessionSettings.WindowState = this.WindowState; } protected override void OnClosing(CancelEventArgs e) { base.OnClosing(e); - var sessionSettings = SettingsService.Instance.SessionSettings; - sessionSettings.ActiveAssemblyList = assemblyList.ListName; - sessionSettings.ActiveTreeViewPath = GetPathForNode(AssemblyTreeView.SelectedItem as SharpTreeNode); - sessionSettings.ActiveAutoLoadedAssembly = GetAutoLoadedAssemblyNode(AssemblyTreeView.SelectedItem as SharpTreeNode); - sessionSettings.WindowBounds = this.RestoreBounds; - sessionSettings.DockLayout.Serialize(new XmlLayoutSerializer(dockManager)); - sessionSettings.Save(); - } + var snapshot = SettingsService.Instance.CreateSnapshot(); - private string? GetAutoLoadedAssemblyNode(SharpTreeNode node) - { - if (node == null) - return null; - while (!(node is TreeNodes.AssemblyTreeNode) && node.Parent != null) - { - node = node.Parent; - } - //this should be an assembly node - var assyNode = node as TreeNodes.AssemblyTreeNode; - var loadedAssy = assyNode.LoadedAssembly; - if (!(loadedAssy.IsLoaded && loadedAssy.IsAutoLoaded)) - return null; + var sessionSettings = snapshot.GetSettings<SessionSettings>(); - return loadedAssy.FileName; - } + sessionSettings.ActiveAssemblyList = AssemblyTreeModel.AssemblyList.ListName; + sessionSettings.ActiveTreeViewPath = AssemblyTreeModel.SelectedPath; + sessionSettings.ActiveAutoLoadedAssembly = GetAutoLoadedAssemblyNode(AssemblyTreeModel.SelectedItem); + sessionSettings.WindowBounds = this.RestoreBounds; + sessionSettings.DockLayout.Serialize(new XmlLayoutSerializer(dockManager)); - public void UnselectAll() - { - AssemblyTreeView.UnselectAll(); + snapshot.Save(); } - public void SetStatus(string status, Brush foreground) + private static string GetAutoLoadedAssemblyNode(SharpTreeNode node) { - if (this.statusBar.Visibility == Visibility.Collapsed) - this.statusBar.Visibility = Visibility.Visible; - this.statusLabel.Foreground = foreground; - this.statusLabel.Text = status; - } + var assemblyTreeNode = node? + .AncestorsAndSelf() + .OfType<AssemblyTreeNode>() + .FirstOrDefault(); - public ItemCollection GetMainMenuItems() - { - return mainMenu.Items; - } + var loadedAssembly = assemblyTreeNode?.LoadedAssembly; - public ItemCollection GetToolBarItems() - { - return toolBar.Items; + return loadedAssembly is not { IsLoaded: true, IsAutoLoaded: true } + ? null + : loadedAssembly.FileName; } } } diff --git a/ILSpy/MainWindowViewModel.cs b/ILSpy/MainWindowViewModel.cs index 16b2cc0fd..270f007ec 100644 --- a/ILSpy/MainWindowViewModel.cs +++ b/ILSpy/MainWindowViewModel.cs @@ -19,12 +19,14 @@ using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpyX; +using TomsToolbox.Wpf; + namespace ICSharpCode.ILSpy { - class MainWindowViewModel + class MainWindowViewModel : ObservableObject { - public DockWorkspace Workspace { get; set; } - public SessionSettings SessionSettings { get; set; } - public AssemblyListManager AssemblyListManager { get; set; } + public DockWorkspace Workspace => DockWorkspace.Instance; + public SessionSettings SessionSettings => SettingsService.Instance.SessionSettings; + public AssemblyListManager AssemblyListManager => SettingsService.Instance.AssemblyListManager; } } diff --git a/ILSpy/Metadata/CoffHeaderTreeNode.cs b/ILSpy/Metadata/CoffHeaderTreeNode.cs index ab6859024..065b1b6c1 100644 --- a/ILSpy/Metadata/CoffHeaderTreeNode.cs +++ b/ILSpy/Metadata/CoffHeaderTreeNode.cs @@ -28,6 +28,8 @@ using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX.Extensions; +using TomsToolbox.Essentials; + namespace ICSharpCode.ILSpy.Metadata { class CoffHeaderTreeNode : ILSpyTreeNode diff --git a/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs b/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs index 3dc8eeee0..d6b8fd795 100644 --- a/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs @@ -97,7 +97,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference("metadata", classLayout.Parent)); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference("metadata", classLayout.Parent))); } string? parentTooltip; diff --git a/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs b/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs index 4ca9f575a..e6ccc60c2 100644 --- a/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs @@ -88,7 +88,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, constant.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, constant.Parent, protocol: "metadata"))); } string? parentTooltip; diff --git a/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs b/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs index a687035ad..ca6089cf4 100644 --- a/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs @@ -83,7 +83,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, customAttr.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, customAttr.Parent, protocol: "metadata"))); } string? parentTooltip; @@ -94,7 +94,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnConstructorClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, customAttr.Constructor, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, customAttr.Constructor, protocol: "metadata"))); } string? constructorTooltip; diff --git a/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs b/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs index cc297a2fd..dce6bd87b 100644 --- a/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs @@ -84,7 +84,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, declSecAttr.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, declSecAttr.Parent, protocol: "metadata"))); } string? parentTooltip; diff --git a/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs index ee7c4359f..b138480f9 100644 --- a/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs @@ -95,7 +95,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, eventMap.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, eventMap.Parent, protocol: "metadata"))); } string? parentTooltip; @@ -106,7 +106,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnEventListClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, eventMap.EventList, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, eventMap.EventList, protocol: "metadata"))); } string? eventListTooltip; diff --git a/ILSpy/Metadata/CorTables/EventTableTreeNode.cs b/ILSpy/Metadata/CorTables/EventTableTreeNode.cs index 484346d91..966620c1c 100644 --- a/ILSpy/Metadata/CorTables/EventTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/EventTableTreeNode.cs @@ -103,7 +103,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnTypeClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, eventDef.Type, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, eventDef.Type, protocol: "metadata"))); } string? typeTooltip; diff --git a/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs b/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs index e66d16d22..62e33ec2c 100644 --- a/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs @@ -107,7 +107,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnImplementationClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, type.Implementation, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, type.Implementation, protocol: "metadata"))); } string? implementationTooltip; diff --git a/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs index 117db920d..c76104422 100644 --- a/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs @@ -96,7 +96,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnFieldClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, fieldLayout.Field, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, fieldLayout.Field, protocol: "metadata"))); } string? fieldTooltip; diff --git a/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs index 5edae567c..d1ca21b22 100644 --- a/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs @@ -95,7 +95,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, fieldMarshal.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, fieldMarshal.Parent, protocol: "metadata"))); } string? parentTooltip; diff --git a/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs index 02f26f619..10971d52e 100644 --- a/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs @@ -99,7 +99,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnFieldClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, fieldRVA.Field, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, fieldRVA.Field, protocol: "metadata"))); } string? fieldTooltip; diff --git a/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs b/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs index 91bd5ef97..a6b69295e 100644 --- a/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs @@ -84,7 +84,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnOwnerClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, genericParamConstraint.Parameter, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, genericParamConstraint.Parameter, protocol: "metadata"))); } string? ownerTooltip; @@ -108,7 +108,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnTypeClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, genericParamConstraint.Type, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, genericParamConstraint.Type, protocol: "metadata"))); } string? typeTooltip; diff --git a/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs b/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs index cfdbfaecb..c09cf535c 100644 --- a/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs @@ -92,7 +92,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnOwnerClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, genericParam.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, genericParam.Parent, protocol: "metadata"))); } string? ownerTooltip; diff --git a/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs index c8d4c0f91..bec563233 100644 --- a/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs @@ -113,7 +113,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnMemberForwardedClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, implMap.MemberForwarded, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, implMap.MemberForwarded, protocol: "metadata"))); } string? memberForwardedTooltip; @@ -124,7 +124,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnImportScopeClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, implMap.ImportScope, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, implMap.ImportScope, protocol: "metadata"))); } string? importScopeTooltip; diff --git a/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs b/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs index 5068aead0..b37d097e3 100644 --- a/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs @@ -96,7 +96,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnClassClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, interfaceImpl.Class, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, interfaceImpl.Class, protocol: "metadata"))); } string? classTooltip; @@ -107,7 +107,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnInterfaceClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, interfaceImpl.Interface, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, interfaceImpl.Interface, protocol: "metadata"))); } string? interfaceTooltip; diff --git a/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs b/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs index a0fd9dc08..0f655e357 100644 --- a/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs @@ -93,7 +93,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnImplementationClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, manifestResource.Implementation, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, manifestResource.Implementation, protocol: "metadata"))); } string? implementationTooltip; diff --git a/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs b/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs index c084df6bc..daba41c50 100644 --- a/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs @@ -83,7 +83,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, memberRef.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, memberRef.Parent, protocol: "metadata"))); } string? parentTooltip; diff --git a/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs index 0573fbbb9..3a5de96d0 100644 --- a/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs @@ -82,7 +82,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnMethodDeclarationClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, methodImpl.MethodDeclaration, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, methodImpl.MethodDeclaration, protocol: "metadata"))); } string? methodDeclarationTooltip; @@ -93,7 +93,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnMethodBodyClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, methodImpl.MethodBody, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, methodImpl.MethodBody, protocol: "metadata"))); } string? methodBodyTooltip; @@ -104,7 +104,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnTypeClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, methodImpl.Type, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, methodImpl.Type, protocol: "metadata"))); } string? typeTooltip; diff --git a/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs index 0878e7d76..63270937d 100644 --- a/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs @@ -90,7 +90,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnMethodClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, method, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, method, protocol: "metadata"))); } string? methodTooltip; @@ -101,7 +101,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnAssociationClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, association, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, association, protocol: "metadata"))); } string? associationTooltip; diff --git a/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs index e77d7fb1e..12a1dae83 100644 --- a/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs @@ -85,7 +85,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnMethodClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, methodSpec.Method, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, methodSpec.Method, protocol: "metadata"))); } string? methodTooltip; diff --git a/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs index 4d7566526..a351da9b1 100644 --- a/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs @@ -118,7 +118,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnParamListClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, methodDef.GetParameters().FirstOrDefault(), protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, methodDef.GetParameters().FirstOrDefault(), protocol: "metadata"))); } string? paramListTooltip; diff --git a/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs b/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs index ecbf99b37..a2af5d434 100644 --- a/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs @@ -95,7 +95,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnNestedClassClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, nestedClass.Nested, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, nestedClass.Nested, protocol: "metadata"))); } string? nestedClassTooltip; @@ -106,7 +106,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnEnclosingClassClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, nestedClass.Enclosing, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, nestedClass.Enclosing, protocol: "metadata"))); } string? enclosingClassTooltip; diff --git a/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs index 4addf69bc..70370559b 100644 --- a/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs @@ -95,7 +95,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, propertyMap.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, propertyMap.Parent, protocol: "metadata"))); } string? parentTooltip; @@ -106,7 +106,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnPropertyListClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, propertyMap.PropertyList, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, propertyMap.PropertyList, protocol: "metadata"))); } string? propertyListTooltip; diff --git a/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs b/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs index 53a3a3d70..de76f4018 100644 --- a/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs @@ -112,7 +112,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnHandleClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, handlePtr.Handle, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, handlePtr.Handle, protocol: "metadata"))); } string? handleTooltip; diff --git a/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs b/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs index f36940c47..053f393aa 100644 --- a/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs @@ -111,7 +111,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnBaseTypeClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, typeDef.BaseType, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, typeDef.BaseType, protocol: "metadata"))); } public string? BaseTypeTooltip { @@ -142,7 +142,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnFieldListClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, typeDef.GetFields().FirstOrDefault(), protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, typeDef.GetFields().FirstOrDefault(), protocol: "metadata"))); } string? fieldListTooltip; @@ -160,7 +160,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnMethodListClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, typeDef.GetMethods().FirstOrDefault(), protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, typeDef.GetMethods().FirstOrDefault(), protocol: "metadata"))); } string? methodListTooltip; diff --git a/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs b/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs index cff75d02d..8457f1007 100644 --- a/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs @@ -83,7 +83,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnResolutionScopeClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, typeRef.ResolutionScope, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, typeRef.ResolutionScope, protocol: "metadata"))); } string? resolutionScopeTooltip; diff --git a/ILSpy/Metadata/DebugMetadataTablesTreeNode.cs b/ILSpy/Metadata/DebugMetadataTablesTreeNode.cs index 9ec211751..d3067d713 100644 --- a/ILSpy/Metadata/DebugMetadataTablesTreeNode.cs +++ b/ILSpy/Metadata/DebugMetadataTablesTreeNode.cs @@ -21,7 +21,6 @@ using System.Reflection.Metadata.Ecma335; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.ILSpy.TreeNodes; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpy.ViewModels; namespace ICSharpCode.ILSpy.Metadata diff --git a/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs b/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs index 8715c2f0e..ea4dc5b04 100644 --- a/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs @@ -188,7 +188,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, debugInfo.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, debugInfo.Parent, protocol: "metadata"))); } string parentTooltip; diff --git a/ILSpy/Metadata/DebugTables/ImportScopeTableTreeNode.cs b/ILSpy/Metadata/DebugTables/ImportScopeTableTreeNode.cs index 750a18ae7..65639ad1f 100644 --- a/ILSpy/Metadata/DebugTables/ImportScopeTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/ImportScopeTableTreeNode.cs @@ -80,7 +80,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, localScope.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, localScope.Parent, protocol: "metadata"))); } [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] diff --git a/ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs b/ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs index d2f6b0b63..0a4fb3e07 100644 --- a/ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs @@ -81,7 +81,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnMethodClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, localScope.Method, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, localScope.Method, protocol: "metadata"))); } string? methodTooltip; @@ -92,7 +92,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnImportScopeClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, localScope.ImportScope, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, localScope.ImportScope, protocol: "metadata"))); } [ColumnInfo("X8", Kind = ColumnKind.Token)] @@ -100,7 +100,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnVariableListClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, localScope.GetLocalVariables().FirstOrDefault(), protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, localScope.GetLocalVariables().FirstOrDefault(), protocol: "metadata"))); } [ColumnInfo("X8", Kind = ColumnKind.Token)] @@ -108,7 +108,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnConstantListClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, localScope.GetLocalConstants().FirstOrDefault(), protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, localScope.GetLocalConstants().FirstOrDefault(), protocol: "metadata"))); } public int StartOffset => localScope.StartOffset; diff --git a/ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs b/ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs index 4059cce6d..a93054fac 100644 --- a/ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs @@ -83,7 +83,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnDocumentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, debugInfo.Document, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, debugInfo.Document, protocol: "metadata"))); } public string? DocumentTooltip { @@ -116,7 +116,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnLocalSignatureClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, debugInfo.LocalSignature, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, debugInfo.LocalSignature, protocol: "metadata"))); } public string? LocalSignatureTooltip { diff --git a/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs b/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs index d9efe7ff6..8602a955b 100644 --- a/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs @@ -83,7 +83,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnMoveNextMethodClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, moveNextMethod, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, moveNextMethod, protocol: "metadata"))); } string? moveNextMethodTooltip; @@ -94,7 +94,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnKickofMethodClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, kickoffMethod, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, kickoffMethod, protocol: "metadata"))); } string? kickoffMethodTooltip; diff --git a/ILSpy/Metadata/GoToTokenCommand.cs b/ILSpy/Metadata/GoToTokenCommand.cs index 51b3ab038..17c488d5b 100644 --- a/ILSpy/Metadata/GoToTokenCommand.cs +++ b/ILSpy/Metadata/GoToTokenCommand.cs @@ -23,12 +23,13 @@ using System.Reflection; using System.Reflection.Metadata.Ecma335; using System.Windows; using System.Windows.Controls; -using System.Windows.Media; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.ILSpy.Metadata; using ICSharpCode.ILSpy.Properties; +using TomsToolbox.Wpf; + namespace ICSharpCode.ILSpy.Commands { [ExportContextMenuEntry(Header = nameof(Resources.GoToToken), Order = 10)] @@ -38,7 +39,7 @@ namespace ICSharpCode.ILSpy.Commands public void Execute(TextViewContext context) { int token = GetSelectedToken(context.DataGrid, out MetadataFile module).Value; - MainWindow.Instance.JumpToReference(new EntityReference(module, MetadataTokens.Handle(token), protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(module, MetadataTokens.Handle(token), protocol: "metadata"))); } public bool IsEnabled(TextViewContext context) @@ -75,7 +76,7 @@ namespace ICSharpCode.ILSpy.Commands { public void Execute(TextViewContext context) { - string? content = GetSelectedCellContent(context.DataGrid, context.MousePosition); + string? content = GetSelectedCellContent(context.OriginalSource); Clipboard.SetText(content); } @@ -87,21 +88,14 @@ namespace ICSharpCode.ILSpy.Commands public bool IsVisible(TextViewContext context) { return context.DataGrid?.Name == "MetadataView" - && GetSelectedCellContent(context.DataGrid, context.MousePosition) != null; + && GetSelectedCellContent(context.OriginalSource) != null; } - private string? GetSelectedCellContent(DataGrid grid, Point position) + private static string? GetSelectedCellContent(DependencyObject originalSource) { - position = grid.PointFromScreen(position); - var hit = VisualTreeHelper.HitTest(grid, position); - if (hit == null) - return null; - var cell = hit.VisualHit.GetParent<DataGridCell>(); - if (cell == null) - return null; - return cell.DataContext.GetType() - .GetProperty(cell.Column.Header.ToString(), BindingFlags.Instance | BindingFlags.Public) - .GetValue(cell.DataContext).ToString(); + var cell = originalSource.AncestorsAndSelf().OfType<DataGridCell>().FirstOrDefault(); + + return cell?.Column.OnCopyingCellClipboardContent(cell.DataContext).ToString(); } } } diff --git a/ILSpy/Metadata/MetadataProtocolHandler.cs b/ILSpy/Metadata/MetadataProtocolHandler.cs index a0008d21e..a70cb523b 100644 --- a/ILSpy/Metadata/MetadataProtocolHandler.cs +++ b/ILSpy/Metadata/MetadataProtocolHandler.cs @@ -34,7 +34,7 @@ namespace ICSharpCode.ILSpy.Metadata newTabPage = true; if (protocol != "metadata") return null; - var assemblyTreeNode = MainWindow.Instance.FindTreeNode(module) as AssemblyTreeNode; + var assemblyTreeNode = MainWindow.Instance.AssemblyTreeModel.FindTreeNode(module) as AssemblyTreeNode; if (assemblyTreeNode == null) return null; var mxNode = assemblyTreeNode.Children.OfType<MetadataTreeNode>().FirstOrDefault(); diff --git a/ILSpy/Metadata/MetadataTablesTreeNode.cs b/ILSpy/Metadata/MetadataTablesTreeNode.cs index 3724ba590..1fb55be81 100644 --- a/ILSpy/Metadata/MetadataTablesTreeNode.cs +++ b/ILSpy/Metadata/MetadataTablesTreeNode.cs @@ -23,7 +23,6 @@ using System.Reflection.Metadata.Ecma335; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.ILSpy.TreeNodes; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpy.ViewModels; namespace ICSharpCode.ILSpy.Metadata diff --git a/ILSpy/Metadata/OptionalHeaderTreeNode.cs b/ILSpy/Metadata/OptionalHeaderTreeNode.cs index dc1be30ec..8353ead80 100644 --- a/ILSpy/Metadata/OptionalHeaderTreeNode.cs +++ b/ILSpy/Metadata/OptionalHeaderTreeNode.cs @@ -27,6 +27,8 @@ using ICSharpCode.Decompiler.Metadata; using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpyX.Extensions; +using TomsToolbox.Essentials; + namespace ICSharpCode.ILSpy.Metadata { class OptionalHeaderTreeNode : ILSpyTreeNode diff --git a/ILSpy/Options/DecompilerSettings.cs b/ILSpy/Options/DecompilerSettings.cs new file mode 100644 index 000000000..14f1d32e9 --- /dev/null +++ b/ILSpy/Options/DecompilerSettings.cs @@ -0,0 +1,52 @@ +using System.ComponentModel; + +using ICSharpCode.ILSpyX.Settings; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; + +#nullable enable + +namespace ICSharpCode.ILSpy.Options +{ + public class DecompilerSettings : Decompiler.DecompilerSettings, ISettingsSection + { + static readonly PropertyInfo[] properties = typeof(Decompiler.DecompilerSettings).GetProperties() + .Where(p => p.GetCustomAttribute<BrowsableAttribute>()?.Browsable != false) + .ToArray(); + + public XName SectionName => "DecompilerSettings"; + + public XElement SaveToXml() + { + var section = new XElement(SectionName); + + foreach (var p in properties) + { + section.SetAttributeValue(p.Name, p.GetValue(this)); + } + + return section; + } + + public void LoadFromXml(XElement section) + { + foreach (var p in properties) + { + var value = (bool?)section.Attribute(p.Name); + if (value.HasValue) + p.SetValue(this, value.Value); + } + } + + public new DecompilerSettings Clone() + { + var section = SaveToXml(); + + var newSettings = new DecompilerSettings(); + newSettings.LoadFromXml(section); + + return newSettings; + } + } +} diff --git a/ILSpy/Options/DecompilerSettingsPanel.xaml.cs b/ILSpy/Options/DecompilerSettingsPanel.xaml.cs index d530079dc..e65f72087 100644 --- a/ILSpy/Options/DecompilerSettingsPanel.xaml.cs +++ b/ILSpy/Options/DecompilerSettingsPanel.xaml.cs @@ -19,47 +19,22 @@ using System.ComponentModel.Composition; using System.Xml.Linq; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX.Settings; +using TomsToolbox.Wpf.Composition.Mef; + namespace ICSharpCode.ILSpy.Options { /// <summary> /// Interaction logic for DecompilerSettingsPanel.xaml /// </summary> - [ExportOptionPage(Title = nameof(Properties.Resources.Decompiler), Order = 10)] + [DataTemplate(typeof(DecompilerSettingsViewModel))] [PartCreationPolicy(CreationPolicy.NonShared)] - internal partial class DecompilerSettingsPanel : IOptionPage + internal partial class DecompilerSettingsPanel { public DecompilerSettingsPanel() { InitializeComponent(); } - - public static Decompiler.DecompilerSettings LoadDecompilerSettings(ILSpySettings settings) - { - return ISettingsProvider.LoadDecompilerSettings(settings); - } - - public void Load(ILSpySettings settings) - { - this.DataContext = new DecompilerSettingsViewModel(LoadDecompilerSettings(settings)); - } - - public void Save(XElement root) - { - var newSettings = ((DecompilerSettingsViewModel)this.DataContext).ToDecompilerSettings(); - ISettingsProvider.SaveDecompilerSettings(root, newSettings); - - SettingsService.Instance.DecompilerSettings = newSettings; - SettingsService.Instance.AssemblyListManager.ApplyWinRTProjections = newSettings.ApplyWindowsRuntimeProjections; - SettingsService.Instance.AssemblyListManager.UseDebugSymbols = newSettings.UseDebugSymbols; - } - - public void LoadDefaults() - { - SettingsService.Instance.DecompilerSettings = new(); - this.DataContext = new DecompilerSettingsViewModel(SettingsService.Instance.DecompilerSettings); - } } } diff --git a/ILSpy/Options/DecompilerSettingsViewModel.cs b/ILSpy/Options/DecompilerSettingsViewModel.cs index af9eeeb10..eb0b9d5a8 100644 --- a/ILSpy/Options/DecompilerSettingsViewModel.cs +++ b/ILSpy/Options/DecompilerSettingsViewModel.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.ComponentModel; +using System.ComponentModel.Composition; using System.Linq; using System.Reflection; @@ -28,44 +29,63 @@ using TomsToolbox.Wpf; namespace ICSharpCode.ILSpy.Options { - public sealed class DecompilerSettingsViewModel : ObservableObjectBase + [ExportOptionPage(Order = 10)] + [PartCreationPolicy(CreationPolicy.NonShared)] + public sealed class DecompilerSettingsViewModel : ObservableObjectBase, IOptionPage { - public DecompilerSettingsGroupViewModel[] Settings { get; } + private static readonly PropertyInfo[] propertyInfos = typeof(Decompiler.DecompilerSettings).GetProperties() + .Where(p => p.GetCustomAttribute<BrowsableAttribute>()?.Browsable != false) + .ToArray(); - public DecompilerSettingsViewModel(Decompiler.DecompilerSettings settings) + public string Title => Resources.Decompiler; + + private DecompilerSettingsGroupViewModel[] settings; + public DecompilerSettingsGroupViewModel[] Settings { + get => settings; + set => SetProperty(ref settings, value); + } + + private DecompilerSettings decompilerSettings; + + public void Load(SettingsSnapshot snapshot) { - Settings = typeof(Decompiler.DecompilerSettings).GetProperties() - .Where(p => p.GetCustomAttribute<BrowsableAttribute>()?.Browsable != false) - .Select(p => new DecompilerSettingsItemViewModel(p) { IsEnabled = p.GetValue(settings) is true }) + decompilerSettings = snapshot.GetSettings<DecompilerSettings>(); + LoadSettings(); + } + + private void LoadSettings() + { + this.Settings = propertyInfos + .Select(p => new DecompilerSettingsItemViewModel(p, decompilerSettings)) .OrderBy(item => item.Category, NaturalStringComparer.Instance) .GroupBy(p => p.Category) .Select(g => new DecompilerSettingsGroupViewModel(g.Key, g.OrderBy(i => i.Description).ToArray())) .ToArray(); } - public Decompiler.DecompilerSettings ToDecompilerSettings() + public void LoadDefaults() { - var settings = new Decompiler.DecompilerSettings(); + var defaults = new Decompiler.DecompilerSettings(); - foreach (var item in Settings.SelectMany(group => group.Settings)) + foreach (var propertyInfo in propertyInfos) { - item.Property.SetValue(settings, item.IsEnabled); + propertyInfo.SetValue(decompilerSettings, propertyInfo.GetValue(defaults)); } - return settings; + LoadSettings(); } } public sealed class DecompilerSettingsGroupViewModel : ObservableObjectBase { - private bool? _areAllItemsChecked; + private bool? areAllItemsChecked; public DecompilerSettingsGroupViewModel(string category, DecompilerSettingsItemViewModel[] settings) { Settings = settings; Category = category; - _areAllItemsChecked = GetAreAllItemsChecked(Settings); + areAllItemsChecked = GetAreAllItemsChecked(Settings); foreach (DecompilerSettingsItemViewModel viewModel in settings) { @@ -74,9 +94,9 @@ namespace ICSharpCode.ILSpy.Options } public bool? AreAllItemsChecked { - get => _areAllItemsChecked; + get => areAllItemsChecked; set { - SetProperty(ref _areAllItemsChecked, value); + SetProperty(ref areAllItemsChecked, value); if (!value.HasValue) return; @@ -114,15 +134,20 @@ namespace ICSharpCode.ILSpy.Options } } - public sealed class DecompilerSettingsItemViewModel(PropertyInfo property) : ObservableObjectBase + public sealed class DecompilerSettingsItemViewModel(PropertyInfo property, DecompilerSettings decompilerSettings) : ObservableObjectBase { - private bool _isEnabled; + private bool isEnabled = property.GetValue(decompilerSettings) is true; - public PropertyInfo Property { get; } = property; + public PropertyInfo Property => property; public bool IsEnabled { - get => _isEnabled; - set => SetProperty(ref _isEnabled, value); + get => isEnabled; + set { + if (SetProperty(ref isEnabled, value)) + { + property.SetValue(decompilerSettings, value); + } + } } public string Description { get; set; } = GetResourceString(property.GetCustomAttribute<DescriptionAttribute>()?.Description ?? property.Name); diff --git a/ILSpy/Options/DisplaySettings.cs b/ILSpy/Options/DisplaySettings.cs index 4ab7a05f2..9eb07b394 100644 --- a/ILSpy/Options/DisplaySettings.cs +++ b/ILSpy/Options/DisplaySettings.cs @@ -16,341 +16,190 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System.ComponentModel; -using System.Runtime.CompilerServices; using System.Windows.Media; +using System.Xml.Linq; -using ICSharpCode.ILSpy.Themes; +using TomsToolbox.Wpf; namespace ICSharpCode.ILSpy.Options { /// <summary> /// Description of DisplaySettings. /// </summary> - public class DisplaySettings : INotifyPropertyChanged + public class DisplaySettings : ObservableObject, ISettingsSection { - public DisplaySettings() - { - this.theme = ThemeManager.Current.DefaultTheme; - this.selectedFont = new FontFamily("Consolas"); - this.selectedFontSize = 10.0 * 4 / 3; - this.sortResults = true; - this.indentationUseTabs = true; - this.indentationSize = 4; - this.indentationTabSize = 4; - this.highlightMatchingBraces = true; - } - - #region INotifyPropertyChanged implementation - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) - { - PropertyChanged?.Invoke(this, e); - } - - protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); - } - #endregion - - string theme; - - public string Theme { - get { return theme; } - set { - if (theme != value) - { - theme = value; - OnPropertyChanged(); - } - } - } - FontFamily selectedFont; - public FontFamily SelectedFont { - get { return selectedFont; } - set { - if (selectedFont != value) - { - selectedFont = value; - OnPropertyChanged(); - } - } + get => selectedFont; + set => SetProperty(ref selectedFont, value); } double selectedFontSize; - public double SelectedFontSize { - get { return selectedFontSize; } - set { - if (selectedFontSize != value) - { - selectedFontSize = value; - OnPropertyChanged(); - } - } + get => selectedFontSize; + set => SetProperty(ref selectedFontSize, value); } bool showLineNumbers; - public bool ShowLineNumbers { - get { return showLineNumbers; } - set { - if (showLineNumbers != value) - { - showLineNumbers = value; - OnPropertyChanged(); - } - } + get => showLineNumbers; + set => SetProperty(ref showLineNumbers, value); } bool showMetadataTokens; - public bool ShowMetadataTokens { - get { return showMetadataTokens; } - set { - if (showMetadataTokens != value) - { - showMetadataTokens = value; - OnPropertyChanged(); - } - } + get => showMetadataTokens; + set => SetProperty(ref showMetadataTokens, value); } bool showMetadataTokensInBase10; - public bool ShowMetadataTokensInBase10 { - get { return showMetadataTokensInBase10; } - set { - if (showMetadataTokensInBase10 != value) - { - showMetadataTokensInBase10 = value; - OnPropertyChanged(); - } - } + get => showMetadataTokensInBase10; + set => SetProperty(ref showMetadataTokensInBase10, value); } bool enableWordWrap; - public bool EnableWordWrap { - get { return enableWordWrap; } - set { - if (enableWordWrap != value) - { - enableWordWrap = value; - OnPropertyChanged(); - } - } + get => enableWordWrap; + set => SetProperty(ref enableWordWrap, value); } - bool sortResults = true; - + bool sortResults; public bool SortResults { - get { return sortResults; } - set { - if (sortResults != value) - { - sortResults = value; - OnPropertyChanged(); - } - } + get => sortResults; + set => SetProperty(ref sortResults, value); } - bool foldBraces = false; - + bool foldBraces; public bool FoldBraces { - get { return foldBraces; } - set { - if (foldBraces != value) - { - foldBraces = value; - OnPropertyChanged(); - } - } + get => foldBraces; + set => SetProperty(ref foldBraces, value); } - bool expandMemberDefinitions = false; - + bool expandMemberDefinitions; public bool ExpandMemberDefinitions { - get { return expandMemberDefinitions; } - set { - if (expandMemberDefinitions != value) - { - expandMemberDefinitions = value; - OnPropertyChanged(); - } - } + get => expandMemberDefinitions; + set => SetProperty(ref expandMemberDefinitions, value); } - bool expandUsingDeclarations = false; - + bool expandUsingDeclarations; public bool ExpandUsingDeclarations { - get { return expandUsingDeclarations; } - set { - if (expandUsingDeclarations != value) - { - expandUsingDeclarations = value; - OnPropertyChanged(); - } - } + get => expandUsingDeclarations; + set => SetProperty(ref expandUsingDeclarations, value); } bool showDebugInfo; - public bool ShowDebugInfo { - get { return showDebugInfo; } - set { - if (showDebugInfo != value) - { - showDebugInfo = value; - OnPropertyChanged(); - } - } + get => showDebugInfo; + set => SetProperty(ref showDebugInfo, value); } - bool indentationUseTabs = true; - + bool indentationUseTabs; public bool IndentationUseTabs { - get { return indentationUseTabs; } - set { - if (indentationUseTabs != value) - { - indentationUseTabs = value; - OnPropertyChanged(); - } - } + get => indentationUseTabs; + set => SetProperty(ref indentationUseTabs, value); } - int indentationTabSize = 4; - + int indentationTabSize; public int IndentationTabSize { - get { return indentationTabSize; } - set { - if (indentationTabSize != value) - { - indentationTabSize = value; - OnPropertyChanged(); - } - } + get => indentationTabSize; + set => SetProperty(ref indentationTabSize, value); } - int indentationSize = 4; - + int indentationSize; public int IndentationSize { - get { return indentationSize; } - set { - if (indentationSize != value) - { - indentationSize = value; - OnPropertyChanged(); - } - } + get => indentationSize; + set => SetProperty(ref indentationSize, value); } - bool highlightMatchingBraces = true; - + bool highlightMatchingBraces; public bool HighlightMatchingBraces { - get { return highlightMatchingBraces; } - set { - if (highlightMatchingBraces != value) - { - highlightMatchingBraces = value; - OnPropertyChanged(); - } - } + get => highlightMatchingBraces; + set => SetProperty(ref highlightMatchingBraces, value); } - bool highlightCurrentLine = false; - + bool highlightCurrentLine; public bool HighlightCurrentLine { - get { return highlightCurrentLine; } - set { - if (highlightCurrentLine != value) - { - highlightCurrentLine = value; - OnPropertyChanged(); - } - } + get => highlightCurrentLine; + set => SetProperty(ref highlightCurrentLine, value); } - bool hideEmptyMetadataTables = true; - + bool hideEmptyMetadataTables; public bool HideEmptyMetadataTables { - get { return hideEmptyMetadataTables; } - set { - if (hideEmptyMetadataTables != value) - { - hideEmptyMetadataTables = value; - OnPropertyChanged(); - } - } + get => hideEmptyMetadataTables; + set => SetProperty(ref hideEmptyMetadataTables, value); } - bool useNestedNamespaceNodes = true; - + bool useNestedNamespaceNodes; public bool UseNestedNamespaceNodes { - get { return useNestedNamespaceNodes; } - set { - if (useNestedNamespaceNodes != value) - { - useNestedNamespaceNodes = value; - OnPropertyChanged(); - } - } + get => useNestedNamespaceNodes; + set => SetProperty(ref useNestedNamespaceNodes, value); } private bool styleWindowTitleBar; - public bool StyleWindowTitleBar { - get { return styleWindowTitleBar; } - set { - if (styleWindowTitleBar != value) - { - styleWindowTitleBar = value; - OnPropertyChanged(); - } - } + get => styleWindowTitleBar; + set => SetProperty(ref styleWindowTitleBar, value); } private bool showRawOffsetsAndBytesBeforeInstruction; - public bool ShowRawOffsetsAndBytesBeforeInstruction { - get { return showRawOffsetsAndBytesBeforeInstruction; } - set { - if (showRawOffsetsAndBytesBeforeInstruction != value) - { - showRawOffsetsAndBytesBeforeInstruction = value; - OnPropertyChanged(); - } - } + get => showRawOffsetsAndBytesBeforeInstruction; + set => SetProperty(ref showRawOffsetsAndBytesBeforeInstruction, value); } - public void CopyValues(DisplaySettings s) + public XName SectionName => "DisplaySettings"; + + public void LoadFromXml(XElement section) + { + SelectedFont = new FontFamily((string)section.Attribute("Font") ?? "Consolas"); + SelectedFontSize = (double?)section.Attribute("FontSize") ?? 10.0 * 4 / 3; + ShowLineNumbers = (bool?)section.Attribute("ShowLineNumbers") ?? false; + ShowMetadataTokens = (bool?)section.Attribute("ShowMetadataTokens") ?? false; + ShowMetadataTokensInBase10 = (bool?)section.Attribute("ShowMetadataTokensInBase10") ?? false; + ShowDebugInfo = (bool?)section.Attribute("ShowDebugInfo") ?? false; + EnableWordWrap = (bool?)section.Attribute("EnableWordWrap") ?? false; + SortResults = (bool?)section.Attribute("SortResults") ?? true; + FoldBraces = (bool?)section.Attribute("FoldBraces") ?? false; + ExpandMemberDefinitions = (bool?)section.Attribute("ExpandMemberDefinitions") ?? false; + ExpandUsingDeclarations = (bool?)section.Attribute("ExpandUsingDeclarations") ?? false; + IndentationUseTabs = (bool?)section.Attribute("IndentationUseTabs") ?? true; + IndentationSize = (int?)section.Attribute("IndentationSize") ?? 4; + IndentationTabSize = (int?)section.Attribute("IndentationTabSize") ?? 4; + HighlightMatchingBraces = (bool?)section.Attribute("HighlightMatchingBraces") ?? true; + HighlightCurrentLine = (bool?)section.Attribute("HighlightCurrentLine") ?? false; + HideEmptyMetadataTables = (bool?)section.Attribute("HideEmptyMetadataTables") ?? true; + UseNestedNamespaceNodes = (bool?)section.Attribute("UseNestedNamespaceNodes") ?? false; + ShowRawOffsetsAndBytesBeforeInstruction = (bool?)section.Attribute("ShowRawOffsetsAndBytesBeforeInstruction") ?? false; + StyleWindowTitleBar = (bool?)section.Attribute("StyleWindowTitleBar") ?? false; + } + + public XElement SaveToXml() { - this.Theme = s.Theme; - this.SelectedFont = s.selectedFont; - this.SelectedFontSize = s.selectedFontSize; - this.ShowLineNumbers = s.showLineNumbers; - this.ShowMetadataTokens = s.showMetadataTokens; - this.ShowMetadataTokensInBase10 = s.showMetadataTokensInBase10; - this.ShowDebugInfo = s.showDebugInfo; - this.EnableWordWrap = s.enableWordWrap; - this.SortResults = s.sortResults; - this.FoldBraces = s.foldBraces; - this.ExpandMemberDefinitions = s.expandMemberDefinitions; - this.ExpandUsingDeclarations = s.expandUsingDeclarations; - this.IndentationUseTabs = s.indentationUseTabs; - this.IndentationTabSize = s.indentationTabSize; - this.IndentationSize = s.indentationSize; - this.HighlightMatchingBraces = s.highlightMatchingBraces; - this.HighlightCurrentLine = s.highlightCurrentLine; - this.HideEmptyMetadataTables = s.hideEmptyMetadataTables; - this.UseNestedNamespaceNodes = s.useNestedNamespaceNodes; - this.ShowRawOffsetsAndBytesBeforeInstruction = s.showRawOffsetsAndBytesBeforeInstruction; - this.StyleWindowTitleBar = s.styleWindowTitleBar; + var section = new XElement(SectionName); + + section.SetAttributeValue("Font", SelectedFont.Source); + section.SetAttributeValue("FontSize", SelectedFontSize); + section.SetAttributeValue("ShowLineNumbers", ShowLineNumbers); + section.SetAttributeValue("ShowMetadataTokens", ShowMetadataTokens); + section.SetAttributeValue("ShowMetadataTokensInBase10", ShowMetadataTokensInBase10); + section.SetAttributeValue("ShowDebugInfo", ShowDebugInfo); + section.SetAttributeValue("EnableWordWrap", EnableWordWrap); + section.SetAttributeValue("SortResults", SortResults); + section.SetAttributeValue("FoldBraces", FoldBraces); + section.SetAttributeValue("ExpandMemberDefinitions", ExpandMemberDefinitions); + section.SetAttributeValue("ExpandUsingDeclarations", ExpandUsingDeclarations); + section.SetAttributeValue("IndentationUseTabs", IndentationUseTabs); + section.SetAttributeValue("IndentationSize", IndentationSize); + section.SetAttributeValue("IndentationTabSize", IndentationTabSize); + section.SetAttributeValue("HighlightMatchingBraces", HighlightMatchingBraces); + section.SetAttributeValue("HighlightCurrentLine", HighlightCurrentLine); + section.SetAttributeValue("HideEmptyMetadataTables", HideEmptyMetadataTables); + section.SetAttributeValue("UseNestedNamespaceNodes", UseNestedNamespaceNodes); + section.SetAttributeValue("ShowRawOffsetsAndBytesBeforeInstruction", ShowRawOffsetsAndBytesBeforeInstruction); + section.SetAttributeValue("StyleWindowTitleBar", StyleWindowTitleBar); + + return section; } } } diff --git a/ILSpy/Options/DisplaySettingsPanel.xaml b/ILSpy/Options/DisplaySettingsPanel.xaml index 1170a8f5b..9fb50d7a9 100644 --- a/ILSpy/Options/DisplaySettingsPanel.xaml +++ b/ILSpy/Options/DisplaySettingsPanel.xaml @@ -3,19 +3,15 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties" xmlns:local="clr-namespace:ICSharpCode.ILSpy.Options" - xmlns:system="clr-namespace:System;assembly=mscorlib" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes" - d:DataContext="{d:DesignInstance local:DisplaySettings}"> - <UserControl.Resources> - <local:FontSizeConverter x:Key="fontSizeConv" /> - </UserControl.Resources> + d:DataContext="{d:DesignInstance local:DisplaySettingsViewModel}"> <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"> <StackPanel Orientation="Vertical"> <DockPanel> <Label DockPanel.Dock="Left" Content="{x:Static properties:Resources.DisplaySettingsPanel_Theme}" /> - <ComboBox ItemsSource="{x:Static themes:ThemeManager.AllThemes}" SelectedItem="{Binding Theme}" VerticalContentAlignment="Center" Margin="0,0,8,0" /> + <ComboBox ItemsSource="{x:Static themes:ThemeManager.AllThemes}" SelectedItem="{Binding SessionSettings.Theme}" VerticalContentAlignment="Center" Margin="0,0,8,0" /> </DockPanel> <GroupBox Header="{x:Static properties:Resources.Font}"> <Grid> @@ -30,78 +26,58 @@ <RowDefinition Height="50" /> </Grid.RowDefinitions> <Label Margin="3,0" Content="{x:Static properties:Resources.DisplaySettingsPanel_Font}"></Label> - <ComboBox x:Name="fontSelector" VerticalContentAlignment="Center" SelectedItem="{Binding SelectedFont}" Grid.Column="1"> + <ComboBox Grid.Row="0" ItemsSource="{Binding FontFamilies}" VerticalContentAlignment="Center" SelectedItem="{Binding Settings.SelectedFont}" Grid.Column="1"> <ComboBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Source}" /> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> - <Label Grid.Column="2" Margin="3,0" Content="{x:Static properties:Resources.Size}"></Label> - <ComboBox Grid.Column="3" Text="{Binding SelectedFontSize, Converter={StaticResource fontSizeConv}}" IsEditable="True" Margin="3,0"> - <system:Int32>6</system:Int32> - <system:Int32>7</system:Int32> - <system:Int32>8</system:Int32> - <system:Int32>9</system:Int32> - <system:Int32>10</system:Int32> - <system:Int32>11</system:Int32> - <system:Int32>12</system:Int32> - <system:Int32>13</system:Int32> - <system:Int32>14</system:Int32> - <system:Int32>15</system:Int32> - <system:Int32>16</system:Int32> - <system:Int32>17</system:Int32> - <system:Int32>18</system:Int32> - <system:Int32>19</system:Int32> - <system:Int32>20</system:Int32> - <system:Int32>21</system:Int32> - <system:Int32>22</system:Int32> - <system:Int32>23</system:Int32> - <system:Int32>24</system:Int32> - </ComboBox> - <Border Grid.Row="1" Grid.ColumnSpan="4" BorderBrush="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}" BorderThickness="1" Margin="3,5"> - <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="AaBbCcXxYyZz" FontFamily="{Binding SelectedFont}" FontSize="{Binding SelectedFontSize}" /> + <Label Grid.Row="0" Grid.Column="2" Margin="3,0" Content="{x:Static properties:Resources.Size}"></Label> + <ComboBox Grid.Row="0" Grid.Column="3" ItemsSource="{Binding FontSizes}" Text="{Binding Settings.SelectedFontSize, Converter={local:FontSizeConverter}}" IsEditable="True" Margin="3,0" /> + <Border Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4" BorderBrush="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}" BorderThickness="1" Margin="3,5"> + <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="AaBbCcXxYyZz" FontFamily="{Binding Settings.SelectedFont}" FontSize="{Binding Settings.SelectedFontSize}" /> </Border> </Grid> </GroupBox> <GroupBox Header="{x:Static properties:Resources.Indentation}"> <StackPanel Margin="3"> - <CheckBox IsChecked="{Binding IndentationUseTabs}" Content="{x:Static properties:Resources.UseTabsInsteadOfSpaces}"></CheckBox> + <CheckBox IsChecked="{Binding Settings.IndentationUseTabs}" Content="{x:Static properties:Resources.UseTabsInsteadOfSpaces}"></CheckBox> <StackPanel Orientation="Horizontal"> <Label Content="{x:Static properties:Resources.TabSize}"></Label> - <TextBox x:Name="tabSizeTextBox" Width="50" Margin="3" Text="{Binding IndentationTabSize}" PreviewTextInput="TextBox_PreviewTextInput" /> + <TextBox x:Name="tabSizeTextBox" Width="50" Margin="3" Text="{Binding Settings.IndentationTabSize}" PreviewTextInput="TextBox_PreviewTextInput" /> </StackPanel> <StackPanel Orientation="Horizontal"> <Label Content="{x:Static properties:Resources.IndentSize}"></Label> - <TextBox x:Name="indentSizeTextBox" Width="50" Margin="3" Text="{Binding IndentationSize}" PreviewTextInput="TextBox_PreviewTextInput" /> + <TextBox x:Name="indentSizeTextBox" Width="50" Margin="3" Text="{Binding Settings.IndentationSize}" PreviewTextInput="TextBox_PreviewTextInput" /> </StackPanel> </StackPanel> </GroupBox> <GroupBox Header="{x:Static properties:Resources.DecompilationViewOptions}"> <StackPanel Margin="3"> - <CheckBox IsChecked="{Binding ShowLineNumbers}" Content="{x:Static properties:Resources.ShowLineNumbers}"></CheckBox> - <CheckBox IsChecked="{Binding EnableWordWrap}" Content="{x:Static properties:Resources.EnableWordWrap}"></CheckBox> - <CheckBox IsChecked="{Binding FoldBraces}" Content="{x:Static properties:Resources.EnableFoldingBlocksBraces}"></CheckBox> - <CheckBox IsChecked="{Binding HighlightMatchingBraces}" Content="{x:Static properties:Resources.HighlightMatchingBraces}"></CheckBox> - <CheckBox IsChecked="{Binding HighlightCurrentLine}" Content="{x:Static properties:Resources.HighlightCurrentLine}"></CheckBox> - <CheckBox IsChecked="{Binding ExpandMemberDefinitions}" Content="{x:Static properties:Resources.ExpandMemberDefinitionsAfterDecompilation}"></CheckBox> - <CheckBox IsChecked="{Binding ExpandUsingDeclarations}" Content="{x:Static properties:Resources.ExpandUsingDeclarationsAfterDecompilation}"></CheckBox> - <CheckBox IsChecked="{Binding ShowDebugInfo}" Content="{x:Static properties:Resources.ShowInfoFromDebugSymbolsAvailable}"></CheckBox> - <CheckBox IsChecked="{Binding ShowRawOffsetsAndBytesBeforeInstruction}" Content="{x:Static properties:Resources.ShowRawOffsetsAndBytesBeforeInstruction}"></CheckBox> + <CheckBox IsChecked="{Binding Settings.ShowLineNumbers}" Content="{x:Static properties:Resources.ShowLineNumbers}"></CheckBox> + <CheckBox IsChecked="{Binding Settings.EnableWordWrap}" Content="{x:Static properties:Resources.EnableWordWrap}"></CheckBox> + <CheckBox IsChecked="{Binding Settings.FoldBraces}" Content="{x:Static properties:Resources.EnableFoldingBlocksBraces}"></CheckBox> + <CheckBox IsChecked="{Binding Settings.HighlightMatchingBraces}" Content="{x:Static properties:Resources.HighlightMatchingBraces}"></CheckBox> + <CheckBox IsChecked="{Binding Settings.HighlightCurrentLine}" Content="{x:Static properties:Resources.HighlightCurrentLine}"></CheckBox> + <CheckBox IsChecked="{Binding Settings.ExpandMemberDefinitions}" Content="{x:Static properties:Resources.ExpandMemberDefinitionsAfterDecompilation}"></CheckBox> + <CheckBox IsChecked="{Binding Settings.ExpandUsingDeclarations}" Content="{x:Static properties:Resources.ExpandUsingDeclarationsAfterDecompilation}"></CheckBox> + <CheckBox IsChecked="{Binding Settings.ShowDebugInfo}" Content="{x:Static properties:Resources.ShowInfoFromDebugSymbolsAvailable}"></CheckBox> + <CheckBox IsChecked="{Binding Settings.ShowRawOffsetsAndBytesBeforeInstruction}" Content="{x:Static properties:Resources.ShowRawOffsetsAndBytesBeforeInstruction}"></CheckBox> </StackPanel> </GroupBox> <GroupBox Header="{x:Static properties:Resources.TreeViewOptions}"> <StackPanel Margin="3"> - <CheckBox IsChecked="{Binding ShowMetadataTokens}" Content="{x:Static properties:Resources.ShowMetadataTokens}"></CheckBox> - <CheckBox IsChecked="{Binding ShowMetadataTokensInBase10}" Content="{x:Static properties:Resources.ShowMetadataTokensInBase10}"></CheckBox> - <CheckBox IsChecked="{Binding HideEmptyMetadataTables}" Content="{x:Static properties:Resources.HideEmptyMetadataTables}"></CheckBox> - <CheckBox IsChecked="{Binding UseNestedNamespaceNodes}" Content="{x:Static properties:Resources.UseNestedNamespaceNodes}"></CheckBox> + <CheckBox IsChecked="{Binding Settings.ShowMetadataTokens}" Content="{x:Static properties:Resources.ShowMetadataTokens}"></CheckBox> + <CheckBox IsChecked="{Binding Settings.ShowMetadataTokensInBase10}" Content="{x:Static properties:Resources.ShowMetadataTokensInBase10}"></CheckBox> + <CheckBox IsChecked="{Binding Settings.HideEmptyMetadataTables}" Content="{x:Static properties:Resources.HideEmptyMetadataTables}"></CheckBox> + <CheckBox IsChecked="{Binding Settings.UseNestedNamespaceNodes}" Content="{x:Static properties:Resources.UseNestedNamespaceNodes}"></CheckBox> </StackPanel> </GroupBox> <GroupBox Header="{x:Static properties:Resources.OtherOptions}"> <StackPanel Margin="3"> - <CheckBox IsChecked="{Binding SortResults}" Content="{x:Static properties:Resources.SortResultsFitness}"></CheckBox> - <CheckBox IsChecked="{Binding StyleWindowTitleBar}" Content="{x:Static properties:Resources.StyleTheWindowTitleBar}"></CheckBox> + <CheckBox IsChecked="{Binding Settings.SortResults}" Content="{x:Static properties:Resources.SortResultsFitness}"></CheckBox> + <CheckBox IsChecked="{Binding Settings.StyleWindowTitleBar}" Content="{x:Static properties:Resources.StyleTheWindowTitleBar}"></CheckBox> </StackPanel> </GroupBox> </StackPanel> diff --git a/ILSpy/Options/DisplaySettingsPanel.xaml.cs b/ILSpy/Options/DisplaySettingsPanel.xaml.cs index 7101665c7..3b51a16ba 100644 --- a/ILSpy/Options/DisplaySettingsPanel.xaml.cs +++ b/ILSpy/Options/DisplaySettingsPanel.xaml.cs @@ -27,17 +27,19 @@ using System.Windows.Media; using System.Windows.Threading; using System.Xml.Linq; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX.Settings; +using TomsToolbox.Wpf.Composition.Mef; +using TomsToolbox.Wpf.Converters; + namespace ICSharpCode.ILSpy.Options { /// <summary> /// Interaction logic for DisplaySettingsPanel.xaml /// </summary> - [ExportOptionPage(Title = nameof(Properties.Resources.Display), Order = 20)] [PartCreationPolicy(CreationPolicy.NonShared)] - public partial class DisplaySettingsPanel : UserControl, IOptionPage + [DataTemplate(typeof(DisplaySettingsViewModel))] + public partial class DisplaySettingsPanel { public DisplaySettingsPanel() { @@ -45,132 +47,6 @@ namespace ICSharpCode.ILSpy.Options DataObject.AddPastingHandler(tabSizeTextBox, OnPaste); DataObject.AddPastingHandler(indentSizeTextBox, OnPaste); - - Task<FontFamily[]> task = new Task<FontFamily[]>(FontLoader); - task.Start(); - task.ContinueWith( - delegate (Task continuation) { - App.Current.Dispatcher.Invoke( - DispatcherPriority.Normal, - (Action)( - () => { - fontSelector.ItemsSource = task.Result; - if (continuation.Exception != null) - { - foreach (var ex in continuation.Exception.InnerExceptions) - { - MessageBox.Show(ex.ToString()); - } - } - }) - ); - } - ); - } - - public void Load(ILSpySettings settings) - { - this.DataContext = LoadDisplaySettings(settings); - } - - static bool IsSymbolFont(FontFamily fontFamily) - { - foreach (var tf in fontFamily.GetTypefaces()) - { - GlyphTypeface glyph; - try - { - if (tf.TryGetGlyphTypeface(out glyph)) - return glyph.Symbol; - } - catch (Exception) - { - return true; - } - } - return false; - } - - static FontFamily[] FontLoader() - { - return (from ff in Fonts.SystemFontFamilies - where !IsSymbolFont(ff) - orderby ff.Source - select ff).ToArray(); - } - - public static DisplaySettings LoadDisplaySettings(ILSpySettings settings, SessionSettings? sessionSettings = null) - { - XElement e = settings["DisplaySettings"]; - var s = new DisplaySettings(); - s.SelectedFont = new FontFamily((string?)e.Attribute("Font") ?? "Consolas"); - s.SelectedFontSize = (double?)e.Attribute("FontSize") ?? 10.0 * 4 / 3; - s.ShowLineNumbers = (bool?)e.Attribute("ShowLineNumbers") ?? false; - s.ShowMetadataTokens = (bool?)e.Attribute("ShowMetadataTokens") ?? false; - s.ShowMetadataTokensInBase10 = (bool?)e.Attribute("ShowMetadataTokensInBase10") ?? false; - s.ShowDebugInfo = (bool?)e.Attribute("ShowDebugInfo") ?? false; - s.EnableWordWrap = (bool?)e.Attribute("EnableWordWrap") ?? false; - s.SortResults = (bool?)e.Attribute("SortResults") ?? true; - s.FoldBraces = (bool?)e.Attribute("FoldBraces") ?? false; - s.ExpandMemberDefinitions = (bool?)e.Attribute("ExpandMemberDefinitions") ?? false; - s.ExpandUsingDeclarations = (bool?)e.Attribute("ExpandUsingDeclarations") ?? false; - s.IndentationUseTabs = (bool?)e.Attribute("IndentationUseTabs") ?? true; - s.IndentationSize = (int?)e.Attribute("IndentationSize") ?? 4; - s.IndentationTabSize = (int?)e.Attribute("IndentationTabSize") ?? 4; - s.HighlightMatchingBraces = (bool?)e.Attribute("HighlightMatchingBraces") ?? true; - s.HighlightCurrentLine = (bool?)e.Attribute("HighlightCurrentLine") ?? false; - s.HideEmptyMetadataTables = (bool?)e.Attribute("HideEmptyMetadataTables") ?? true; - s.UseNestedNamespaceNodes = (bool?)e.Attribute("UseNestedNamespaceNodes") ?? false; - s.ShowRawOffsetsAndBytesBeforeInstruction = (bool?)e.Attribute("ShowRawOffsetsAndBytesBeforeInstruction") ?? false; - s.StyleWindowTitleBar = (bool?)e.Attribute("StyleWindowTitleBar") ?? false; - - s.Theme = (sessionSettings ?? SettingsService.Instance.SessionSettings).Theme; - - return s; - } - - public void Save(XElement root) - { - var s = (DisplaySettings)this.DataContext; - - var section = new XElement("DisplaySettings"); - section.SetAttributeValue("Font", s.SelectedFont.Source); - section.SetAttributeValue("FontSize", s.SelectedFontSize); - section.SetAttributeValue("ShowLineNumbers", s.ShowLineNumbers); - section.SetAttributeValue("ShowMetadataTokens", s.ShowMetadataTokens); - section.SetAttributeValue("ShowMetadataTokensInBase10", s.ShowMetadataTokensInBase10); - section.SetAttributeValue("ShowDebugInfo", s.ShowDebugInfo); - section.SetAttributeValue("EnableWordWrap", s.EnableWordWrap); - section.SetAttributeValue("SortResults", s.SortResults); - section.SetAttributeValue("FoldBraces", s.FoldBraces); - section.SetAttributeValue("ExpandMemberDefinitions", s.ExpandMemberDefinitions); - section.SetAttributeValue("ExpandUsingDeclarations", s.ExpandUsingDeclarations); - section.SetAttributeValue("IndentationUseTabs", s.IndentationUseTabs); - section.SetAttributeValue("IndentationSize", s.IndentationSize); - section.SetAttributeValue("IndentationTabSize", s.IndentationTabSize); - section.SetAttributeValue("HighlightMatchingBraces", s.HighlightMatchingBraces); - section.SetAttributeValue("HighlightCurrentLine", s.HighlightCurrentLine); - section.SetAttributeValue("HideEmptyMetadataTables", s.HideEmptyMetadataTables); - section.SetAttributeValue("UseNestedNamespaceNodes", s.UseNestedNamespaceNodes); - section.SetAttributeValue("ShowRawOffsetsAndBytesBeforeInstruction", s.ShowRawOffsetsAndBytesBeforeInstruction); - section.SetAttributeValue("StyleWindowTitleBar", s.StyleWindowTitleBar); - - SettingsService.Instance.SessionSettings.Theme = s.Theme; - var sessionSettings = SettingsService.Instance.SessionSettings.ToXml(); - - SettingsService.Instance.DisplaySettings.CopyValues(s); - - Update(section); - Update(sessionSettings); - - void Update(XElement element) - { - var existingElement = root.Element(element.Name); - if (existingElement != null) - existingElement.ReplaceWith(element); - else - root.Add(element); - } } private void TextBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e) @@ -179,44 +55,39 @@ namespace ICSharpCode.ILSpy.Options e.Handled = true; } - private void OnPaste(object sender, DataObjectPastingEventArgs e) + private static void OnPaste(object sender, DataObjectPastingEventArgs e) { if (!e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true)) return; + var text = (string)e.SourceDataObject.GetData(DataFormats.UnicodeText, true) ?? string.Empty; + if (!text.All(char.IsDigit)) e.CancelCommand(); } - - public void LoadDefaults() - { - SettingsService.Instance.DisplaySettings.CopyValues(new DisplaySettings()); - this.DataContext = SettingsService.Instance.DisplaySettings; - } } - public class FontSizeConverter : IValueConverter + public sealed class FontSizeConverter : ValueConverter { - public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + protected override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (value is double d) { return Math.Round(d / 4 * 3); } - throw new NotImplementedException(); + return DependencyProperty.UnsetValue; } - public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + protected override object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { - if (value is string s) - { - if (double.TryParse(s, out double d)) - return d * 4 / 3; - return 11.0 * 4 / 3; - } + if (value is not string s) + return DependencyProperty.UnsetValue; + + if (double.TryParse(s, out double d)) + return d * 4 / 3; - throw new NotImplementedException(); + return 11.0 * 4 / 3; } } } \ No newline at end of file diff --git a/ILSpy/Options/DisplaySettingsViewModel.cs b/ILSpy/Options/DisplaySettingsViewModel.cs new file mode 100644 index 000000000..4d7c53f43 --- /dev/null +++ b/ILSpy/Options/DisplaySettingsViewModel.cs @@ -0,0 +1,94 @@ +using ICSharpCode.ILSpyX.Settings; +using System.Windows.Media; +using System.Xml.Linq; +using System; +using System.ComponentModel.Composition; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +using TomsToolbox.Wpf; +using ICSharpCode.ILSpy.Themes; + +namespace ICSharpCode.ILSpy.Options +{ + [ExportOptionPage(Order = 20)] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class DisplaySettingsViewModel : ObservableObject, IOptionPage + { + private DisplaySettings settings = new(); + private FontFamily[] fontFamilies; + private SessionSettings sessionSettings; + + public DisplaySettingsViewModel() + { + fontFamilies = [settings.SelectedFont]; + + Task.Run(FontLoader).ContinueWith(continuation => { + FontFamilies = continuation.Result; + if (continuation.Exception == null) + return; + foreach (var ex in continuation.Exception.InnerExceptions) + { + MessageBox.Show(ex.ToString()); + } + }); + } + + public string Title => Properties.Resources.Display; + + public DisplaySettings Settings { + get => settings; + set => SetProperty(ref settings, value); + } + + public SessionSettings SessionSettings { + get => sessionSettings; + set => SetProperty(ref sessionSettings, value); + } + + public FontFamily[] FontFamilies { + get => fontFamilies; + set => SetProperty(ref fontFamilies, value); + } + + public int[] FontSizes { get; } = Enumerable.Range(6, 24 - 6 + 1).ToArray(); + + public void Load(SettingsSnapshot snapshot) + { + Settings = snapshot.GetSettings<DisplaySettings>(); + SessionSettings = snapshot.GetSettings<SessionSettings>(); + } + + static bool IsSymbolFont(FontFamily fontFamily) + { + foreach (var tf in fontFamily.GetTypefaces()) + { + try + { + if (tf.TryGetGlyphTypeface(out GlyphTypeface glyph)) + return glyph.Symbol; + } + catch (Exception) + { + return true; + } + } + return false; + } + + static FontFamily[] FontLoader() + { + return Fonts.SystemFontFamilies + .Where(ff => !IsSymbolFont(ff)) + .OrderBy(ff => ff.Source) + .ToArray(); + } + + public void LoadDefaults() + { + Settings.LoadFromXml(new XElement("empty")); + SessionSettings.Theme = ThemeManager.Current.DefaultTheme; + } + } +} diff --git a/ICSharpCode.ILSpyX/Settings/MiscSettings.cs b/ILSpy/Options/MiscSettings.cs similarity index 56% rename from ICSharpCode.ILSpyX/Settings/MiscSettings.cs rename to ILSpy/Options/MiscSettings.cs index d349764d4..75f35e81d 100644 --- a/ICSharpCode.ILSpyX/Settings/MiscSettings.cs +++ b/ILSpy/Options/MiscSettings.cs @@ -19,25 +19,42 @@ using System; using System.Xml.Linq; +using TomsToolbox.Wpf; + namespace ICSharpCode.ILSpyX.Settings { - public class MiscSettings : IMiscSettings, ISettingsSection<MiscSettings> + public class MiscSettings : ObservableObject, ISettingsSection { - private MiscSettings() - { + private bool allowMultipleInstances; + private bool loadPreviousAssemblies = true; + + public bool AllowMultipleInstances { + get => allowMultipleInstances; + set => SetProperty(ref allowMultipleInstances, value); + } + + public bool LoadPreviousAssemblies { + get => loadPreviousAssemblies; + set => SetProperty(ref loadPreviousAssemblies, value); } - public bool AllowMultipleInstances { get; set; } - public bool LoadPreviousAssemblies { get; set; } + public XName SectionName => "MiscSettings"; - public static MiscSettings Load(ISettingsProvider settingsProvider) + public void LoadFromXml(XElement e) { - XElement e = settingsProvider["MiscSettings"]; - var s = new MiscSettings(); - s.AllowMultipleInstances = (bool?)e.Attribute(nameof(s.AllowMultipleInstances)) ?? false; - s.LoadPreviousAssemblies = (bool?)e.Attribute(nameof(s.LoadPreviousAssemblies)) ?? true; + AllowMultipleInstances = (bool?)e.Attribute(nameof(AllowMultipleInstances)) ?? false; + LoadPreviousAssemblies = (bool?)e.Attribute(nameof(LoadPreviousAssemblies)) ?? true; + } + + public XElement SaveToXml() + { + var section = new XElement(SectionName); - return s; + section.SetAttributeValue(nameof(AllowMultipleInstances), AllowMultipleInstances); + section.SetAttributeValue(nameof(LoadPreviousAssemblies), LoadPreviousAssemblies); + + return section; } } } + diff --git a/ILSpy/Options/MiscSettingsPanel.xaml b/ILSpy/Options/MiscSettingsPanel.xaml index 502f65fcc..c3a984c9b 100644 --- a/ILSpy/Options/MiscSettingsPanel.xaml +++ b/ILSpy/Options/MiscSettingsPanel.xaml @@ -2,16 +2,17 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 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:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties" - mc:Ignorable="d" + xmlns:options="clr-namespace:ICSharpCode.ILSpy.Options" + d:DataContext="{d:DesignInstance options:MiscSettingsViewModel}" d:DesignHeight="300" d:DesignWidth="300"> <StackPanel Orientation="Vertical"> <GroupBox Header="{x:Static properties:Resources.Misc}"> <StackPanel Margin="10"> - <CheckBox IsChecked="{Binding AllowMultipleInstances}" Content="{x:Static properties:Resources.AllowMultipleInstances}" /> - <CheckBox IsChecked="{Binding LoadPreviousAssemblies}" Content="{x:Static properties:Resources.LoadAssembliesThatWereLoadedInTheLastInstance}"/> - <Button Command="{Binding AddRemoveShellIntegrationCommand}" IsEnabled="{Binding EnableShellIntegrationCommand}" Content="{Binding AddRemoveShellIntegrationText}" Margin="3" /> + <CheckBox IsChecked="{Binding Settings.AllowMultipleInstances}" Content="{x:Static properties:Resources.AllowMultipleInstances}" /> + <CheckBox IsChecked="{Binding Settings.LoadPreviousAssemblies}" Content="{x:Static properties:Resources.LoadAssembliesThatWereLoadedInTheLastInstance}"/> + <Button Command="{Binding AddRemoveShellIntegrationCommand}" Content="{Binding AddRemoveShellIntegrationText, FallbackValue=temp}" Margin="3" /> </StackPanel> </GroupBox> </StackPanel> diff --git a/ILSpy/Options/MiscSettingsPanel.xaml.cs b/ILSpy/Options/MiscSettingsPanel.xaml.cs index d72778b40..d6c7c672d 100644 --- a/ILSpy/Options/MiscSettingsPanel.xaml.cs +++ b/ILSpy/Options/MiscSettingsPanel.xaml.cs @@ -20,53 +20,20 @@ using System.ComponentModel.Composition; using System.Windows.Controls; using System.Xml.Linq; -using ICSharpCode.ILSpyX.Settings; +using TomsToolbox.Wpf.Composition.Mef; namespace ICSharpCode.ILSpy.Options { /// <summary> /// Interaction logic for MiscSettingsPanel.xaml /// </summary> - [ExportOptionPage(Title = nameof(Properties.Resources.Misc), Order = 30)] + [DataTemplate(typeof(MiscSettingsViewModel))] [PartCreationPolicy(CreationPolicy.NonShared)] - public partial class MiscSettingsPanel : UserControl, IOptionPage + public partial class MiscSettingsPanel { public MiscSettingsPanel() { InitializeComponent(); } - - public void Load(ILSpySettings settings) - { - this.DataContext = LoadMiscSettings(settings); - } - - static MiscSettingsViewModel? currentMiscSettings; - - public static MiscSettingsViewModel CurrentMiscSettings { - get { - return currentMiscSettings ?? (currentMiscSettings = LoadMiscSettings(ILSpySettings.Load())); - } - } - - public static MiscSettingsViewModel LoadMiscSettings(ILSpySettings settings) - { - var s = MiscSettings.Load(settings); - return new MiscSettingsViewModel(s); - } - - public void Save(XElement root) - { - var s = (MiscSettingsViewModel)this.DataContext; - IMiscSettings.Save(root, s); - - currentMiscSettings = null; // invalidate cached settings - } - - public void LoadDefaults() - { - currentMiscSettings = new MiscSettingsViewModel(MiscSettings.Load(ILSpySettings.Load())); - this.DataContext = currentMiscSettings; - } } } diff --git a/ILSpy/Options/MiscSettingsViewModel.cs b/ILSpy/Options/MiscSettingsViewModel.cs index 727773a45..2ed4b9868 100644 --- a/ILSpy/Options/MiscSettingsViewModel.cs +++ b/ILSpy/Options/MiscSettingsViewModel.cs @@ -16,89 +16,61 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System.ComponentModel; +using System.ComponentModel.Composition; using System.IO; using System.Reflection; -using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Input; +using System.Xml.Linq; using ICSharpCode.ILSpy.AppEnv; -using ICSharpCode.ILSpy.Commands; using ICSharpCode.ILSpyX.Settings; using Microsoft.Win32; +using TomsToolbox.Wpf; + namespace ICSharpCode.ILSpy.Options { - public class MiscSettingsViewModel : IMiscSettings, INotifyPropertyChanged + [ExportOptionPage(Order = 30)] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class MiscSettingsViewModel : ObservableObject, IOptionPage { - bool allowMultipleInstances; - bool loadPreviousAssemblies = true; - - public MiscSettingsViewModel(MiscSettings s) - { - AllowMultipleInstances = s.AllowMultipleInstances; - LoadPreviousAssemblies = s.LoadPreviousAssemblies; - - if (EnableShellIntegrationCommand) - { - AddRemoveShellIntegrationCommand = new DelegateCommand<object>(AddRemoveShellIntegration); - } - } - - /// <summary> - /// Allow multiple instances. - /// </summary> - public bool AllowMultipleInstances { - get { return allowMultipleInstances; } - set { - if (allowMultipleInstances != value) - { - allowMultipleInstances = value; - OnPropertyChanged(); - } - } + private MiscSettings settings; + public MiscSettings Settings { + get => settings; + set => SetProperty(ref settings, value); } - /// <summary> - /// Load assemblies that were loaded in the previous instance - /// </summary> - public bool LoadPreviousAssemblies { - get { return loadPreviousAssemblies; } - set { - if (loadPreviousAssemblies != value) - { - loadPreviousAssemblies = value; - OnPropertyChanged(); - } - } - } - - public ICommand AddRemoveShellIntegrationCommand { get; } - public bool EnableShellIntegrationCommand => AppEnvironment.IsWindows; + public ICommand AddRemoveShellIntegrationCommand => new DelegateCommand(() => AppEnvironment.IsWindows, AddRemoveShellIntegration); const string rootPath = @"Software\Classes\{0}\shell"; const string fullPath = @"Software\Classes\{0}\shell\Open with ILSpy\command"; - private void AddRemoveShellIntegration(object obj) + private void AddRemoveShellIntegration() { - string commandLine = CommandLineTools.ArgumentArrayToCommandLine(Path.ChangeExtension(Assembly.GetEntryAssembly().Location, ".exe")) + " \"%L\""; + string commandLine = CommandLineTools.ArgumentArrayToCommandLine(Path.ChangeExtension(Assembly.GetEntryAssembly()?.Location, ".exe")) + " \"%L\""; if (RegistryEntriesExist()) { if (MessageBox.Show(string.Format(Properties.Resources.RemoveShellIntegrationMessage, commandLine), "ILSpy", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes) { - Registry.CurrentUser.CreateSubKey(string.Format(rootPath, "dllfile")).DeleteSubKeyTree("Open with ILSpy"); - Registry.CurrentUser.CreateSubKey(string.Format(rootPath, "exefile")).DeleteSubKeyTree("Open with ILSpy"); + Registry.CurrentUser + .CreateSubKey(string.Format(rootPath, "dllfile"))? + .DeleteSubKeyTree("Open with ILSpy"); + Registry.CurrentUser + .CreateSubKey(string.Format(rootPath, "exefile"))? + .DeleteSubKeyTree("Open with ILSpy"); } } else { if (MessageBox.Show(string.Format(Properties.Resources.AddShellIntegrationMessage, commandLine), "ILSpy", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes) { - Registry.CurrentUser.CreateSubKey(string.Format(fullPath, "dllfile"))? + Registry.CurrentUser + .CreateSubKey(string.Format(fullPath, "dllfile"))? .SetValue("", commandLine); - Registry.CurrentUser.CreateSubKey(string.Format(fullPath, "exefile"))? + Registry.CurrentUser + .CreateSubKey(string.Format(fullPath, "exefile"))? .SetValue("", commandLine); } } @@ -117,20 +89,16 @@ namespace ICSharpCode.ILSpy.Options } } - #region INotifyPropertyChanged Implementation + public string Title => Properties.Resources.Misc; - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) + public void Load(SettingsSnapshot settings) { - PropertyChanged?.Invoke(this, e); + Settings = settings.GetSettings<MiscSettings>(); } - protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) + public void LoadDefaults() { - OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); + Settings.LoadFromXml(new XElement("dummy")); } - - #endregion } } diff --git a/ILSpy/Options/OptionsDialog.xaml b/ILSpy/Options/OptionsDialog.xaml index 7dd6d38df..ce6102195 100644 --- a/ILSpy/Options/OptionsDialog.xaml +++ b/ILSpy/Options/OptionsDialog.xaml @@ -1,35 +1,43 @@ <Window x:Class="ICSharpCode.ILSpy.Options.OptionsDialog" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties" - xmlns:options="clr-namespace:ICSharpCode.ILSpy.Options" - Style="{DynamicResource DialogWindow}" + xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties" + xmlns:options="clr-namespace:ICSharpCode.ILSpy.Options" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:toms="urn:TomsToolbox" + mc:Ignorable="d" + d:DataContext="{d:DesignInstance options:OptionsDialogViewModel}" + Style="{StaticResource DialogWindow}" WindowStartupLocation="CenterOwner" ResizeMode="CanResizeWithGrip" Title="{x:Static properties:Resources.Options}" Height="500" Width="600"> - <Grid> - <Grid.RowDefinitions> - <RowDefinition - Height="1*" /> - <RowDefinition - Height="Auto" /> - </Grid.RowDefinitions> - <TabControl Name="tabControl" SelectedValuePath="Content"> + <DockPanel> + <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Margin="12,8"> + <Button Margin="2,0" + Command="{Binding ResetDefaultsCommand}" + Content="{x:Static properties:Resources.ResetToDefaults}" /> + <Button IsDefault="True" Margin="2,0" + toms:Button.DialogResult="true" + Command="{Binding CommitCommand}" + Content="{x:Static properties:Resources.OK}" /> + <Button IsCancel="True" Margin="2,0" + Content="{x:Static properties:Resources.Cancel}" /> + </StackPanel> + <TabControl ItemsSource="{Binding OptionPages}" + SelectedIndex="0" + SelectedValuePath="Content" + SelectedValue="{Binding SelectedPage}"> <TabControl.ItemTemplate> - <DataTemplate DataType="options:TabItemViewModel"> - <TextBlock Text="{Binding Header}" /> + <DataTemplate DataType="options:OptionsItemViewModel"> + <TextBlock Text="{Binding Title}" /> </DataTemplate> </TabControl.ItemTemplate> <TabControl.ContentTemplate> - <DataTemplate DataType="options:TabItemViewModel"> + <DataTemplate DataType="options:OptionsItemViewModel"> <ContentPresenter Content="{Binding Content}" /> </DataTemplate> </TabControl.ContentTemplate> </TabControl> - <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="12,8"> - <Button Margin="2,0" Name="defaultsButton" Click="DefaultsButton_Click" Content="{x:Static properties:Resources.ResetToDefaults}" /> - <Button IsDefault="True" Margin="2,0" Name="okButton" Click="OKButton_Click" Content="{x:Static properties:Resources.OK}" /> - <Button IsCancel="True" Margin="2,0" Content="{x:Static properties:Resources.Cancel}" /> - </StackPanel> - </Grid> + </DockPanel> </Window> \ No newline at end of file diff --git a/ILSpy/Options/OptionsDialog.xaml.cs b/ILSpy/Options/OptionsDialog.xaml.cs index 184dc9b1c..783488175 100644 --- a/ILSpy/Options/OptionsDialog.xaml.cs +++ b/ILSpy/Options/OptionsDialog.xaml.cs @@ -18,105 +18,42 @@ using System; using System.ComponentModel.Composition; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Documents; -using System.Windows.Media; -using System.Xml.Linq; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Properties; -using ICSharpCode.ILSpyX.Settings; - -using TomsToolbox.Composition; namespace ICSharpCode.ILSpy.Options { - public class TabItemViewModel - { - public TabItemViewModel(string header, UIElement content) - { - Header = header; - Content = content; - } - - public string Header { get; } - public UIElement Content { get; } - } - /// <summary> /// Interaction logic for OptionsDialog.xaml /// </summary> - public partial class OptionsDialog : Window + public sealed partial class OptionsDialog { - readonly IExport<UIElement, IOptionsMetadata>[] optionPages; - public OptionsDialog() { + DataContext = new OptionsDialogViewModel(); InitializeComponent(); - var ep = App.ExportProvider; - this.optionPages = ep.GetExports<UIElement, IOptionsMetadata>("OptionPages").ToArray(); - ILSpySettings settings = ILSpySettings.Load(); - foreach (var optionPage in optionPages.OrderBy(p => p.Metadata.Order)) - { - var tabItem = new TabItemViewModel(MainWindow.GetResourceString(optionPage.Metadata.Title), optionPage.Value); - - tabControl.Items.Add(tabItem); - - IOptionPage? page = optionPage.Value as IOptionPage; - if (page != null) - page.Load(settings); - } - } - - void OKButton_Click(object sender, RoutedEventArgs e) - { - ILSpySettings.Update( - delegate (XElement root) { - foreach (var optionPage in optionPages) - { - IOptionPage? page = optionPage.Value as IOptionPage; - if (page != null) - page.Save(root); - } - }); - this.DialogResult = true; - Close(); - } - - private void DefaultsButton_Click(object sender, RoutedEventArgs e) - { - if (MessageBox.Show(Properties.Resources.ResetToDefaultsConfirmationMessage, "ILSpy", MessageBoxButton.YesNo) == MessageBoxResult.Yes) - { - var page = tabControl.SelectedValue as IOptionPage; - if (page != null) - page.LoadDefaults(); - } } } public interface IOptionsMetadata { - string Title { get; } int Order { get; } } public interface IOptionPage { - void Load(ILSpySettings settings); - void Save(XElement root); + string Title { get; } + + void Load(SettingsSnapshot settings); + void LoadDefaults(); } [MetadataAttribute] - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class ExportOptionPageAttribute : ExportAttribute + [AttributeUsage(AttributeTargets.Class)] + public sealed class ExportOptionPageAttribute() : ExportAttribute("OptionPages", typeof(IOptionPage)), IOptionsMetadata { - public ExportOptionPageAttribute() : base("OptionPages", typeof(UIElement)) - { } - - public string Title { get; set; } - public int Order { get; set; } } @@ -124,13 +61,22 @@ namespace ICSharpCode.ILSpy.Options [PartCreationPolicy(CreationPolicy.Shared)] sealed class ShowOptionsCommand : SimpleCommand { - public override void Execute(object? parameter) + private readonly AssemblyTreeModel assemblyTreeModel; + + [ImportingConstructor] + public ShowOptionsCommand(AssemblyTreeModel assemblyTreeModel) + { + this.assemblyTreeModel = assemblyTreeModel; + } + + public override void Execute(object parameter) { - OptionsDialog dlg = new OptionsDialog(); - dlg.Owner = MainWindow.Instance; + OptionsDialog dlg = new() { + Owner = MainWindow.Instance, + }; if (dlg.ShowDialog() == true) { - new RefreshCommand().Execute(parameter); + assemblyTreeModel.Refresh(); } } } diff --git a/ILSpy/Options/OptionsDialogViewModel.cs b/ILSpy/Options/OptionsDialogViewModel.cs new file mode 100644 index 000000000..38aad4e4b --- /dev/null +++ b/ILSpy/Options/OptionsDialogViewModel.cs @@ -0,0 +1,68 @@ +using System.Linq; +using System.Windows; +using System.Windows.Input; + +using TomsToolbox.Essentials; +using TomsToolbox.Wpf; + +#nullable enable + +namespace ICSharpCode.ILSpy.Options +{ + public class OptionsItemViewModel(IOptionPage content) : ObservableObject + { + public string Title { get; } = content.Title; + + public IOptionPage Content { get; } = content; + } + + public class OptionsDialogViewModel : ObservableObject + { + private IOptionPage? selectedPage; + + private SettingsSnapshot snapshot; + + private readonly IOptionPage[] optionPages = App.ExportProvider.GetExports<IOptionPage, IOptionsMetadata>("OptionPages") + .OrderBy(page => page.Metadata?.Order) + .Select(item => item.Value) + .ExceptNullItems() + .ToArray(); + + public OptionsDialogViewModel() + { + this.snapshot = SettingsService.Instance.CreateSnapshot(); + + foreach (var optionPage in optionPages) + { + optionPage.Load(this.snapshot); + } + + OptionPages = optionPages.Select(page => new OptionsItemViewModel(page)).ToArray(); + SelectedPage = optionPages.FirstOrDefault(); + } + + public OptionsItemViewModel[] OptionPages { get; } + + public IOptionPage? SelectedPage { + get => selectedPage; + set => SetProperty(ref selectedPage, value); + } + + public ICommand CommitCommand => new DelegateCommand(Commit); + + public ICommand ResetDefaultsCommand => new DelegateCommand(ResetDefaults); + + private void ResetDefaults() + { + if (MessageBox.Show(Properties.Resources.ResetToDefaultsConfirmationMessage, "ILSpy", MessageBoxButton.YesNo) == MessageBoxResult.Yes) + { + SelectedPage?.LoadDefaults(); + } + } + + private void Commit() + { + snapshot.Save(); + } + } +} diff --git a/ILSpy/Properties/AssemblyInfo.cs b/ILSpy/Properties/AssemblyInfo.cs index 8e5ce2530..cf41117da 100644 --- a/ILSpy/Properties/AssemblyInfo.cs +++ b/ILSpy/Properties/AssemblyInfo.cs @@ -1,6 +1,5 @@ #region Using directives -using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Resources; @@ -26,7 +25,7 @@ using System.Runtime.Versioning; [assembly: ComVisible(false)] [assembly: AssemblyVersion(DecompilerVersionInfo.Major + "." + DecompilerVersionInfo.Minor + "." + DecompilerVersionInfo.Build + "." + DecompilerVersionInfo.Revision)] -[assembly: AssemblyInformationalVersion(DecompilerVersionInfo.FullVersionWithShortCommitHash)] +[assembly: AssemblyInformationalVersion(DecompilerVersionInfo.FullVersionWithCommitHash)] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SupportedOSPlatform("Windows7.0")] diff --git a/ILSpy/Search/SearchPane.xaml.cs b/ILSpy/Search/SearchPane.xaml.cs index c9ae39930..e1484c806 100644 --- a/ILSpy/Search/SearchPane.xaml.cs +++ b/ILSpy/Search/SearchPane.xaml.cs @@ -20,6 +20,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.ComponentModel; using System.ComponentModel.Composition; using System.Diagnostics; using System.Text.RegularExpressions; @@ -32,7 +33,7 @@ using System.Windows.Media; using System.Windows.Threading; using ICSharpCode.ILSpy.AppEnv; -using ICSharpCode.ILSpy.Util; +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.Extensions; @@ -67,7 +68,7 @@ namespace ICSharpCode.ILSpy.Search ContextMenuProvider.Add(listBox); MessageBus<CurrentAssemblyListChangedEventArgs>.Subscribers += (sender, e) => CurrentAssemblyList_Changed(); - MessageBus<LanguageSettingsChangedEventArgs>.Subscribers += (sender, e) => LanguageSettings_PropertyChanged(); + MessageBus<SettingsChangedEventArgs>.Subscribers += (sender, e) => Settings_PropertyChanged(sender, e); CompositionTarget.Rendering += UpdateResults; } @@ -85,8 +86,11 @@ namespace ICSharpCode.ILSpy.Search } } - void LanguageSettings_PropertyChanged() + void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e) { + if (sender is not LanguageSettings) + return; + UpdateFilter(); } @@ -250,8 +254,8 @@ namespace ICSharpCode.ILSpy.Search { searchProgressBar.IsIndeterminate = true; - startedSearch = new(await mainWindow.CurrentAssemblyList.GetAllAssemblies(), searchTerm, - (SearchMode)searchModeComboBox.SelectedIndex, mainWindow.CurrentLanguage, + startedSearch = new(await mainWindow.AssemblyTreeModel.AssemblyList.GetAllAssemblies(), searchTerm, + (SearchMode)searchModeComboBox.SelectedIndex, mainWindow.AssemblyTreeModel.CurrentLanguage, SettingsService.Instance.SessionSettings.LanguageSettings.ShowApiLevel); currentSearch = startedSearch; @@ -269,7 +273,7 @@ namespace ICSharpCode.ILSpy.Search { if (listBox.SelectedItem is SearchResult result) { - MainWindow.Instance.JumpToReference(result.Reference); + MessageBus.Send(this, new NavigateToReferenceEventArgs(result.Reference)); } } @@ -543,5 +547,10 @@ namespace ICSharpCode.ILSpy.Search gestures.Add(new KeyGesture(Key.F, ModifierKeys.Control | ModifierKeys.Shift)); gestures.Add(new KeyGesture(Key.E, ModifierKeys.Control)); } + + protected override void OnExecute(object sender, ExecutedRoutedEventArgs e) + { + DockWorkspace.Instance.ShowToolPane(SearchPaneModel.PaneContentId); + } } } \ No newline at end of file diff --git a/ILSpy/Search/SearchPaneModel.cs b/ILSpy/Search/SearchPaneModel.cs index 6ddcd9f04..b3bc30c40 100644 --- a/ILSpy/Search/SearchPaneModel.cs +++ b/ILSpy/Search/SearchPaneModel.cs @@ -20,7 +20,6 @@ using System.ComponentModel.Composition; using System.Windows.Input; using System.Windows.Media; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX.Search; diff --git a/ILSpy/SessionSettings.cs b/ILSpy/SessionSettings.cs index 74aaaa177..ddd9f7826 100644 --- a/ILSpy/SessionSettings.cs +++ b/ILSpy/SessionSettings.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.Linq; @@ -28,9 +29,7 @@ using System.Xml.Linq; using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Themes; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX.Search; -using ICSharpCode.ILSpyX.Settings; namespace ICSharpCode.ILSpy { @@ -38,58 +37,37 @@ namespace ICSharpCode.ILSpy /// Per-session setting: /// Loaded at startup; saved at exit. /// </summary> - public sealed class SessionSettings : INotifyPropertyChanged + public sealed class SessionSettings : ISettingsSection { - public SessionSettings(ILSpySettings spySettings) - { - XElement doc = spySettings["SessionSettings"]; - - XElement? filterSettings = doc.Element("FilterSettings"); - if (filterSettings == null) - filterSettings = new XElement("FilterSettings"); - - this.LanguageSettings = new LanguageSettings(filterSettings); - - this.ActiveAssemblyList = (string)doc.Element("ActiveAssemblyList"); - - XElement? activeTreeViewPath = doc.Element("ActiveTreeViewPath"); - if (activeTreeViewPath != null) - { - this.ActiveTreeViewPath = activeTreeViewPath.Elements().Select(e => Unescape((string)e)).ToArray(); - } - this.ActiveAutoLoadedAssembly = (string)doc.Element("ActiveAutoLoadedAssembly"); + public XName SectionName => "SessionSettings"; - this.WindowState = FromString((string)doc.Element("WindowState"), WindowState.Normal); - this.WindowBounds = FromString((string)doc.Element("WindowBounds"), DefaultWindowBounds); - this.SelectedSearchMode = FromString((string)doc.Element("SelectedSearchMode"), SearchMode.TypeAndMember); - this.Theme = FromString((string)doc.Element(nameof(Theme)), ThemeManager.Current.DefaultTheme); - string? currentCulture = (string)doc.Element(nameof(CurrentCulture)); - this.CurrentCulture = string.IsNullOrEmpty(currentCulture) ? null : currentCulture; - - this.DockLayout = new DockLayoutSettings(doc.Element("DockLayout")); - } - - public event PropertyChangedEventHandler? PropertyChanged; - - void OnPropertyChanged([CallerMemberName] string? propertyName = null) + public void LoadFromXml(XElement section) { - var args = new PropertyChangedEventArgs(propertyName); - - PropertyChanged?.Invoke(this, args); - - MessageBus.Send(this, new SessionSettingsChangedEventArgs(args)); + XElement filterSettings = section.Element("FilterSettings") ?? new XElement("FilterSettings"); + + LanguageSettings = new(filterSettings, this); + LanguageSettings.PropertyChanged += (sender, e) => PropertyChanged?.Invoke(sender, e); + + ActiveAssemblyList = (string)section.Element("ActiveAssemblyList"); + ActiveTreeViewPath = section.Element("ActiveTreeViewPath")?.Elements().Select(e => Unescape((string)e)).ToArray(); + ActiveAutoLoadedAssembly = (string)section.Element("ActiveAutoLoadedAssembly"); + WindowState = FromString((string)section.Element("WindowState"), WindowState.Normal); + WindowBounds = FromString((string)section.Element("WindowBounds"), DefaultWindowBounds); + SelectedSearchMode = FromString((string)section.Element("SelectedSearchMode"), SearchMode.TypeAndMember); + Theme = FromString((string)section.Element(nameof(Theme)), ThemeManager.Current.DefaultTheme); + var culture = (string)section.Element(nameof(CurrentCulture)); + CurrentCulture = string.IsNullOrEmpty(culture) ? null : culture; + DockLayout = new(section.Element("DockLayout")); } - public LanguageSettings LanguageSettings { get; } + public LanguageSettings LanguageSettings { get; set; } public SearchMode SelectedSearchMode { get; set; } + private string theme; public string Theme { - get => ThemeManager.Current.Theme; - set { - ThemeManager.Current.Theme = value; - OnPropertyChanged(); - } + get => theme; + set => SetProperty(ref theme, value); } public string[] ActiveTreeViewPath; @@ -121,56 +99,51 @@ namespace ICSharpCode.ILSpy public WindowState WindowState; public Rect WindowBounds; - internal static Rect DefaultWindowBounds = new Rect(10, 10, 750, 550); + internal static Rect DefaultWindowBounds = new(10, 10, 750, 550); - public DockLayoutSettings DockLayout { get; } + public DockLayoutSettings DockLayout { get; set; } - public XElement ToXml() + public XElement SaveToXml() { - XElement doc = new XElement("SessionSettings"); - doc.Add(this.LanguageSettings.SaveAsXml()); + var section = new XElement(SectionName); + + section.Add(this.LanguageSettings.SaveAsXml()); if (this.ActiveAssemblyList != null) { - doc.Add(new XElement("ActiveAssemblyList", this.ActiveAssemblyList)); + section.Add(new XElement("ActiveAssemblyList", this.ActiveAssemblyList)); } if (this.ActiveTreeViewPath != null) { - doc.Add(new XElement("ActiveTreeViewPath", ActiveTreeViewPath.Select(p => new XElement("Node", Escape(p))))); + section.Add(new XElement("ActiveTreeViewPath", ActiveTreeViewPath.Select(p => new XElement("Node", Escape(p))))); } if (this.ActiveAutoLoadedAssembly != null) { - doc.Add(new XElement("ActiveAutoLoadedAssembly", this.ActiveAutoLoadedAssembly)); + section.Add(new XElement("ActiveAutoLoadedAssembly", this.ActiveAutoLoadedAssembly)); } - doc.Add(new XElement("WindowState", ToString(this.WindowState))); - doc.Add(new XElement("WindowBounds", ToString(this.WindowBounds))); - doc.Add(new XElement("SelectedSearchMode", ToString(this.SelectedSearchMode))); - doc.Add(new XElement(nameof(Theme), ToString(this.Theme))); + section.Add(new XElement("WindowState", ToString(this.WindowState))); + section.Add(new XElement("WindowBounds", ToString(this.WindowBounds))); + section.Add(new XElement("SelectedSearchMode", ToString(this.SelectedSearchMode))); + section.Add(new XElement(nameof(Theme), ToString(this.Theme))); if (this.CurrentCulture != null) { - doc.Add(new XElement(nameof(CurrentCulture), this.CurrentCulture)); + section.Add(new XElement(nameof(CurrentCulture), this.CurrentCulture)); } - var dockLayoutElement = new XElement("DockLayout"); if (DockLayout.Valid) { dockLayoutElement.Add(DockLayout.SaveAsXml()); } - doc.Add(dockLayoutElement); - return doc; - } + section.Add(dockLayoutElement); - public void Save() - { - var doc = ToXml(); - ILSpySettings.SaveSettings(doc); + return section; } - static Regex regex = new Regex("\\\\x(?<num>[0-9A-f]{4})"); + static Regex regex = new("\\\\x(?<num>[0-9A-f]{4})"); private string? activeAssemblyList; static string Escape(string p) { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new(); foreach (char ch in p) { if (char.IsLetterOrDigit(ch)) @@ -206,5 +179,21 @@ namespace ICSharpCode.ILSpy TypeConverter c = TypeDescriptor.GetConverter(typeof(T)); return c.ConvertToInvariantString(obj); } + + public event PropertyChangedEventHandler PropertyChanged; + + private void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new(propertyName)); + } + + private bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null) + { + if (EqualityComparer<T>.Default.Equals(field, value)) + return false; + field = value; + OnPropertyChanged(propertyName); + return true; + } } } diff --git a/ILSpy/SolutionWriter.cs b/ILSpy/SolutionWriter.cs index a3f412d09..dd2361ddc 100644 --- a/ILSpy/SolutionWriter.cs +++ b/ILSpy/SolutionWriter.cs @@ -28,6 +28,7 @@ using System.Threading.Tasks; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Solution; using ICSharpCode.Decompiler.Util; +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpyX; @@ -212,7 +213,7 @@ namespace ICSharpCode.ILSpy using (var projectFileWriter = new StreamWriter(projectFileName)) { var projectFileOutput = new PlainTextOutput(projectFileWriter); - var options = MainWindow.Instance.CreateDecompilationOptions(); + var options = SettingsService.Instance.CreateDecompilationOptions(DockWorkspace.Instance.ActiveTabPage); options.FullDecompilation = true; options.CancellationToken = ct; options.SaveAsProjectDirectory = targetDirectory; diff --git a/ILSpy/TaskHelper.cs b/ILSpy/TaskHelper.cs index 630d1d7d2..462143edd 100644 --- a/ILSpy/TaskHelper.cs +++ b/ILSpy/TaskHelper.cs @@ -198,7 +198,7 @@ namespace ICSharpCode.ILSpy public static void HandleExceptions(this Task task) { task.Catch<Exception>(exception => MainWindow.Instance.Dispatcher.BeginInvoke(new Action(delegate { - AvalonEditTextOutput output = new AvalonEditTextOutput(); + AvalonEditTextOutput output = new(); output.Write(exception.ToString()); Docking.DockWorkspace.Instance.ShowText(output); }))).IgnoreExceptions(); diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index f500fd374..927215a25 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -52,11 +52,11 @@ using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.Decompiler.Documentation; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.AvalonEdit; using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpy.Themes; using ICSharpCode.ILSpy.TreeNodes; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX; @@ -414,7 +414,7 @@ namespace ICSharpCode.ILSpy.TextView } else if (segment.Reference is EntityReference unresolvedEntity) { - var module = unresolvedEntity.ResolveAssembly(MainWindow.Instance.CurrentAssemblyList); + var module = unresolvedEntity.ResolveAssembly(MainWindow.Instance.AssemblyTreeModel.AssemblyList); if (module == null) return null; var typeSystem = new DecompilerTypeSystem(module, @@ -474,7 +474,7 @@ namespace ICSharpCode.ILSpy.TextView IEntity? ResolveReference(string idString) { - return MainWindow.FindEntityInRelevantAssemblies(idString, MainWindow.Instance.CurrentAssemblyList.GetAssemblies()); + return AssemblyTreeModel.FindEntityInRelevantAssemblies(idString, MainWindow.Instance.AssemblyTreeModel.AssemblyList.GetAssemblies()); } } @@ -1001,7 +1001,7 @@ namespace ICSharpCode.ILSpy.TextView return; } } - MainWindow.Instance.JumpToReference(reference, openInNewTab); + MessageBus.Send(this, new NavigateToReferenceEventArgs(reference, openInNewTab)); } Point? mouseDownPos; diff --git a/ILSpy/TextView/DocumentationUIBuilder.cs b/ILSpy/TextView/DocumentationUIBuilder.cs index 1e0cd1e8d..75b3d552f 100644 --- a/ILSpy/TextView/DocumentationUIBuilder.cs +++ b/ILSpy/TextView/DocumentationUIBuilder.cs @@ -37,7 +37,6 @@ using ICSharpCode.Decompiler.Documentation; using ICSharpCode.Decompiler.Output; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.ILSpy.Options; -using ICSharpCode.ILSpy.Util; namespace ICSharpCode.ILSpy.TextView { @@ -339,7 +338,7 @@ namespace ICSharpCode.ILSpy.TextView { var h = new Hyperlink(new Run(ambience.ConvertSymbol(referencedEntity))); h.Click += (sender, e) => { - MainWindow.Instance.JumpToReference(referencedEntity); + MessageBus.Send(this, new NavigateToReferenceEventArgs(referencedEntity)); }; return h; } @@ -382,7 +381,7 @@ namespace ICSharpCode.ILSpy.TextView { Hyperlink link = new Hyperlink(); link.Click += (sender, e) => { - MainWindow.Instance.JumpToReference(referencedEntity); + MessageBus.Send(this, new NavigateToReferenceEventArgs(referencedEntity)); }; AddSpan(link, element.Children); } diff --git a/ILSpy/Themes/ThemeManager.cs b/ILSpy/Themes/ThemeManager.cs index 419e4026c..dae84fc93 100644 --- a/ILSpy/Themes/ThemeManager.cs +++ b/ILSpy/Themes/ThemeManager.cs @@ -20,6 +20,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Controls; @@ -42,6 +43,7 @@ namespace ICSharpCode.ILSpy.Themes private ThemeManager() { Application.Current.Resources.MergedDictionaries.Add(_themeDictionaryContainer); + MessageBus<SettingsChangedEventArgs>.Subscribers += (sender, e) => Settings_Changed(sender, e); } public string DefaultTheme => "Light"; @@ -199,5 +201,13 @@ namespace ICSharpCode.ILSpy.Themes var b = (byte)((b1 + m) * 255f); return (r, g, b); } + + private void Settings_Changed(object? sender, PropertyChangedEventArgs e) + { + if (sender is not SessionSettings settings || e.PropertyName != nameof(SessionSettings.Theme)) + return; + + Theme = settings.Theme; + } } } diff --git a/ILSpy/Themes/WindowStyleManagerBehavior.cs b/ILSpy/Themes/WindowStyleManagerBehavior.cs index 8388be987..4b8aa62a2 100644 --- a/ILSpy/Themes/WindowStyleManagerBehavior.cs +++ b/ILSpy/Themes/WindowStyleManagerBehavior.cs @@ -24,7 +24,6 @@ using System.Windows.Interop; using System.Windows.Media; using ICSharpCode.ILSpy.Options; -using ICSharpCode.ILSpy.Util; using TomsToolbox.Essentials; using TomsToolbox.Wpf; diff --git a/ILSpy/TreeNodes/AssemblyListTreeNode.cs b/ILSpy/TreeNodes/AssemblyListTreeNode.cs index 218771aa7..ee8bae8ef 100644 --- a/ILSpy/TreeNodes/AssemblyListTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyListTreeNode.cs @@ -87,13 +87,10 @@ namespace ICSharpCode.ILSpy.TreeNodes e.Effects = XPlatDragDropEffects.Move | XPlatDragDropEffects.Copy | XPlatDragDropEffects.Link; if (e.Data.GetDataPresent(AssemblyTreeNode.DataFormat)) return true; - else if (e.Data.GetDataPresent(DataFormats.FileDrop)) + if (e.Data.GetDataPresent(DataFormats.FileDrop)) return true; - else - { - e.Effects = XPlatDragDropEffects.None; - return false; - } + e.Effects = XPlatDragDropEffects.None; + return false; } public override void Drop(IPlatformDragEventArgs e, int index) @@ -110,8 +107,8 @@ namespace ICSharpCode.ILSpy.TreeNodes .Distinct() .ToArray(); assemblyList.Move(assemblies, index); - var nodes = assemblies.SelectArray(MainWindow.Instance.FindTreeNode); - MainWindow.Instance.SelectNodes(nodes); + var nodes = assemblies.SelectArray(MainWindow.Instance.AssemblyTreeModel.FindTreeNode); + MainWindow.Instance.AssemblyTreeModel.SelectNodes(nodes); } } @@ -187,7 +184,7 @@ namespace ICSharpCode.ILSpy.TreeNodes if (bundle == null) return null; bundle.EnsureLazyChildren(); - foreach (var node in ILSpyX.TreeView.TreeTraversal.PreOrder(bundle.Children, ExpandAndGetChildren).OfType<AssemblyTreeNode>()) + foreach (var node in TreeTraversal.PreOrder(bundle.Children, ExpandAndGetChildren).OfType<AssemblyTreeNode>()) { if (node.LoadedAssembly == asm) return node; diff --git a/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs b/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs index 25602d875..57a326353 100644 --- a/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs @@ -26,7 +26,6 @@ using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.ILSpy.Themes; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions; namespace ICSharpCode.ILSpy.TreeNodes diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs index c0540530e..724688d59 100644 --- a/ILSpy/TreeNodes/AssemblyTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs @@ -30,10 +30,11 @@ using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Controls.TreeView; +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Metadata; using ICSharpCode.ILSpy.Properties; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.FileLoaders; @@ -546,7 +547,7 @@ namespace ICSharpCode.ILSpy.TreeNodes dlg.Filter = language.Name + " project|*" + language.ProjectFileExtension + "|" + language.Name + " single file|*" + language.FileExtension + "|All files|*.*"; if (dlg.ShowDialog() == true) { - DecompilationOptions options = MainWindow.Instance.CreateDecompilationOptions(); + var options = SettingsService.Instance.CreateDecompilationOptions(DockWorkspace.Instance.ActiveTabPage); options.FullDecompilation = true; if (dlg.FilterIndex == 1) { @@ -630,13 +631,13 @@ namespace ICSharpCode.ILSpy.TreeNodes { foreach (var node in context.SelectedTreeNodes) { - paths.Add(MainWindow.GetPathForNode(node)); + paths.Add(AssemblyTreeModel.GetPathForNode(node)); var la = ((AssemblyTreeNode)node).LoadedAssembly; la.AssemblyList.ReloadAssembly(la.FileName); } } - MainWindow.Instance.SelectNodes(paths.Select(p => MainWindow.Instance.FindNodeByPath(p, true)).ToArray()); - MainWindow.Instance.RefreshDecompiledView(); + MainWindow.Instance.AssemblyTreeModel.SelectNodes(paths.Select(p => MainWindow.Instance.AssemblyTreeModel.FindNodeByPath(p, true)).ToArray()); + MainWindow.Instance.AssemblyTreeModel.RefreshDecompiledView(); } } @@ -676,7 +677,7 @@ namespace ICSharpCode.ILSpy.TreeNodes } } await Task.WhenAll(tasks); - MainWindow.Instance.RefreshDecompiledView(); + MainWindow.Instance.AssemblyTreeModel.RefreshDecompiledView(); } } @@ -711,7 +712,7 @@ namespace ICSharpCode.ILSpy.TreeNodes node.RaisePropertyChanged(nameof(ILSpyTreeNode.IsAutoLoaded)); } } - MainWindow.Instance.CurrentAssemblyList.RefreshSave(); + MainWindow.Instance.AssemblyTreeModel.AssemblyList.RefreshSave(); } } diff --git a/ILSpy/TreeNodes/DerivedTypesTreeNode.cs b/ILSpy/TreeNodes/DerivedTypesTreeNode.cs index 36fdcd729..760685dc9 100644 --- a/ILSpy/TreeNodes/DerivedTypesTreeNode.cs +++ b/ILSpy/TreeNodes/DerivedTypesTreeNode.cs @@ -73,7 +73,6 @@ namespace ICSharpCode.ILSpy.TreeNodes continue; var metadata = module.Metadata; var assembly = module.GetTypeSystemOrNull()?.MainModule as MetadataModule; - if (assembly == null) continue; foreach (var h in metadata.TypeDefinitions) @@ -108,4 +107,4 @@ namespace ICSharpCode.ILSpy.TreeNodes threading.Decompile(language, output, options, EnsureLazyChildren); } } -} \ No newline at end of file +} diff --git a/ILSpy/TreeNodes/ILSpyTreeNode.cs b/ILSpy/TreeNodes/ILSpyTreeNode.cs index 9be0705a0..edbb9c213 100644 --- a/ILSpy/TreeNodes/ILSpyTreeNode.cs +++ b/ILSpy/TreeNodes/ILSpyTreeNode.cs @@ -27,7 +27,6 @@ using System.Windows.Threading; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.TypeSystem; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX.Abstractions; using ICSharpCode.ILSpyX.TreeView; using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions; @@ -41,9 +40,9 @@ namespace ICSharpCode.ILSpy.TreeNodes { bool childrenNeedFiltering; - public ILSpyTreeNode() + protected ILSpyTreeNode() { - MessageBus<LanguageSettingsChangedEventArgs>.Subscribers += LanguageSettings_Changed; + MessageBus<SettingsChangedEventArgs>.Subscribers += (sender, e) => Settings_Changed(sender, e); } LanguageSettings LanguageSettings => SettingsService.Instance.SessionSettings.LanguageSettings; @@ -72,8 +71,8 @@ namespace ICSharpCode.ILSpy.TreeNodes public override void ActivateItemSecondary(IPlatformRoutedEventArgs e) { - MainWindow.Instance.SelectNode(this, inNewTabPage: true); - MainWindow.Instance.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)MainWindow.Instance.RefreshDecompiledView); + MainWindow.Instance.AssemblyTreeModel.SelectNode(this, inNewTabPage: true); + MainWindow.Instance.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)MainWindow.Instance.AssemblyTreeModel.RefreshDecompiledView); } /// <summary> @@ -86,7 +85,7 @@ namespace ICSharpCode.ILSpy.TreeNodes return false; } - protected internal override void OnChildrenChanged(NotifyCollectionChangedEventArgs e) + public override void OnChildrenChanged(NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) { @@ -128,8 +127,13 @@ namespace ICSharpCode.ILSpy.TreeNodes } } - protected virtual void LanguageSettings_Changed(object? sender, EventArgs e) + protected virtual void Settings_Changed(object? sender, PropertyChangedEventArgs e) { + if (sender is not ILSpy.LanguageSettings) + return; + if (e.PropertyName is not (nameof(LanguageSettings.Language) or nameof(LanguageSettings.LanguageVersion))) + return; + RaisePropertyChanged(nameof(Text)); if (IsVisible) { diff --git a/ILSpy/Updates/NotifyOfUpdatesStrategy.cs b/ILSpy/Updates/NotifyOfUpdatesStrategy.cs index d02565784..166d87d42 100644 --- a/ILSpy/Updates/NotifyOfUpdatesStrategy.cs +++ b/ILSpy/Updates/NotifyOfUpdatesStrategy.cs @@ -61,7 +61,7 @@ namespace ICSharpCode.ILSpy.Updates /// Returns the download URL if an update is available. /// Returns null if no update is available, or if no check was performed. /// </summary> - public static async Task<string?> CheckForUpdatesIfEnabledAsync(ILSpySettings spySettings) + public static async Task<string?> CheckForUpdatesIfEnabledAsync(ISettingsProvider spySettings) { UpdateSettings s = new UpdateSettings(spySettings); @@ -87,7 +87,7 @@ namespace ICSharpCode.ILSpy.Updates } } - public static Task<string?> CheckForUpdatesAsync(ILSpySettings spySettings) + public static Task<string?> CheckForUpdatesAsync(ISettingsProvider spySettings) { UpdateSettings s = new UpdateSettings(spySettings); return CheckForUpdateInternal(s); diff --git a/ILSpy/Updates/UpdateSettings.cs b/ILSpy/Updates/UpdateSettings.cs index 14022c323..dbf18fbc4 100644 --- a/ILSpy/Updates/UpdateSettings.cs +++ b/ILSpy/Updates/UpdateSettings.cs @@ -26,7 +26,7 @@ namespace ICSharpCode.ILSpy.Updates { sealed class UpdateSettings : INotifyPropertyChanged { - public UpdateSettings(ILSpySettings spySettings) + public UpdateSettings(ISettingsProvider spySettings) { XElement s = spySettings["UpdateSettings"]; this.automaticUpdateCheckEnabled = (bool?)s.Element("AutomaticUpdateCheckEnabled") ?? true; @@ -75,7 +75,7 @@ namespace ICSharpCode.ILSpy.Updates updateSettings.Add(new XElement("AutomaticUpdateCheckEnabled", automaticUpdateCheckEnabled)); if (lastSuccessfulUpdateCheck != null) updateSettings.Add(new XElement("LastSuccessfulUpdateCheck", lastSuccessfulUpdateCheck)); - ILSpySettings.SaveSettings(updateSettings); + SettingsService.Instance.SpySettings.SaveSettings(updateSettings); } public event PropertyChangedEventHandler? PropertyChanged; diff --git a/ILSpy/Util/MenuService.cs b/ILSpy/Util/MenuService.cs new file mode 100644 index 000000000..7766ab77a --- /dev/null +++ b/ILSpy/Util/MenuService.cs @@ -0,0 +1,237 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Input; + +using ICSharpCode.ILSpy.Commands; +using ICSharpCode.ILSpy.Docking; +using ICSharpCode.ILSpy.Themes; +using ICSharpCode.ILSpy.ViewModels; + +using TomsToolbox.Composition; +using TomsToolbox.ObservableCollections; +using TomsToolbox.Wpf.Converters; + +namespace ICSharpCode.ILSpy.Util +{ + internal class MenuService + { + public static readonly MenuService Instance = new(); + + private readonly DockWorkspace dockWorkspace = DockWorkspace.Instance; + + public void Init(Menu mainMenu, ToolBar toolBar, InputBindingCollection inputBindings) + { + InitMainMenu(mainMenu); + InitWindowMenu(mainMenu, inputBindings); + InitToolbar(toolBar); + } + + static void InitMainMenu(Menu mainMenu) + { + var mainMenuCommands = App.ExportProvider.GetExports<ICommand, IMainMenuCommandMetadata>("MainMenuCommand"); + // Start by constructing the individual flat menus + var parentMenuItems = new Dictionary<string, MenuItem>(); + var menuGroups = mainMenuCommands.OrderBy(c => c.Metadata?.MenuOrder).GroupBy(c => c.Metadata?.ParentMenuID).ToArray(); + foreach (var menu in menuGroups) + { + // Get or add the target menu item and add all items grouped by menu category + var parentMenuItem = GetOrAddParentMenuItem(menu.Key, menu.Key); + foreach (var category in menu.GroupBy(c => c.Metadata?.MenuCategory)) + { + if (parentMenuItem.Items.Count > 0) + { + parentMenuItem.Items.Add(new Separator { Tag = category.Key }); + } + foreach (var entry in category) + { + if (menuGroups.Any(g => g.Key == entry.Metadata?.MenuID)) + { + var menuItem = GetOrAddParentMenuItem(entry.Metadata?.MenuID, entry.Metadata?.Header); + // replace potential dummy text with real name + menuItem.Header = ResourceHelper.GetString(entry.Metadata?.Header); + parentMenuItem.Items.Add(menuItem); + } + else + { + var menuItem = new MenuItem { + Command = CommandWrapper.Unwrap(entry.Value), + Tag = entry.Metadata?.MenuID, + Header = ResourceHelper.GetString(entry.Metadata?.Header) + }; + if (!string.IsNullOrEmpty(entry.Metadata?.MenuIcon)) + { + menuItem.Icon = new Image { + Width = 16, + Height = 16, + Source = Images.Load(entry.Value, entry.Metadata.MenuIcon) + }; + } + + menuItem.IsEnabled = entry.Metadata?.IsEnabled ?? false; + if (entry.Value is ToggleableCommand) + { + menuItem.IsCheckable = true; + menuItem.SetBinding(MenuItem.IsCheckedProperty, new Binding("IsChecked") { Source = entry.Value, Mode = BindingMode.OneWay }); + } + + menuItem.InputGestureText = entry.Metadata?.InputGestureText; + parentMenuItem.Items.Add(menuItem); + } + } + } + } + + foreach (var item in parentMenuItems.Values) + { + if (item.Parent == null) + { + mainMenu.Items.Add(item); + } + } + + MenuItem GetOrAddParentMenuItem(string menuId, string resourceKey) + { + if (!parentMenuItems.TryGetValue(menuId, out var parentMenuItem)) + { + var topLevelMenuItem = mainMenu.Items.OfType<MenuItem>().FirstOrDefault(m => (string)m.Tag == menuId); + if (topLevelMenuItem == null) + { + parentMenuItem = new() { + Header = ResourceHelper.GetString(resourceKey), + Tag = menuId + }; + parentMenuItems.Add(menuId, parentMenuItem); + } + else + { + parentMenuItems.Add(menuId, topLevelMenuItem); + parentMenuItem = topLevelMenuItem; + } + } + return parentMenuItem; + } + } + + void InitWindowMenu(Menu mainMenu, InputBindingCollection inputBindings) + { + var windowMenuItem = mainMenu.Items.OfType<MenuItem>().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); + + var defaultItems = windowMenuItem.Items.Cast<Control>().ToArray(); + + windowMenuItem.Items.Clear(); + + var toolItems = dockWorkspace.ToolPanes.ObservableSelect(toolPane => CreateMenuItem(toolPane, inputBindings)); + var tabItems = dockWorkspace.TabPages.ObservableSelect(tabPage => CreateMenuItem(tabPage, dockWorkspace)); + + var allItems = new ObservableCompositeCollection<Control>(defaultItems, [new Separator()], toolItems, [new Separator()], tabItems); + + windowMenuItem.ItemsSource = allItems; + } + + static void InitToolbar(ToolBar toolBar) + { + int navigationPos = 0; + int openPos = 1; + var toolbarCommandsByTitle = App.ExportProvider.GetExports<ICommand, IToolbarCommandMetadata>("ToolbarCommand") + .OrderBy(c => c.Metadata?.ToolbarOrder) + .GroupBy(c => c.Metadata?.ToolbarCategory); + + foreach (var commandGroup in toolbarCommandsByTitle) + { + if (commandGroup.Key == nameof(Properties.Resources.Navigation)) + { + foreach (var command in commandGroup) + { + toolBar.Items.Insert(navigationPos++, CreateToolbarItem(command)); + openPos++; + } + } + else if (commandGroup.Key == nameof(Properties.Resources.Open)) + { + foreach (var command in commandGroup) + { + toolBar.Items.Insert(openPos++, CreateToolbarItem(command)); + } + } + else + { + toolBar.Items.Add(new Separator()); + foreach (var command in commandGroup) + { + toolBar.Items.Add(CreateToolbarItem(command)); + } + } + } + + } + + static Control CreateMenuItem(TabPageModel pane, DockWorkspace dock) + { + var header = new TextBlock { + MaxWidth = 200, + TextTrimming = TextTrimming.CharacterEllipsis + }; + + header.SetBinding(TextBlock.TextProperty, new Binding(nameof(pane.Title)) { + Source = pane + }); + + MenuItem menuItem = new() { + Command = new TabPageCommand(pane), + Header = header, + IsCheckable = true + }; + + menuItem.SetBinding(MenuItem.IsCheckedProperty, new Binding(nameof(dock.ActiveTabPage)) { + Source = dock, + ConverterParameter = pane, + Converter = BinaryOperationConverter.Equality + }); + + return menuItem; + } + + static Control CreateMenuItem(ToolPaneModel pane, InputBindingCollection inputBindings) + { + MenuItem menuItem = new() { + Command = pane.AssociatedCommand ?? new ToolPaneCommand(pane.ContentId), + Header = pane.Title + }; + var shortcutKey = pane.ShortcutKey; + if (shortcutKey != null) + { + inputBindings.Add(new(menuItem.Command, shortcutKey)); + menuItem.InputGestureText = shortcutKey.GetDisplayStringForCulture(CultureInfo.CurrentUICulture); + } + if (!string.IsNullOrEmpty(pane.Icon)) + { + menuItem.Icon = new Image { + Width = 16, + Height = 16, + Source = Images.Load(pane, pane.Icon) + }; + } + + return menuItem; + } + + static Button CreateToolbarItem(IExport<ICommand, IToolbarCommandMetadata> command) + { + return new() { + Style = ThemeManager.Current.CreateToolBarButtonStyle(), + Command = CommandWrapper.Unwrap(command.Value), + ToolTip = Properties.Resources.ResourceManager.GetString(command.Metadata?.ToolTip), + Tag = command.Metadata?.Tag, + Content = new Image { + Width = 16, + Height = 16, + Source = Images.Load(command.Value, command.Metadata?.ToolbarIcon) + } + }; + } + } +} diff --git a/ILSpy/Util/MessageBus.cs b/ILSpy/Util/MessageBus.cs index 7573cb33c..b08b29dc6 100644 --- a/ILSpy/Util/MessageBus.cs +++ b/ILSpy/Util/MessageBus.cs @@ -22,16 +22,16 @@ namespace ICSharpCode.ILSpy.Util public static class MessageBus<T> where T : EventArgs { - private static readonly WeakEventSource<T> Subscriptions = new(); + private static readonly WeakEventSource<T> subscriptions = new(); public static event EventHandler<T> Subscribers { - add => Subscriptions.Subscribe(value); - remove => Subscriptions.Unsubscribe(value); + add => subscriptions.Subscribe(value); + remove => subscriptions.Unsubscribe(value); } public static void Send(object sender, T e) { - Subscriptions.Raise(sender, e); + subscriptions.Raise(sender, e); } } @@ -52,9 +52,14 @@ namespace ICSharpCode.ILSpy.Util public class CurrentAssemblyListChangedEventArgs(NotifyCollectionChangedEventArgs e) : WrappedEventArgs<NotifyCollectionChangedEventArgs>(e); - public class LanguageSettingsChangedEventArgs(PropertyChangedEventArgs e) : WrappedEventArgs<PropertyChangedEventArgs>(e); + public class SettingsChangedEventArgs(PropertyChangedEventArgs e) : WrappedEventArgs<PropertyChangedEventArgs>(e); - public class SessionSettingsChangedEventArgs(PropertyChangedEventArgs e) : WrappedEventArgs<PropertyChangedEventArgs>(e); + public class NavigateToReferenceEventArgs(object reference, bool inNewTabPage = false) : EventArgs + { + public object Reference { get; } = reference; + + public bool InNewTabPage { get; } = inNewTabPage; + } - public class DockWorkspaceActiveTabPageChangedEventArgs : EventArgs; + public class AssemblyTreeSelectionChangedEventArgs() : EventArgs; } \ No newline at end of file diff --git a/ILSpy/Util/ResourceHelper.cs b/ILSpy/Util/ResourceHelper.cs new file mode 100644 index 000000000..b37bb2cbb --- /dev/null +++ b/ILSpy/Util/ResourceHelper.cs @@ -0,0 +1,15 @@ +namespace ICSharpCode.ILSpy.Util +{ + internal static class ResourceHelper + { + internal static string GetString(string key) + { + if (string.IsNullOrEmpty(key)) + return null; + + string value = Properties.Resources.ResourceManager.GetString(key); + + return !string.IsNullOrEmpty(value) ? value : key; + } + } +} diff --git a/ILSpy/Util/SettingsService.cs b/ILSpy/Util/SettingsService.cs index 9068733f3..092339b48 100644 --- a/ILSpy/Util/SettingsService.cs +++ b/ILSpy/Util/SettingsService.cs @@ -1,34 +1,196 @@ -using ICSharpCode.Decompiler; +using System; +using System.Collections.Concurrent; +using System.ComponentModel; +using System.Xml.Linq; + +using ICSharpCode.Decompiler; using ICSharpCode.ILSpy.Options; +using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.Settings; +using DecompilerSettings = ICSharpCode.ILSpy.Options.DecompilerSettings; + +#nullable enable + namespace ICSharpCode.ILSpy.Util { - public class SettingsService + public interface IChildSettings + { + ISettingsSection Parent { get; } + } + + public interface ISettingsSection : INotifyPropertyChanged + { + XName SectionName { get; } + + void LoadFromXml(XElement section); + + XElement SaveToXml(); + } + + public abstract class SettingsServiceBase + { + protected readonly ConcurrentDictionary<Type, ISettingsSection> sections = new(); + + public ISettingsProvider SpySettings; + + protected SettingsServiceBase(ISettingsProvider spySettings) + { + SpySettings = spySettings; + } + + public T GetSettings<T>() where T : ISettingsSection, new() + { + return (T)sections.GetOrAdd(typeof(T), _ => { + T section = new T(); + + var sectionElement = SpySettings[section.SectionName]; + + section.LoadFromXml(sectionElement); + section.PropertyChanged += Section_PropertyChanged; + + return section; + }); + } + + protected void SaveSection(ISettingsSection section, XElement root) + { + var element = section.SaveToXml(); + + var existingElement = root.Element(section.SectionName); + if (existingElement != null) + existingElement.ReplaceWith(element); + else + root.Add(element); + } + + protected virtual void Section_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + } + } + + public class SettingsSnapshot : SettingsServiceBase + { + private readonly SettingsService parent; + + public SettingsSnapshot(SettingsService parent) : base(parent.SpySettings) + { + this.parent = parent; + } + + public void Save() + { + SpySettings.Update(root => { + foreach (var section in sections.Values) + { + SaveSection(section, root); + } + }); + + parent.Reload(); + } + } + + public class SettingsService : SettingsServiceBase { public static readonly SettingsService Instance = new(); - private SettingsService() + private SettingsService() : base(LoadSettings()) + { + } + + public SessionSettings SessionSettings => GetSettings<SessionSettings>(); + + public DecompilerSettings DecompilerSettings => GetSettings<DecompilerSettings>(); + + public DisplaySettings DisplaySettings => GetSettings<DisplaySettings>(); + + public MiscSettings MiscSettings => GetSettings<MiscSettings>(); + + private AssemblyListManager? assemblyListManager; + public AssemblyListManager AssemblyListManager => assemblyListManager ??= new(SpySettings) { + ApplyWinRTProjections = DecompilerSettings.ApplyWindowsRuntimeProjections, + UseDebugSymbols = DecompilerSettings.UseDebugSymbols + }; + + public DecompilationOptions CreateDecompilationOptions(TabPageModel tabPage) + { + return new(SessionSettings.LanguageSettings.LanguageVersion, DecompilerSettings, DisplaySettings) { Progress = tabPage.Content as IProgress<DecompilationProgress> }; + } + + public AssemblyList LoadInitialAssemblyList() { - SpySettings = ILSpySettings.Load(); - SessionSettings = new(SpySettings); - DecompilerSettings = DecompilerSettingsPanel.LoadDecompilerSettings(SpySettings); - DisplaySettings = DisplaySettingsPanel.LoadDisplaySettings(SpySettings, SessionSettings); - AssemblyListManager = new(SpySettings) { - ApplyWinRTProjections = DecompilerSettings.ApplyWindowsRuntimeProjections, - UseDebugSymbols = DecompilerSettings.UseDebugSymbols - }; + var loadPreviousAssemblies = MiscSettings.LoadPreviousAssemblies; + + if (loadPreviousAssemblies) + { + return AssemblyListManager.LoadList(SessionSettings.ActiveAssemblyList); + } + else + { + AssemblyListManager.ClearAll(); + return AssemblyListManager.CreateList(AssemblyListManager.DefaultListName); + } } - public ILSpySettings SpySettings { get; } + private bool reloading; - public SessionSettings SessionSettings { get; } + public void Reload() + { + reloading = true; - public DecompilerSettings DecompilerSettings { get; set; } + try + { + SpySettings = ILSpySettings.Load(); - public DisplaySettings DisplaySettings { get; } + foreach (var section in sections.Values) + { + var element = SpySettings[section.SectionName]; + + section.LoadFromXml(element); + } + } + finally + { + reloading = false; + } + } - public AssemblyListManager AssemblyListManager { get; } + public SettingsSnapshot CreateSnapshot() + { + return new(this); + } + + protected override void Section_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + base.Section_PropertyChanged(sender, e); + + if (!reloading) + { + var section = (sender as IChildSettings)?.Parent ?? sender as ISettingsSection; + + if (section != null) + { + SpySettings.Update(root => { + SaveSection(section, root); + }); + }; + } + + if (sender is DecompilerSettings decompilerSettings && assemblyListManager != null) + { + assemblyListManager.ApplyWinRTProjections = decompilerSettings.ApplyWindowsRuntimeProjections; + assemblyListManager.UseDebugSymbols = decompilerSettings.UseDebugSymbols; + } + + MessageBus.Send(sender, new SettingsChangedEventArgs(e)); + } + + private static ILSpySettings LoadSettings() + { + ILSpySettings.SettingsFilePathProvider = new ILSpySettingsFilePathProvider(); + return ILSpySettings.Load(); + } } } diff --git a/ILSpy/ViewModels/AssemblyListPaneModel.cs b/ILSpy/ViewModels/AssemblyListPaneModel.cs deleted file mode 100644 index d6156cf8f..000000000 --- a/ILSpy/ViewModels/AssemblyListPaneModel.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this -// software and associated documentation files (the "Software"), to deal in the Software -// without restriction, including without limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -// to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System.ComponentModel.Composition; -using System.Windows; -using System.Windows.Input; - -using ICSharpCode.ILSpy.Properties; - -namespace ICSharpCode.ILSpy.ViewModels -{ - [ExportToolPane] - [PartCreationPolicy(CreationPolicy.Shared)] - public class AssemblyListPaneModel : ToolPaneModel - { - public const string PaneContentId = "assemblyListPane"; - - public AssemblyListPaneModel() - { - Title = Resources.Assemblies; - ContentId = PaneContentId; - IsCloseable = false; - ShortcutKey = new KeyGesture(Key.F6); - } - } -} diff --git a/ILSpy/ViewModels/ManageAssemblyListsViewModel.cs b/ILSpy/ViewModels/ManageAssemblyListsViewModel.cs index 4b88b4dbe..a9e5ae7af 100644 --- a/ILSpy/ViewModels/ManageAssemblyListsViewModel.cs +++ b/ILSpy/ViewModels/ManageAssemblyListsViewModel.cs @@ -27,7 +27,6 @@ using System.Windows.Input; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.ILSpy.Commands; using ICSharpCode.ILSpy.Properties; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; namespace ICSharpCode.ILSpy.ViewModels diff --git a/ILSpy/ViewModels/PaneModel.cs b/ILSpy/ViewModels/PaneModel.cs index c8856c7dc..0f4ce987d 100644 --- a/ILSpy/ViewModels/PaneModel.cs +++ b/ILSpy/ViewModels/PaneModel.cs @@ -21,7 +21,7 @@ using System.ComponentModel; using System.Windows; using System.Windows.Input; -using ICSharpCode.ILSpy.Docking; +using ICSharpCode.Decompiler.IL; using TomsToolbox.Wpf; @@ -29,6 +29,13 @@ namespace ICSharpCode.ILSpy.ViewModels { public abstract class PaneModel : ObservableObject { + private Throttle titleChangeThrottle; + + protected PaneModel() + { + titleChangeThrottle = new Throttle(() => OnPropertyChanged(nameof(Title))); + } + class CloseCommandImpl : ICommand { readonly PaneModel model; @@ -107,7 +114,10 @@ namespace ICSharpCode.ILSpy.ViewModels public string Title { get => title; - set => SetProperty(ref title, value); + set { + title = value; + titleChangeThrottle.Tick(); + } } } diff --git a/ILSpy/ViewModels/TabPageModel.cs b/ILSpy/ViewModels/TabPageModel.cs index 7972b84b6..90ff9ce57 100644 --- a/ILSpy/ViewModels/TabPageModel.cs +++ b/ILSpy/ViewModels/TabPageModel.cs @@ -20,8 +20,6 @@ using System; using System.Threading.Tasks; using ICSharpCode.ILSpy.TextView; -using ICSharpCode.ILSpy.Util; -using ICSharpCode.ILSpyX; namespace ICSharpCode.ILSpy.ViewModels { diff --git a/ILSpy/Views/DebugSteps.xaml.cs b/ILSpy/Views/DebugSteps.xaml.cs index e69199aff..b7336a43b 100644 --- a/ILSpy/Views/DebugSteps.xaml.cs +++ b/ILSpy/Views/DebugSteps.xaml.cs @@ -8,7 +8,6 @@ using System.Windows.Input; using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.IL.Transforms; using ICSharpCode.ILSpy.Docking; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpy.ViewModels; using TomsToolbox.Wpf.Composition.Mef; @@ -34,9 +33,9 @@ namespace ICSharpCode.ILSpy InitializeComponent(); #if DEBUG - MessageBus<LanguageSettingsChangedEventArgs>.Subscribers += (sender, e) => LanguageSettings_PropertyChanged(sender, e); + MessageBus<SettingsChangedEventArgs>.Subscribers += (sender, e) => Settings_PropertyChanged(sender, e); + MessageBus<AssemblyTreeSelectionChangedEventArgs>.Subscribers += SelectionChanged; - MainWindow.Instance.SelectionChanged += SelectionChanged; writingOptions.PropertyChanged += WritingOptions_PropertyChanged; if (SettingsService.Instance.SessionSettings.LanguageSettings.Language is ILAstLanguage l) @@ -53,7 +52,7 @@ namespace ICSharpCode.ILSpy DecompileAsync(lastSelectedStep); } - private void SelectionChanged(object sender, SelectionChangedEventArgs e) + private void SelectionChanged(object sender, EventArgs e) { Dispatcher.Invoke(() => { tree.ItemsSource = null; @@ -61,10 +60,13 @@ namespace ICSharpCode.ILSpy }); } - private void LanguageSettings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + private void Settings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { #if DEBUG - if (e.PropertyName == "Language") + if (sender is not LanguageSettings) + return; + + if (e.PropertyName == nameof(LanguageSettings.Language)) { if (language != null) { @@ -123,8 +125,8 @@ namespace ICSharpCode.ILSpy lastSelectedStep = step; var window = MainWindow.Instance; var state = DockWorkspace.Instance.ActiveTabPage.GetState(); - DockWorkspace.Instance.ActiveTabPage.ShowTextViewAsync(textView => textView.DecompileAsync(window.CurrentLanguage, window.SelectedNodes, - new DecompilationOptions(window.CurrentLanguageVersion, SettingsService.Instance.DecompilerSettings, SettingsService.Instance.DisplaySettings) { + DockWorkspace.Instance.ActiveTabPage.ShowTextViewAsync(textView => textView.DecompileAsync(window.AssemblyTreeModel.CurrentLanguage, window.AssemblyTreeModel.SelectedNodes, + new DecompilationOptions(window.AssemblyTreeModel.CurrentLanguageVersion, SettingsService.Instance.DecompilerSettings, SettingsService.Instance.DisplaySettings) { StepLimit = step, IsDebug = isDebug, TextViewState = state as TextView.DecompilerTextViewState diff --git a/ILSpy/Views/OpenFromGacDialog.xaml.cs b/ILSpy/Views/OpenFromGacDialog.xaml.cs index 5189be034..24b9f7b2b 100644 --- a/ILSpy/Views/OpenFromGacDialog.xaml.cs +++ b/ILSpy/Views/OpenFromGacDialog.xaml.cs @@ -31,6 +31,8 @@ using ICSharpCode.Decompiler.Metadata; using ICSharpCode.ILSpy.Controls; using ICSharpCode.ILSpyX.Extensions; +using TomsToolbox.Essentials; + namespace ICSharpCode.ILSpy { /// <summary> diff --git a/TestPlugin/CustomOptionPage.xaml b/TestPlugin/CustomOptionPage.xaml index 2aa86fcc1..b5533e7e5 100644 --- a/TestPlugin/CustomOptionPage.xaml +++ b/TestPlugin/CustomOptionPage.xaml @@ -1,8 +1,13 @@ <UserControl x:Class="TestPlugin.CustomOptionPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d:DesignHeight="500" d:DesignWidth="500" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" + xmlns:testPlugin="clr-namespace:TestPlugin" + d:DataContext="{d:DesignInstance testPlugin:CustomOptionsViewModel}" + > <StackPanel> - <CheckBox IsChecked="{Binding UselessOption1}">Useless option 1</CheckBox> - <Slider Minimum="0" Maximum="100" Value="{Binding UselessOption2}"/> + <CheckBox IsChecked="{Binding Options.UselessOption1}">Useless option 1</CheckBox> + <Slider Minimum="0" Maximum="100" Value="{Binding Options.UselessOption2}"/> </StackPanel> </UserControl> \ No newline at end of file diff --git a/TestPlugin/CustomOptionPage.xaml.cs b/TestPlugin/CustomOptionPage.xaml.cs index b4d607917..c550e02c0 100644 --- a/TestPlugin/CustomOptionPage.xaml.cs +++ b/TestPlugin/CustomOptionPage.xaml.cs @@ -1,99 +1,85 @@ // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under MIT X11 license (for details please see \doc\license.txt) -using System.ComponentModel; using System.ComponentModel.Composition; -using System.Windows.Controls; using System.Xml.Linq; -using ICSharpCode.ILSpy; using ICSharpCode.ILSpy.Options; -using ICSharpCode.ILSpyX.Settings; +using ICSharpCode.ILSpy.Util; + +using TomsToolbox.Wpf; +using TomsToolbox.Wpf.Composition.Mef; namespace TestPlugin { - [ExportOptionPage(Title = "TestPlugin", Order = 0)] + [DataTemplate(typeof(CustomOptionsViewModel))] [PartCreationPolicy(CreationPolicy.NonShared)] - partial class CustomOptionPage : UserControl, IOptionPage + partial class CustomOptionPage { - static readonly XNamespace ns = "http://www.ilspy.net/testplugin"; - public CustomOptionPage() { InitializeComponent(); } + } - public void Load(ILSpySettings settings) - { - // For loading options, use ILSpySetting's indexer. - // If the specified section does exist, the indexer will return a new empty element. - XElement e = settings[ns + "CustomOptions"]; - // Now load the options from the XML document: - Options s = new Options(); - s.UselessOption1 = (bool?)e.Attribute("useless1") ?? s.UselessOption1; - s.UselessOption2 = (double?)e.Attribute("useless2") ?? s.UselessOption2; - this.DataContext = s; + [ExportOptionPage(Order = 0)] + [PartCreationPolicy(CreationPolicy.NonShared)] + class CustomOptionsViewModel : ObservableObject, IOptionPage + { + private Options options; + + public string Title => "TestPlugin"; + + public Options Options { + get => options; + set => SetProperty(ref options, value); } - public void LoadDefaults() + public void Load(SettingsSnapshot snapshot) { - this.DataContext = new Options(); + this.Options = snapshot.GetSettings<Options>(); } - public void Save(XElement root) + public void LoadDefaults() { - Options s = (Options)this.DataContext; - // Save the options back into XML: - XElement section = new XElement(ns + "CustomOptions"); - section.SetAttributeValue("useless1", s.UselessOption1); - section.SetAttributeValue("useless2", s.UselessOption2); - - // Replace the existing section in the settings file, or add a new section, - // if required. - XElement? existingElement = root.Element(ns + "CustomOptions"); - if (existingElement != null) - existingElement.ReplaceWith(section); - else - root.Add(section); + Options.LoadFromXml(new XElement("dummy")); } } - class Options : INotifyPropertyChanged + class Options : ObservableObject, ISettingsSection { + static readonly XNamespace ns = "http://www.ilspy.net/testplugin"; + bool uselessOption1; public bool UselessOption1 { - get { return uselessOption1; } - set { - if (uselessOption1 != value) - { - uselessOption1 = value; - OnPropertyChanged("UselessOption1"); - } - } + get => uselessOption1; + set => SetProperty(ref uselessOption1, value); } double uselessOption2; public double UselessOption2 { - get { return uselessOption2; } - set { - if (uselessOption2 != value) - { - uselessOption2 = value; - OnPropertyChanged("UselessOption2"); - } - } + get => uselessOption2; + set => SetProperty(ref uselessOption2, value); } - public event PropertyChangedEventHandler? PropertyChanged; + public XName SectionName { get; } = ns + "CustomOptions"; - protected virtual void OnPropertyChanged(string propertyName) + public void LoadFromXml(XElement e) { - if (PropertyChanged != null) - { - PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); - } + UselessOption1 = (bool?)e.Attribute("useless1") ?? false; + UselessOption2 = (double?)e.Attribute("useless2") ?? 50.0; + } + + public XElement SaveToXml() + { + var section = new XElement(SectionName); + + section.SetAttributeValue("useless1", UselessOption1); + section.SetAttributeValue("useless2", UselessOption2); + + return section; } } } \ No newline at end of file diff --git a/TestPlugin/MainMenuCommand.cs b/TestPlugin/MainMenuCommand.cs index 3832716c2..99c1c0958 100644 --- a/TestPlugin/MainMenuCommand.cs +++ b/TestPlugin/MainMenuCommand.cs @@ -23,7 +23,7 @@ namespace TestPlugin { public override void Execute(object? parameter) { - foreach (var loadedAssembly in MainWindow.Instance.CurrentAssemblyList.GetAssemblies()) + foreach (var loadedAssembly in MainWindow.Instance.AssemblyTreeModel.AssemblyList.GetAssemblies()) { loadedAssembly.AssemblyList.Unload(loadedAssembly); }