diff --git a/ICSharpCode.ILSpyX/AssemblyListManager.cs b/ICSharpCode.ILSpyX/AssemblyListManager.cs index 510af433b3..35e1021ce9 100644 --- a/ICSharpCode.ILSpyX/AssemblyListManager.cs +++ b/ICSharpCode.ILSpyX/AssemblyListManager.cs @@ -59,9 +59,9 @@ public AssemblyListManager(ISettingsProvider settingsProvider) public bool UseDebugSymbols { get; set; } - public ObservableCollection AssemblyLists { get; } = new ObservableCollection(); + public ObservableCollection AssemblyLists { get; } = []; - public FileLoaderRegistry LoaderRegistry { get; } = new FileLoaderRegistry(); + public FileLoaderRegistry LoaderRegistry { get; } = new(); /// /// Loads an assembly list from the ILSpySettings. diff --git a/ICSharpCode.ILSpyX/Settings/ILSpySettings.cs b/ICSharpCode.ILSpyX/Settings/ILSpySettings.cs index c7dc899f1e..d19998587b 100644 --- a/ICSharpCode.ILSpyX/Settings/ILSpySettings.cs +++ b/ICSharpCode.ILSpyX/Settings/ILSpySettings.cs @@ -123,11 +123,6 @@ public void Update(Action action) } } - void ISettingsProvider.Update(Action action) - { - Update(action); - } - static string GetConfigFile() { if (null != SettingsFilePathProvider) @@ -148,17 +143,16 @@ sealed class MutexProtector : IDisposable 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 363dba66be..0000000000 --- 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 15b16a09b0..5e3ffba9b5 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 664acb5e22..1ddcec1310 100644 --- a/ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs +++ b/ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs @@ -17,9 +17,6 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.ComponentModel; -using System.Linq; -using System.Reflection; using System.Xml.Linq; namespace ICSharpCode.ILSpyX.Settings @@ -30,37 +27,6 @@ public interface ISettingsProvider void Update(Action action); - public static Decompiler.DecompilerSettings LoadDecompilerSettings(ISettingsProvider settingsProvider) - { - XElement e = settingsProvider["DecompilerSettings"]; - var newSettings = new Decompiler.DecompilerSettings(); - var properties = typeof(Decompiler.DecompilerSettings).GetProperties() - .Where(p => p.GetCustomAttribute()?.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, Decompiler.DecompilerSettings newSettings) - { - var properties = typeof(Decompiler.DecompilerSettings).GetProperties() - .Where(p => p.GetCustomAttribute()?.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 1735289efa..0000000000 --- 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 - { - // This should be abstract, but that needs C# 11.0 (see IParseable) - // Keep it to be enabled in the future - public static TSelf Load(ISettingsProvider settingsProvider) - { - throw new NotImplementedException(); - } - } -} diff --git a/ILSpy.ReadyToRun/ReadyToRunDisassembler.cs b/ILSpy.ReadyToRun/ReadyToRunDisassembler.cs index e9c11ff938..ce9e62a1e3 100644 --- a/ILSpy.ReadyToRun/ReadyToRunDisassembler.cs +++ b/ILSpy.ReadyToRun/ReadyToRunDisassembler.cs @@ -51,11 +51,13 @@ public void Disassemble(PEFile currentFile, int bitness, ulong address, bool sho ReadyToRunMethod readyToRunMethod = runtimeFunction.Method; WriteCommentLine(readyToRunMethod.SignatureString); - if (ReadyToRunOptions.GetIsShowGCInfo(null)) + var options = SettingsService.Instance.GetSettings(); + + 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) { @@ -69,12 +71,12 @@ public void Disassemble(PEFile currentFile, int bitness, ulong address, bool sho } Dictionary 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) { @@ -98,7 +100,7 @@ public void Disassemble(PEFile currentFile, int bitness, ulong address, bool sho decoder.Decode(out instructions.AllocUninitializedElement()); } - string disassemblyFormat = ReadyToRunOptions.GetDisassemblyFormat(null); + string disassemblyFormat = options.DisassemblyFormat; Formatter formatter = null; if (disassemblyFormat.Equals(ReadyToRunOptions.intel)) { @@ -145,7 +147,7 @@ public void Disassemble(PEFile currentFile, int bitness, ulong address, bool sho } } } - 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 137e0a6a14..3889236780 100644 --- a/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml +++ b/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml @@ -1,7 +1,11 @@  + 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}"> @@ -14,12 +18,12 @@ - - - + + + - + - + \ No newline at end of file diff --git a/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs b/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs index f03336f364..5e3c6b6371 100644 --- a/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs +++ b/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs @@ -16,114 +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(Order = 40)] + [DataTemplate(typeof(ReadyToRunOptionsViewModel))] [PartCreationPolicy(CreationPolicy.NonShared)] - partial class ReadyToRunOptionPage : UserControl, IOptionPage + partial class ReadyToRunOptionPage { public ReadyToRunOptionPage() { InitializeComponent(); } - - public string Title => global::ILSpy.ReadyToRun.Properties.Resources.ReadyToRun; - - public void Load(ILSpySettings spySettings) - { - Options s = new Options(); - s.DisassemblyFormat = ReadyToRunOptions.GetDisassemblyFormat(spySettings); - s.IsShowUnwindInfo = ReadyToRunOptions.GetIsShowUnwindInfo(spySettings); - s.IsShowDebugInfo = ReadyToRunOptions.GetIsShowDebugInfo(spySettings); - s.IsShowGCInfo = ReadyToRunOptions.GetIsShowGCInfo(spySettings); - - 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 bool IsShowGCInfo { - get { - return isShowGCInfo; - } - set { - isShowGCInfo = value; - OnPropertyChanged(nameof(IsShowGCInfo)); - } - } - - private string disassemblyFormat; + public string Title => global::ILSpy.ReadyToRun.Properties.Resources.ReadyToRun; - public string DisassemblyFormat { - get { return disassemblyFormat; } - set { - if (disassemblyFormat != value) - { - disassemblyFormat = value; - OnPropertyChanged(nameof(DisassemblyFormat)); - } - } + public void Load(SettingsSnapshot snapshot) + { + Options = snapshot.GetSettings(); } - public event PropertyChangedEventHandler PropertyChanged; - - protected virtual void OnPropertyChanged(string propertyName) + public void LoadDefaults() { - if (PropertyChanged != null) - { - PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); - } + Options.LoadFromSection(new("empty")); } } } \ No newline at end of file diff --git a/ILSpy.ReadyToRun/ReadyToRunOptions.cs b/ILSpy.ReadyToRun/ReadyToRunOptions.cs index 1167497bba..07e537157e 100644 --- a/ILSpy.ReadyToRun/ReadyToRunOptions.cs +++ b/ILSpy.ReadyToRun/ReadyToRunOptions.cs @@ -19,102 +19,72 @@ using System.Xml.Linq; using ICSharpCode.ILSpy.Util; -using ICSharpCode.ILSpyX; -using ICSharpCode.ILSpyX.Settings; + +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 }; - - public static string GetDisassemblyFormat(ILSpySettings settings) - { - settings ??= SettingsService.Instance.SpySettings; + internal static string[] disassemblyFormats = [intel, gas]; - 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) - { - settings ??= SettingsService.Instance.SpySettings; - - XElement e = settings[ns + "ReadyToRunOptions"]; - XAttribute a = e.Attribute("IsShowUnwindInfo"); - - if (a == null) - { - return false; - } - else - { - return (bool)a; - } + private bool isShowUnwindInfo; + public bool IsShowUnwindInfo { + get => isShowUnwindInfo; + set => SetProperty(ref isShowUnwindInfo, value); } - public static bool GetIsShowDebugInfo(ILSpySettings settings) - { - settings ??= SettingsService.Instance.SpySettings; + private bool isShowDebugInfo; + public bool IsShowDebugInfo { + get => isShowDebugInfo; + set => SetProperty(ref isShowDebugInfo, value); + } - 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 LoadFromSection(XElement e) { - settings ??= SettingsService.Instance.SpySettings; + XAttribute format = e.Attribute("DisassemblyFormat"); + DisassemblyFormat = format == null ? intel : (string)format; - XElement e = settings[ns + "ReadyToRunOptions"]; - XAttribute a = e.Attribute("IsShowGCInfo"); + XAttribute unwind = e.Attribute("IsShowUnwindInfo"); + IsShowUnwindInfo = unwind != null && (bool)unwind; - if (a == null) - { - return false; - } - else - { - return (bool)a; - } + 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 void SaveToSection(XElement section) { - XElement section = new XElement(ns + "ReadyToRunOptions"); 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); - } } } } diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs index 886a24f88f..c96e21dac0 100644 --- a/ILSpy/App.xaml.cs +++ b/ILSpy/App.xaml.cs @@ -32,9 +32,7 @@ using ICSharpCode.ILSpy.AppEnv; using ICSharpCode.ILSpy.AssemblyTree; -using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpyX.Analyzers; -using ICSharpCode.ILSpyX.Settings; using Medo.Application; @@ -66,11 +64,17 @@ internal class ExceptionData public App() { - ILSpySettings.SettingsFilePathProvider = new ILSpySettingsFilePathProvider(); - var cmdArgs = Environment.GetCommandLineArgs().Skip(1); CommandLineArguments = CommandLineArguments.Create(cmdArgs); + 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(); @@ -85,14 +89,6 @@ public App() TaskScheduler.UnobservedTaskException += DotNet40_UnobservedTaskException; InitializeMef().GetAwaiter().GetResult(); - bool forceSingleInstance = (CommandLineArguments.SingleInstance ?? true) - && !SettingsService.Instance.MiscSettings.AllowMultipleInstances; - if (forceSingleInstance) - { - SingleInstance.Attach(); // will auto-exit for second instance - SingleInstance.NewInstanceDetected += SingleInstance_NewInstanceDetected; - } - // Register the export provider so that it can be accessed from WPF/XAML components. ExportProviderLocator.Register(ExportProvider); // Add data templates registered via MEF. diff --git a/ILSpy/AssemblyTree/AssemblyListPaneModel.cs b/ILSpy/AssemblyTree/AssemblyListPaneModel.cs index e97b832afa..da36d0c977 100644 --- a/ILSpy/AssemblyTree/AssemblyListPaneModel.cs +++ b/ILSpy/AssemblyTree/AssemblyListPaneModel.cs @@ -77,30 +77,40 @@ public AssemblyListPaneModel() ShortcutKey = new KeyGesture(Key.F6); MessageBus.Subscribers += JumpToReference; - MessageBus.Subscribers += (sender, e) => SessionSettings_PropertyChanged(sender, e); - MessageBus.Subscribers += (sender, e) => LanguageSettings_PropertyChanged(sender, e); + MessageBus.Subscribers += (sender, e) => Settings_PropertyChanged(sender, e); var selectionChangeThrottle = new DispatcherThrottle(DispatcherPriority.Background, TreeView_SelectionChanged); SelectedItems.CollectionChanged += (_, _) => selectionChangeThrottle.Tick(); } - private void SessionSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) + private void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e) { - var sessionSettings = SettingsService.Instance.SessionSettings; - - switch (e.PropertyName) + if (sender is SessionSettings sessionSettings) { - 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; + 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(recordHistory: false); + break; + } } } @@ -136,7 +146,7 @@ public bool HandleCommandLineArguments(CommandLineArguments args) /// 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. /// - public void HandleCommandLineArgumentsAfterShowList(CommandLineArguments args, ILSpySettings spySettings = null) + public void HandleCommandLineArgumentsAfterShowList(CommandLineArguments args, ISettingsProvider spySettings = null) { var sessionSettings = SettingsService.Instance.SessionSettings; @@ -172,7 +182,7 @@ await Dispatcher.InvokeAsync(() => { }); } - public async void NavigateOnLaunch(string navigateTo, string[] activeTreeViewPath, ILSpySettings spySettings, List relevantAssemblies) + public async void NavigateOnLaunch(string navigateTo, string[] activeTreeViewPath, ISettingsProvider spySettings, List relevantAssemblies) { var initialSelection = SelectedItem; if (navigateTo != null) @@ -458,14 +468,6 @@ void LoadInitialAssemblies() AssemblyList.OpenAssembly(asm.Location); } - void LanguageSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName is nameof(LanguageSettings.Language) or nameof(LanguageSettings.LanguageVersion)) - { - DecompileSelectedNodes(recordHistory: false); - } - } - public AssemblyTreeNode FindAssemblyNode(LoadedAssembly asm) { return assemblyListTreeNode.FindAssemblyNode(asm); diff --git a/ILSpy/Commands/CheckForUpdatesCommand.cs b/ILSpy/Commands/CheckForUpdatesCommand.cs index 69e28573c2..779f854a10 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 { diff --git a/ILSpy/Commands/SetThemeCommand.cs b/ILSpy/Commands/SetThemeCommand.cs index 7f10c21f76..47b68c1003 100644 --- a/ILSpy/Commands/SetThemeCommand.cs +++ b/ILSpy/Commands/SetThemeCommand.cs @@ -6,7 +6,14 @@ public class SetThemeCommand : SimpleCommand public override void Execute(object parameter) { if (parameter is string theme) - SettingsService.Instance.SessionSettings.Theme = theme; + { + var snapshot = SettingsService.Instance.CreateSnapshot(); + var sessionSettings = snapshot.GetSettings(); + + sessionSettings.Theme = theme; + + snapshot.Save(); + } } } } diff --git a/ILSpy/DecompilationOptions.cs b/ILSpy/DecompilationOptions.cs index e5d480c3e0..c7c87322d5 100644 --- a/ILSpy/DecompilationOptions.cs +++ b/ILSpy/DecompilationOptions.cs @@ -23,6 +23,8 @@ using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpyX; +using DecompilerSettings = ICSharpCode.ILSpy.Options.DecompilerSettings; + namespace ICSharpCode.ILSpy { /// @@ -87,7 +89,9 @@ public DecompilationOptions(LanguageVersion version, DecompilerSettings settings { 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/LanguageSettings.cs b/ILSpy/LanguageSettings.cs index 82245963c3..67965d623f 100644 --- a/ILSpy/LanguageSettings.cs +++ b/ILSpy/LanguageSettings.cs @@ -25,12 +25,14 @@ using ICSharpCode.ILSpyX; +using TomsToolbox.Wpf; + namespace ICSharpCode.ILSpy { /// /// Represents the filters applied to the tree view. /// - public class LanguageSettings : INotifyPropertyChanged + public class LanguageSettings : ObservableObject { /// /// This dictionary is necessary to remember language versions across language changes. For example, @@ -172,20 +174,6 @@ public LanguageVersion LanguageVersion { } } - 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/MainWindow.xaml b/ILSpy/MainWindow.xaml index 4e7df1d724..2ff2a0621c 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -29,8 +29,6 @@ - - @@ -93,7 +91,7 @@ - + diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 396bcdd321..3e378805ae 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -34,6 +34,7 @@ using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Search; using ICSharpCode.ILSpy.TextView; +using ICSharpCode.ILSpy.Themes; using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.Updates; using ICSharpCode.ILSpyX.FileLoaders; @@ -70,6 +71,7 @@ public MainWindow() instance = this; var sessionSettings = SettingsService.Instance.SessionSettings; + ThemeManager.Current.Theme = sessionSettings.Theme; // Make sure Images are initialized on the UI thread. this.Icon = Images.ILSpyIcon; @@ -165,7 +167,7 @@ protected override void OnKeyDown(KeyEventArgs e) #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) @@ -337,14 +339,18 @@ protected override void OnStateChanged(EventArgs e) protected override void OnClosing(CancelEventArgs e) { base.OnClosing(e); - var sessionSettings = SettingsService.Instance.SessionSettings; + + var snapshot = SettingsService.Instance.CreateSnapshot(); + + var sessionSettings = snapshot.GetSettings(); sessionSettings.ActiveAssemblyList = AssemblyTreeModel.AssemblyList.ListName; sessionSettings.ActiveTreeViewPath = AssemblyTreeModel.SelectedPath; sessionSettings.ActiveAutoLoadedAssembly = GetAutoLoadedAssemblyNode(AssemblyTreeModel.SelectedItem); sessionSettings.WindowBounds = this.RestoreBounds; sessionSettings.DockLayout.Serialize(new XmlLayoutSerializer(dockManager)); - sessionSettings.Save(); + + snapshot.Save(); } private static string GetAutoLoadedAssemblyNode(SharpTreeNode node) diff --git a/ILSpy/Options/DecompilerSettings.cs b/ILSpy/Options/DecompilerSettings.cs new file mode 100644 index 0000000000..e5a42c965e --- /dev/null +++ b/ILSpy/Options/DecompilerSettings.cs @@ -0,0 +1,48 @@ +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()?.Browsable != false) + .ToArray(); + + public XName SectionName => "DecompilerSettings"; + + public void SaveToSection(XElement section) + { + foreach (var p in properties) + { + section.SetAttributeValue(p.Name, p.GetValue(this)); + } + } + + public void LoadFromSection(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 = new XElement("DecompilerSettings"); + SaveToSection(section); + var newSettings = new DecompilerSettings(); + newSettings.LoadFromSection(section); + + return newSettings; + } + } +} diff --git a/ILSpy/Options/DecompilerSettingsViewModel.cs b/ILSpy/Options/DecompilerSettingsViewModel.cs index 43531a388a..4b0787b2b0 100644 --- a/ILSpy/Options/DecompilerSettingsViewModel.cs +++ b/ILSpy/Options/DecompilerSettingsViewModel.cs @@ -21,12 +21,9 @@ using System.ComponentModel.Composition; using System.Linq; using System.Reflection; -using System.Xml.Linq; -using ICSharpCode.Decompiler; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TreeNodes; -using ICSharpCode.ILSpyX.Settings; using TomsToolbox.Wpf; @@ -36,56 +33,46 @@ namespace ICSharpCode.ILSpy.Options [PartCreationPolicy(CreationPolicy.NonShared)] public sealed class DecompilerSettingsViewModel : ObservableObjectBase, IOptionPage { - private DecompilerSettingsGroupViewModel[] settings; + private static readonly PropertyInfo[] propertyInfos = typeof(Decompiler.DecompilerSettings).GetProperties() + .Where(p => p.GetCustomAttribute()?.Browsable != false) + .ToArray(); public string Title => Resources.Decompiler; + private DecompilerSettingsGroupViewModel[] settings; public DecompilerSettingsGroupViewModel[] Settings { get => settings; set => SetProperty(ref settings, value); } - public void Load(ILSpySettings spySettings) + private DecompilerSettings decompilerSettings; + + public void Load(SettingsSnapshot snapshot) { - Load(ISettingsProvider.LoadDecompilerSettings(spySettings)); + decompilerSettings = snapshot.GetSettings(); + LoadSettings(); } - private void Load(DecompilerSettings decompilerSettings) + private void LoadSettings() { - this.Settings = typeof(Decompiler.DecompilerSettings).GetProperties() - .Where(p => p.GetCustomAttribute()?.Browsable != false) - .Select(p => new DecompilerSettingsItemViewModel(p) { IsEnabled = p.GetValue(decompilerSettings) is true }) + 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 void Save(XElement root) - { - var newSettings = ToDecompilerSettings(); - ISettingsProvider.SaveDecompilerSettings(root, newSettings); - - SettingsService.Instance.DecompilerSettings = newSettings; - SettingsService.Instance.AssemblyListManager.ApplyWinRTProjections = newSettings.ApplyWindowsRuntimeProjections; - SettingsService.Instance.AssemblyListManager.UseDebugSymbols = newSettings.UseDebugSymbols; - } - public void LoadDefaults() { - Load(new DecompilerSettings()); - } - - private DecompilerSettings ToDecompilerSettings() - { - var newSettings = new DecompilerSettings(); + var defaults = new Decompiler.DecompilerSettings(); - foreach (var item in Settings.SelectMany(group => group.Settings)) + foreach (var propertyInfo in propertyInfos) { - item.Property.SetValue(newSettings, item.IsEnabled); + propertyInfo.SetValue(decompilerSettings, propertyInfo.GetValue(defaults)); } - return newSettings; + LoadSettings(); } } @@ -147,15 +134,20 @@ private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e) } } - 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 bool IsEnabled { get => isEnabled; - set => SetProperty(ref isEnabled, value); + set { + if (SetProperty(ref isEnabled, value)) + { + property.SetValue(decompilerSettings, value); + } + } } public string Description { get; set; } = GetResourceString(property.GetCustomAttribute()?.Description ?? property.Name); diff --git a/ILSpy/Options/DisplaySettings.cs b/ILSpy/Options/DisplaySettings.cs index d2f00189bb..47349627ae 100644 --- a/ILSpy/Options/DisplaySettings.cs +++ b/ILSpy/Options/DisplaySettings.cs @@ -19,9 +19,6 @@ using System.Windows.Media; using System.Xml.Linq; -using ICSharpCode.ILSpy.Themes; -using ICSharpCode.ILSpyX.Settings; - using TomsToolbox.Wpf; namespace ICSharpCode.ILSpy.Options @@ -29,264 +26,176 @@ namespace ICSharpCode.ILSpy.Options /// /// Description of DisplaySettings. /// - public class DisplaySettings : ObservableObject + 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; - } - - string theme; - - public string Theme { - get => theme; - set => SetProperty(ref theme, value); - } - FontFamily selectedFont; - public FontFamily SelectedFont { get => selectedFont; set => SetProperty(ref selectedFont, value); } double selectedFontSize; - public double SelectedFontSize { get => selectedFontSize; set => SetProperty(ref selectedFontSize, value); } bool showLineNumbers; - public bool ShowLineNumbers { get => showLineNumbers; set => SetProperty(ref showLineNumbers, value); } bool showMetadataTokens; - public bool ShowMetadataTokens { get => showMetadataTokens; set => SetProperty(ref showMetadataTokens, value); } bool showMetadataTokensInBase10; - public bool ShowMetadataTokensInBase10 { get => showMetadataTokensInBase10; set => SetProperty(ref showMetadataTokensInBase10, value); } bool enableWordWrap; - public bool EnableWordWrap { get => enableWordWrap; set => SetProperty(ref enableWordWrap, value); } bool sortResults; - public bool SortResults { get => sortResults; set => SetProperty(ref sortResults, value); } bool foldBraces; - public bool FoldBraces { get => foldBraces; set => SetProperty(ref foldBraces, value); } bool expandMemberDefinitions; - public bool ExpandMemberDefinitions { get => expandMemberDefinitions; set => SetProperty(ref expandMemberDefinitions, value); } bool expandUsingDeclarations; - public bool ExpandUsingDeclarations { get => expandUsingDeclarations; set => SetProperty(ref expandUsingDeclarations, value); } bool showDebugInfo; - public bool ShowDebugInfo { get => showDebugInfo; set => SetProperty(ref showDebugInfo, value); } bool indentationUseTabs; - public bool IndentationUseTabs { get => indentationUseTabs; set => SetProperty(ref indentationUseTabs, value); } int indentationTabSize; - public int IndentationTabSize { get => indentationTabSize; set => SetProperty(ref indentationTabSize, value); } int indentationSize; - public int IndentationSize { get => indentationSize; set => SetProperty(ref indentationSize, value); } bool highlightMatchingBraces; - public bool HighlightMatchingBraces { get => highlightMatchingBraces; set => SetProperty(ref highlightMatchingBraces, value); } bool highlightCurrentLine; - public bool HighlightCurrentLine { get => highlightCurrentLine; set => SetProperty(ref highlightCurrentLine, value); } bool hideEmptyMetadataTables; - public bool HideEmptyMetadataTables { get => hideEmptyMetadataTables; set => SetProperty(ref hideEmptyMetadataTables, value); } bool useNestedNamespaceNodes; - public bool UseNestedNamespaceNodes { get => useNestedNamespaceNodes; set => SetProperty(ref useNestedNamespaceNodes, value); } private bool styleWindowTitleBar; - public bool StyleWindowTitleBar { get => styleWindowTitleBar; set => SetProperty(ref styleWindowTitleBar, value); } private bool showRawOffsetsAndBytesBeforeInstruction; - public bool ShowRawOffsetsAndBytesBeforeInstruction { get => showRawOffsetsAndBytesBeforeInstruction; set => SetProperty(ref showRawOffsetsAndBytesBeforeInstruction, value); } - public void CopyValues(DisplaySettings s) - { - 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; - } + public XName SectionName => "DisplaySettings"; - public static DisplaySettings Load(ILSpySettings settings, SessionSettings sessionSettings = null) + public void LoadFromSection(XElement section) { - XElement e = settings["DisplaySettings"]; - var s = new DisplaySettings { - SelectedFont = new FontFamily((string)e.Attribute("Font") ?? "Consolas"), - SelectedFontSize = (double?)e.Attribute("FontSize") ?? 10.0 * 4 / 3, - ShowLineNumbers = (bool?)e.Attribute("ShowLineNumbers") ?? false, - ShowMetadataTokens = (bool?)e.Attribute("ShowMetadataTokens") ?? false, - ShowMetadataTokensInBase10 = (bool?)e.Attribute("ShowMetadataTokensInBase10") ?? false, - ShowDebugInfo = (bool?)e.Attribute("ShowDebugInfo") ?? false, - EnableWordWrap = (bool?)e.Attribute("EnableWordWrap") ?? false, - SortResults = (bool?)e.Attribute("SortResults") ?? true, - FoldBraces = (bool?)e.Attribute("FoldBraces") ?? false, - ExpandMemberDefinitions = (bool?)e.Attribute("ExpandMemberDefinitions") ?? false, - ExpandUsingDeclarations = (bool?)e.Attribute("ExpandUsingDeclarations") ?? false, - IndentationUseTabs = (bool?)e.Attribute("IndentationUseTabs") ?? true, - IndentationSize = (int?)e.Attribute("IndentationSize") ?? 4, - IndentationTabSize = (int?)e.Attribute("IndentationTabSize") ?? 4, - HighlightMatchingBraces = (bool?)e.Attribute("HighlightMatchingBraces") ?? true, - HighlightCurrentLine = (bool?)e.Attribute("HighlightCurrentLine") ?? false, - HideEmptyMetadataTables = (bool?)e.Attribute("HideEmptyMetadataTables") ?? true, - UseNestedNamespaceNodes = (bool?)e.Attribute("UseNestedNamespaceNodes") ?? false, - ShowRawOffsetsAndBytesBeforeInstruction = (bool?)e.Attribute("ShowRawOffsetsAndBytesBeforeInstruction") ?? false, - StyleWindowTitleBar = (bool?)e.Attribute("StyleWindowTitleBar") ?? false, - Theme = (sessionSettings ?? SettingsService.Instance.SessionSettings).Theme - }; - - return s; - } - - public void Save(XElement root) + 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 void SaveToSection(XElement section) { - var s = this; - - 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); - } + 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); } } } diff --git a/ILSpy/Options/DisplaySettingsPanel.xaml b/ILSpy/Options/DisplaySettingsPanel.xaml index a8cd5c8716..9fb50d7a91 100644 --- a/ILSpy/Options/DisplaySettingsPanel.xaml +++ b/ILSpy/Options/DisplaySettingsPanel.xaml @@ -3,7 +3,6 @@ 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" @@ -12,7 +11,7 @@ diff --git a/ILSpy/Options/DisplaySettingsViewModel.cs b/ILSpy/Options/DisplaySettingsViewModel.cs index 69505fb9ff..1fc1399fdd 100644 --- a/ILSpy/Options/DisplaySettingsViewModel.cs +++ b/ILSpy/Options/DisplaySettingsViewModel.cs @@ -8,6 +8,7 @@ using System.Windows; using TomsToolbox.Wpf; +using ICSharpCode.ILSpy.Themes; namespace ICSharpCode.ILSpy.Options { @@ -17,6 +18,7 @@ public class DisplaySettingsViewModel : ObservableObject, IOptionPage { private DisplaySettings settings = new(); private FontFamily[] fontFamilies; + private SessionSettings sessionSettings; public DisplaySettingsViewModel() { @@ -40,6 +42,11 @@ public DisplaySettings 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); @@ -47,9 +54,10 @@ public FontFamily[] FontFamilies { public int[] FontSizes { get; } = Enumerable.Range(6, 24 - 6 + 1).ToArray(); - public void Load(ILSpySettings spySettings) + public void Load(SettingsSnapshot snapshot) { - Settings = DisplaySettings.Load(spySettings); + Settings = snapshot.GetSettings(); + SessionSettings = snapshot.GetSettings(); } static bool IsSymbolFont(FontFamily fontFamily) @@ -77,14 +85,10 @@ static FontFamily[] FontLoader() .ToArray(); } - public void Save(XElement root) - { - Settings.Save(root); - } - public void LoadDefaults() { - Settings = new(); + Settings.LoadFromSection(new XElement("empty")); + SessionSettings.Theme = ThemeManager.Current.DefaultTheme; } } } diff --git a/ICSharpCode.ILSpyX/Settings/MiscSettings.cs b/ILSpy/Options/MiscSettings.cs similarity index 57% rename from ICSharpCode.ILSpyX/Settings/MiscSettings.cs rename to ILSpy/Options/MiscSettings.cs index c47d5cff2d..31cd2cfe14 100644 --- a/ICSharpCode.ILSpyX/Settings/MiscSettings.cs +++ b/ILSpy/Options/MiscSettings.cs @@ -19,25 +19,38 @@ using System; using System.Xml.Linq; +using TomsToolbox.Wpf; + namespace ICSharpCode.ILSpyX.Settings { - public class MiscSettings : IMiscSettings, ISettingsSection + public class MiscSettings : ObservableObject, ISettingsSection { - public 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; } = true; + public XName SectionName => "MiscSettings"; - public static MiscSettings Load(ISettingsProvider settingsProvider) + public void LoadFromSection(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; + } - return s; + public void SaveToSection(XElement section) + { + section.SetAttributeValue(nameof(AllowMultipleInstances), AllowMultipleInstances); + section.SetAttributeValue(nameof(LoadPreviousAssemblies), LoadPreviousAssemblies); } } } + diff --git a/ILSpy/Options/MiscSettingsPanel.xaml b/ILSpy/Options/MiscSettingsPanel.xaml index 09a9c54854..c3a984c9b0 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"> - - - - 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"); - - 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); - var culture = (string)doc.Element(nameof(CurrentCulture)); - this.CurrentCulture = string.IsNullOrEmpty(culture) ? null : culture; + public XName SectionName => "SessionSettings"; - this.DockLayout = new DockLayoutSettings(doc.Element("DockLayout")); - } - - public event PropertyChangedEventHandler PropertyChanged; - - void OnPropertyChanged([CallerMemberName] string propertyName = null) + public void LoadFromSection(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); + 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; @@ -120,56 +99,49 @@ public string ActiveAssemblyList { 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 void SaveToSection(XElement section) { - XElement doc = new XElement("SessionSettings"); - doc.Add(this.LanguageSettings.SaveAsXml()); + section.RemoveAll(); + + 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(); - SettingsService.Instance.SpySettings.SaveSettings(doc); - } - - static Regex regex = new Regex("\\\\x(?[0-9A-f]{4})"); + static Regex regex = new("\\\\x(?[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)) @@ -205,5 +177,21 @@ static string ToString(T obj) 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(ref T field, T value, [CallerMemberName] string propertyName = null) + { + if (EqualityComparer.Default.Equals(field, value)) + return false; + field = value; + OnPropertyChanged(propertyName); + return true; + } } } diff --git a/ILSpy/Themes/ThemeManager.cs b/ILSpy/Themes/ThemeManager.cs index 419e4026ce..dae84fc933 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 @@ public class ThemeManager private ThemeManager() { Application.Current.Resources.MergedDictionaries.Add(_themeDictionaryContainer); + MessageBus.Subscribers += (sender, e) => Settings_Changed(sender, e); } public string DefaultTheme => "Light"; @@ -199,5 +201,13 @@ private static (byte r, byte g, byte b) HslToRgb(float h, float s, float l) 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/TreeNodes/ILSpyTreeNode.cs b/ILSpy/TreeNodes/ILSpyTreeNode.cs index 4c151f4d5d..99e63e36ec 100644 --- a/ILSpy/TreeNodes/ILSpyTreeNode.cs +++ b/ILSpy/TreeNodes/ILSpyTreeNode.cs @@ -40,9 +40,9 @@ public abstract class ILSpyTreeNode : SharpTreeNode, ITreeNode { bool childrenNeedFiltering; - public ILSpyTreeNode() + protected ILSpyTreeNode() { - MessageBus.Subscribers += LanguageSettings_Changed; + MessageBus.Subscribers += (sender, e) => Settings_Changed(sender, e); } LanguageSettings LanguageSettings => SettingsService.Instance.SessionSettings.LanguageSettings; @@ -127,8 +127,13 @@ void ApplyFilterToChild(ILSpyTreeNode child) } } - 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 1d5123c261..d2ab85fc97 100644 --- a/ILSpy/Updates/NotifyOfUpdatesStrategy.cs +++ b/ILSpy/Updates/NotifyOfUpdatesStrategy.cs @@ -61,7 +61,7 @@ public static async Task GetLatestVersionAsync() /// Returns the download URL if an update is available. /// Returns null if no update is available, or if no check was performed. /// - public static async Task CheckForUpdatesIfEnabledAsync(ILSpySettings spySettings) + public static async Task CheckForUpdatesIfEnabledAsync(ISettingsProvider spySettings) { UpdateSettings s = new UpdateSettings(spySettings); @@ -87,7 +87,7 @@ public static async Task CheckForUpdatesIfEnabledAsync(ILSpySettings spy } } - public static Task CheckForUpdatesAsync(ILSpySettings spySettings) + public static Task CheckForUpdatesAsync(ISettingsProvider spySettings) { UpdateSettings s = new UpdateSettings(spySettings); return CheckForUpdateInternal(s); diff --git a/ILSpy/Updates/UpdateSettings.cs b/ILSpy/Updates/UpdateSettings.cs index 653f078a99..23b4b06638 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; diff --git a/ILSpy/Util/MessageBus.cs b/ILSpy/Util/MessageBus.cs index 0ba600ce8d..b08b29dc64 100644 --- a/ILSpy/Util/MessageBus.cs +++ b/ILSpy/Util/MessageBus.cs @@ -52,9 +52,7 @@ public static implicit operator T(WrappedEventArgs outer) public class CurrentAssemblyListChangedEventArgs(NotifyCollectionChangedEventArgs e) : WrappedEventArgs(e); - public class LanguageSettingsChangedEventArgs(PropertyChangedEventArgs e) : WrappedEventArgs(e); - - public class SessionSettingsChangedEventArgs(PropertyChangedEventArgs e) : WrappedEventArgs(e); + public class SettingsChangedEventArgs(PropertyChangedEventArgs e) : WrappedEventArgs(e); public class NavigateToReferenceEventArgs(object reference, bool inNewTabPage = false) : EventArgs { diff --git a/ILSpy/Util/SettingsService.cs b/ILSpy/Util/SettingsService.cs index 23c0474588..03365eb34f 100644 --- a/ILSpy/Util/SettingsService.cs +++ b/ILSpy/Util/SettingsService.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Concurrent; +using System.ComponentModel; +using System.Xml.Linq; using ICSharpCode.Decompiler; using ICSharpCode.ILSpy.Options; @@ -6,36 +9,102 @@ using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.Settings; +using DecompilerSettings = ICSharpCode.ILSpy.Options.DecompilerSettings; + +#nullable enable + namespace ICSharpCode.ILSpy.Util { - public class SettingsService + public interface ISettingsSection : INotifyPropertyChanged { - public static readonly SettingsService Instance = new(); + XName SectionName { get; } + + void LoadFromSection(XElement section); + + void SaveToSection(XElement section); + } + + public abstract class SettingsServiceBase + { + protected readonly ConcurrentDictionary sections = new(); + + public ISettingsProvider SpySettings; + + protected SettingsServiceBase(ISettingsProvider spySettings) + { + SpySettings = spySettings; + } + + public T GetSettings() where T : ISettingsSection, new() + { + return (T)sections.GetOrAdd(typeof(T), _ => { + T section = new T(); + + var sectionElement = SpySettings[section.SectionName]; + + section.LoadFromSection(sectionElement); + section.PropertyChanged += Section_PropertyChanged; - private SettingsService() + return section; + }); + } + + protected virtual void Section_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + } + } + + public class SettingsSnapshot : SettingsServiceBase + { + private readonly SettingsService parent; + + public SettingsSnapshot(SettingsService parent) : base(parent.SpySettings) { - SpySettings = ILSpySettings.Load(); - SessionSettings = new(SpySettings); - DecompilerSettings = ISettingsProvider.LoadDecompilerSettings(SpySettings); - DisplaySettings = DisplaySettings.Load(SpySettings, SessionSettings); - MiscSettings = MiscSettings.Load(SpySettings); - AssemblyListManager = new(SpySettings) { - ApplyWinRTProjections = DecompilerSettings.ApplyWindowsRuntimeProjections, - UseDebugSymbols = DecompilerSettings.UseDebugSymbols - }; + this.parent = parent; } - public ILSpySettings SpySettings { get; } + public void Save() + { + SpySettings.Update(root => { + foreach (var section in sections.Values) + { + var element = SpySettings[section.SectionName]; + + section.SaveToSection(element); - public SessionSettings SessionSettings { get; } + var existingElement = root.Element(section.SectionName); + if (existingElement != null) + existingElement.ReplaceWith(element); + else + root.Add(element); + } + }); - public DecompilerSettings DecompilerSettings { get; set; } + parent.Reload(); + } + } - public DisplaySettings DisplaySettings { get; } + public class SettingsService : SettingsServiceBase + { + public static readonly SettingsService Instance = new(); - public MiscSettings MiscSettings { get; set; } + private SettingsService() : base(LoadSettings()) + { + } - public AssemblyListManager AssemblyListManager { get; } + public SessionSettings SessionSettings => GetSettings(); + + public DecompilerSettings DecompilerSettings => GetSettings(); + + public DisplaySettings DisplaySettings => GetSettings(); + + public MiscSettings MiscSettings => GetSettings(); + + private AssemblyListManager? assemblyListManager; + public AssemblyListManager AssemblyListManager => assemblyListManager ??= new(SpySettings) { + ApplyWinRTProjections = DecompilerSettings.ApplyWindowsRuntimeProjections, + UseDebugSymbols = DecompilerSettings.UseDebugSymbols + }; public DecompilationOptions CreateDecompilationOptions(TabPageModel tabPage) { @@ -56,5 +125,57 @@ public AssemblyList LoadInitialAssemblyList() return AssemblyListManager.CreateList(AssemblyListManager.DefaultListName); } } + + private bool reloading; + + public void Reload() + { + reloading = true; + + try + { + SpySettings = ILSpySettings.Load(); + + foreach (var section in sections.Values) + { + var element = SpySettings[section.SectionName]; + + section.LoadFromSection(element); + } + } + finally + { + reloading = false; + } + } + + public SettingsSnapshot CreateSnapshot() + { + return new(this); + } + + protected override void Section_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + base.Section_PropertyChanged(sender, e); + + if (!reloading) + { + throw new InvalidOperationException("Settings are read only, use a snapshot to modify."); + } + + 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/Views/DebugSteps.xaml.cs b/ILSpy/Views/DebugSteps.xaml.cs index ff82daeba0..b2745089e0 100644 --- a/ILSpy/Views/DebugSteps.xaml.cs +++ b/ILSpy/Views/DebugSteps.xaml.cs @@ -33,7 +33,7 @@ public DebugSteps() InitializeComponent(); #if DEBUG - MessageBus.Subscribers += (sender, e) => LanguageSettings_PropertyChanged(sender, e); + MessageBus.Subscribers += (sender, e) => Settings_PropertyChanged(sender, e); MessageBus.Subscribers += SelectionChanged; writingOptions.PropertyChanged += WritingOptions_PropertyChanged; @@ -60,10 +60,13 @@ private void SelectionChanged(object sender, EventArgs e) }); } - 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) { diff --git a/TestPlugin/CustomOptionPage.xaml b/TestPlugin/CustomOptionPage.xaml index 2aa86fcc15..b5533e7e59 100644 --- a/TestPlugin/CustomOptionPage.xaml +++ b/TestPlugin/CustomOptionPage.xaml @@ -1,8 +1,13 @@  + 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}" + > - Useless option 1 - + Useless option 1 + \ No newline at end of file diff --git a/TestPlugin/CustomOptionPage.xaml.cs b/TestPlugin/CustomOptionPage.xaml.cs index 043c0d317a..d54b156573 100644 --- a/TestPlugin/CustomOptionPage.xaml.cs +++ b/TestPlugin/CustomOptionPage.xaml.cs @@ -1,100 +1,80 @@ // 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.Options; -using ICSharpCode.ILSpyX.Settings; +using ICSharpCode.ILSpy.Util; + +using TomsToolbox.Wpf; +using TomsToolbox.Wpf.Composition.Mef; namespace TestPlugin { - [ExportOptionPage(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(); } + } + + [ExportOptionPage(Order = 0)] + class CustomOptionsViewModel : ObservableObject, IOptionPage + { + private Options options; public string Title => "TestPlugin"; - public void Load(ILSpySettings spySettings) - { - // For loading options, use ILSpySetting's indexer. - // If the specified section does exist, the indexer will return a new empty element. - XElement e = spySettings[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; + 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(); } - 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.LoadFromSection(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"; + + public void LoadFromSection(XElement e) + { + UselessOption1 = (bool?)e.Attribute("useless1") ?? false; + UselessOption2 = (double?)e.Attribute("useless2") ?? 50.0; + } - protected virtual void OnPropertyChanged(string propertyName) + public void SaveToSection(XElement section) { - if (PropertyChanged != null) - { - PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); - } + section.SetAttributeValue("useless1", UselessOption1); + section.SetAttributeValue("useless2", UselessOption2); } } } \ No newline at end of file