// Copyright (c) 2011 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.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media.Imaging; using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.TreeView; using Microsoft.Win32; namespace ICSharpCode.ILSpy { /// /// The main window of the application. /// partial class MainWindow : Window { NavigationHistory history = new NavigationHistory(); ILSpySettings spySettings; SessionSettings sessionSettings; AssemblyListManager assemblyListManager; AssemblyList assemblyList; AssemblyListTreeNode assemblyListTreeNode; public MainWindow() { spySettings = ILSpySettings.Load(); this.sessionSettings = new SessionSettings(spySettings); this.assemblyListManager = new AssemblyListManager(spySettings); if (Environment.OSVersion.Version.Major >= 6) this.Icon = new BitmapImage(new Uri("pack://application:,,,/ILSpy;component/images/ILSpy.ico")); else this.Icon = Images.AssemblyLoading; this.DataContext = sessionSettings; this.Left = sessionSettings.WindowBounds.Left; this.Top = sessionSettings.WindowBounds.Top; this.Width = sessionSettings.WindowBounds.Width; this.Height = sessionSettings.WindowBounds.Height; // TODO: validate bounds (maybe a monitor was removed...) this.WindowState = sessionSettings.WindowState; InitializeComponent(); decompilerTextView.mainWindow = this; if (sessionSettings.SplitterPosition > 0 && sessionSettings.SplitterPosition < 1) { leftColumn.Width = new GridLength(sessionSettings.SplitterPosition, GridUnitType.Star); rightColumn.Width = new GridLength(1 - sessionSettings.SplitterPosition, GridUnitType.Star); } sessionSettings.FilterSettings.PropertyChanged += filterSettings_PropertyChanged; this.Loaded += new RoutedEventHandler(MainWindow_Loaded); } void MainWindow_Loaded(object sender, RoutedEventArgs e) { ILSpySettings spySettings = this.spySettings; this.spySettings = null; // Load AssemblyList only in Loaded event so that WPF is initialized before we start the CPU-heavy stuff. // This makes the UI come up a bit faster. this.assemblyList = assemblyListManager.LoadList(spySettings, sessionSettings.ActiveAssemblyList); ShowAssemblyList(this.assemblyList); string[] args = Environment.GetCommandLineArgs(); for (int i = 1; i < args.Length; i++) { assemblyList.OpenAssembly(args[i]); } if (assemblyList.GetAssemblies().Length == 0) LoadInitialAssemblies(); SharpTreeNode node = FindNodeByPath(sessionSettings.ActiveTreeViewPath, true); if (node != null) { SelectNode(node); // only if not showing the about page, perform the update check: ShowMessageIfUpdatesAvailableAsync(spySettings); } else { AboutPage.Display(decompilerTextView); } } #region Update Check string updateAvailableDownloadUrl; void ShowMessageIfUpdatesAvailableAsync(ILSpySettings spySettings) { AboutPage.CheckForUpdatesIfEnabledAsync(spySettings).ContinueWith( delegate (Task task) { if (task.Result != null) { updateAvailableDownloadUrl = task.Result; updateAvailablePanel.Visibility = Visibility.Visible; } }, TaskScheduler.FromCurrentSynchronizationContext() ); } void updateAvailablePanelCloseButtonClick(object sender, RoutedEventArgs e) { updateAvailablePanel.Visibility = Visibility.Collapsed; } void downloadUpdateButtonClick(object sender, RoutedEventArgs e) { Process.Start(updateAvailableDownloadUrl); } #endregion void ShowAssemblyList(AssemblyList assemblyList) { history.Clear(); this.assemblyList = assemblyList; assemblyList.assemblies.CollectionChanged += assemblyList_Assemblies_CollectionChanged; assemblyListTreeNode = new AssemblyListTreeNode(assemblyList); assemblyListTreeNode.FilterSettings = sessionSettings.FilterSettings.Clone(); assemblyListTreeNode.Select = node => SelectNode(node); treeView.Root = assemblyListTreeNode; if (assemblyList.ListName == AssemblyListManager.DefaultListName) this.Title = "ILSpy"; else this.Title = "ILSpy - " + assemblyList.ListName; } void assemblyList_Assemblies_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) foreach (LoadedAssembly asm in e.OldItems) history.RemoveAll(n => n.AncestorsAndSelf().OfType().Any(a => a.LoadedAssembly == asm)); } void LoadInitialAssemblies() { // Called when loading an empty assembly list; so that // the user can see something initially. System.Reflection.Assembly[] initialAssemblies = { typeof(object).Assembly, typeof(Uri).Assembly, typeof(System.Linq.Enumerable).Assembly, typeof(System.Xml.XmlDocument).Assembly, typeof(System.Windows.Markup.MarkupExtension).Assembly, typeof(System.Windows.Rect).Assembly, typeof(System.Windows.UIElement).Assembly, typeof(System.Windows.FrameworkElement).Assembly, typeof(ICSharpCode.TreeView.SharpTreeView).Assembly, typeof(Mono.Cecil.AssemblyDefinition).Assembly, typeof(ICSharpCode.AvalonEdit.TextEditor).Assembly, typeof(ICSharpCode.Decompiler.GraphVizGraph).Assembly, typeof(MainWindow).Assembly }; foreach (System.Reflection.Assembly asm in initialAssemblies) assemblyList.OpenAssembly(asm.Location); } void filterSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) { // filterSettings is mutable; but the ILSpyTreeNode filtering assumes that filter settings are immutable. // Thus, the main window will use one mutable instance (for data-binding), and assign a new clone to the ILSpyTreeNodes whenever the main // mutable instance changes. if (assemblyListTreeNode != null) assemblyListTreeNode.FilterSettings = sessionSettings.FilterSettings.Clone(); if (e.PropertyName == "Language") { TreeView_SelectionChanged(null, null); } } internal AssemblyList AssemblyList { get { return assemblyList; } } internal AssemblyListTreeNode AssemblyListTreeNode { get { return assemblyListTreeNode; } } #region Node Selection internal void SelectNode(SharpTreeNode obj, bool recordNavigationInHistory = true) { if (obj != null) { SharpTreeNode oldNode = treeView.SelectedItem as SharpTreeNode; if (oldNode != null && recordNavigationInHistory) history.Record(oldNode); // Set both the selection and focus to ensure that keyboard navigation works as expected. treeView.FocusNode(obj); treeView.SelectedItem = obj; } } /// /// Retrieves a node using the .ToString() representations of its ancestors. /// SharpTreeNode FindNodeByPath(string[] path, bool returnBestMatch) { if (path == null) return null; SharpTreeNode node = treeView.Root; SharpTreeNode bestMatch = node; foreach (var element in path) { if (node == null) break; bestMatch = node; node.EnsureLazyChildren(); node = node.Children.FirstOrDefault(c => c.ToString() == element); } if (returnBestMatch) return node ?? bestMatch; else return node; } /// /// Gets the .ToString() representation of the node's ancestors. /// string[] GetPathForNode(SharpTreeNode node) { if (node == null) return null; List path = new List(); while (node.Parent != null) { path.Add(node.ToString()); node = node.Parent; } path.Reverse(); return path.ToArray(); } #endregion #region Open/Refresh void OpenCommandExecuted(object sender, ExecutedRoutedEventArgs e) { e.Handled = true; OpenFileDialog dlg = new OpenFileDialog(); dlg.Filter = ".NET assemblies|*.dll;*.exe|All files|*.*"; dlg.Multiselect = true; dlg.RestoreDirectory = true; if (dlg.ShowDialog() == true) { OpenFiles(dlg.FileNames); } } void OpenFiles(string[] fileNames) { treeView.UnselectAll(); SharpTreeNode lastNode = null; foreach (string file in fileNames) { var asm = assemblyList.OpenAssembly(file); if (asm != null) { var node = assemblyListTreeNode.FindAssemblyNode(asm); if (node != null) { treeView.SelectedItems.Add(node); lastNode = node; } } } if (lastNode != null) treeView.FocusNode(lastNode); } void RefreshCommandExecuted(object sender, ExecutedRoutedEventArgs e) { e.Handled = true; var path = GetPathForNode(treeView.SelectedItem as SharpTreeNode); ShowAssemblyList(assemblyListManager.LoadList(ILSpySettings.Load(), assemblyList.ListName)); SelectNode(FindNodeByPath(path, true)); } void OpenFromGac_Click(object sender, RoutedEventArgs e) { OpenFromGacDialog dlg = new OpenFromGacDialog(); dlg.Owner = this; if (dlg.ShowDialog() == true) { OpenFiles(dlg.SelectedFileNames); } } #endregion #region Exit/About void ExitClick(object sender, RoutedEventArgs e) { Close(); } void AboutClick(object sender, RoutedEventArgs e) { AboutPage.Display(decompilerTextView); } #endregion #region Decompile / Save void TreeView_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (treeView.SelectedItems.Count == 1) { ILSpyTreeNode node = treeView.SelectedItem as ILSpyTreeNode; if (node != null && node.View(decompilerTextView)) return; } decompilerTextView.Decompile(sessionSettings.FilterSettings.Language, treeView.GetTopLevelSelection().OfType(), new DecompilationOptions()); } void saveCode_Click(object sender, RoutedEventArgs e) { if (treeView.SelectedItems.Count == 1) { ILSpyTreeNode node = treeView.SelectedItem as ILSpyTreeNode; if (node != null && node.Save(decompilerTextView)) return; } decompilerTextView.SaveToDisk(sessionSettings.FilterSettings.Language, treeView.GetTopLevelSelection().OfType(), new DecompilationOptions() { FullDecompilation = true }); } #endregion #region Back/Forward navigation void BackCommandCanExecute(object sender, CanExecuteRoutedEventArgs e) { e.Handled = true; e.CanExecute = history.CanNavigateBack; } void BackCommandExecuted(object sender, ExecutedRoutedEventArgs e) { if (history.CanNavigateBack) { e.Handled = true; SelectNode(history.GoBack(treeView.SelectedItem as SharpTreeNode), false); } } void ForwardCommandCanExecute(object sender, CanExecuteRoutedEventArgs e) { e.Handled = true; e.CanExecute = history.CanNavigateForward; } void ForwardCommandExecuted(object sender, ExecutedRoutedEventArgs e) { if (history.CanNavigateForward) { e.Handled = true; SelectNode(history.GoForward(treeView.SelectedItem as SharpTreeNode), false); } } #endregion protected override void OnStateChanged(EventArgs e) { base.OnStateChanged(e); // store window state in settings only if it's not minimized if (this.WindowState != System.Windows.WindowState.Minimized) sessionSettings.WindowState = this.WindowState; } protected override void OnClosing(CancelEventArgs e) { base.OnClosing(e); sessionSettings.ActiveAssemblyList = assemblyList.ListName; sessionSettings.ActiveTreeViewPath = GetPathForNode(treeView.SelectedItem as SharpTreeNode); sessionSettings.WindowBounds = this.RestoreBounds; sessionSettings.SplitterPosition = leftColumn.Width.Value / (leftColumn.Width.Value + rightColumn.Width.Value); sessionSettings.Save(); } } }