From 0a3bf39aa8680d144078b0350422eef6f3a56c82 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Feb 2011 01:49:17 +0100 Subject: [PATCH] Store settings in appdata. --- ILSpy/App.xaml.cs | 24 ++++++++++ ILSpy/FilterSettings.cs | 21 +++++++- ILSpy/ILSpy.csproj | 2 + ILSpy/ILSpySettings.cs | 101 +++++++++++++++++++++++++++++++++++++++ ILSpy/Language.cs | 6 +++ ILSpy/MainWindow.xaml | 2 - ILSpy/MainWindow.xaml.cs | 65 ++++++++++++++++++++++--- ILSpy/SessionSettings.cs | 73 ++++++++++++++++++++++++++++ 8 files changed, 284 insertions(+), 10 deletions(-) create mode 100644 ILSpy/ILSpySettings.cs create mode 100644 ILSpy/SessionSettings.cs diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs index 079b6d00f..39af423cb 100644 --- a/ILSpy/App.xaml.cs +++ b/ILSpy/App.xaml.cs @@ -18,9 +18,11 @@ using System; using System.Diagnostics; +using System.Threading; using System.Windows; using System.Windows.Documents; using System.Windows.Navigation; +using System.Windows.Threading; namespace ICSharpCode.ILSpy { @@ -33,11 +35,33 @@ namespace ICSharpCode.ILSpy { InitializeComponent(); + if (!Debugger.IsAttached) { + AppDomain.CurrentDomain.UnhandledException += ShowErrorBox; + Dispatcher.CurrentDispatcher.UnhandledException += Dispatcher_UnhandledException; + } + EventManager.RegisterClassHandler(typeof(Window), Hyperlink.RequestNavigateEvent, new RequestNavigateEventHandler(Window_RequestNavigate)); } + + static void Dispatcher_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) + { + Debug.WriteLine(e.Exception.ToString()); + MessageBox.Show(e.Exception.ToString(), "Sorry, we crashed"); + e.Handled = true; + } + + static void ShowErrorBox(object sender, UnhandledExceptionEventArgs e) + { + Exception ex = e.ExceptionObject as Exception; + if (ex != null) { + Debug.WriteLine(ex.ToString()); + MessageBox.Show(ex.ToString(), "Sorry, we crashed"); + } + } + void Window_RequestNavigate(object sender, RequestNavigateEventArgs e) { Process.Start(e.Uri.ToString()); diff --git a/ILSpy/FilterSettings.cs b/ILSpy/FilterSettings.cs index df6213104..5283c2d46 100644 --- a/ILSpy/FilterSettings.cs +++ b/ILSpy/FilterSettings.cs @@ -18,6 +18,8 @@ using System; using System.ComponentModel; +using System.Linq; +using System.Xml.Linq; namespace ICSharpCode.ILSpy { @@ -31,6 +33,21 @@ namespace ICSharpCode.ILSpy /// public class FilterSettings : INotifyPropertyChanged { + public FilterSettings(XElement element) + { + this.ShowInternalApi = (bool?)element.Element("ShowInternalAPI") ?? true; + this.Language = Languages.GetLanguage((string)element.Element("Language")); + } + + public XElement SaveAsXml() + { + return new XElement( + "FilterSettings", + new XElement("ShowInternalAPI", this.ShowInternalApi), + new XElement("Language", this.Language.Name) + ); + } + string searchTerm; public string SearchTerm { @@ -50,7 +67,7 @@ namespace ICSharpCode.ILSpy return text.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase) >= 0; } - bool showInternalApi = true; + bool showInternalApi; public bool ShowInternalApi { get { return showInternalApi; } @@ -62,7 +79,7 @@ namespace ICSharpCode.ILSpy } } - Language language = Languages.AllLanguages[0]; + Language language; public Language Language { get { return language; } diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index d49ac75a4..0aaf7e826 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -89,6 +89,7 @@ + @@ -103,6 +104,7 @@ Code MainWindow.xaml + diff --git a/ILSpy/ILSpySettings.cs b/ILSpy/ILSpySettings.cs new file mode 100644 index 000000000..10b46c836 --- /dev/null +++ b/ILSpy/ILSpySettings.cs @@ -0,0 +1,101 @@ +// 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.IO; +using System.Linq; +using System.Threading; +using System.Xml; +using System.Xml.Linq; + +namespace ICSharpCode.ILSpy +{ + /// + /// Manages IL Spy settings. + /// + public static class ILSpySettings + { + public static XElement LoadSettings(string section) + { + using (new MutexProtector(ConfigFileMutex)) { + try { + XDocument doc = XDocument.Load(GetConfigFile()); + return doc.Root.Element(section) ?? new XElement(section); + } catch (IOException) { + return new XElement(section); + } catch (XmlException) { + return new XElement(section); + } + } + } + + public static void SaveSettings(XElement section) + { + using (new MutexProtector(ConfigFileMutex)) { + string config = GetConfigFile(); + XDocument doc; + try { + doc = XDocument.Load(config); + } catch (IOException) { + // ensure the directory exists + Directory.CreateDirectory(Path.GetDirectoryName(config)); + doc = new XDocument(new XElement("ILSpy")); + } catch (XmlException) { + doc = new XDocument(new XElement("ILSpy")); + } + doc.Root.SetAttributeValue("version", RevisionClass.Major + "." + RevisionClass.Minor + "." + RevisionClass.Build + "." + RevisionClass.Revision); + XElement existingElement = doc.Root.Element(section.Name); + if (existingElement != null) + existingElement.ReplaceWith(section); + else + doc.Root.Add(section); + doc.Save(config); + } + } + + public static string GetConfigFile() + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ICSharpCode\\ILSpy.xml"); + } + + const string ConfigFileMutex = "01A91708-49D1-410D-B8EB-4DE2662B3971"; + + sealed class MutexProtector : IDisposable + { + Mutex mutex; + + public MutexProtector(string name) + { + bool createdNew; + this.mutex = new Mutex(true, name, out createdNew); + if (!createdNew) { + try { + mutex.WaitOne(); + } catch (AbandonedMutexException) { + } + } + } + + public void Dispose() + { + mutex.ReleaseMutex(); + mutex.Dispose(); + } + } + } +} diff --git a/ILSpy/Language.cs b/ILSpy/Language.cs index 3f8876d62..92ac30b07 100644 --- a/ILSpy/Language.cs +++ b/ILSpy/Language.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Linq; using ICSharpCode.Decompiler; using Mono.Cecil; @@ -80,5 +81,10 @@ namespace ICSharpCode.ILSpy new ILLanguage(false), new ILLanguage(true) }; + + public static Language GetLanguage(string name) + { + return AllLanguages.FirstOrDefault(l => l.Name == name) ?? AllLanguages.First(); + } } } diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index 1a63c6319..79b02d79c 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -3,8 +3,6 @@ x:Class="ICSharpCode.ILSpy.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:tv="clr-namespace:ICSharpCode.TreeView;assembly=ICSharpCode.TreeView" xmlns:local="clr-namespace:ICSharpCode.ILSpy" xmlns:textView="clr-namespace:ICSharpCode.ILSpy.TextView" Title="ILSpy" - Width="790" - Height="500" MinWidth="250" MinHeight="200" UseLayoutRounding="True" diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 2f6750a79..49501bcf8 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; @@ -39,7 +40,7 @@ namespace ICSharpCode.ILSpy { AssemblyList assemblyList = new AssemblyList(); AssemblyListTreeNode assemblyListTreeNode; - FilterSettings filterSettings = new FilterSettings(); + SessionSettings settings = new SessionSettings(); static readonly System.Reflection.Assembly[] initialAssemblies = { typeof(object).Assembly, @@ -58,13 +59,20 @@ namespace ICSharpCode.ILSpy public MainWindow() { - this.DataContext = filterSettings; + this.DataContext = settings.FilterSettings; + this.Left = settings.WindowBounds.Left; + this.Top = settings.WindowBounds.Top; + this.Width = settings.WindowBounds.Width; + this.Height = settings.WindowBounds.Height; + // TODO: validate bounds (maybe a screen was removed...) + this.WindowState = settings.WindowState; + InitializeComponent(); decompilerTextView.mainWindow = this; assemblyListTreeNode = new AssemblyListTreeNode(assemblyList); - assemblyListTreeNode.FilterSettings = filterSettings.Clone(); - filterSettings.PropertyChanged += new PropertyChangedEventHandler(filterSettings_PropertyChanged); + assemblyListTreeNode.FilterSettings = settings.FilterSettings.Clone(); + settings.FilterSettings.PropertyChanged += new PropertyChangedEventHandler(filterSettings_PropertyChanged); treeView.Root = assemblyListTreeNode; assemblyListTreeNode.Select = SelectNode; @@ -75,6 +83,8 @@ namespace ICSharpCode.ILSpy assemblyList.OpenAssembly(args[i]); } + SelectNode(FindNodeByPath(settings.ActiveTreeViewPath)); + #if DEBUG AddDebugItemsToToolbar(); #endif @@ -85,7 +95,7 @@ namespace ICSharpCode.ILSpy // 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. - assemblyListTreeNode.FilterSettings = filterSettings.Clone(); + assemblyListTreeNode.FilterSettings = settings.FilterSettings.Clone(); if (e.PropertyName == "Language") { TreeView_SelectionChanged(null, null); } @@ -103,6 +113,33 @@ namespace ICSharpCode.ILSpy } } + SharpTreeNode FindNodeByPath(string[] path) + { + if (path == null) + return null; + SharpTreeNode node = treeView.Root; + foreach (var element in path) { + if (node == null) + break; + node.EnsureLazyChildren(); + node = node.Children.FirstOrDefault(c => c.ToString() == element); + } + return node; + } + + 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(); + } + #region Debugging CFG #if DEBUG void AddDebugItemsToToolbar() @@ -213,7 +250,23 @@ namespace ICSharpCode.ILSpy void TreeView_SelectionChanged(object sender, SelectionChangedEventArgs e) { - decompilerTextView.Decompile(filterSettings.Language, treeView.SelectedItems.OfType()); + decompilerTextView.Decompile(settings.FilterSettings.Language, treeView.SelectedItems.OfType()); + } + + 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) + settings.WindowState = this.WindowState; + } + + protected override void OnClosing(CancelEventArgs e) + { + base.OnClosing(e); + settings.ActiveTreeViewPath = GetPathForNode(treeView.SelectedItem as SharpTreeNode); + settings.WindowBounds = this.RestoreBounds; + settings.Save(); } } } \ No newline at end of file diff --git a/ILSpy/SessionSettings.cs b/ILSpy/SessionSettings.cs new file mode 100644 index 000000000..47c8e681e --- /dev/null +++ b/ILSpy/SessionSettings.cs @@ -0,0 +1,73 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; +using System.ComponentModel; +using System.Linq; +using System.Windows; +using System.Xml.Linq; + +namespace ICSharpCode.ILSpy +{ + /// + /// Per-session setting: + /// Loaded at startup; saved at exit. + /// + public class SessionSettings + { + public SessionSettings() + { + XElement doc = ILSpySettings.LoadSettings("SessionSettings"); + + XElement filterSettings = doc.Element("FilterSettings"); + if (filterSettings == null) filterSettings = new XElement("FilterSettings"); + + this.FilterSettings = new FilterSettings(filterSettings); + + XElement activeTreeViewPath = doc.Element("ActiveTreeViewPath"); + if (activeTreeViewPath != null) { + this.ActiveTreeViewPath = activeTreeViewPath.Elements().Select(e => (string)e).ToArray(); + } + + this.WindowState = FromString((string)doc.Element("WindowState"), WindowState.Normal); + this.WindowBounds = FromString((string)doc.Element("WindowBounds"), new Rect(10, 10, 750, 550)); + } + + public FilterSettings FilterSettings; + + public string[] ActiveTreeViewPath; + + public WindowState WindowState = WindowState.Normal; + public Rect WindowBounds; + + public void Save() + { + XElement doc = new XElement("SessionSettings"); + doc.Add(this.FilterSettings.SaveAsXml()); + if (this.ActiveTreeViewPath != null) { + doc.Add(new XElement("ActiveTreeViewPath", ActiveTreeViewPath.Select(p => new XElement("Node", p)))); + } + doc.Add(new XElement("WindowState", ToString(this.WindowState))); + doc.Add(new XElement("WindowBounds", ToString(this.WindowBounds))); + ILSpySettings.SaveSettings(doc); + } + + static T FromString(string s, T defaultValue) + { + if (s == null) + return defaultValue; + try { + TypeConverter c = TypeDescriptor.GetConverter(typeof(T)); + return (T)c.ConvertFromInvariantString(s); + } catch (FormatException) { + return defaultValue; + } + } + + static string ToString(T obj) + { + TypeConverter c = TypeDescriptor.GetConverter(typeof(T)); + return c.ConvertToInvariantString(obj); + } + } +}