diff --git a/ILSpy/Docking/PaneCollection.cs b/ILSpy/Docking/PaneCollection.cs index 72b1b851b..a8e397fe5 100644 --- a/ILSpy/Docking/PaneCollection.cs +++ b/ILSpy/Docking/PaneCollection.cs @@ -16,6 +16,7 @@ // 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; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -25,10 +26,10 @@ using ICSharpCode.ILSpy.ViewModels; namespace ICSharpCode.ILSpy.Docking { - public class PaneCollection : INotifyCollectionChanged, ICollection + public class PaneCollection : INotifyCollectionChanged, IList where T : PaneModel, new() { - private ObservableCollection observableCollection = new ObservableCollection(); + private readonly ObservableCollection observableCollection = []; public event NotifyCollectionChangedEventHandler CollectionChanged; @@ -46,7 +47,6 @@ namespace ICSharpCode.ILSpy.Docking item.IsVisible = true; item.IsActive = true; } - public int Count => observableCollection.Count; public bool IsReadOnly => false; public void Clear() => observableCollection.Clear(); @@ -55,5 +55,12 @@ namespace ICSharpCode.ILSpy.Docking public bool Remove(T item) => observableCollection.Remove(item); public IEnumerator GetEnumerator() => observableCollection.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => observableCollection.GetEnumerator(); + int IList.IndexOf(T item) => observableCollection.IndexOf(item); + void IList.Insert(int index, T item) => throw new NotImplementedException("Only Add is supported"); + void IList.RemoveAt(int index) => observableCollection.RemoveAt(index); + T IList.this[int index] { + get => observableCollection[index]; + set => observableCollection[index] = value; + } } } diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index 62c51508c..8b253493a 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -1,27 +1,25 @@  - + @@ -78,9 +76,12 @@ - - - + + + @@ -101,9 +102,12 @@ - - - + + + diff --git a/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs b/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs index 245507368..cf196ef03 100644 --- a/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs @@ -112,7 +112,7 @@ namespace ICSharpCode.ILSpy.Metadata public void OnHandleClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, handlePtr.Handle, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, handlePtr.Handle, protocol: "metadata"))); } string handleTooltip; diff --git a/ILSpy/Util/MenuService.cs b/ILSpy/Util/MenuService.cs index 6220f73af..7766ab77a 100644 --- a/ILSpy/Util/MenuService.cs +++ b/ILSpy/Util/MenuService.cs @@ -1,9 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.ComponentModel; +using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; @@ -14,6 +12,8 @@ using ICSharpCode.ILSpy.Themes; using ICSharpCode.ILSpy.ViewModels; using TomsToolbox.Composition; +using TomsToolbox.ObservableCollections; +using TomsToolbox.Wpf.Converters; namespace ICSharpCode.ILSpy.Util { @@ -21,7 +21,16 @@ namespace ICSharpCode.ILSpy.Util { public static readonly MenuService Instance = new(); - public void InitMainMenu(Menu mainMenu) + private readonly DockWorkspace dockWorkspace = DockWorkspace.Instance; + + public void Init(Menu mainMenu, ToolBar toolBar, InputBindingCollection inputBindings) + { + InitMainMenu(mainMenu); + InitWindowMenu(mainMenu, inputBindings); + InitToolbar(toolBar); + } + + static void InitMainMenu(Menu mainMenu) { var mainMenuCommands = App.ExportProvider.GetExports("MainMenuCommand"); // Start by constructing the individual flat menus @@ -48,10 +57,11 @@ namespace ICSharpCode.ILSpy.Util } else { - MenuItem menuItem = new MenuItem(); - menuItem.Command = CommandWrapper.Unwrap(entry.Value); - menuItem.Tag = entry.Metadata?.MenuID; - menuItem.Header = ResourceHelper.GetString(entry.Metadata?.Header); + var menuItem = new MenuItem { + Command = CommandWrapper.Unwrap(entry.Value), + Tag = entry.Metadata?.MenuID, + Header = ResourceHelper.GetString(entry.Metadata?.Header) + }; if (!string.IsNullOrEmpty(entry.Metadata?.MenuIcon)) { menuItem.Icon = new Image { @@ -62,7 +72,7 @@ namespace ICSharpCode.ILSpy.Util } menuItem.IsEnabled = entry.Metadata?.IsEnabled ?? false; - if (entry.Value is ToggleableCommand toggle) + if (entry.Value is ToggleableCommand) { menuItem.IsCheckable = true; menuItem.SetBinding(MenuItem.IsCheckedProperty, new Binding("IsChecked") { Source = entry.Value, Mode = BindingMode.OneWay }); @@ -75,7 +85,7 @@ namespace ICSharpCode.ILSpy.Util } } - foreach (var (key, item) in parentMenuItems) + foreach (var item in parentMenuItems.Values) { if (item.Parent == null) { @@ -83,21 +93,22 @@ namespace ICSharpCode.ILSpy.Util } } - MenuItem GetOrAddParentMenuItem(string menuID, string resourceKey) + MenuItem GetOrAddParentMenuItem(string menuId, string resourceKey) { - if (!parentMenuItems.TryGetValue(menuID, out var parentMenuItem)) + if (!parentMenuItems.TryGetValue(menuId, out var parentMenuItem)) { - var topLevelMenuItem = mainMenu.Items.OfType().FirstOrDefault(m => (string)m.Tag == menuID); + var topLevelMenuItem = mainMenu.Items.OfType().FirstOrDefault(m => (string)m.Tag == menuId); if (topLevelMenuItem == null) { - parentMenuItem = new MenuItem(); - parentMenuItem.Header = ResourceHelper.GetString(resourceKey); - parentMenuItem.Tag = menuID; - parentMenuItems.Add(menuID, parentMenuItem); + parentMenuItem = new() { + Header = ResourceHelper.GetString(resourceKey), + Tag = menuId + }; + parentMenuItems.Add(menuId, parentMenuItem); } else { - parentMenuItems.Add(menuID, topLevelMenuItem); + parentMenuItems.Add(menuId, topLevelMenuItem); parentMenuItem = topLevelMenuItem; } } @@ -105,219 +116,45 @@ namespace ICSharpCode.ILSpy.Util } } - public void InitWindowMenu(Menu mainMenu, InputBindingCollection inputBindings) + void InitWindowMenu(Menu mainMenu, InputBindingCollection inputBindings) { var windowMenuItem = mainMenu.Items.OfType().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); - var separatorBeforeTools = new Separator(); - var separatorBeforeDocuments = new Separator(); - - windowMenuItem.Items.Add(separatorBeforeTools); - windowMenuItem.Items.Add(separatorBeforeDocuments); - - var dock = DockWorkspace.Instance; + var defaultItems = windowMenuItem.Items.Cast().ToArray(); - dock.ToolPanes.CollectionChanged += ToolsChanged; - dock.TabPages.CollectionChanged += TabsChanged; - MessageBus.Subscribers += ActiveTabPageChanged; - - ToolsChanged(dock.ToolPanes, new(NotifyCollectionChangedAction.Reset)); - TabsChanged(dock.TabPages, new(NotifyCollectionChangedAction.Reset)); - - void ToolsChanged(object sender, NotifyCollectionChangedEventArgs e) - { - int endIndex = windowMenuItem.Items.IndexOf(separatorBeforeDocuments); - int startIndex = windowMenuItem.Items.IndexOf(separatorBeforeTools) + 1; - int insertionIndex; - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - insertionIndex = Math.Min(endIndex, startIndex + e.NewStartingIndex); - foreach (ToolPaneModel pane in e.NewItems) - { - MenuItem menuItem = CreateMenuItem(pane); - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - case NotifyCollectionChangedAction.Remove: - foreach (ToolPaneModel pane in e.OldItems) - { - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[i]; - if (pane == item.Tag) - { - windowMenuItem.Items.RemoveAt(i); - item.Tag = null; - endIndex--; - break; - } - } - } - break; - case NotifyCollectionChangedAction.Replace: - break; - case NotifyCollectionChangedAction.Move: - break; - case NotifyCollectionChangedAction.Reset: - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[0]; - item.Tag = null; - windowMenuItem.Items.RemoveAt(i); - endIndex--; - } - insertionIndex = endIndex; - foreach (ToolPaneModel pane in dock.ToolPanes) - { - MenuItem menuItem = CreateMenuItem(pane); - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - } + windowMenuItem.Items.Clear(); - MenuItem CreateMenuItem(ToolPaneModel pane) - { - MenuItem menuItem = new MenuItem(); - menuItem.Command = pane.AssociatedCommand ?? new ToolPaneCommand(pane.ContentId); - menuItem.Header = pane.Title; - menuItem.Tag = pane; - var shortcutKey = pane.ShortcutKey; - if (shortcutKey != null) - { - inputBindings.Add(new InputBinding(menuItem.Command, shortcutKey)); - menuItem.InputGestureText = shortcutKey.GetDisplayStringForCulture(CultureInfo.CurrentUICulture); - } - if (!string.IsNullOrEmpty(pane.Icon)) - { - menuItem.Icon = new Image { - Width = 16, - Height = 16, - Source = Images.Load(pane, pane.Icon) - }; - } + var toolItems = dockWorkspace.ToolPanes.ObservableSelect(toolPane => CreateMenuItem(toolPane, inputBindings)); + var tabItems = dockWorkspace.TabPages.ObservableSelect(tabPage => CreateMenuItem(tabPage, dockWorkspace)); - return menuItem; - } - } + var allItems = new ObservableCompositeCollection(defaultItems, [new Separator()], toolItems, [new Separator()], tabItems); - void TabsChanged(object sender, NotifyCollectionChangedEventArgs e) - { - int endIndex = windowMenuItem.Items.Count; - int startIndex = windowMenuItem.Items.IndexOf(separatorBeforeDocuments) + 1; - int insertionIndex; - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - insertionIndex = Math.Min(endIndex, startIndex + e.NewStartingIndex); - foreach (TabPageModel pane in e.NewItems) - { - MenuItem menuItem = CreateMenuItem(pane); - pane.PropertyChanged += TabPageChanged; - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - case NotifyCollectionChangedAction.Remove: - foreach (TabPageModel pane in e.OldItems) - { - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[i]; - if (pane == item.Tag) - { - windowMenuItem.Items.RemoveAt(i); - pane.PropertyChanged -= TabPageChanged; - item.Tag = null; - endIndex--; - break; - } - } - } - break; - case NotifyCollectionChangedAction.Replace: - break; - case NotifyCollectionChangedAction.Move: - break; - case NotifyCollectionChangedAction.Reset: - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[i]; - windowMenuItem.Items.RemoveAt(i); - ((TabPageModel)item.Tag).PropertyChanged -= TabPageChanged; - endIndex--; - } - insertionIndex = endIndex; - foreach (TabPageModel pane in dock.TabPages) - { - MenuItem menuItem = CreateMenuItem(pane); - pane.PropertyChanged += TabPageChanged; - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - } - - MenuItem CreateMenuItem(TabPageModel pane) - { - MenuItem menuItem = new MenuItem(); - menuItem.Command = new TabPageCommand(pane); - menuItem.Header = pane.Title.Length > 20 ? pane.Title.Substring(20) + "..." : pane.Title; - menuItem.Tag = pane; - menuItem.IsCheckable = true; - menuItem.IsChecked = menuItem.Tag == dock.ActiveTabPage; - - return menuItem; - } - } - - void TabPageChanged(object sender, PropertyChangedEventArgs e) - { - var windowMenu = mainMenu.Items.OfType().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); - foreach (MenuItem menuItem in windowMenu.Items.OfType()) - { - if (menuItem.IsCheckable && menuItem.Tag == sender) - { - string title = ((TabPageModel)sender).Title; - menuItem.Header = title.Length > 20 ? title.Substring(0, 20) + "..." : title; - } - } - } - - void ActiveTabPageChanged(object sender, EventArgs e) - { - foreach (MenuItem menuItem in windowMenuItem.Items.OfType()) - { - if (menuItem.IsCheckable && menuItem.Tag is TabPageModel) - { - menuItem.IsChecked = menuItem.Tag == dock.ActiveTabPage; - } - } - } + windowMenuItem.ItemsSource = allItems; } - public void InitToolbar(ToolBar toolBar) + static void InitToolbar(ToolBar toolBar) { int navigationPos = 0; int openPos = 1; - var toolbarCommands = App.ExportProvider.GetExports("ToolbarCommand"); - foreach (var commandGroup in toolbarCommands.OrderBy(c => c.Metadata.ToolbarOrder).GroupBy(c => Properties.Resources.ResourceManager.GetString(c.Metadata.ToolbarCategory))) + var toolbarCommandsByTitle = App.ExportProvider.GetExports("ToolbarCommand") + .OrderBy(c => c.Metadata?.ToolbarOrder) + .GroupBy(c => c.Metadata?.ToolbarCategory); + + foreach (var commandGroup in toolbarCommandsByTitle) { - if (commandGroup.Key == Properties.Resources.ResourceManager.GetString("Navigation")) + if (commandGroup.Key == nameof(Properties.Resources.Navigation)) { foreach (var command in commandGroup) { - toolBar.Items.Insert(navigationPos++, MakeToolbarItem(command)); + toolBar.Items.Insert(navigationPos++, CreateToolbarItem(command)); openPos++; } } - else if (commandGroup.Key == Properties.Resources.ResourceManager.GetString("Open")) + else if (commandGroup.Key == nameof(Properties.Resources.Open)) { foreach (var command in commandGroup) { - toolBar.Items.Insert(openPos++, MakeToolbarItem(command)); + toolBar.Items.Insert(openPos++, CreateToolbarItem(command)); } } else @@ -325,33 +162,76 @@ namespace ICSharpCode.ILSpy.Util toolBar.Items.Add(new Separator()); foreach (var command in commandGroup) { - toolBar.Items.Add(MakeToolbarItem(command)); + toolBar.Items.Add(CreateToolbarItem(command)); } } } } - Button MakeToolbarItem(IExport command) + static Control CreateMenuItem(TabPageModel pane, DockWorkspace dock) { - return new Button { + var header = new TextBlock { + MaxWidth = 200, + TextTrimming = TextTrimming.CharacterEllipsis + }; + + header.SetBinding(TextBlock.TextProperty, new Binding(nameof(pane.Title)) { + Source = pane + }); + + MenuItem menuItem = new() { + Command = new TabPageCommand(pane), + Header = header, + IsCheckable = true + }; + + menuItem.SetBinding(MenuItem.IsCheckedProperty, new Binding(nameof(dock.ActiveTabPage)) { + Source = dock, + ConverterParameter = pane, + Converter = BinaryOperationConverter.Equality + }); + + return menuItem; + } + + static Control CreateMenuItem(ToolPaneModel pane, InputBindingCollection inputBindings) + { + MenuItem menuItem = new() { + Command = pane.AssociatedCommand ?? new ToolPaneCommand(pane.ContentId), + Header = pane.Title + }; + var shortcutKey = pane.ShortcutKey; + if (shortcutKey != null) + { + inputBindings.Add(new(menuItem.Command, shortcutKey)); + menuItem.InputGestureText = shortcutKey.GetDisplayStringForCulture(CultureInfo.CurrentUICulture); + } + if (!string.IsNullOrEmpty(pane.Icon)) + { + menuItem.Icon = new Image { + Width = 16, + Height = 16, + Source = Images.Load(pane, pane.Icon) + }; + } + + return menuItem; + } + + static Button CreateToolbarItem(IExport command) + { + return new() { Style = ThemeManager.Current.CreateToolBarButtonStyle(), Command = CommandWrapper.Unwrap(command.Value), - ToolTip = Properties.Resources.ResourceManager.GetString(command.Metadata.ToolTip), - Tag = command.Metadata.Tag, + ToolTip = Properties.Resources.ResourceManager.GetString(command.Metadata?.ToolTip), + Tag = command.Metadata?.Tag, Content = new Image { Width = 16, Height = 16, - Source = Images.Load(command.Value, command.Metadata.ToolbarIcon) + Source = Images.Load(command.Value, command.Metadata?.ToolbarIcon) } }; } - - public void Init(Menu mainMenu, ToolBar toolBar, InputBindingCollection inputBindings) - { - InitMainMenu(mainMenu); - InitWindowMenu(mainMenu, inputBindings); - InitToolbar(toolBar); - } } }