diff --git a/.gitmodules b/.gitmodules index 4566a77eb..ed9a2a9ab 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,7 @@ [submodule "ILSpy-tests"] path = ILSpy-tests url = https://github.com/icsharpcode/ILSpy-tests - \ No newline at end of file + +[submodule "AvalonDock"] + path = AvalonDock + url = https://github.com/siegfriedpammer/AvalonDock diff --git a/AvalonDock b/AvalonDock new file mode 160000 index 000000000..e5ec95d62 --- /dev/null +++ b/AvalonDock @@ -0,0 +1 @@ +Subproject commit e5ec95d624d5c3e6998832c4d8adefdaf11fd9c8 diff --git a/BuildTools/update-assemblyinfo.ps1 b/BuildTools/update-assemblyinfo.ps1 index 7e14e23da..f021a331d 100644 --- a/BuildTools/update-assemblyinfo.ps1 +++ b/BuildTools/update-assemblyinfo.ps1 @@ -9,11 +9,11 @@ $masterBranches = @("master", "5.0.x"); $globalAssemblyInfoTemplateFile = "ILSpy/Properties/AssemblyInfo.template.cs"; function Test-File([string]$filename) { - return [System.IO.File]::Exists( (Join-Path (Get-Location) $filename) ); + return [System.IO.File]::Exists((Join-Path (Get-Location) $filename)); } function Test-Dir([string]$name) { - return [System.IO.Directory]::Exists( (Join-Path (Get-Location) $name) ); + return [System.IO.Directory]::Exists((Join-Path (Get-Location) $name)); } function Find-Git() { @@ -38,22 +38,26 @@ function Find-Git() { return $false; } +function No-Git() { + return -not (((Test-Dir ".git") -or (Test-File ".git")) -and (Find-Git)); +} + function gitVersion() { - if (-not ((Test-Dir ".git") -and (Find-Git))) { + if (No-Git) { return 0; } return [Int32]::Parse((git rev-list --count "$baseCommit..HEAD")) + $baseCommitRev; } function gitCommitHash() { - if (-not ((Test-Dir ".git") -and (Find-Git))) { + if (No-Git) { return "0000000000000000000000000000000000000000"; } return (git rev-list "$baseCommit..HEAD") | Select -First 1; } function gitBranch() { - if (-not ((Test-Dir ".git" -or Test-File ".git") -and (Find-Git))) { + if (No-Git) { return "no-branch"; } diff --git a/ILSpy.sln b/ILSpy.sln index 58bdc2b3e..fba43890f 100644 --- a/ILSpy.sln +++ b/ILSpy.sln @@ -33,6 +33,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.Decompiler.PdbP EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ILSpy.Tests", "ILSpy.Tests\ILSpy.Tests.csproj", "{B51C6636-B8D1-4200-9869-08F2689DE6C2}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xceed.Wpf.AvalonDock", "AvalonDock\source\Components\Xceed.Wpf.AvalonDock\Xceed.Wpf.AvalonDock.csproj", "{D87D783A-A8EE-4A36-AAED-3AB21DC98046}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -79,6 +81,10 @@ Global {B51C6636-B8D1-4200-9869-08F2689DE6C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {B51C6636-B8D1-4200-9869-08F2689DE6C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {B51C6636-B8D1-4200-9869-08F2689DE6C2}.Release|Any CPU.Build.0 = Release|Any CPU + {D87D783A-A8EE-4A36-AAED-3AB21DC98046}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D87D783A-A8EE-4A36-AAED-3AB21DC98046}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D87D783A-A8EE-4A36-AAED-3AB21DC98046}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D87D783A-A8EE-4A36-AAED-3AB21DC98046}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ILSpy/AboutPage.cs b/ILSpy/AboutPage.cs index 85d2b794a..7b966da95 100644 --- a/ILSpy/AboutPage.cs +++ b/ILSpy/AboutPage.cs @@ -41,13 +41,10 @@ namespace ICSharpCode.ILSpy [ExportMainMenuCommand(Menu = nameof(Resources._Help), Header = nameof(Resources._About), MenuOrder = 99999)] sealed class AboutPage : SimpleCommand { - [Import] - DecompilerTextView decompilerTextView = null; - public override void Execute(object parameter) { MainWindow.Instance.UnselectAll(); - Display(decompilerTextView); + Display(Docking.DockWorkspace.Instance.GetTextView()); } static readonly Uri UpdateUrl = new Uri("https://ilspy.net/updates.xml"); @@ -57,7 +54,7 @@ namespace ICSharpCode.ILSpy public static void Display(DecompilerTextView textView) { - AvalonEditTextOutput output = new AvalonEditTextOutput() { EnableHyperlinks = true }; + AvalonEditTextOutput output = new AvalonEditTextOutput() { Title = Resources.About, EnableHyperlinks = true }; output.WriteLine(Resources.ILSpyVersion + RevisionClass.FullVersion); if(WindowsVersionHelper.HasPackageIdentity) { output.WriteLine($"Package Name: {WindowsVersionHelper.GetPackageFamilyName()}"); diff --git a/ILSpy/Analyzers/AnalyzeCommand.cs b/ILSpy/Analyzers/AnalyzeCommand.cs index 3d8ae667c..35ae8a987 100644 --- a/ILSpy/Analyzers/AnalyzeCommand.cs +++ b/ILSpy/Analyzers/AnalyzeCommand.cs @@ -58,17 +58,17 @@ namespace ICSharpCode.ILSpy.Analyzers { if (context.SelectedTreeNodes != null) { foreach (IMemberTreeNode node in context.SelectedTreeNodes) { - AnalyzerTreeView.Instance.Analyze(node.Member); + MainWindow.Instance.AnalyzerTreeView.Analyze(node.Member); } } else if (context.Reference != null && context.Reference.Reference is IEntity entity) { - AnalyzerTreeView.Instance.Analyze(entity); + MainWindow.Instance.AnalyzerTreeView.Analyze(entity); } } public override bool CanExecute(object parameter) { - if (AnalyzerTreeView.Instance.IsKeyboardFocusWithin) { - return AnalyzerTreeView.Instance.SelectedItems.OfType().All(n => n is IMemberTreeNode); + if (MainWindow.Instance.AnalyzerTreeView.IsKeyboardFocusWithin) { + return MainWindow.Instance.AnalyzerTreeView.SelectedItems.OfType().All(n => n is IMemberTreeNode); } else { return MainWindow.Instance.SelectedNodes.All(n => n is IMemberTreeNode); } @@ -76,13 +76,13 @@ namespace ICSharpCode.ILSpy.Analyzers public override void Execute(object parameter) { - if (AnalyzerTreeView.Instance.IsKeyboardFocusWithin) { - foreach (IMemberTreeNode node in AnalyzerTreeView.Instance.SelectedItems.OfType().ToArray()) { - AnalyzerTreeView.Instance.Analyze(node.Member); + if (MainWindow.Instance.AnalyzerTreeView.IsKeyboardFocusWithin) { + foreach (IMemberTreeNode node in MainWindow.Instance.AnalyzerTreeView.SelectedItems.OfType().ToArray()) { + MainWindow.Instance.AnalyzerTreeView.Analyze(node.Member); } } else { foreach (IMemberTreeNode node in MainWindow.Instance.SelectedNodes) { - AnalyzerTreeView.Instance.Analyze(node.Member); + MainWindow.Instance.AnalyzerTreeView.Analyze(node.Member); } } } diff --git a/ILSpy/Analyzers/AnalyzerTreeView.cs b/ILSpy/Analyzers/AnalyzerTreeView.cs index 13b29bd3f..2a8bb2fa1 100644 --- a/ILSpy/Analyzers/AnalyzerTreeView.cs +++ b/ILSpy/Analyzers/AnalyzerTreeView.cs @@ -23,6 +23,8 @@ using System.Linq; using System.Windows; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.ILSpy.Analyzers.TreeNodes; +using ICSharpCode.ILSpy.Docking; +using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.TreeView; namespace ICSharpCode.ILSpy.Analyzers @@ -32,21 +34,7 @@ namespace ICSharpCode.ILSpy.Analyzers /// public class AnalyzerTreeView : SharpTreeView, IPane { - static AnalyzerTreeView instance; - - public static AnalyzerTreeView Instance - { - get - { - if (instance == null) { - App.Current.VerifyAccess(); - instance = new AnalyzerTreeView(); - } - return instance; - } - } - - private AnalyzerTreeView() + public AnalyzerTreeView() { this.ShowRoot = false; this.Root = new AnalyzerRootNode { Language = MainWindow.Instance.CurrentLanguage }; @@ -72,8 +60,7 @@ namespace ICSharpCode.ILSpy.Analyzers public void Show() { - if (!IsVisible) - MainWindow.Instance.ShowInNewPane("Analyzer", this, PanePosition.Bottom); + DockWorkspace.Instance.ToolPanes.Add(AnalyzerPaneModel.Instance); } public void Show(AnalyzerTreeNode node) diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs index 1417af930..ec05fb882 100644 --- a/ILSpy/App.xaml.cs +++ b/ILSpy/App.xaml.cs @@ -244,7 +244,7 @@ namespace ICSharpCode.ILSpy } } } - ILSpy.MainWindow.Instance.TextView.ShowText(output); + Docking.DockWorkspace.Instance.ShowText(output); e.Handled = true; } } diff --git a/ILSpy/Commands/DecompileAllCommand.cs b/ILSpy/Commands/DecompileAllCommand.cs index 9cf8cbc11..ac263edf7 100644 --- a/ILSpy/Commands/DecompileAllCommand.cs +++ b/ILSpy/Commands/DecompileAllCommand.cs @@ -38,7 +38,7 @@ namespace ICSharpCode.ILSpy public override void Execute(object parameter) { - MainWindow.Instance.TextView.RunWithCancellation(ct => Task.Factory.StartNew(() => { + Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task.Factory.StartNew(() => { AvalonEditTextOutput output = new AvalonEditTextOutput(); Parallel.ForEach(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, delegate(LoadedAssembly asm) { if (!asm.HasLoadError) { @@ -64,7 +64,7 @@ namespace ICSharpCode.ILSpy } }); return output; - }, ct)).Then(output => MainWindow.Instance.TextView.ShowText(output)).HandleExceptions(); + }, ct)).Then(output => Docking.DockWorkspace.Instance.ShowText(output)).HandleExceptions(); } } @@ -77,7 +77,7 @@ namespace ICSharpCode.ILSpy var language = MainWindow.Instance.CurrentLanguage; var nodes = MainWindow.Instance.SelectedNodes.ToArray(); var options = new DecompilationOptions(); - MainWindow.Instance.TextView.RunWithCancellation(ct => Task.Factory.StartNew(() => { + Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task.Factory.StartNew(() => { options.CancellationToken = ct; Stopwatch w = Stopwatch.StartNew(); for (int i = 0; i < numRuns; ++i) { @@ -90,7 +90,7 @@ namespace ICSharpCode.ILSpy double msPerRun = w.Elapsed.TotalMilliseconds / numRuns; output.Write($"Average time: {msPerRun.ToString("f1")}ms\n"); return output; - }, ct)).Then(output => MainWindow.Instance.TextView.ShowText(output)).HandleExceptions(); + }, ct)).Then(output => Docking.DockWorkspace.Instance.ShowText(output)).HandleExceptions(); } } } diff --git a/ILSpy/Commands/DecompileInNewViewCommand.cs b/ILSpy/Commands/DecompileInNewViewCommand.cs index 3b335743d..94e81d360 100644 --- a/ILSpy/Commands/DecompileInNewViewCommand.cs +++ b/ILSpy/Commands/DecompileInNewViewCommand.cs @@ -16,37 +16,50 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; using System.Linq; +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; namespace ICSharpCode.ILSpy.Commands { - // [ExportContextMenuEntry(Header = nameof(Resources.DecompileToNewPanel), Icon = "images/Search", Category = nameof(Resources.Analyze), Order = 90)] + [ExportContextMenuEntry(Header = nameof(Resources.DecompileToNewPanel), Icon = "images/Search", Category = nameof(Resources.Analyze), Order = 90)] internal sealed class DecompileInNewViewCommand : IContextMenuEntry { public bool IsVisible(TextViewContext context) { - if (context.SelectedTreeNodes == null) - return false; - return true; + return context.SelectedTreeNodes != null || context.Reference?.Reference is IEntity; } public bool IsEnabled(TextViewContext context) { - if (context.SelectedTreeNodes == null) - return false; - return true; + return context.SelectedTreeNodes != null || context.Reference?.Reference is IEntity; } - public async void Execute(TextViewContext context) + public void Execute(TextViewContext context) + { + if (context.SelectedTreeNodes != null) { + var nodes = context.SelectedTreeNodes.Cast().ToArray(); + DecompileNodes(nodes); + } else if (context.Reference?.Reference is IEntity entity) { + var node = MainWindow.Instance.FindTreeNode(entity); + if (node != null) { + DecompileNodes(node); + } + } + } + + private static void DecompileNodes(params ILSpyTreeNode[] nodes) { - var dtv = new DecompilerTextView(); - var nodes = context.SelectedTreeNodes.Cast().ToArray(); var title = string.Join(", ", nodes.Select(x => x.ToString())); - MainWindow.Instance.ShowInNewPane(title, dtv, PanePosition.Document); - await dtv.DecompileAsync(MainWindow.Instance.CurrentLanguage, nodes, new DecompilationOptions()); + DockWorkspace.Instance.Documents.Add(new ViewModels.DecompiledDocumentModel(title, title) { Language = MainWindow.Instance.CurrentLanguage, LanguageVersion = MainWindow.Instance.CurrentLanguageVersion }); + DockWorkspace.Instance.ActiveDocument = DockWorkspace.Instance.Documents.Last(); + MainWindow.Instance.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background, (Action)delegate { + DockWorkspace.Instance.GetTextView().DecompileAsync(MainWindow.Instance.CurrentLanguage, nodes, new DecompilationOptions()); + }); } } } diff --git a/ILSpy/Commands/DisassembleAllCommand.cs b/ILSpy/Commands/DisassembleAllCommand.cs index 74af340a1..23319be78 100644 --- a/ILSpy/Commands/DisassembleAllCommand.cs +++ b/ILSpy/Commands/DisassembleAllCommand.cs @@ -35,7 +35,7 @@ namespace ICSharpCode.ILSpy public override void Execute(object parameter) { - MainWindow.Instance.TextView.RunWithCancellation(ct => Task.Factory.StartNew(() => { + Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task.Factory.StartNew(() => { AvalonEditTextOutput output = new AvalonEditTextOutput(); Parallel.ForEach(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, delegate(LoadedAssembly asm) { if (!asm.HasLoadError) { @@ -61,7 +61,7 @@ namespace ICSharpCode.ILSpy } }); return output; - }, ct)).Then(output => MainWindow.Instance.TextView.ShowText(output)).HandleExceptions(); + }, ct)).Then(output => Docking.DockWorkspace.Instance.ShowText(output)).HandleExceptions(); } } } diff --git a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs index a632ee27a..de254cfd4 100644 --- a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs +++ b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs @@ -64,7 +64,7 @@ namespace ICSharpCode.ILSpy if (dlg.ShowDialog() != true) return; DecompilationOptions options = new DecompilationOptions(); string fileName = dlg.FileName; - MainWindow.Instance.TextView.RunWithCancellation(ct => Task.Factory.StartNew(() => { + Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task.Factory.StartNew(() => { AvalonEditTextOutput output = new AvalonEditTextOutput(); Stopwatch stopwatch = Stopwatch.StartNew(); using (FileStream stream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write)) { @@ -83,7 +83,7 @@ namespace ICSharpCode.ILSpy output.AddButton(null, "Open Explorer", delegate { Process.Start("explorer", "/select,\"" + fileName + "\""); }); output.WriteLine(); return output; - }, ct)).Then(output => MainWindow.Instance.TextView.ShowText(output)).HandleExceptions(); + }, ct)).Then(output => Docking.DockWorkspace.Instance.ShowText(output)).HandleExceptions(); } } diff --git a/ILSpy/Commands/Pdb2XmlCommand.cs b/ILSpy/Commands/Pdb2XmlCommand.cs index 96932a1d9..91b481f7b 100644 --- a/ILSpy/Commands/Pdb2XmlCommand.cs +++ b/ILSpy/Commands/Pdb2XmlCommand.cs @@ -49,7 +49,7 @@ namespace ICSharpCode.ILSpy { var highlighting = HighlightingManager.Instance.GetDefinitionByExtension(".xml"); var options = PdbToXmlOptions.IncludeEmbeddedSources | PdbToXmlOptions.IncludeMethodSpans | PdbToXmlOptions.IncludeTokens; - MainWindow.Instance.TextView.RunWithCancellation(ct => Task.Factory.StartNew(() => { + Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task.Factory.StartNew(() => { AvalonEditTextOutput output = new AvalonEditTextOutput(); var writer = new TextOutputWriter(output); foreach (var node in nodes) { @@ -60,7 +60,7 @@ namespace ICSharpCode.ILSpy PdbToXmlConverter.ToXml(writer, pdbStream, peStream, options); } return output; - }, ct)).Then(output => MainWindow.Instance.TextView.ShowNodes(output, null, highlighting)).HandleExceptions(); + }, ct)).Then(output => Docking.DockWorkspace.Instance.ShowNodes(output, null, highlighting)).HandleExceptions(); } } diff --git a/ILSpy/Commands/SaveCodeContextMenuEntry.cs b/ILSpy/Commands/SaveCodeContextMenuEntry.cs index 3715b588d..e8cf4c4ac 100644 --- a/ILSpy/Commands/SaveCodeContextMenuEntry.cs +++ b/ILSpy/Commands/SaveCodeContextMenuEntry.cs @@ -55,7 +55,7 @@ namespace ICSharpCode.ILSpy.TextView public static void Execute(IReadOnlyList selectedNodes) { var currentLanguage = MainWindow.Instance.CurrentLanguage; - var textView = MainWindow.Instance.TextView; + var textView = Docking.DockWorkspace.Instance.GetTextView(); if (selectedNodes.Count == 1 && selectedNodes[0] is ILSpyTreeNode singleSelection) { // if there's only one treenode selected // we will invoke the custom Save logic diff --git a/ILSpy/Commands/ShowDebugSteps.cs b/ILSpy/Commands/ShowDebugSteps.cs index d7b12f155..e9824b488 100644 --- a/ILSpy/Commands/ShowDebugSteps.cs +++ b/ILSpy/Commands/ShowDebugSteps.cs @@ -1,6 +1,8 @@ #if DEBUG +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Properties; +using ICSharpCode.ILSpy.ViewModels; namespace ICSharpCode.ILSpy.Commands { @@ -9,7 +11,7 @@ namespace ICSharpCode.ILSpy.Commands { public override void Execute(object parameter) { - DebugSteps.Show(); + DockWorkspace.Instance.ToolPanes.Add(DebugStepsPaneModel.Instance); } } } diff --git a/ILSpy/ContextMenuEntry.cs b/ILSpy/ContextMenuEntry.cs index d04155f43..c6325650d 100644 --- a/ILSpy/ContextMenuEntry.cs +++ b/ILSpy/ContextMenuEntry.cs @@ -126,19 +126,22 @@ namespace ICSharpCode.ILSpy /// /// Enables extensible context menu support for the specified tree view. /// - public static void Add(SharpTreeView treeView, DecompilerTextView textView = null) + public static void Add(SharpTreeView treeView) { - var provider = new ContextMenuProvider(treeView, textView); + var provider = new ContextMenuProvider(treeView); treeView.ContextMenuOpening += provider.treeView_ContextMenuOpening; // Context menu is shown only when the ContextMenu property is not null before the // ContextMenuOpening event handler is called. treeView.ContextMenu = new ContextMenu(); - if (textView != null) { - textView.ContextMenuOpening += provider.textView_ContextMenuOpening; - // Context menu is shown only when the ContextMenu property is not null before the - // ContextMenuOpening event handler is called. - textView.ContextMenu = new ContextMenu(); - } + } + + public static void Add(DecompilerTextView textView) + { + var provider = new ContextMenuProvider(textView); + textView.ContextMenuOpening += provider.textView_ContextMenuOpening; + // Context menu is shown only when the ContextMenu property is not null before the + // ContextMenuOpening event handler is called. + textView.ContextMenu = new ContextMenu(); } public static void Add(ListBox listBox) @@ -157,16 +160,23 @@ namespace ICSharpCode.ILSpy { entries = App.ExportProvider.GetExports().ToArray(); } + + ContextMenuProvider(DecompilerTextView textView) + : this() + { + this.textView = textView ?? throw new ArgumentNullException(nameof(textView)); + } - ContextMenuProvider(SharpTreeView treeView, DecompilerTextView textView = null) : this() + ContextMenuProvider(SharpTreeView treeView) + : this() { - this.treeView = treeView; - this.textView = textView; + this.treeView = treeView ?? throw new ArgumentNullException(nameof(treeView)); } - ContextMenuProvider(ListBox listBox) : this() + ContextMenuProvider(ListBox listBox) + : this() { - this.listBox = listBox; + this.listBox = listBox ?? throw new ArgumentNullException(nameof(listBox)); } void treeView_ContextMenuOpening(object sender, ContextMenuEventArgs e) diff --git a/ILSpy/DebugSteps.xaml.cs b/ILSpy/DebugSteps.xaml.cs index ba13f1d84..71c4f448e 100644 --- a/ILSpy/DebugSteps.xaml.cs +++ b/ILSpy/DebugSteps.xaml.cs @@ -4,6 +4,8 @@ using System.Windows.Controls; using System.Windows.Input; using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.IL.Transforms; +using ICSharpCode.ILSpy.Docking; +using ICSharpCode.ILSpy.ViewModels; namespace ICSharpCode.ILSpy { @@ -20,7 +22,7 @@ namespace ICSharpCode.ILSpy ILAstLanguage language; #endif - DebugSteps() + public DebugSteps() { InitializeComponent(); @@ -79,7 +81,7 @@ namespace ICSharpCode.ILSpy public static void Show() { - MainWindow.Instance.ShowInNewPane(Properties.Resources.DebugSteps, new DebugSteps(), PanePosition.Top); + DockWorkspace.Instance.ToolPanes.Add(DebugStepsPaneModel.Instance); } void IPane.Closed() @@ -121,8 +123,8 @@ namespace ICSharpCode.ILSpy { lastSelectedStep = step; var window = MainWindow.Instance; - var state = window.TextView.GetState(); - window.TextView.DecompileAsync(window.CurrentLanguage, window.SelectedNodes, + var state = DockWorkspace.Instance.GetState(); + DockWorkspace.Instance.GetTextView().DecompileAsync(window.CurrentLanguage, window.SelectedNodes, new DecompilationOptions(window.CurrentLanguageVersion) { StepLimit = step, IsDebug = isDebug, diff --git a/ILSpy/Docking/ActiveDocumentConverter.cs b/ILSpy/Docking/ActiveDocumentConverter.cs new file mode 100644 index 000000000..072f5e39a --- /dev/null +++ b/ILSpy/Docking/ActiveDocumentConverter.cs @@ -0,0 +1,43 @@ +// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Windows.Data; +using ICSharpCode.ILSpy.ViewModels; + +namespace ICSharpCode.ILSpy.Docking +{ + public class ActiveDocumentConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is DocumentModel) + return value; + + return Binding.DoNothing; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is DocumentModel) + return value; + + return Binding.DoNothing; + } + } +} diff --git a/ILSpy/Docking/DockLayoutSettings.cs b/ILSpy/Docking/DockLayoutSettings.cs index 737910f6f..23588db97 100644 --- a/ILSpy/Docking/DockLayoutSettings.cs +++ b/ILSpy/Docking/DockLayoutSettings.cs @@ -1,4 +1,22 @@ -using System; +// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; using System.IO; using System.Linq; using System.Xml; @@ -32,7 +50,7 @@ namespace ICSharpCode.ILSpy.Docking public void Deserialize(XmlLayoutSerializer serializer) { if (!Valid) - return; + rawSettings = ""; using (StringReader reader = new StringReader(rawSettings)) { serializer.Deserialize(reader); diff --git a/ILSpy/Docking/DockWorkspace.cs b/ILSpy/Docking/DockWorkspace.cs new file mode 100644 index 000000000..aec46085a --- /dev/null +++ b/ILSpy/Docking/DockWorkspace.cs @@ -0,0 +1,116 @@ +// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using ICSharpCode.AvalonEdit.Highlighting; +using ICSharpCode.ILSpy.TextView; +using ICSharpCode.ILSpy.ViewModels; + +namespace ICSharpCode.ILSpy.Docking +{ + public class DockWorkspace : INotifyPropertyChanged + { + private SessionSettings sessionSettings; + + public event PropertyChangedEventHandler PropertyChanged; + + public static DockWorkspace Instance { get; } = new DockWorkspace(); + + private DockWorkspace() + { + } + + public PaneCollection Documents { get; } = new PaneCollection(); + + public PaneCollection ToolPanes { get; } = new PaneCollection(); + + public void Remove(PaneModel model) + { + Documents.Remove(model as DocumentModel); + ToolPanes.Remove(model as ToolPaneModel); + } + + private DocumentModel _activeDocument = null; + public DocumentModel ActiveDocument { + get { + return _activeDocument; + } + set { + if (_activeDocument != value) { + _activeDocument = value; + if (value is DecompiledDocumentModel ddm) { + this.sessionSettings.FilterSettings.Language = ddm.Language; + this.sessionSettings.FilterSettings.LanguageVersion = ddm.LanguageVersion; + } + RaisePropertyChanged(nameof(ActiveDocument)); + } + } + } + + protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public void ShowText(AvalonEditTextOutput textOutput) + { + GetTextView().ShowText(textOutput); + } + + public DecompilerTextView GetTextView() + { + return ((DecompiledDocumentModel)ActiveDocument).TextView; + } + + public DecompilerTextViewState GetState() + { + return GetTextView().GetState(); + } + + public Task RunWithCancellation(Func> taskCreation) + { + return GetTextView().RunWithCancellation(taskCreation); + } + + internal void ShowNodes(AvalonEditTextOutput output, TreeNodes.ILSpyTreeNode[] nodes, IHighlightingDefinition highlighting) + { + GetTextView().ShowNodes(output, nodes, highlighting); + } + + internal void LoadSettings(SessionSettings sessionSettings) + { + this.sessionSettings = sessionSettings; + sessionSettings.FilterSettings.PropertyChanged += FilterSettings_PropertyChanged; + } + + private void FilterSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (ActiveDocument is DecompiledDocumentModel ddm) { + if (e.PropertyName == "Language" || e.PropertyName == "LanguageVersion") { + ddm.Language = sessionSettings.FilterSettings.Language; + ddm.LanguageVersion = sessionSettings.FilterSettings.LanguageVersion; + } + } + } + } +} diff --git a/ILSpy/Docking/DockingHelper.cs b/ILSpy/Docking/DockingHelper.cs deleted file mode 100644 index 157837a6c..000000000 --- a/ILSpy/Docking/DockingHelper.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Windows; -using System.Windows.Controls; -using Xceed.Wpf.AvalonDock.Layout; - -namespace ICSharpCode.ILSpy.Docking -{ - public static class DockingHelper - { - public static void DockHorizontal(LayoutContent layoutContent, ILayoutElement paneRelativeTo, GridLength dockHeight, bool dockBefore = false) - { - if (paneRelativeTo is ILayoutDocumentPane parentDocumentPane) { - var parentDocumentGroup = paneRelativeTo.FindParent(); - if (parentDocumentGroup == null) { - var grandParent = parentDocumentPane.Parent as ILayoutContainer; - parentDocumentGroup = new LayoutDocumentPaneGroup() { Orientation = System.Windows.Controls.Orientation.Vertical }; - grandParent.ReplaceChild(paneRelativeTo, parentDocumentGroup); - parentDocumentGroup.Children.Add(parentDocumentPane); - } - parentDocumentGroup.Orientation = System.Windows.Controls.Orientation.Vertical; - int indexOfParentPane = parentDocumentGroup.IndexOfChild(parentDocumentPane); - var layoutDocumentPane = new LayoutDocumentPane(layoutContent) { DockHeight = dockHeight }; - parentDocumentGroup.InsertChildAt(dockBefore ? indexOfParentPane : indexOfParentPane + 1, layoutDocumentPane); - layoutContent.IsActive = true; - layoutContent.Root.CollectGarbage(); - Application.Current.MainWindow.Dispatcher.Invoke(() => { - - layoutDocumentPane.DockHeight = dockHeight; - }, System.Windows.Threading.DispatcherPriority.Loaded); - } - } - } -} diff --git a/ILSpy/Docking/LayoutUpdateStrategy.cs b/ILSpy/Docking/LayoutUpdateStrategy.cs new file mode 100644 index 000000000..478a92c09 --- /dev/null +++ b/ILSpy/Docking/LayoutUpdateStrategy.cs @@ -0,0 +1,143 @@ +// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using ICSharpCode.ILSpy.ViewModels; +using Xceed.Wpf.AvalonDock.Layout; + +namespace ICSharpCode.ILSpy.Docking +{ + public class LayoutUpdateStrategy : ILayoutUpdateStrategy + { + public bool BeforeInsertAnchorable(LayoutRoot layout, LayoutAnchorable anchorableToShow, ILayoutContainer destinationContainer) + { + if (destinationContainer?.FindParent() != null) + return false; + + PanePosition targetPosition = anchorableToShow.Content is PaneModel model ? model.DefaultPosition : PanePosition.Document; + + switch (targetPosition) { + case PanePosition.Top: + case PanePosition.Bottom: + case PanePosition.Left: + case PanePosition.Right: + var pane = GetOrCreatePane(layout, targetPosition.ToString()); + if (pane == null) + return false; + anchorableToShow.CanDockAsTabbedDocument = false; + pane.Children.Add(anchorableToShow); + return true; + case PanePosition.Document: + var documentPane = GetOrCreateDocumentPane(layout); + if (documentPane == null) + return false; + documentPane.Children.Add(anchorableToShow); + return true; + default: + throw new NotSupportedException($"Enum value {targetPosition} is not supported"); + } + } + + private LayoutAnchorablePane GetOrCreatePane(LayoutRoot layout, string name) + { + var pane = layout.Descendents().OfType().FirstOrDefault(p => p.Name == name + "Pane"); + if (pane != null) + return pane; + var layoutPanel = layout.Children.OfType().FirstOrDefault(); + if (layoutPanel == null) { + layout.RootPanel = new LayoutPanel() { Orientation = Orientation.Horizontal }; + } + if (layoutPanel.Orientation != Orientation.Horizontal) { + layoutPanel.Orientation = Orientation.Horizontal; + } + LayoutAnchorablePane result = null; + switch (name) { + case "Top": + case "Bottom": + var centerLayoutPanel = layoutPanel.Children.OfType().FirstOrDefault(); + if (centerLayoutPanel == null) { + layoutPanel.Children.Insert(0, centerLayoutPanel = new LayoutPanel() { Orientation = Orientation.Vertical }); + } + if (centerLayoutPanel.Orientation != Orientation.Vertical) { + centerLayoutPanel.Orientation = Orientation.Vertical; + } + if (name == "Top") + centerLayoutPanel.Children.Insert(0, result = new LayoutAnchorablePane { Name = name + "Pane", DockMinHeight = 250 }); + else + centerLayoutPanel.Children.Add(result = new LayoutAnchorablePane { Name = name + "Pane", DockMinHeight = 250 }); + return result; + case "Left": + case "Right": + if (name == "Left") + layoutPanel.Children.Insert(0, result = new LayoutAnchorablePane { Name = name + "Pane", DockMinWidth = 250 }); + else + layoutPanel.Children.Add(result = new LayoutAnchorablePane { Name = name + "Pane", DockMinWidth = 250 }); + return result; + default: + throw new NotImplementedException(); + } + } + + public void AfterInsertAnchorable(LayoutRoot layout, LayoutAnchorable anchorableShown) + { + } + + public bool BeforeInsertDocument(LayoutRoot layout, LayoutDocument anchorableToShow, ILayoutContainer destinationContainer) + { + if (destinationContainer?.FindParent() != null) + return false; + + var documentPane = GetOrCreateDocumentPane(layout); + if (documentPane == null) + return false; + documentPane.Children.Add(anchorableToShow); + return true; + } + + private LayoutDocumentPane GetOrCreateDocumentPane(LayoutRoot layout) + { + var pane = layout.Descendents().OfType().FirstOrDefault(); + if (pane != null) + return pane; + var layoutPanel = layout.Children.OfType().FirstOrDefault(); + if (layoutPanel == null) { + layout.RootPanel = new LayoutPanel() { Orientation = Orientation.Horizontal }; + } + if (layoutPanel.Orientation != Orientation.Horizontal) { + layoutPanel.Orientation = Orientation.Horizontal; + } + var centerLayoutPanel = layoutPanel.Children.OfType().FirstOrDefault(); + if (centerLayoutPanel == null) { + layoutPanel.Children.Insert(0, centerLayoutPanel = new LayoutPanel() { Orientation = Orientation.Vertical }); + } + if (centerLayoutPanel.Orientation != Orientation.Vertical) { + centerLayoutPanel.Orientation = Orientation.Vertical; + } + LayoutDocumentPane result; + centerLayoutPanel.Children.Add(result = new LayoutDocumentPane()); + return result; + } + + public void AfterInsertDocument(LayoutRoot layout, LayoutDocument anchorableShown) + { + } + } +} diff --git a/ILSpy/Docking/PaneCollection.cs b/ILSpy/Docking/PaneCollection.cs new file mode 100644 index 000000000..945740791 --- /dev/null +++ b/ILSpy/Docking/PaneCollection.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using ICSharpCode.ILSpy.ViewModels; + +namespace ICSharpCode.ILSpy.Docking +{ + public class PaneCollection : INotifyCollectionChanged, INotifyPropertyChanged, ICollection + where T : PaneModel + { + private ObservableCollection observableCollection = new ObservableCollection(); + + public event NotifyCollectionChangedEventHandler CollectionChanged; + public event PropertyChangedEventHandler PropertyChanged; + + public PaneCollection() + { + observableCollection.CollectionChanged += (sender, e) => CollectionChanged?.Invoke(this, e); + } + + public void Add(T item) + { + if (!this.Any(pane => pane.ContentId == item.ContentId)) { + observableCollection.Add(item); + } + + item.IsVisible = true; + item.IsActive = true; + } + + public int Count => observableCollection.Count(); + public bool IsReadOnly => false; + public void Clear() => observableCollection.Clear(); + public bool Contains(T item) => observableCollection.Contains(item); + public void CopyTo(T[] array, int arrayIndex) => observableCollection.CopyTo(array, arrayIndex); + public bool Remove(T item) => observableCollection.Remove(item); + public IEnumerator GetEnumerator() => observableCollection.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => observableCollection.GetEnumerator(); + } +} diff --git a/ILSpy/Docking/PanePosition.cs b/ILSpy/Docking/PanePosition.cs index d162d5d7b..7ebb1b72a 100644 --- a/ILSpy/Docking/PanePosition.cs +++ b/ILSpy/Docking/PanePosition.cs @@ -1,9 +1,29 @@ -namespace ICSharpCode.ILSpy +// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace ICSharpCode.ILSpy { public enum PanePosition { Top, Bottom, + Left, + Right, Document } } diff --git a/ILSpy/Docking/PaneStyleSelector.cs b/ILSpy/Docking/PaneStyleSelector.cs new file mode 100644 index 000000000..0d92b559a --- /dev/null +++ b/ILSpy/Docking/PaneStyleSelector.cs @@ -0,0 +1,41 @@ +using System.Windows; +using System.Windows.Controls; +using ICSharpCode.ILSpy.ViewModels; +// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace ICSharpCode.ILSpy.Docking +{ + public class PaneStyleSelector : StyleSelector + { + public Style ToolPaneStyle { get; set; } + + public Style DocumentStyle { get; set; } + + public override Style SelectStyle(object item, DependencyObject container) + { + if (item is DocumentModel) + return DocumentStyle; + + if (item is ToolPaneModel) + return ToolPaneStyle; + + return base.SelectStyle(item, container); + } + } +} diff --git a/ILSpy/Docking/PaneTemplateSelector.cs b/ILSpy/Docking/PaneTemplateSelector.cs new file mode 100644 index 000000000..06b299b8e --- /dev/null +++ b/ILSpy/Docking/PaneTemplateSelector.cs @@ -0,0 +1,43 @@ +// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Windows; +using System.Windows.Controls; + +namespace ICSharpCode.ILSpy.Docking +{ + public class TemplateMapping + { + public Type Type { get; set; } + public DataTemplate Template { get; set; } + } + + public class PaneTemplateSelector : DataTemplateSelector + { + public Collection Mappings { get; set; } = new Collection(); + + public override DataTemplate SelectTemplate(object item, DependencyObject container) + { + return Mappings.FirstOrDefault(m => m.Type == item.GetType())?.Template + ?? base.SelectTemplate(item, container); + } + } +} diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 24bda5088..54176b95d 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -50,7 +50,6 @@ - @@ -60,6 +59,7 @@ + @@ -139,8 +139,19 @@ DebugSteps.xaml - + + + + + + + + + + + + @@ -253,6 +264,7 @@ + LGPL.txt diff --git a/ILSpy/ISmartTextOutput.cs b/ILSpy/ISmartTextOutput.cs index d429a7ae5..62098956e 100644 --- a/ILSpy/ISmartTextOutput.cs +++ b/ILSpy/ISmartTextOutput.cs @@ -38,6 +38,11 @@ namespace ICSharpCode.ILSpy void BeginSpan(HighlightingColor highlightingColor); void EndSpan(); + + /// + /// Gets/sets the title displayed in the document tab's header. + /// + string Title { get; set; } } public static class SmartTextOutputExtensions diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index e34781457..9b1aee398 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -6,7 +6,12 @@ xmlns:local="clr-namespace:ICSharpCode.ILSpy" xmlns:avalondock="http://schemas.xceed.com/wpf/xaml/avalondock" xmlns:controls="clr-namespace:ICSharpCode.ILSpy.Controls" - xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties" + xmlns:avalondockproperties="clr-namespace:Xceed.Wpf.AvalonDock.Properties;assembly=Xceed.Wpf.AvalonDock" + xmlns:docking="clr-namespace:ICSharpCode.ILSpy.Docking" + xmlns:textview="clr-namespace:ICSharpCode.ILSpy.TextView" + xmlns:analyzers="clr-namespace:ICSharpCode.ILSpy.Analyzers" + xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties" + xmlns:viewmodels="clr-namespace:ICSharpCode.ILSpy.ViewModels" Title="ILSpy" MinWidth="250" MinHeight="200" @@ -18,7 +23,46 @@ > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + @@ -79,23 +123,23 @@ - + - + - + + SelectedItem="{Binding SessionSettings.FilterSettings.Language}"/> + SelectedItem="{Binding SessionSettings.FilterSettings.LanguageVersion, UpdateSourceTrigger=PropertyChanged}"/> @@ -116,41 +160,79 @@ Text="{x:Static properties:Resources.StandBy}"/> - - - - - - - - - - - - - - - - - - - - - - + DataContext="{Binding Workspace}" + AnchorablesSource="{Binding ToolPanes}" + DocumentsSource="{Binding Documents}" + ActiveContent="{Binding ActiveDocument, Mode=TwoWay, Converter={StaticResource ActiveDocumentConverter}}" + AllowMixedOrientation="True"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 5a06a9946..64f787a5a 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -38,9 +38,12 @@ using ICSharpCode.Decompiler.Documentation; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem.Implementation; +using ICSharpCode.ILSpy.Analyzers; using ICSharpCode.ILSpy.Controls; +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.TreeView; using Microsoft.Win32; using OSVersionHelper; @@ -49,6 +52,12 @@ using Xceed.Wpf.AvalonDock.Layout.Serialization; namespace ICSharpCode.ILSpy { + class MainWindowDataContext + { + public DockWorkspace Workspace { get; set; } + public SessionSettings SessionSettings { get; set; } + } + /// /// The main window of the application. /// @@ -64,8 +73,6 @@ namespace ICSharpCode.ILSpy AssemblyList assemblyList; AssemblyListTreeNode assemblyListTreeNode; - readonly DecompilerTextView decompilerTextView; - static MainWindow instance; public static MainWindow Instance { @@ -76,6 +83,30 @@ namespace ICSharpCode.ILSpy get { return sessionSettings; } } + public ContentPresenter mainPane { + get { + return FindResource("MainPane") as ContentPresenter; + } + } + + public SharpTreeView treeView { + get { + return FindResource("TreeView") as SharpTreeView; + } + } + + public AnalyzerTreeView AnalyzerTreeView { + get { + return FindResource("AnalyzerTreeView") as AnalyzerTreeView; + } + } + + public SearchPane SearchPane { + get { + return FindResource("SearchPane") as SearchPane; + } + } + public MainWindow() { instance = this; @@ -86,11 +117,14 @@ namespace ICSharpCode.ILSpy this.Icon = new BitmapImage(new Uri("pack://application:,,,/ILSpy;component/images/ILSpy.ico")); - this.DataContext = sessionSettings; + this.DataContext = new MainWindowDataContext { + Workspace = DockWorkspace.Instance, + SessionSettings = sessionSettings + }; + + DockWorkspace.Instance.LoadSettings(sessionSettings); InitializeComponent(); - decompilerTextView = App.ExportProvider.GetExportedValue(); - mainPane.Content = decompilerTextView; sessionSettings.DockLayout.Deserialize(new XmlLayoutSerializer(DockManager)); @@ -98,7 +132,7 @@ namespace ICSharpCode.ILSpy InitMainMenu(); InitToolbar(); - ContextMenuProvider.Add(treeView, decompilerTextView); + ContextMenuProvider.Add(treeView); this.Loaded += MainWindow_Loaded; } @@ -295,8 +329,8 @@ namespace ICSharpCode.ILSpy commandLineLoadedAssemblies.Clear(); // clear references once we don't need them anymore NavigateOnLaunch(args.NavigateTo, sessionSettings.ActiveTreeViewPath, spySettings, relevantAssemblies); if (args.Search != null) { - SearchPane.Instance.SearchTerm = args.Search; - SearchPane.Instance.Show(); + SearchPane.SearchTerm = args.Search; + SearchPane.Show(); } } @@ -339,7 +373,7 @@ namespace ICSharpCode.ILSpy if (!found && treeView.SelectedItem == initialSelection) { AvalonEditTextOutput output = new AvalonEditTextOutput(); output.Write(string.Format("Cannot find '{0}' in command line specified assemblies.", navigateTo)); - decompilerTextView.ShowText(output); + DockWorkspace.Instance.ShowText(output); } } else if (relevantAssemblies.Count == 1) { // NavigateTo == null and an assembly was given on the command-line: @@ -367,7 +401,7 @@ namespace ICSharpCode.ILSpy // only if not showing the about page, perform the update check: await ShowMessageIfUpdatesAvailableAsync(spySettings); } else { - AboutPage.Display(decompilerTextView); + AboutPage.Display(DockWorkspace.Instance.GetTextView()); } } } @@ -428,6 +462,10 @@ namespace ICSharpCode.ILSpy void MainWindow_Loaded(object sender, RoutedEventArgs e) { + DockWorkspace.Instance.ToolPanes.Add(AssemblyListPaneModel.Instance); + DockWorkspace.Instance.Documents.Add(new DecompiledDocumentModel() { IsCloseable = false, Language = CurrentLanguage, LanguageVersion = CurrentLanguageVersion }); + DockWorkspace.Instance.ActiveDocument = DockWorkspace.Instance.Documents.First(); + ILSpySettings spySettings = this.spySettingsForMainWindow_Loaded; this.spySettingsForMainWindow_Loaded = null; var loadPreviousAssemblies = Options.MiscSettingsPanel.CurrentMiscSettings.LoadPreviousAssemblies; @@ -464,7 +502,7 @@ namespace ICSharpCode.ILSpy AvalonEditTextOutput output = new AvalonEditTextOutput(); if (FormatExceptions(App.StartupExceptions.ToArray(), output)) - decompilerTextView.ShowText(output); + DockWorkspace.Instance.ShowText(output); } bool FormatExceptions(App.ExceptionData[] exceptions, ITextOutput output) @@ -882,7 +920,7 @@ namespace ICSharpCode.ILSpy void SearchCommandExecuted(object sender, ExecutedRoutedEventArgs e) { - SearchPane.Instance.Show(); + DockWorkspace.Instance.ToolPanes.Add(SearchPaneModel.Instance); } #endregion @@ -891,8 +929,7 @@ namespace ICSharpCode.ILSpy { DecompileSelectedNodes(); - if (SelectionChanged != null) - SelectionChanged(sender, e); + SelectionChanged?.Invoke(sender, e); } Task decompilationTask; @@ -907,7 +944,7 @@ namespace ICSharpCode.ILSpy return; if (recordHistory) { - var dtState = decompilerTextView.GetState(); + var dtState = DockWorkspace.Instance.GetState(); if (dtState != null) history.UpdateCurrent(new NavigationState(dtState)); history.Record(new NavigationState(treeView.SelectedItems.OfType())); @@ -915,10 +952,10 @@ namespace ICSharpCode.ILSpy if (treeView.SelectedItems.Count == 1) { ILSpyTreeNode node = treeView.SelectedItem as ILSpyTreeNode; - if (node != null && node.View(decompilerTextView)) + if (node != null && node.View(DockWorkspace.Instance.GetTextView())) return; } - decompilationTask = decompilerTextView.DecompileAsync(this.CurrentLanguage, this.SelectedNodes, new DecompilationOptions() { TextViewState = state }); + decompilationTask = DockWorkspace.Instance.GetTextView().DecompileAsync(this.CurrentLanguage, this.SelectedNodes, new DecompilationOptions() { TextViewState = state }); } void SaveCommandCanExecute(object sender, CanExecuteRoutedEventArgs e) @@ -942,10 +979,6 @@ namespace ICSharpCode.ILSpy } } - public DecompilerTextView TextView { - get { return decompilerTextView; } - } - public Language CurrentLanguage => sessionSettings.FilterSettings.Language; public LanguageVersion CurrentLanguageVersion => sessionSettings.FilterSettings.LanguageVersion; @@ -989,7 +1022,7 @@ namespace ICSharpCode.ILSpy void NavigateHistory(bool forward) { - var dtState = decompilerTextView.GetState(); + var dtState = DockWorkspace.Instance.GetState(); if (dtState != null) history.UpdateCurrent(new NavigationState(dtState)); var newState = forward ? history.GoForward() : history.GoBack(); @@ -1042,31 +1075,6 @@ namespace ICSharpCode.ILSpy return loadedAssy.FileName; } - #region Top/Bottom Pane management - - public void ShowInNewPane(string title, object content, PanePosition panePosition, string toolTip = null) - { - // Hack to avoid opening the same pane multiple times - var existingPane = DockManager.Layout.Descendents().OfType().FirstOrDefault( - a => (a.Title == title) && (a.Content != null)); - if (existingPane != null) { - existingPane.IsActive = true; - return; - } - - if (panePosition == PanePosition.Document) { - var layoutDocument = new LayoutDocument() { Title = title, Content = content, ToolTip = toolTip, CanClose = true }; - var documentPane = this.DockManager.Layout.Descendents().OfType().FirstOrDefault(); - documentPane.Children.Add(layoutDocument); - } else { - var layoutAnchorable = new LayoutAnchorable() { Title = title, Content = content, ToolTip = toolTip, CanClose = true, CanHide = true }; - var documentPane = this.DockManager.Layout.Descendents().OfType().FirstOrDefault(); - Docking.DockingHelper.DockHorizontal(layoutAnchorable, documentPane, new GridLength(200), panePosition == PanePosition.Top); - } - } - - #endregion - public void UnselectAll() { treeView.UnselectAll(); diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index dd8e2a829..8b6d01b8a 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -267,6 +267,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to About. + /// + public static string About { + get { + return ResourceManager.GetString("About", resourceCulture); + } + } + /// /// Looks up a localized string similar to |All Files|*.*. /// @@ -303,6 +312,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Assemblies. + /// + public static string Assemblies { + get { + return ResourceManager.GetString("Assemblies", resourceCulture); + } + } + /// /// Looks up a localized string similar to Assembly. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index 7aa7db1df..92bb623ca 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -775,4 +775,10 @@ Are you sure you want to continue? Detect awaited using and foreach statements + + About + + + Assemblies + \ No newline at end of file diff --git a/ILSpy/Properties/Resources.zh-Hans.resx b/ILSpy/Properties/Resources.zh-Hans.resx index 4f04dba0b..0540d4faf 100644 --- a/ILSpy/Properties/Resources.zh-Hans.resx +++ b/ILSpy/Properties/Resources.zh-Hans.resx @@ -729,4 +729,7 @@ 搜索MSDN... + + 关于 + \ No newline at end of file diff --git a/ILSpy/Search/SearchPane.cs b/ILSpy/Search/SearchPane.cs index 2201273eb..91fad1788 100644 --- a/ILSpy/Search/SearchPane.cs +++ b/ILSpy/Search/SearchPane.cs @@ -31,7 +31,9 @@ using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Search; +using ICSharpCode.ILSpy.ViewModels; namespace ICSharpCode.ILSpy { @@ -42,7 +44,6 @@ namespace ICSharpCode.ILSpy { const int MAX_RESULTS = 1000; const int MAX_REFRESH_TIME_MS = 10; // More means quicker forward of data, less means better responsibility - static SearchPane instance; RunningSearch currentSearch; bool runSearchOnNextShow; @@ -53,17 +54,7 @@ namespace ICSharpCode.ILSpy get { return (ObservableCollection)GetValue(ResultsProperty); } } - public static SearchPane Instance { - get { - if (instance == null) { - App.Current.VerifyAccess(); - instance = new SearchPane(); - } - return instance; - } - } - - private SearchPane() + public SearchPane() { InitializeComponent(); searchModeComboBox.Items.Add(new { Image = Images.Library, Name = "Types and Members" }); @@ -114,7 +105,7 @@ namespace ICSharpCode.ILSpy public void Show() { if (!IsVisible) { - MainWindow.Instance.ShowInNewPane(Properties.Resources.SearchPane_Search, this, PanePosition.Top); + DockWorkspace.Instance.ToolPanes.Add(SearchPaneModel.Instance); if (runSearchOnNextShow) { runSearchOnNextShow = false; StartSearch(this.SearchTerm); diff --git a/ILSpy/TaskHelper.cs b/ILSpy/TaskHelper.cs index 8137ec152..bb76ffdbc 100644 --- a/ILSpy/TaskHelper.cs +++ b/ILSpy/TaskHelper.cs @@ -196,7 +196,7 @@ namespace ICSharpCode.ILSpy task.Catch(exception => MainWindow.Instance.Dispatcher.BeginInvoke(new Action(delegate { AvalonEditTextOutput output = new AvalonEditTextOutput(); output.Write(exception.ToString()); - MainWindow.Instance.TextView.ShowText(output); + Docking.DockWorkspace.Instance.ShowText(output); }))).IgnoreExceptions(); } } diff --git a/ILSpy/TextView/AvalonEditTextOutput.cs b/ILSpy/TextView/AvalonEditTextOutput.cs index 6c03a8b2d..79f6698c8 100644 --- a/ILSpy/TextView/AvalonEditTextOutput.cs +++ b/ILSpy/TextView/AvalonEditTextOutput.cs @@ -81,6 +81,8 @@ namespace ICSharpCode.ILSpy.TextView bool needsIndent; public string IndentationString { get; set; } = "\t"; + + public string Title { get; set; } internal readonly List elementGenerators = new List(); diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 52917540e..888fd865d 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -53,6 +53,7 @@ using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.ILSpy.AvalonEdit; using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.ILSpy.ViewModels; using Microsoft.Win32; namespace ICSharpCode.ILSpy.TextView @@ -61,7 +62,6 @@ namespace ICSharpCode.ILSpy.TextView /// Manages the TextEditor showing the decompiled code. /// Contains all the threading logic that makes the decompiler work in the background. /// - [Export, PartCreationPolicy(CreationPolicy.Shared)] public sealed partial class DecompilerTextView : UserControl, IDisposable { readonly ReferenceElementGenerator referenceElementGenerator; @@ -103,7 +103,7 @@ namespace ICSharpCode.ILSpy.TextView }); InitializeComponent(); - + this.referenceElementGenerator = new ReferenceElementGenerator(this.JumpToReference, this.IsLink); textEditor.TextArea.TextView.ElementGenerators.Add(referenceElementGenerator); this.uiElementGenerator = new UIElementGenerator(); @@ -140,6 +140,8 @@ namespace ICSharpCode.ILSpy.TextView // add marker service & margin textEditor.TextArea.TextView.BackgroundRenderers.Add(textMarkerService); textEditor.TextArea.TextView.LineTransformers.Add(textMarkerService); + + ContextMenuProvider.Add(this); } void RemoveEditCommand(RoutedUICommand command) @@ -627,6 +629,10 @@ namespace ICSharpCode.ILSpy.TextView foldingStrategy.UpdateFoldings(foldingManager, textEditor.Document); Debug.WriteLine(" Updating folding: {0}", w.Elapsed); w.Restart(); } + + if (this.DataContext is PaneModel model) { + model.Title = textOutput.Title; + } } #endregion @@ -749,6 +755,9 @@ namespace ICSharpCode.ILSpy.TextView void DecompileNodes(DecompilationContext context, ITextOutput textOutput) { var nodes = context.TreeNodes; + if (textOutput is ISmartTextOutput smartTextOutput) { + smartTextOutput.Title = string.Join(", ", nodes.Select(n => n.ToString())); + } for (int i = 0; i < nodes.Length; i++) { if (i > 0) textOutput.WriteLine(); @@ -1032,6 +1041,14 @@ namespace ICSharpCode.ILSpy.TextView } } #endregion + + private void self_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) + { + if (e.OldValue is DecompiledDocumentModel oldModel) + oldModel.TextView = null; + if (e.NewValue is DecompiledDocumentModel newModel) + newModel.TextView = this; + } } public class DecompilerTextViewState diff --git a/ILSpy/TextView/DecompilerTextView.xaml b/ILSpy/TextView/DecompilerTextView.xaml index eed20cda4..fbeaf98d3 100644 --- a/ILSpy/TextView/DecompilerTextView.xaml +++ b/ILSpy/TextView/DecompilerTextView.xaml @@ -1,8 +1,9 @@  + xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties" + xmlns:ae="clr-namespace:ICSharpCode.AvalonEdit;assembly=ICSharpCode.AvalonEdit" + DataContextChanged="self_DataContextChanged"> diff --git a/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs b/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs index ff57aa944..c13118e1e 100644 --- a/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs +++ b/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs @@ -65,7 +65,7 @@ namespace ICSharpCode.ILSpy.TreeNodes ISmartTextOutput smartOutput = output as ISmartTextOutput; if (smartOutput != null) { - smartOutput.AddButton(Images.Save, Resources.Save, delegate { Save(MainWindow.Instance.TextView); }); + smartOutput.AddButton(Images.Save, Resources.Save, delegate { Save(Docking.DockWorkspace.Instance.GetTextView()); }); output.WriteLine(); } } diff --git a/ILSpy/ViewModels/AnalyzerPaneModel.cs b/ILSpy/ViewModels/AnalyzerPaneModel.cs new file mode 100644 index 000000000..bf7497c06 --- /dev/null +++ b/ILSpy/ViewModels/AnalyzerPaneModel.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace ICSharpCode.ILSpy.ViewModels +{ + public class AnalyzerPaneModel : ToolPaneModel + { + public const string PaneContentId = "analyzerPane"; + + public static AnalyzerPaneModel Instance { get; } = new AnalyzerPaneModel(); + + public override PanePosition DefaultPosition => PanePosition.Bottom; + + private AnalyzerPaneModel() + { + ContentId = PaneContentId; + Title = Properties.Resources.Analyze; + } + } +} diff --git a/ILSpy/ViewModels/AssemblyListPaneModel.cs b/ILSpy/ViewModels/AssemblyListPaneModel.cs new file mode 100644 index 000000000..117b74796 --- /dev/null +++ b/ILSpy/ViewModels/AssemblyListPaneModel.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using ICSharpCode.ILSpy.Properties; + +namespace ICSharpCode.ILSpy.ViewModels +{ + public class AssemblyListPaneModel : ToolPaneModel + { + public const string PaneContentId = "assemblyListPane"; + + public static AssemblyListPaneModel Instance { get; } = new AssemblyListPaneModel(); + + public override PanePosition DefaultPosition => PanePosition.Left; + + private AssemblyListPaneModel() + { + Title = Resources.Assemblies; + ContentId = PaneContentId; + IsCloseable = false; + } + } +} diff --git a/ILSpy/ViewModels/DebugStepsPaneModel.cs b/ILSpy/ViewModels/DebugStepsPaneModel.cs new file mode 100644 index 000000000..2ae7a6845 --- /dev/null +++ b/ILSpy/ViewModels/DebugStepsPaneModel.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace ICSharpCode.ILSpy.ViewModels +{ + public class DebugStepsPaneModel : ToolPaneModel + { + public const string PaneContentId = "debugStepsPane"; + + public static DebugStepsPaneModel Instance { get; } = new DebugStepsPaneModel(); + + public override PanePosition DefaultPosition => PanePosition.Top; + + private DebugStepsPaneModel() + { + ContentId = PaneContentId; + Title = Properties.Resources.DebugSteps; + } + } +} diff --git a/ILSpy/ViewModels/DocumentModel.cs b/ILSpy/ViewModels/DocumentModel.cs new file mode 100644 index 000000000..47341cad6 --- /dev/null +++ b/ILSpy/ViewModels/DocumentModel.cs @@ -0,0 +1,80 @@ +// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using ICSharpCode.ILSpy.Properties; +using ICSharpCode.ILSpy.TextView; + +namespace ICSharpCode.ILSpy.ViewModels +{ + public abstract class DocumentModel : PaneModel + { + public override PanePosition DefaultPosition => PanePosition.Document; + + protected DocumentModel(string contentId, string title) + { + this.ContentId = contentId; + this.Title = title; + } + } + + public class DecompiledDocumentModel : DocumentModel + { + public DecompiledDocumentModel() + : base("//Decompiled", Resources.View) + { + } + + public DecompiledDocumentModel(string id, string title) + : base("//Decompiled/" + id, title) + { + } + + private DecompilerTextView textView; + public DecompilerTextView TextView { + get => textView; + set { + if (textView != value) { + textView = value; + RaisePropertyChanged(nameof(TextView)); + } + } + } + + private Language language; + public Language Language { + get => language; + set { + if (language != value) { + language = value; + RaisePropertyChanged(nameof(Language)); + } + } + } + + private LanguageVersion languageVersion; + public LanguageVersion LanguageVersion { + get => languageVersion; + set { + if (languageVersion != value) { + languageVersion = value; + RaisePropertyChanged(nameof(LanguageVersion)); + } + } + } + } +} \ No newline at end of file diff --git a/ILSpy/ViewModels/PaneModel.cs b/ILSpy/ViewModels/PaneModel.cs new file mode 100644 index 000000000..dd6e12877 --- /dev/null +++ b/ILSpy/ViewModels/PaneModel.cs @@ -0,0 +1,140 @@ +// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.ComponentModel; +using System.Windows.Input; + +namespace ICSharpCode.ILSpy.ViewModels +{ + public abstract class PaneModel : INotifyPropertyChanged + { + class CloseCommandImpl : ICommand + { + readonly PaneModel model; + + public CloseCommandImpl(PaneModel model) + { + this.model = model; + } + + public event EventHandler CanExecuteChanged; + + public bool CanExecute(object parameter) + { + return model.IsCloseable; + } + + public void Execute(object parameter) + { + Docking.DockWorkspace.Instance.Remove(model); + } + } + + public PaneModel() + { + this.closeCommand = new CloseCommandImpl(this); + } + + public abstract PanePosition DefaultPosition { get; } + + public event PropertyChangedEventHandler PropertyChanged; + + protected void RaisePropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + private bool isSelected = false; + public bool IsSelected { + get => isSelected; + set { + if (isSelected != value) { + isSelected = value; + RaisePropertyChanged(nameof(IsSelected)); + } + } + } + + private bool isActive = false; + public bool IsActive { + get => isActive; + set { + if (isActive != value) { + isActive = value; + RaisePropertyChanged(nameof(IsActive)); + } + } + } + + private bool isVisible = true; + public bool IsVisible { + get { return isVisible; } + set { + if (isVisible != value) { + isVisible = value; + RaisePropertyChanged(nameof(IsVisible)); + } + } + } + + private bool isCloseable = true; + public bool IsCloseable { + get { return isCloseable; } + set { + if (isCloseable != value) { + isCloseable = value; + RaisePropertyChanged(nameof(IsCloseable)); + } + } + } + + private ICommand closeCommand; + public ICommand CloseCommand { + get { return closeCommand; } + set { + if (closeCommand != value) { + closeCommand = value; + RaisePropertyChanged(nameof(CloseCommand)); + } + } + } + + private string contentId; + public string ContentId { + get => contentId; + set { + if (contentId != value) { + contentId = value; + RaisePropertyChanged(nameof(ContentId)); + } + } + } + + private string title; + public string Title { + get => title; + set { + if (title != value) { + title = value; + RaisePropertyChanged(nameof(Title)); + } + } + } + } +} diff --git a/ILSpy/ViewModels/SearchPaneModel.cs b/ILSpy/ViewModels/SearchPaneModel.cs new file mode 100644 index 000000000..ef8dce27c --- /dev/null +++ b/ILSpy/ViewModels/SearchPaneModel.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace ICSharpCode.ILSpy.ViewModels +{ + public class SearchPaneModel : ToolPaneModel + { + public const string PaneContentId = "searchPane"; + + public static SearchPaneModel Instance { get; } = new SearchPaneModel(); + + public override PanePosition DefaultPosition => PanePosition.Top; + + private SearchPaneModel() + { + ContentId = PaneContentId; + Title = Properties.Resources.SearchPane_Search; + IsCloseable = true; + } + } +} diff --git a/ILSpy/ViewModels/ToolPaneModel.cs b/ILSpy/ViewModels/ToolPaneModel.cs new file mode 100644 index 000000000..8843fdd1a --- /dev/null +++ b/ILSpy/ViewModels/ToolPaneModel.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace ICSharpCode.ILSpy.ViewModels +{ + public abstract class ToolPaneModel : PaneModel + { + + } +}