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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
<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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
<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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
<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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
<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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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