mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
Replaces the WPF modal Options dialog with a Dock document tab — opens via View → Options at the same MenuOrder=999 mount point, hosts the same three panels (Decompiler / Display / Misc), and uses the same SettingsSnapshot commit pattern so closing the tab without Apply discards in-flight edits. Re-invoking View → Options while one's open just focuses the existing tab. Assisted-by: Claude:claude-opus-4-7:Claude Codepull/3755/head
26 changed files with 1421 additions and 12 deletions
@ -0,0 +1,210 @@
@@ -0,0 +1,210 @@
|
||||
// Copyright (c) 2026 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.Linq; |
||||
using System.Threading.Tasks; |
||||
|
||||
using Avalonia.Headless.NUnit; |
||||
|
||||
using AwesomeAssertions; |
||||
|
||||
using ICSharpCode.ILSpy.Properties; |
||||
|
||||
using ILSpy; |
||||
using ILSpy.AppEnv; |
||||
using ILSpy.Commands; |
||||
using ILSpy.Docking; |
||||
using ILSpy.Options; |
||||
using ILSpy.ViewModels; |
||||
using ILSpy.Views; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.ILSpy.Tests; |
||||
|
||||
[TestFixture] |
||||
public class OptionsTabTests |
||||
{ |
||||
[AvaloniaTest] |
||||
public void Options_Command_Is_Exported_To_View_Menu_With_Last_MenuOrder() |
||||
{ |
||||
// Mirrors the WPF mounting point. MenuOrder 999 puts it last under View;
|
||||
// MenuCategory "Options" gives it its own separator group.
|
||||
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>(); |
||||
var entry = registry.Commands |
||||
.SingleOrDefault(c => c.Metadata.Header == nameof(Resources._Options)); |
||||
((object?)entry).Should().NotBeNull( |
||||
"View → Options must be exported via [ExportMainMenuCommand]"); |
||||
entry!.Metadata.ParentMenuID.Should().Be(nameof(Resources._View)); |
||||
entry.Metadata.MenuOrder.Should().Be(999); |
||||
entry.Metadata.MenuCategory.Should().Be(nameof(Resources.Options)); |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public void Invoking_ShowOptionsCommand_Opens_Document_Tab_With_OptionsPageModel() |
||||
{ |
||||
// The Options window-equivalent shows up as a regular Dock document tab with
|
||||
// OptionsPageModel as its Content. IsStaticContent flags it so tree-node selections
|
||||
// route to a fresh decompile tab instead of overwriting it.
|
||||
var window = AppComposition.Current.GetExport<MainWindow>(); |
||||
window.Show(); |
||||
var vm = (MainWindowViewModel)window.DataContext!; |
||||
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>(); |
||||
var command = registry.Commands |
||||
.Single(c => c.Metadata.Header == nameof(Resources._Options)) |
||||
.CreateExport().Value; |
||||
|
||||
command.Execute(null); |
||||
|
||||
var docs = vm.DockWorkspace.Documents?.VisibleDockables; |
||||
((object?)docs).Should().NotBeNull(); |
||||
var optionsTab = docs!.OfType<ContentTabPage>() |
||||
.SingleOrDefault(t => t.Content is OptionsPageModel); |
||||
((object?)optionsTab).Should().NotBeNull( |
||||
"a ContentTabPage hosting an OptionsPageModel must land in the documents dock"); |
||||
var model = (OptionsPageModel)optionsTab!.Content!; |
||||
model.IsStaticContent.Should().BeTrue( |
||||
"the Options tab must be flagged static so tree-node selections leave it alone"); |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public void OptionsPageModel_Surfaces_The_Three_Panels_In_MEF_Order() |
||||
{ |
||||
// Decompiler / Display / Misc, ordered by ExportOptionPage(Order=10/20/30).
|
||||
// Titles come from the embedded WPF Resources.resx so they match the WPF host
|
||||
// byte-for-byte.
|
||||
var window = AppComposition.Current.GetExport<MainWindow>(); |
||||
window.Show(); |
||||
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>(); |
||||
var command = registry.Commands |
||||
.Single(c => c.Metadata.Header == nameof(Resources._Options)) |
||||
.CreateExport().Value; |
||||
command.Execute(null); |
||||
|
||||
var vm = (MainWindowViewModel)window.DataContext!; |
||||
var model = (OptionsPageModel)vm.DockWorkspace.Documents!.VisibleDockables! |
||||
.OfType<ContentTabPage>().First(t => t.Content is OptionsPageModel).Content!; |
||||
|
||||
model.Pages.Should().HaveCount(3); |
||||
model.Pages[0].Title.Should().Be(Resources.Decompiler); |
||||
model.Pages[1].Title.Should().Be(Resources.Display); |
||||
model.Pages[2].Title.Should().Be(Resources.Misc); |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public void Reinvoking_ShowOptionsCommand_Focuses_Existing_Tab_Without_Spawning_A_Second() |
||||
{ |
||||
// Single-instance behaviour — re-firing the command while Options is already open
|
||||
// just reactivates the existing tab. Mirrors WPF's modal-stack uniqueness.
|
||||
var window = AppComposition.Current.GetExport<MainWindow>(); |
||||
window.Show(); |
||||
var vm = (MainWindowViewModel)window.DataContext!; |
||||
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>(); |
||||
var command = registry.Commands |
||||
.Single(c => c.Metadata.Header == nameof(Resources._Options)) |
||||
.CreateExport().Value; |
||||
|
||||
command.Execute(null); |
||||
command.Execute(null); |
||||
|
||||
var optionsTabs = vm.DockWorkspace.Documents!.VisibleDockables! |
||||
.OfType<ContentTabPage>().Where(t => t.Content is OptionsPageModel).ToList(); |
||||
optionsTabs.Should().HaveCount(1, "re-firing the command must focus, not duplicate"); |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public async Task Apply_Writes_Snapshot_Through_To_Live_DecompilerSettings() |
||||
{ |
||||
// Snapshot pattern: changes in the panel's settings instance don't reach the live
|
||||
// service until Apply. After Apply, SettingsService.DecompilerSettings reflects the
|
||||
// new value. Toggling a property on the snapshot copy alone shouldn't affect the
|
||||
// live instance until Apply is called.
|
||||
var window = AppComposition.Current.GetExport<MainWindow>(); |
||||
window.Show(); |
||||
var settings = AppComposition.Current.GetExport<SettingsService>(); |
||||
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>(); |
||||
|
||||
var liveBefore = settings.DecompilerSettings.UsingDeclarations; |
||||
|
||||
registry.Commands.Single(c => c.Metadata.Header == nameof(Resources._Options)) |
||||
.CreateExport().Value.Execute(null); |
||||
|
||||
var vm = (MainWindowViewModel)window.DataContext!; |
||||
var model = (OptionsPageModel)vm.DockWorkspace.Documents!.VisibleDockables! |
||||
.OfType<ContentTabPage>().First(t => t.Content is OptionsPageModel).Content!; |
||||
var decompilerPage = (DecompilerSettingsViewModel)model.Pages[0]; |
||||
|
||||
// Find the UsingDeclarations item in the reflection-built tree and flip it.
|
||||
var usingDecl = decompilerPage.Settings |
||||
.SelectMany(g => g.Settings) |
||||
.First(s => s.Property.Name == nameof(global::ICSharpCode.Decompiler.DecompilerSettings.UsingDeclarations)); |
||||
usingDecl.IsEnabled = !liveBefore; |
||||
|
||||
// Live service unaffected so far — only the snapshot copy changed.
|
||||
// Snapshot edits must not leak into the live service before Apply.
|
||||
settings.DecompilerSettings.UsingDeclarations.Should().Be(liveBefore); |
||||
|
||||
// Apply — flushes snapshot to XML and reloads sections so live values match.
|
||||
model.ApplyCommand.Execute(null); |
||||
await Task.Yield(); |
||||
|
||||
// After Apply, the live service must see the panel's toggled value.
|
||||
settings.DecompilerSettings.UsingDeclarations.Should().Be(!liveBefore); |
||||
|
||||
// Clean-up: restore the original so the persisted XML doesn't pollute later tests.
|
||||
usingDecl.IsEnabled = liveBefore; |
||||
model.ApplyCommand.Execute(null); |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public void Reset_Current_Page_Restores_Defaults_For_The_Active_Panel_Only() |
||||
{ |
||||
// Reset operates on the selected panel only. After flipping a few values and clicking
|
||||
// Reset, every DecompilerSettings property is back to its `new DecompilerSettings()`
|
||||
// default; Display and Misc panels are untouched (verified by leaving them alone).
|
||||
var window = AppComposition.Current.GetExport<MainWindow>(); |
||||
window.Show(); |
||||
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>(); |
||||
registry.Commands.Single(c => c.Metadata.Header == nameof(Resources._Options)) |
||||
.CreateExport().Value.Execute(null); |
||||
|
||||
var vm = (MainWindowViewModel)window.DataContext!; |
||||
var model = (OptionsPageModel)vm.DockWorkspace.Documents!.VisibleDockables! |
||||
.OfType<ContentTabPage>().First(t => t.Content is OptionsPageModel).Content!; |
||||
var decompilerPage = (DecompilerSettingsViewModel)model.Pages[0]; |
||||
model.SelectedPage = decompilerPage; |
||||
|
||||
// Flip a known-default-true setting to false.
|
||||
var item = decompilerPage.Settings |
||||
.SelectMany(g => g.Settings) |
||||
.First(s => s.Property.Name == nameof(global::ICSharpCode.Decompiler.DecompilerSettings.UsingDeclarations)); |
||||
var defaultValue = (bool)item.Property.GetValue(new global::ICSharpCode.Decompiler.DecompilerSettings())!; |
||||
item.IsEnabled = !defaultValue; |
||||
// Sanity: precondition flip took effect.
|
||||
item.IsEnabled.Should().Be(!defaultValue); |
||||
|
||||
model.ResetCurrentPageCommand.Execute(null); |
||||
|
||||
// After reset, the reflection-rebuilt item list has the snapshot's defaults.
|
||||
var refreshedItem = decompilerPage.Settings |
||||
.SelectMany(g => g.Settings) |
||||
.First(s => s.Property.Name == nameof(global::ICSharpCode.Decompiler.DecompilerSettings.UsingDeclarations)); |
||||
// Reset must restore the panel's settings to `new DecompilerSettings()` defaults.
|
||||
refreshedItem.IsEnabled.Should().Be(defaultValue); |
||||
} |
||||
} |
||||
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2026 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.Composition; |
||||
using System.Linq; |
||||
|
||||
using ICSharpCode.ILSpy.Properties; |
||||
|
||||
using ILSpy.Docking; |
||||
using ILSpy.Options; |
||||
using ILSpy.ViewModels; |
||||
|
||||
namespace ILSpy.Commands |
||||
{ |
||||
/// <summary>
|
||||
/// Opens (or focuses, if already open) the Options document tab. Mounted under the View
|
||||
/// menu mirroring the WPF host's `ShowOptionsCommand`. Ensures a single open instance —
|
||||
/// re-invocation just reactivates the existing tab.
|
||||
/// </summary>
|
||||
[ExportMainMenuCommand(ParentMenuID = nameof(Resources._View), Header = nameof(Resources._Options), MenuCategory = nameof(Resources.Options), MenuOrder = 999)] |
||||
[Shared] |
||||
[method: ImportingConstructor] |
||||
internal sealed class ShowOptionsCommand( |
||||
DockWorkspace dockWorkspace, |
||||
SettingsService settingsService, |
||||
// "OptionPages" is the named MEF contract every ExportOptionPageAttribute publishes
|
||||
// under (see ExportOptionPageAttribute ctor). [ImportMany] alone resolves to the
|
||||
// default contract for IOptionPage and would come up empty.
|
||||
[ImportMany("OptionPages")] IEnumerable<ExportFactory<IOptionPage, IOptionsMetadata>> optionPages) : SimpleCommand |
||||
{ |
||||
public override void Execute(object? parameter) |
||||
{ |
||||
var existing = FindExistingOptionsTab(); |
||||
if (existing != null && dockWorkspace.Documents != null) |
||||
{ |
||||
dockWorkspace.Factory.SetActiveDockable(existing); |
||||
dockWorkspace.Factory.SetFocusedDockable(dockWorkspace.Documents, existing); |
||||
return; |
||||
} |
||||
|
||||
var model = new OptionsPageModel(settingsService, optionPages); |
||||
dockWorkspace.OpenNewTab(model); |
||||
} |
||||
|
||||
ContentTabPage? FindExistingOptionsTab() |
||||
{ |
||||
var docs = dockWorkspace.Documents?.VisibleDockables; |
||||
if (docs == null) |
||||
return null; |
||||
return docs.OfType<ContentTabPage>().FirstOrDefault(t => t.Content is OptionsPageModel); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
xmlns:vm="using:ILSpy.Options" |
||||
mc:Ignorable="d" d:DesignWidth="700" d:DesignHeight="500" |
||||
x:Class="ILSpy.Options.Panels.DecompilerSettingsPanel" |
||||
x:DataType="vm:DecompilerSettingsViewModel"> |
||||
<ScrollViewer Padding="6"> |
||||
<ItemsControl ItemsSource="{Binding Settings}"> |
||||
<ItemsControl.ItemTemplate> |
||||
<DataTemplate x:DataType="vm:DecompilerSettingsGroupViewModel"> |
||||
<Expander Header="{Binding Category}" IsExpanded="True" Margin="0,2"> |
||||
<ItemsControl ItemsSource="{Binding Settings}" Margin="12,4,0,0"> |
||||
<ItemsControl.ItemTemplate> |
||||
<DataTemplate x:DataType="vm:DecompilerSettingsItemViewModel"> |
||||
<CheckBox IsChecked="{Binding IsEnabled, Mode=TwoWay}" |
||||
Content="{Binding Description}" |
||||
Margin="0,1" /> |
||||
</DataTemplate> |
||||
</ItemsControl.ItemTemplate> |
||||
</ItemsControl> |
||||
</Expander> |
||||
</DataTemplate> |
||||
</ItemsControl.ItemTemplate> |
||||
</ItemsControl> |
||||
</ScrollViewer> |
||||
</UserControl> |
||||
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2026 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 global::Avalonia.Controls; |
||||
using global::Avalonia.Markup.Xaml; |
||||
|
||||
namespace ILSpy.Options.Panels |
||||
{ |
||||
public partial class DecompilerSettingsPanel : UserControl |
||||
{ |
||||
public DecompilerSettingsPanel() |
||||
{ |
||||
InitializeComponent(); |
||||
} |
||||
|
||||
void InitializeComponent() => AvaloniaXamlLoader.Load(this); |
||||
} |
||||
} |
||||
@ -0,0 +1,169 @@
@@ -0,0 +1,169 @@
|
||||
// Copyright (c) 2026 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.ComponentModel; |
||||
using System.Composition; |
||||
using System.Linq; |
||||
using System.Reflection; |
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
|
||||
using ICSharpCode.ILSpy.Properties; |
||||
using ICSharpCode.ILSpyX.Settings; |
||||
|
||||
using ILSpy.TreeNodes; |
||||
|
||||
// `Decompiler` short-form alias keeps the reflection-walk over `Decompiler.DecompilerSettings`
|
||||
// terse and disambiguates from the ILSpyX `DecompilerSettings` wrapper imported just below.
|
||||
using Decompiler = ICSharpCode.Decompiler; |
||||
|
||||
namespace ILSpy.Options |
||||
{ |
||||
/// <summary>
|
||||
/// Reflection-driven viewmodel for the Decompiler-settings panel. Walks every
|
||||
/// non-[Browsable(false)] property on <see cref="Decompiler.DecompilerSettings"/>,
|
||||
/// groups them by their [Category] attribute, and surfaces each as an
|
||||
/// <see cref="DecompilerSettingsItemViewModel"/> with a localised description.
|
||||
/// </summary>
|
||||
// No [Shared] — each Options tab open gets a fresh viewmodel instance bound to a fresh
|
||||
// snapshot. WPF's [NonShared] from TomsToolbox; in System.Composition the absence of
|
||||
// [Shared] gives the same per-call instantiation.
|
||||
[ExportOptionPage(Order = 10)] |
||||
public sealed partial class DecompilerSettingsViewModel : ObservableObject, IOptionPage |
||||
{ |
||||
static readonly PropertyInfo[] propertyInfos = typeof(Decompiler.DecompilerSettings).GetProperties() |
||||
.Where(p => p.GetCustomAttribute<BrowsableAttribute>()?.Browsable != false) |
||||
.ToArray(); |
||||
|
||||
public string Title => Resources.Decompiler; |
||||
|
||||
[ObservableProperty] |
||||
DecompilerSettingsGroupViewModel[] settings = System.Array.Empty<DecompilerSettingsGroupViewModel>(); |
||||
|
||||
DecompilerSettings decompilerSettings = null!; |
||||
|
||||
public void Load(SettingsSnapshot snapshot) |
||||
{ |
||||
decompilerSettings = snapshot.GetSettings<DecompilerSettings>(); |
||||
LoadSettings(); |
||||
} |
||||
|
||||
void LoadSettings() |
||||
{ |
||||
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 LoadDefaults() |
||||
{ |
||||
var defaults = new Decompiler.DecompilerSettings(); |
||||
foreach (var p in propertyInfos) |
||||
p.SetValue(decompilerSettings, p.GetValue(defaults)); |
||||
LoadSettings(); |
||||
} |
||||
} |
||||
|
||||
/// <summary>One Category section in the Decompiler panel. Surfaces a tri-state header
|
||||
/// checkbox (all/none/some) that bulk-toggles every item under it.</summary>
|
||||
public sealed partial class DecompilerSettingsGroupViewModel : ObservableObject |
||||
{ |
||||
[ObservableProperty] |
||||
bool? areAllItemsChecked; |
||||
|
||||
public DecompilerSettingsGroupViewModel(string category, DecompilerSettingsItemViewModel[] settings) |
||||
{ |
||||
Settings = settings; |
||||
Category = category; |
||||
areAllItemsChecked = GetAreAllItemsChecked(Settings); |
||||
foreach (var s in settings) |
||||
s.PropertyChanged += ItemPropertyChanged; |
||||
} |
||||
|
||||
public string Category { get; } |
||||
|
||||
public DecompilerSettingsItemViewModel[] Settings { get; } |
||||
|
||||
partial void OnAreAllItemsCheckedChanged(bool? value) |
||||
{ |
||||
if (!value.HasValue) |
||||
return; |
||||
foreach (var s in Settings) |
||||
s.IsEnabled = value.Value; |
||||
} |
||||
|
||||
void ItemPropertyChanged(object? sender, PropertyChangedEventArgs e) |
||||
{ |
||||
if (e.PropertyName == nameof(DecompilerSettingsItemViewModel.IsEnabled)) |
||||
AreAllItemsChecked = GetAreAllItemsChecked(Settings); |
||||
} |
||||
|
||||
static bool? GetAreAllItemsChecked(ICollection<DecompilerSettingsItemViewModel> settings) |
||||
{ |
||||
var enabled = settings.Count(i => i.IsEnabled); |
||||
if (enabled == settings.Count) |
||||
return true; |
||||
if (enabled == 0) |
||||
return false; |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
/// <summary>One reflection-discovered boolean setting in the Decompiler panel.</summary>
|
||||
public sealed partial class DecompilerSettingsItemViewModel : ObservableObject |
||||
{ |
||||
readonly PropertyInfo property; |
||||
readonly DecompilerSettings settings; |
||||
|
||||
public DecompilerSettingsItemViewModel(PropertyInfo property, DecompilerSettings settings) |
||||
{ |
||||
this.property = property; |
||||
this.settings = settings; |
||||
isEnabled = property.GetValue(settings) is true; |
||||
Description = GetResourceString(property.GetCustomAttribute<DescriptionAttribute>()?.Description ?? property.Name); |
||||
Category = GetResourceString(property.GetCustomAttribute<CategoryAttribute>()?.Category ?? Resources.Other); |
||||
} |
||||
|
||||
[ObservableProperty] |
||||
bool isEnabled; |
||||
|
||||
/// <summary>The <see cref="Decompiler.DecompilerSettings"/> property this item
|
||||
/// reflects. Exposed for tests that need to look up an item by name.</summary>
|
||||
public PropertyInfo Property => property; |
||||
|
||||
public string Description { get; } |
||||
|
||||
public string Category { get; } |
||||
|
||||
partial void OnIsEnabledChanged(bool value) |
||||
{ |
||||
property.SetValue(settings, value); |
||||
} |
||||
|
||||
static string GetResourceString(string key) |
||||
{ |
||||
var str = !string.IsNullOrEmpty(key) ? Resources.ResourceManager.GetString(key) : null; |
||||
return string.IsNullOrEmpty(key) || string.IsNullOrEmpty(str) ? key : str; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,158 @@
@@ -0,0 +1,158 @@
|
||||
// Copyright (c) 2026 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.Xml.Linq; |
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
|
||||
using ICSharpCode.ILSpyX.Settings; |
||||
|
||||
namespace ILSpy.Options |
||||
{ |
||||
/// <summary>
|
||||
/// Editor-and-tree visual preferences. Persisted under the <c><DisplaySettings/></c>
|
||||
/// XML section; the schema matches the WPF host so saved settings round-trip across
|
||||
/// platforms. Font family is a string (Avalonia's <see cref="global::Avalonia.Media.FontFamily"/>
|
||||
/// is constructed from a name at consumption sites — no WPF FontFamily here so this type
|
||||
/// stays UI-framework-free).
|
||||
/// </summary>
|
||||
public sealed partial class DisplaySettings : ObservableObject, ISettingsSection |
||||
{ |
||||
[ObservableProperty] |
||||
string selectedFont = "Consolas"; |
||||
|
||||
[ObservableProperty] |
||||
double selectedFontSize = 10.0 * 4 / 3; |
||||
|
||||
[ObservableProperty] |
||||
bool showLineNumbers; |
||||
|
||||
[ObservableProperty] |
||||
bool showMetadataTokens; |
||||
|
||||
[ObservableProperty] |
||||
bool showMetadataTokensInBase10; |
||||
|
||||
[ObservableProperty] |
||||
bool enableWordWrap; |
||||
|
||||
[ObservableProperty] |
||||
bool sortResults = true; |
||||
|
||||
[ObservableProperty] |
||||
bool foldBraces; |
||||
|
||||
[ObservableProperty] |
||||
bool expandMemberDefinitions; |
||||
|
||||
[ObservableProperty] |
||||
bool expandUsingDeclarations; |
||||
|
||||
[ObservableProperty] |
||||
bool showDebugInfo; |
||||
|
||||
[ObservableProperty] |
||||
bool indentationUseTabs = true; |
||||
|
||||
[ObservableProperty] |
||||
int indentationTabSize = 4; |
||||
|
||||
[ObservableProperty] |
||||
int indentationSize = 4; |
||||
|
||||
[ObservableProperty] |
||||
bool highlightMatchingBraces = true; |
||||
|
||||
[ObservableProperty] |
||||
bool highlightCurrentLine; |
||||
|
||||
[ObservableProperty] |
||||
bool hideEmptyMetadataTables = true; |
||||
|
||||
[ObservableProperty] |
||||
bool useNestedNamespaceNodes; |
||||
|
||||
[ObservableProperty] |
||||
bool styleWindowTitleBar; |
||||
|
||||
[ObservableProperty] |
||||
bool showRawOffsetsAndBytesBeforeInstruction; |
||||
|
||||
[ObservableProperty] |
||||
bool enableSmoothScrolling = true; |
||||
|
||||
[ObservableProperty] |
||||
bool decodeCustomAttributeBlobs; |
||||
|
||||
public XName SectionName => "DisplaySettings"; |
||||
|
||||
public void LoadFromXml(XElement section) |
||||
{ |
||||
SelectedFont = (string?)section.Attribute("Font") ?? "Consolas"; |
||||
SelectedFontSize = (double?)section.Attribute("FontSize") ?? 10.0 * 4 / 3; |
||||
ShowLineNumbers = (bool?)section.Attribute(nameof(ShowLineNumbers)) ?? false; |
||||
ShowMetadataTokens = (bool?)section.Attribute(nameof(ShowMetadataTokens)) ?? false; |
||||
ShowMetadataTokensInBase10 = (bool?)section.Attribute(nameof(ShowMetadataTokensInBase10)) ?? false; |
||||
ShowDebugInfo = (bool?)section.Attribute(nameof(ShowDebugInfo)) ?? false; |
||||
EnableWordWrap = (bool?)section.Attribute(nameof(EnableWordWrap)) ?? false; |
||||
SortResults = (bool?)section.Attribute(nameof(SortResults)) ?? true; |
||||
FoldBraces = (bool?)section.Attribute(nameof(FoldBraces)) ?? false; |
||||
ExpandMemberDefinitions = (bool?)section.Attribute(nameof(ExpandMemberDefinitions)) ?? false; |
||||
ExpandUsingDeclarations = (bool?)section.Attribute(nameof(ExpandUsingDeclarations)) ?? false; |
||||
IndentationUseTabs = (bool?)section.Attribute(nameof(IndentationUseTabs)) ?? true; |
||||
IndentationSize = (int?)section.Attribute(nameof(IndentationSize)) ?? 4; |
||||
IndentationTabSize = (int?)section.Attribute(nameof(IndentationTabSize)) ?? 4; |
||||
HighlightMatchingBraces = (bool?)section.Attribute(nameof(HighlightMatchingBraces)) ?? true; |
||||
HighlightCurrentLine = (bool?)section.Attribute(nameof(HighlightCurrentLine)) ?? false; |
||||
HideEmptyMetadataTables = (bool?)section.Attribute(nameof(HideEmptyMetadataTables)) ?? true; |
||||
UseNestedNamespaceNodes = (bool?)section.Attribute(nameof(UseNestedNamespaceNodes)) ?? false; |
||||
ShowRawOffsetsAndBytesBeforeInstruction = (bool?)section.Attribute(nameof(ShowRawOffsetsAndBytesBeforeInstruction)) ?? false; |
||||
StyleWindowTitleBar = (bool?)section.Attribute(nameof(StyleWindowTitleBar)) ?? false; |
||||
EnableSmoothScrolling = (bool?)section.Attribute(nameof(EnableSmoothScrolling)) ?? true; |
||||
DecodeCustomAttributeBlobs = (bool?)section.Attribute(nameof(DecodeCustomAttributeBlobs)) ?? false; |
||||
} |
||||
|
||||
public XElement SaveToXml() |
||||
{ |
||||
var section = new XElement(SectionName); |
||||
section.SetAttributeValue("Font", SelectedFont); |
||||
section.SetAttributeValue("FontSize", SelectedFontSize); |
||||
section.SetAttributeValue(nameof(ShowLineNumbers), ShowLineNumbers); |
||||
section.SetAttributeValue(nameof(ShowMetadataTokens), ShowMetadataTokens); |
||||
section.SetAttributeValue(nameof(ShowMetadataTokensInBase10), ShowMetadataTokensInBase10); |
||||
section.SetAttributeValue(nameof(ShowDebugInfo), ShowDebugInfo); |
||||
section.SetAttributeValue(nameof(EnableWordWrap), EnableWordWrap); |
||||
section.SetAttributeValue(nameof(SortResults), SortResults); |
||||
section.SetAttributeValue(nameof(FoldBraces), FoldBraces); |
||||
section.SetAttributeValue(nameof(ExpandMemberDefinitions), ExpandMemberDefinitions); |
||||
section.SetAttributeValue(nameof(ExpandUsingDeclarations), ExpandUsingDeclarations); |
||||
section.SetAttributeValue(nameof(IndentationUseTabs), IndentationUseTabs); |
||||
section.SetAttributeValue(nameof(IndentationSize), IndentationSize); |
||||
section.SetAttributeValue(nameof(IndentationTabSize), IndentationTabSize); |
||||
section.SetAttributeValue(nameof(HighlightMatchingBraces), HighlightMatchingBraces); |
||||
section.SetAttributeValue(nameof(HighlightCurrentLine), HighlightCurrentLine); |
||||
section.SetAttributeValue(nameof(HideEmptyMetadataTables), HideEmptyMetadataTables); |
||||
section.SetAttributeValue(nameof(UseNestedNamespaceNodes), UseNestedNamespaceNodes); |
||||
section.SetAttributeValue(nameof(ShowRawOffsetsAndBytesBeforeInstruction), ShowRawOffsetsAndBytesBeforeInstruction); |
||||
section.SetAttributeValue(nameof(StyleWindowTitleBar), StyleWindowTitleBar); |
||||
section.SetAttributeValue(nameof(EnableSmoothScrolling), EnableSmoothScrolling); |
||||
section.SetAttributeValue(nameof(DecodeCustomAttributeBlobs), DecodeCustomAttributeBlobs); |
||||
return section; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,94 @@
@@ -0,0 +1,94 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
xmlns:vm="using:ILSpy.Options" |
||||
xmlns:res="using:ICSharpCode.ILSpy.Properties" |
||||
mc:Ignorable="d" d:DesignWidth="700" d:DesignHeight="500" |
||||
x:Class="ILSpy.Options.Panels.DisplaySettingsPanel" |
||||
x:DataType="vm:DisplaySettingsViewModel"> |
||||
<ScrollViewer Padding="6"> |
||||
<StackPanel Spacing="10"> |
||||
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="Auto,Auto" ColumnSpacing="8" RowSpacing="4"> |
||||
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" |
||||
Text="{x:Static res:Resources.Font}" /> |
||||
<ComboBox Grid.Row="0" Grid.Column="1" MinWidth="200" |
||||
ItemsSource="{Binding AvailableFonts}" |
||||
SelectedItem="{Binding Settings.SelectedFont, Mode=TwoWay}" /> |
||||
<NumericUpDown Grid.Row="0" Grid.Column="2" Width="100" |
||||
Minimum="6" Maximum="72" Increment="1" |
||||
Value="{Binding Settings.SelectedFontSize, Mode=TwoWay}" /> |
||||
<Border Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" |
||||
BorderBrush="#FFCCCEDB" BorderThickness="1" Padding="6" Margin="0,2,0,0"> |
||||
<TextBlock Text="The quick brown fox jumps over the lazy dog" |
||||
FontFamily="{Binding Settings.SelectedFont}" |
||||
FontSize="{Binding Settings.SelectedFontSize}" /> |
||||
</Border> |
||||
</Grid> |
||||
|
||||
<HeaderedContentControl Header="{x:Static res:Resources.Indentation}"> |
||||
<StackPanel Spacing="4" Margin="12,4,0,0"> |
||||
<CheckBox IsChecked="{Binding Settings.IndentationUseTabs, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.UseTabsInsteadOfSpaces}" /> |
||||
<Grid ColumnDefinitions="Auto,Auto,Auto,Auto" ColumnSpacing="6"> |
||||
<TextBlock Grid.Column="0" VerticalAlignment="Center" |
||||
Text="{x:Static res:Resources.IndentSize}" /> |
||||
<NumericUpDown Grid.Column="1" Width="80" Minimum="1" Maximum="16" |
||||
Value="{Binding Settings.IndentationSize, Mode=TwoWay}" /> |
||||
<TextBlock Grid.Column="2" VerticalAlignment="Center" Margin="12,0,0,0" |
||||
Text="{x:Static res:Resources.TabSize}" /> |
||||
<NumericUpDown Grid.Column="3" Width="80" Minimum="1" Maximum="16" |
||||
Value="{Binding Settings.IndentationTabSize, Mode=TwoWay}" /> |
||||
</Grid> |
||||
</StackPanel> |
||||
</HeaderedContentControl> |
||||
|
||||
<HeaderedContentControl Header="{x:Static res:Resources.DecompilationViewOptions}"> |
||||
<StackPanel Spacing="2" Margin="12,4,0,0"> |
||||
<CheckBox IsChecked="{Binding Settings.ShowLineNumbers, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.ShowLineNumbers}" /> |
||||
<CheckBox IsChecked="{Binding Settings.EnableWordWrap, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.EnableWordWrap}" /> |
||||
<CheckBox IsChecked="{Binding Settings.FoldBraces, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.EnableFoldingBlocksBraces}" /> |
||||
<CheckBox IsChecked="{Binding Settings.HighlightMatchingBraces, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.HighlightMatchingBraces}" /> |
||||
<CheckBox IsChecked="{Binding Settings.HighlightCurrentLine, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.HighlightCurrentLine}" /> |
||||
<CheckBox IsChecked="{Binding Settings.ExpandMemberDefinitions, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.ExpandMemberDefinitionsAfterDecompilation}" /> |
||||
<CheckBox IsChecked="{Binding Settings.ExpandUsingDeclarations, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.ExpandUsingDeclarationsAfterDecompilation}" /> |
||||
<CheckBox IsChecked="{Binding Settings.ShowDebugInfo, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.ShowInfoFromDebugSymbolsAvailable}" /> |
||||
<CheckBox IsChecked="{Binding Settings.ShowRawOffsetsAndBytesBeforeInstruction, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.ShowRawOffsetsAndBytesBeforeInstruction}" /> |
||||
<CheckBox IsChecked="{Binding Settings.DecodeCustomAttributeBlobs, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.DecodeCustomAttributeBlobs}" /> |
||||
<CheckBox IsChecked="{Binding Settings.EnableSmoothScrolling, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.EnableSmoothScrolling}" /> |
||||
</StackPanel> |
||||
</HeaderedContentControl> |
||||
|
||||
<HeaderedContentControl Header="{x:Static res:Resources.TreeViewOptions}"> |
||||
<StackPanel Spacing="2" Margin="12,4,0,0"> |
||||
<CheckBox IsChecked="{Binding Settings.ShowMetadataTokens, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.ShowMetadataTokens}" /> |
||||
<CheckBox IsChecked="{Binding Settings.ShowMetadataTokensInBase10, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.ShowMetadataTokensInBase10}" /> |
||||
<CheckBox IsChecked="{Binding Settings.HideEmptyMetadataTables, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.HideEmptyMetadataTables}" /> |
||||
<CheckBox IsChecked="{Binding Settings.UseNestedNamespaceNodes, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.UseNestedNamespaceNodes}" /> |
||||
</StackPanel> |
||||
</HeaderedContentControl> |
||||
|
||||
<HeaderedContentControl Header="{x:Static res:Resources.Other}"> |
||||
<StackPanel Spacing="2" Margin="12,4,0,0"> |
||||
<CheckBox IsChecked="{Binding Settings.SortResults, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.SortResultsFitness}" /> |
||||
</StackPanel> |
||||
</HeaderedContentControl> |
||||
</StackPanel> |
||||
</ScrollViewer> |
||||
</UserControl> |
||||
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2026 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 global::Avalonia.Controls; |
||||
using global::Avalonia.Markup.Xaml; |
||||
|
||||
namespace ILSpy.Options.Panels |
||||
{ |
||||
public partial class DisplaySettingsPanel : UserControl |
||||
{ |
||||
public DisplaySettingsPanel() |
||||
{ |
||||
InitializeComponent(); |
||||
} |
||||
|
||||
void InitializeComponent() => AvaloniaXamlLoader.Load(this); |
||||
} |
||||
} |
||||
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2026 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.Linq; |
||||
using System.Xml.Linq; |
||||
|
||||
using global::Avalonia.Media; |
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
|
||||
using ICSharpCode.ILSpy.Properties; |
||||
|
||||
namespace ILSpy.Options |
||||
{ |
||||
/// <summary>Viewmodel for the Display panel. Exposes the snapshot's
|
||||
/// <see cref="DisplaySettings"/> through <see cref="Settings"/> for direct two-way
|
||||
/// binding in the panel view, plus the list of system font families for the picker.</summary>
|
||||
[ExportOptionPage(Order = 20)] |
||||
public sealed partial class DisplaySettingsViewModel : ObservableObject, IOptionPage |
||||
{ |
||||
public string Title => Resources.Display; |
||||
|
||||
[ObservableProperty] |
||||
DisplaySettings settings = null!; |
||||
|
||||
// Avalonia equivalent of WPF's Fonts.SystemFontFamilies. FontManager.Current populates
|
||||
// from the platform font enumerator (DirectWrite on Windows, FontConfig on Linux,
|
||||
// CoreText on macOS), so the list is realistic on every supported target.
|
||||
public IReadOnlyList<string> AvailableFonts { get; } = FontManager.Current.SystemFonts |
||||
.Select(t => t.Name) |
||||
.OrderBy(n => n, System.StringComparer.OrdinalIgnoreCase) |
||||
.ToArray(); |
||||
|
||||
public void Load(SettingsSnapshot snapshot) |
||||
{ |
||||
Settings = snapshot.GetSettings<DisplaySettings>(); |
||||
} |
||||
|
||||
public void LoadDefaults() |
||||
{ |
||||
// Reset by replaying LoadFromXml against an empty element — every attribute is
|
||||
// absent so each property falls back to its built-in default.
|
||||
Settings.LoadFromXml(new XElement("DisplaySettings")); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2026 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.Composition; |
||||
|
||||
namespace ILSpy.Options |
||||
{ |
||||
/// <summary>
|
||||
/// Marks a class as an Options-tab panel. MEF discovers all such exports under the
|
||||
/// shared contract "OptionPages" with metadata <see cref="IOptionsMetadata"/>; the
|
||||
/// Options host orders them by <see cref="Order"/> and renders them as inner tabs.
|
||||
/// </summary>
|
||||
[MetadataAttribute] |
||||
[AttributeUsage(AttributeTargets.Class)] |
||||
public sealed class ExportOptionPageAttribute() : ExportAttribute("OptionPages", typeof(IOptionPage)) |
||||
{ |
||||
// Public property is reflected by System.Composition into the IOptionsMetadata
|
||||
// metadata view (matched by name). The metadata view is a concrete class in this
|
||||
// MEF host, so there's no "implements IOptionsMetadata" — the wire-up is purely
|
||||
// nominal property matching.
|
||||
public int Order { get; set; } |
||||
} |
||||
} |
||||
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2026 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.
|
||||
|
||||
namespace ILSpy.Options |
||||
{ |
||||
/// <summary>
|
||||
/// Plugin contract for individual panels in the Options tab. Each implementation is
|
||||
/// MEF-discovered via <see cref="ExportOptionPageAttribute"/>; the host calls
|
||||
/// <see cref="Load"/> with a snapshot of the current settings when the tab opens, and
|
||||
/// <see cref="LoadDefaults"/> when the user clicks the per-panel reset button.
|
||||
/// </summary>
|
||||
public interface IOptionPage |
||||
{ |
||||
string Title { get; } |
||||
|
||||
void Load(SettingsSnapshot settings); |
||||
|
||||
void LoadDefaults(); |
||||
} |
||||
} |
||||
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2026 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.
|
||||
|
||||
namespace ILSpy.Options |
||||
{ |
||||
/// <summary>
|
||||
/// Strongly-typed metadata view for <see cref="IOptionPage"/> exports — <see cref="Order"/>
|
||||
/// drives the tab order inside the Options page (lowest first). Concrete class with a
|
||||
/// parameterless constructor because System.Composition (MEF2) requires that for
|
||||
/// metadata views — the WPF host's same-named interface doesn't translate. The "I"
|
||||
/// prefix is kept to mirror the WPF naming so the call sites read identically across
|
||||
/// platforms.
|
||||
/// </summary>
|
||||
public sealed class IOptionsMetadata |
||||
{ |
||||
public int Order { get; set; } |
||||
} |
||||
} |
||||
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2026 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.Xml.Linq; |
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
|
||||
using ICSharpCode.ILSpyX.Settings; |
||||
|
||||
namespace ILSpy.Options |
||||
{ |
||||
/// <summary>
|
||||
/// Cross-cutting application-level toggles that don't fit Decompiler or Display.
|
||||
/// Persisted under the <c><MiscSettings/></c> XML section; XML attribute names
|
||||
/// and defaults match the WPF host so saved settings round-trip across platforms.
|
||||
/// </summary>
|
||||
public sealed partial class MiscSettings : ObservableObject, ISettingsSection |
||||
{ |
||||
[ObservableProperty] |
||||
bool allowMultipleInstances; |
||||
|
||||
[ObservableProperty] |
||||
bool loadPreviousAssemblies = true; |
||||
|
||||
public XName SectionName => "MiscSettings"; |
||||
|
||||
public void LoadFromXml(XElement e) |
||||
{ |
||||
AllowMultipleInstances = (bool?)e.Attribute(nameof(AllowMultipleInstances)) ?? false; |
||||
LoadPreviousAssemblies = (bool?)e.Attribute(nameof(LoadPreviousAssemblies)) ?? true; |
||||
} |
||||
|
||||
public XElement SaveToXml() |
||||
{ |
||||
var section = new XElement(SectionName); |
||||
section.SetAttributeValue(nameof(AllowMultipleInstances), AllowMultipleInstances); |
||||
section.SetAttributeValue(nameof(LoadPreviousAssemblies), LoadPreviousAssemblies); |
||||
return section; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
xmlns:vm="using:ILSpy.Options" |
||||
xmlns:res="using:ICSharpCode.ILSpy.Properties" |
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="300" |
||||
x:Class="ILSpy.Options.Panels.MiscSettingsPanel" |
||||
x:DataType="vm:MiscSettingsViewModel"> |
||||
<StackPanel Spacing="6" Margin="6"> |
||||
<CheckBox IsChecked="{Binding Settings.AllowMultipleInstances, Mode=TwoWay}" |
||||
Content="{x:Static res:Resources.AllowMultipleInstances}" /> |
||||
<CheckBox IsChecked="{Binding Settings.LoadPreviousAssemblies, Mode=TwoWay}" |
||||
Content="Load previously open assemblies on startup" /> |
||||
</StackPanel> |
||||
</UserControl> |
||||
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2026 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 global::Avalonia.Controls; |
||||
using global::Avalonia.Markup.Xaml; |
||||
|
||||
namespace ILSpy.Options.Panels |
||||
{ |
||||
public partial class MiscSettingsPanel : UserControl |
||||
{ |
||||
public MiscSettingsPanel() |
||||
{ |
||||
InitializeComponent(); |
||||
} |
||||
|
||||
void InitializeComponent() => AvaloniaXamlLoader.Load(this); |
||||
} |
||||
} |
||||
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2026 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.Xml.Linq; |
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
|
||||
using ICSharpCode.ILSpy.Properties; |
||||
|
||||
namespace ILSpy.Options |
||||
{ |
||||
/// <summary>Viewmodel for the Misc panel. WPF additionally exposes a shell-integration
|
||||
/// command (Add/Remove "Open with ILSpy" registry keys); deferred for v1 since
|
||||
/// Avalonia's WinExe doesn't register file associations yet.</summary>
|
||||
[ExportOptionPage(Order = 30)] |
||||
public sealed partial class MiscSettingsViewModel : ObservableObject, IOptionPage |
||||
{ |
||||
public string Title => Resources.Misc; |
||||
|
||||
[ObservableProperty] |
||||
MiscSettings settings = null!; |
||||
|
||||
public void Load(SettingsSnapshot snapshot) |
||||
{ |
||||
Settings = snapshot.GetSettings<MiscSettings>(); |
||||
} |
||||
|
||||
public void LoadDefaults() |
||||
{ |
||||
Settings.LoadFromXml(new XElement("MiscSettings")); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2026 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.Composition; |
||||
using System.Linq; |
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
using CommunityToolkit.Mvvm.Input; |
||||
|
||||
using ICSharpCode.ILSpy.Properties; |
||||
|
||||
namespace ILSpy.Options |
||||
{ |
||||
/// <summary>
|
||||
/// Content viewmodel for the Options document tab. Wraps a <see cref="SettingsSnapshot"/>,
|
||||
/// MEF-discovered <see cref="IOptionPage"/> panels (ordered by <see cref="IOptionsMetadata.Order"/>),
|
||||
/// and the page-level Apply / Reset commands the panel views bind to.
|
||||
/// </summary>
|
||||
public sealed partial class OptionsPageModel : ObservableObject |
||||
{ |
||||
readonly SettingsSnapshot snapshot; |
||||
|
||||
public OptionsPageModel(SettingsService settingsService, IEnumerable<ExportFactory<IOptionPage, IOptionsMetadata>> pageFactories) |
||||
{ |
||||
snapshot = settingsService.CreateSnapshot(); |
||||
|
||||
Pages = pageFactories |
||||
.OrderBy(f => f.Metadata.Order) |
||||
.Select(f => f.CreateExport().Value) |
||||
.ToArray(); |
||||
|
||||
foreach (var page in Pages) |
||||
page.Load(snapshot); |
||||
|
||||
selectedPage = Pages.FirstOrDefault(); |
||||
Title = Resources._Options; |
||||
|
||||
ApplyCommand = new RelayCommand(Apply); |
||||
ResetCurrentPageCommand = new RelayCommand(ResetCurrentPage); |
||||
} |
||||
|
||||
public string Title { get; } |
||||
|
||||
/// <summary>Marker for the dock router: tree-node selections must not replace this
|
||||
/// tab. Mirrors the <see cref="ILSpy.TextView.DecompilerTabPageModel.IsStaticContent"/>
|
||||
/// convention used for the About tab.</summary>
|
||||
public bool IsStaticContent => true; |
||||
|
||||
public IReadOnlyList<IOptionPage> Pages { get; } |
||||
|
||||
[ObservableProperty] |
||||
IOptionPage? selectedPage; |
||||
|
||||
public IRelayCommand ApplyCommand { get; } |
||||
|
||||
public IRelayCommand ResetCurrentPageCommand { get; } |
||||
|
||||
void Apply() => snapshot.Save(); |
||||
|
||||
void ResetCurrentPage() => SelectedPage?.LoadDefaults(); |
||||
} |
||||
} |
||||
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
xmlns:vm="using:ILSpy.Options" |
||||
xmlns:panels="using:ILSpy.Options.Panels" |
||||
xmlns:res="using:ICSharpCode.ILSpy.Properties" |
||||
mc:Ignorable="d" d:DesignWidth="700" d:DesignHeight="500" |
||||
x:Class="ILSpy.Options.OptionsPageView" |
||||
x:DataType="vm:OptionsPageModel"> |
||||
<DockPanel LastChildFill="True"> |
||||
<!-- Apply / Reset live in the page footer rather than per-panel. Functionally |
||||
identical to a per-panel Apply (every Apply commits the full snapshot); |
||||
keeps the buttons accessible regardless of which inner tab is active and |
||||
avoids parent-binding gymnastics from inside each panel's DataContext. --> |
||||
<Border DockPanel.Dock="Bottom" BorderBrush="#FFC8CDD3" BorderThickness="0,1,0,0" Padding="8"> |
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="6"> |
||||
<Button Content="{x:Static res:Resources.ResetToDefaults}" |
||||
Command="{Binding ResetCurrentPageCommand}" /> |
||||
<Button Content="{x:Static res:Resources.OK}" |
||||
Command="{Binding ApplyCommand}" /> |
||||
</StackPanel> |
||||
</Border> |
||||
<TabControl ItemsSource="{Binding Pages}" |
||||
SelectedItem="{Binding SelectedPage, Mode=TwoWay}"> |
||||
<TabControl.ItemTemplate> |
||||
<DataTemplate x:DataType="vm:IOptionPage"> |
||||
<TextBlock Text="{Binding Title}" /> |
||||
</DataTemplate> |
||||
</TabControl.ItemTemplate> |
||||
<TabControl.ContentTemplate> |
||||
<DataTemplate> |
||||
<ContentControl Content="{Binding}"> |
||||
<ContentControl.DataTemplates> |
||||
<DataTemplate x:DataType="vm:DecompilerSettingsViewModel"> |
||||
<panels:DecompilerSettingsPanel /> |
||||
</DataTemplate> |
||||
<DataTemplate x:DataType="vm:DisplaySettingsViewModel"> |
||||
<panels:DisplaySettingsPanel /> |
||||
</DataTemplate> |
||||
<DataTemplate x:DataType="vm:MiscSettingsViewModel"> |
||||
<panels:MiscSettingsPanel /> |
||||
</DataTemplate> |
||||
</ContentControl.DataTemplates> |
||||
</ContentControl> |
||||
</DataTemplate> |
||||
</TabControl.ContentTemplate> |
||||
</TabControl> |
||||
</DockPanel> |
||||
</UserControl> |
||||
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2026 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 global::Avalonia.Controls; |
||||
using global::Avalonia.Markup.Xaml; |
||||
|
||||
namespace ILSpy.Options |
||||
{ |
||||
public partial class OptionsPageView : UserControl |
||||
{ |
||||
public OptionsPageView() |
||||
{ |
||||
InitializeComponent(); |
||||
} |
||||
|
||||
void InitializeComponent() => AvaloniaXamlLoader.Load(this); |
||||
} |
||||
} |
||||
@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2026 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 ICSharpCode.ILSpyX; |
||||
using ICSharpCode.ILSpyX.Settings; |
||||
|
||||
namespace ILSpy.Options |
||||
{ |
||||
/// <summary>
|
||||
/// In-memory copy of the persisted settings, used by the Options tab so panel edits
|
||||
/// don't reach the live <see cref="ILSpy.SettingsService"/> until the user
|
||||
/// clicks Apply. Inherits the standard <see cref="SettingsServiceBase.GetSettings{T}"/>
|
||||
/// load+subscribe machinery from the shared base — sections materialise on first
|
||||
/// access reading from the same XML root as the parent service, but are independent
|
||||
/// object graphs. <see cref="Save"/> writes those graphs back through the parent's
|
||||
/// <see cref="ISettingsProvider"/> and asks the parent to reload so subscribers see
|
||||
/// the new values.
|
||||
/// </summary>
|
||||
public sealed class SettingsSnapshot : SettingsServiceBase |
||||
{ |
||||
readonly SettingsService parent; |
||||
|
||||
public SettingsSnapshot(SettingsService parent, ISettingsProvider spySettings) : base(spySettings) |
||||
{ |
||||
this.parent = parent; |
||||
} |
||||
|
||||
public void Save() |
||||
{ |
||||
SpySettings.Update(root => { |
||||
foreach (var section in sections.Values) |
||||
{ |
||||
SaveSection(section, root); |
||||
} |
||||
}); |
||||
|
||||
parent.Reload(); |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue