Browse Source

Merge pull request #2608 from icsharpcode/submenu-support

Submenu support
pull/2626/head
Siegfried Pammer 3 years ago committed by GitHub
parent
commit
2ee248bc2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      ILSpy/AboutPage.cs
  2. 2
      ILSpy/Commands/CheckForUpdatesCommand.cs
  3. 4
      ILSpy/Commands/DecompileAllCommand.cs
  4. 2
      ILSpy/Commands/DisassembleAllCommand.cs
  5. 2
      ILSpy/Commands/ExitCommand.cs
  6. 33
      ILSpy/Commands/ExportCommandAttribute.cs
  7. 2
      ILSpy/Commands/GeneratePdbContextMenuEntry.cs
  8. 2
      ILSpy/Commands/ManageAssemblyListsCommand.cs
  9. 2
      ILSpy/Commands/OpenCommand.cs
  10. 2
      ILSpy/Commands/OpenFromGacCommand.cs
  11. 2
      ILSpy/Commands/Pdb2XmlCommand.cs
  12. 2
      ILSpy/Commands/RefreshCommand.cs
  13. 2
      ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs
  14. 2
      ILSpy/Commands/SaveCommand.cs
  15. 4
      ILSpy/Commands/SortAssemblyListCommand.cs
  16. 98
      ILSpy/ContextMenuEntry.cs
  17. 4
      ILSpy/Docking/CloseAllDocumentsCommand.cs
  18. 16
      ILSpy/ExtensionMethods.cs
  19. 6
      ILSpy/MainWindow.xaml
  20. 103
      ILSpy/MainWindow.xaml.cs
  21. 2
      ILSpy/Options/OptionsDialog.xaml.cs
  22. 2
      TestPlugin/MainMenuCommand.cs

2
ILSpy/AboutPage.cs

@ -38,7 +38,7 @@ using ICSharpCode.ILSpy.Themes;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
[ExportMainMenuCommand(Menu = nameof(Resources._Help), Header = nameof(Resources._About), MenuOrder = 99999)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._Help), Header = nameof(Resources._About), MenuOrder = 99999)]
sealed class AboutPage : SimpleCommand sealed class AboutPage : SimpleCommand
{ {
public override void Execute(object parameter) public override void Execute(object parameter)

2
ILSpy/Commands/CheckForUpdatesCommand.cs

@ -21,7 +21,7 @@ using ICSharpCode.ILSpy.Properties;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
[ExportMainMenuCommand(Menu = nameof(Resources._Help), Header = nameof(Resources._CheckUpdates), MenuOrder = 5000)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._Help), Header = nameof(Resources._CheckUpdates), MenuOrder = 5000)]
sealed class CheckForUpdatesCommand : SimpleCommand sealed class CheckForUpdatesCommand : SimpleCommand
{ {
public override bool CanExecute(object parameter) public override bool CanExecute(object parameter)

4
ILSpy/Commands/DecompileAllCommand.cs

@ -30,7 +30,7 @@ using ICSharpCode.ILSpy.TextView;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
[ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources.DEBUGDecompile), MenuCategory = nameof(Resources.Open), MenuOrder = 2.5)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDecompile), MenuCategory = nameof(Resources.Open), MenuOrder = 2.5)]
sealed class DecompileAllCommand : SimpleCommand sealed class DecompileAllCommand : SimpleCommand
{ {
public override bool CanExecute(object parameter) public override bool CanExecute(object parameter)
@ -79,7 +79,7 @@ namespace ICSharpCode.ILSpy
} }
} }
[ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources.DEBUGDecompile100x), MenuCategory = nameof(Resources.Open), MenuOrder = 2.6)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDecompile100x), MenuCategory = nameof(Resources.Open), MenuOrder = 2.6)]
sealed class Decompile100TimesCommand : SimpleCommand sealed class Decompile100TimesCommand : SimpleCommand
{ {
public override void Execute(object parameter) public override void Execute(object parameter)

2
ILSpy/Commands/DisassembleAllCommand.cs

@ -28,7 +28,7 @@ using ICSharpCode.ILSpy.TextView;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
[ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources.DEBUGDisassemble), MenuCategory = nameof(Resources.Open), MenuOrder = 2.5)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDisassemble), MenuCategory = nameof(Resources.Open), MenuOrder = 2.5)]
sealed class DisassembleAllCommand : SimpleCommand sealed class DisassembleAllCommand : SimpleCommand
{ {
public override bool CanExecute(object parameter) public override bool CanExecute(object parameter)

2
ILSpy/Commands/ExitCommand.cs

@ -19,7 +19,7 @@ using ICSharpCode.ILSpy.Properties;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
[ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources.E_xit), MenuOrder = 99999, MenuCategory = nameof(Resources.Exit))] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.E_xit), MenuOrder = 99999, MenuCategory = nameof(Resources.Exit))]
sealed class ExitCommand : SimpleCommand sealed class ExitCommand : SimpleCommand
{ {
public override void Execute(object parameter) public override void Execute(object parameter)

33
ILSpy/Commands/ExportCommandAttribute.cs

@ -52,8 +52,11 @@ namespace ICSharpCode.ILSpy
#region Main Menu #region Main Menu
public interface IMainMenuCommandMetadata public interface IMainMenuCommandMetadata
{ {
string MenuID { get; }
string MenuIcon { get; } string MenuIcon { get; }
string Header { get; } string Header { get; }
string ParentMenuID { get; }
[Obsolete("Please use ParentMenuID instead. We decided to rename the property for clarity. It will be removed in ILSpy 8.0.")]
string Menu { get; } string Menu { get; }
string MenuCategory { get; } string MenuCategory { get; }
string InputGestureText { get; } string InputGestureText { get; }
@ -65,22 +68,36 @@ namespace ICSharpCode.ILSpy
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ExportMainMenuCommandAttribute : ExportAttribute, IMainMenuCommandMetadata public class ExportMainMenuCommandAttribute : ExportAttribute, IMainMenuCommandMetadata
{ {
bool isEnabled = true;
public ExportMainMenuCommandAttribute() public ExportMainMenuCommandAttribute()
: base("MainMenuCommand", typeof(ICommand)) : base("MainMenuCommand", typeof(ICommand))
{ {
} }
/// <summary>
/// Gets/Sets the ID of this menu item. Menu entries are not required to have an ID,
/// however, setting it allows to declare nested menu structures.
/// The built-in menus have the IDs "_File", "_View", "_Window" and "_Help".
/// Plugin authors are advised to use GUIDs as identifiers to prevent conflicts.
/// <para/>
/// NOTE: Defining cycles (for example by accidentally setting <see cref="MenuID"/> equal to <see cref="ParentMenuID"/>)
/// will lead to a stack-overflow and crash of ILSpy at startup.
/// </summary>
public string MenuID { get; set; }
public string MenuIcon { get; set; } public string MenuIcon { get; set; }
public string Header { get; set; } public string Header { get; set; }
public string Menu { get; set; } /// <summary>
/// Gets/Sets the parent of this menu item. All menu items sharing the same parent will be displayed as sub-menu items.
/// If this property is set to <see langword="null"/>, the menu item is displayed in the top-level menu.
/// The built-in menus have the IDs "_File", "_View", "_Window" and "_Help".
/// <para/>
/// NOTE: Defining cycles (for example by accidentally setting <see cref="MenuID"/> equal to <see cref="ParentMenuID"/>)
/// will lead to a stack-overflow and crash of ILSpy at startup.
/// </summary>
public string ParentMenuID { get; set; }
[Obsolete("Please use ParentMenuID instead. We decided to rename the property for clarity. It will be removed in ILSpy 8.0.")]
public string Menu { get => ParentMenuID; set => ParentMenuID = value; }
public string MenuCategory { get; set; } public string MenuCategory { get; set; }
public string InputGestureText { get; set; } public string InputGestureText { get; set; }
public bool IsEnabled { public bool IsEnabled { get; set; } = true;
get { return isEnabled; }
set { isEnabled = value; }
}
public double MenuOrder { get; set; } public double MenuOrder { get; set; }
} }
#endregion #endregion

2
ILSpy/Commands/GeneratePdbContextMenuEntry.cs

@ -98,7 +98,7 @@ namespace ICSharpCode.ILSpy
} }
} }
[ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources.GeneratePortable), MenuCategory = nameof(Resources.Save))] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.GeneratePortable), MenuCategory = nameof(Resources.Save))]
class GeneratePdbMainMenuEntry : SimpleCommand class GeneratePdbMainMenuEntry : SimpleCommand
{ {
public override bool CanExecute(object parameter) public override bool CanExecute(object parameter)

2
ILSpy/Commands/ManageAssemblyListsCommand.cs

@ -21,7 +21,7 @@ using ICSharpCode.ILSpy.Properties;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
[ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources.ManageAssembly_Lists), MenuIcon = "Images/AssemblyList", MenuCategory = nameof(Resources.Open), MenuOrder = 1.7)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.ManageAssembly_Lists), MenuIcon = "Images/AssemblyList", MenuCategory = nameof(Resources.Open), MenuOrder = 1.7)]
sealed class ManageAssemblyListsCommand : SimpleCommand sealed class ManageAssemblyListsCommand : SimpleCommand
{ {
public override void Execute(object parameter) public override void Execute(object parameter)

2
ILSpy/Commands/OpenCommand.cs

@ -23,7 +23,7 @@ using ICSharpCode.ILSpy.Properties;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
[ExportToolbarCommand(ToolTip = nameof(Resources.Open), ToolbarIcon = "Images/Open", ToolbarCategory = nameof(Resources.Open), ToolbarOrder = 0)] [ExportToolbarCommand(ToolTip = nameof(Resources.Open), ToolbarIcon = "Images/Open", ToolbarCategory = nameof(Resources.Open), ToolbarOrder = 0)]
[ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources._Open), MenuIcon = "Images/Open", MenuCategory = nameof(Resources.Open), MenuOrder = 0)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources._Open), MenuIcon = "Images/Open", MenuCategory = nameof(Resources.Open), MenuOrder = 0)]
sealed class OpenCommand : CommandWrapper sealed class OpenCommand : CommandWrapper
{ {
public OpenCommand() public OpenCommand()

2
ILSpy/Commands/OpenFromGacCommand.cs

@ -19,7 +19,7 @@
using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.Properties;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
[ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources.OpenFrom_GAC), MenuIcon = "Images/AssemblyListGAC", MenuCategory = nameof(Resources.Open), MenuOrder = 1)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.OpenFrom_GAC), MenuIcon = "Images/AssemblyListGAC", MenuCategory = nameof(Resources.Open), MenuOrder = 1)]
sealed class OpenFromGacCommand : SimpleCommand sealed class OpenFromGacCommand : SimpleCommand
{ {
public override void Execute(object parameter) public override void Execute(object parameter)

2
ILSpy/Commands/Pdb2XmlCommand.cs

@ -33,7 +33,7 @@ using Microsoft.DiaSymReader.Tools;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
[ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources.DEBUGDumpPDBAsXML), MenuCategory = nameof(Resources.Open), MenuOrder = 2.6)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDumpPDBAsXML), MenuCategory = nameof(Resources.Open), MenuOrder = 2.6)]
sealed class Pdb2XmlCommand : SimpleCommand sealed class Pdb2XmlCommand : SimpleCommand
{ {
public override bool CanExecute(object parameter) public override bool CanExecute(object parameter)

2
ILSpy/Commands/RefreshCommand.cs

@ -23,7 +23,7 @@ using ICSharpCode.ILSpy.Properties;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
[ExportToolbarCommand(ToolTip = nameof(Resources.RefreshCommand_ReloadAssemblies), ToolbarIcon = "Images/Refresh", ToolbarCategory = nameof(Resources.Open), ToolbarOrder = 2)] [ExportToolbarCommand(ToolTip = nameof(Resources.RefreshCommand_ReloadAssemblies), ToolbarIcon = "Images/Refresh", ToolbarCategory = nameof(Resources.Open), ToolbarOrder = 2)]
[ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources._Reload), MenuIcon = "Images/Refresh", MenuCategory = nameof(Resources.Open), MenuOrder = 2)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources._Reload), MenuIcon = "Images/Refresh", MenuCategory = nameof(Resources.Open), MenuOrder = 2)]
sealed class RefreshCommand : CommandWrapper sealed class RefreshCommand : CommandWrapper
{ {
public RefreshCommand() public RefreshCommand()

2
ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs

@ -24,7 +24,7 @@ using ICSharpCode.ILSpy.Properties;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
[ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources._RemoveAssembliesWithLoadErrors), MenuCategory = nameof(Resources.Remove), MenuOrder = 2.6)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources._RemoveAssembliesWithLoadErrors), MenuCategory = nameof(Resources.Remove), MenuOrder = 2.6)]
class RemoveAssembliesWithLoadErrors : SimpleCommand class RemoveAssembliesWithLoadErrors : SimpleCommand
{ {
public override bool CanExecute(object parameter) public override bool CanExecute(object parameter)

2
ILSpy/Commands/SaveCommand.cs

@ -22,7 +22,7 @@ using ICSharpCode.ILSpy.Properties;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
[ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources._SaveCode), MenuIcon = "Images/Save", MenuCategory = nameof(Resources.Save), MenuOrder = 0)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources._SaveCode), MenuIcon = "Images/Save", MenuCategory = nameof(Resources.Save), MenuOrder = 0)]
sealed class SaveCommand : CommandWrapper sealed class SaveCommand : CommandWrapper
{ {
public SaveCommand() public SaveCommand()

4
ILSpy/Commands/SortAssemblyListCommand.cs

@ -24,7 +24,7 @@ using ICSharpCode.TreeView;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
[ExportMainMenuCommand(Menu = nameof(Resources._View), Header = nameof(Resources.SortAssembly_listName), MenuIcon = "Images/Sort", MenuCategory = nameof(Resources.View))] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._View), Header = nameof(Resources.SortAssembly_listName), MenuIcon = "Images/Sort", MenuCategory = nameof(Resources.View))]
[ExportToolbarCommand(ToolTip = nameof(Resources.SortAssemblyListName), ToolbarIcon = "Images/Sort", ToolbarCategory = nameof(Resources.View))] [ExportToolbarCommand(ToolTip = nameof(Resources.SortAssemblyListName), ToolbarIcon = "Images/Sort", ToolbarCategory = nameof(Resources.View))]
sealed class SortAssemblyListCommand : SimpleCommand, IComparer<LoadedAssembly> sealed class SortAssemblyListCommand : SimpleCommand, IComparer<LoadedAssembly>
{ {
@ -40,7 +40,7 @@ namespace ICSharpCode.ILSpy
} }
} }
[ExportMainMenuCommand(Menu = nameof(Resources._View), Header = nameof(Resources._CollapseTreeNodes), MenuIcon = "Images/CollapseAll", MenuCategory = nameof(Resources.View))] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._View), Header = nameof(Resources._CollapseTreeNodes), MenuIcon = "Images/CollapseAll", MenuCategory = nameof(Resources.View))]
[ExportToolbarCommand(ToolTip = nameof(Resources.CollapseTreeNodes), ToolbarIcon = "Images/CollapseAll", ToolbarCategory = nameof(Resources.View))] [ExportToolbarCommand(ToolTip = nameof(Resources.CollapseTreeNodes), ToolbarIcon = "Images/CollapseAll", ToolbarCategory = nameof(Resources.View))]
sealed class CollapseAllCommand : SimpleCommand sealed class CollapseAllCommand : SimpleCommand
{ {

98
ILSpy/ContextMenuEntry.cs

@ -17,6 +17,7 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.Collections.Generic;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
@ -117,6 +118,8 @@ namespace ICSharpCode.ILSpy
public interface IContextMenuEntryMetadata public interface IContextMenuEntryMetadata
{ {
string MenuID { get; }
string ParentMenuID { get; }
string Icon { get; } string Icon { get; }
string Header { get; } string Header { get; }
string Category { get; } string Category { get; }
@ -135,7 +138,23 @@ namespace ICSharpCode.ILSpy
// entries default to end of menu unless given specific order position // entries default to end of menu unless given specific order position
Order = double.MaxValue; Order = double.MaxValue;
} }
/// <summary>
/// Gets/Sets the ID of this menu item. Menu entries are not required to have an ID,
/// however, setting it allows to declare nested menu structures.
/// Plugin authors are advised to use GUIDs as identifiers to prevent conflicts.
/// <para/>
/// NOTE: Defining cycles (for example by accidentally setting <see cref="MenuID"/> equal to <see cref="ParentMenuID"/>)
/// will lead to a stack-overflow and crash of ILSpy at startup.
/// </summary>
public string MenuID { get; set; }
/// <summary>
/// Gets/Sets the parent of this menu item. All menu items sharing the same parent will be displayed as sub-menu items.
/// If this property is set to <see langword="null"/>, the menu item is displayed in the top-level menu.
/// <para/>
/// NOTE: Defining cycles (for example by accidentally setting <see cref="MenuID"/> equal to <see cref="ParentMenuID"/>)
/// will lead to a stack-overflow and crash of ILSpy at startup.
/// </summary>
public string ParentMenuID { get; set; }
public string Icon { get; set; } public string Icon { get; set; }
public string Header { get; set; } public string Header { get; set; }
public string Category { get; set; } public string Category { get; set; }
@ -267,41 +286,64 @@ namespace ICSharpCode.ILSpy
bool ShowContextMenu(TextViewContext context, out ContextMenu menu) bool ShowContextMenu(TextViewContext context, out ContextMenu menu)
{ {
menu = new ContextMenu(); menu = new ContextMenu();
foreach (var category in entries.OrderBy(c => c.Metadata.Order).GroupBy(c => c.Metadata.Category)) var menuGroups = new Dictionary<string, Lazy<IContextMenuEntry, IContextMenuEntryMetadata>[]>();
Lazy<IContextMenuEntry, IContextMenuEntryMetadata>[] topLevelGroup = null;
foreach (var group in entries.OrderBy(c => c.Metadata.Order).GroupBy(c => c.Metadata.ParentMenuID))
{ {
bool needSeparatorForCategory = menu.Items.Count > 0; if (group.Key == null)
foreach (var entryPair in category)
{ {
IContextMenuEntry entry = entryPair.Value; topLevelGroup = group.ToArray();
if (entry.IsVisible(context)) }
else
{
menuGroups.Add(group.Key, group.ToArray());
}
}
BuildMenu(topLevelGroup ?? Array.Empty<Lazy<IContextMenuEntry, IContextMenuEntryMetadata>>(), menu.Items);
return menu.Items.Count > 0;
void BuildMenu(Lazy<IContextMenuEntry, IContextMenuEntryMetadata>[] menuGroup, ItemCollection parent)
{
foreach (var category in menuGroup.GroupBy(c => c.Metadata.Category))
{
bool needSeparatorForCategory = parent.Count > 0;
foreach (var entryPair in category)
{ {
if (needSeparatorForCategory) IContextMenuEntry entry = entryPair.Value;
{ if (entry.IsVisible(context))
menu.Items.Add(new Separator());
needSeparatorForCategory = false;
}
MenuItem menuItem = new MenuItem();
menuItem.Header = MainWindow.GetResourceString(entryPair.Metadata.Header);
menuItem.InputGestureText = entryPair.Metadata.InputGestureText;
if (!string.IsNullOrEmpty(entryPair.Metadata.Icon))
{ {
menuItem.Icon = new Image { if (needSeparatorForCategory)
Width = 16, {
Height = 16, parent.Add(new Separator());
Source = Images.Load(entryPair.Value, entryPair.Metadata.Icon) needSeparatorForCategory = false;
}; }
} MenuItem menuItem = new MenuItem();
if (entryPair.Value.IsEnabled(context)) menuItem.Header = MainWindow.GetResourceString(entryPair.Metadata.Header);
{ menuItem.InputGestureText = entryPair.Metadata.InputGestureText;
menuItem.Click += delegate { entry.Execute(context); }; if (!string.IsNullOrEmpty(entryPair.Metadata.Icon))
{
menuItem.Icon = new Image {
Width = 16,
Height = 16,
Source = Images.Load(entryPair.Value, entryPair.Metadata.Icon)
};
}
if (entryPair.Value.IsEnabled(context))
{
menuItem.Click += delegate { entry.Execute(context); };
}
else
menuItem.IsEnabled = false;
parent.Add(menuItem);
if (entryPair.Metadata.MenuID != null && menuGroups.TryGetValue(entryPair.Metadata.MenuID, out var group))
{
BuildMenu(group, menuItem.Items);
}
} }
else
menuItem.IsEnabled = false;
menu.Items.Add(menuItem);
} }
} }
} }
return menu.Items.Count > 0;
} }
} }
} }

4
ILSpy/Docking/CloseAllDocumentsCommand.cs

@ -8,7 +8,7 @@ using ICSharpCode.ILSpy.Properties;
namespace ICSharpCode.ILSpy.Docking namespace ICSharpCode.ILSpy.Docking
{ {
[ExportMainMenuCommand(Header = nameof(Resources.Window_CloseAllDocuments), Menu = nameof(Resources._Window))] [ExportMainMenuCommand(Header = nameof(Resources.Window_CloseAllDocuments), ParentMenuID = nameof(Resources._Window))]
class CloseAllDocumentsCommand : SimpleCommand class CloseAllDocumentsCommand : SimpleCommand
{ {
public override void Execute(object parameter) public override void Execute(object parameter)
@ -17,7 +17,7 @@ namespace ICSharpCode.ILSpy.Docking
} }
} }
[ExportMainMenuCommand(Header = nameof(Resources.Window_ResetLayout), Menu = nameof(Resources._Window))] [ExportMainMenuCommand(Header = nameof(Resources.Window_ResetLayout), ParentMenuID = nameof(Resources._Window))]
class ResetLayoutCommand : SimpleCommand class ResetLayoutCommand : SimpleCommand
{ {
public override void Execute(object parameter) public override void Execute(object parameter)

16
ILSpy/ExtensionMethods.cs

@ -112,20 +112,12 @@ namespace ICSharpCode.ILSpy
} }
} }
/* internal static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> pair, out TKey key, out TValue value)
public static bool IsCustomAttribute(this TypeDefinition type)
{ {
while (type.FullName != "System.Object") { key = pair.Key;
var resolvedBaseType = type.BaseType.Resolve(); value = pair.Value;
if (resolvedBaseType == null)
return false;
if (resolvedBaseType.FullName == "System.Attribute")
return true;
type = resolvedBaseType;
}
return false;
} }
*/
public static string ToSuffixString(this System.Reflection.Metadata.EntityHandle handle) public static string ToSuffixString(this System.Reflection.Metadata.EntityHandle handle)
{ {
if (!DisplaySettingsPanel.CurrentDisplaySettings.ShowMetadataTokens) if (!DisplaySettingsPanel.CurrentDisplaySettings.ShowMetadataTokens)

6
ILSpy/MainWindow.xaml

@ -132,9 +132,9 @@
<DockPanel> <DockPanel>
<!-- Main menu --> <!-- Main menu -->
<Menu DockPanel.Dock="Top" Name="mainMenu" Height="23" KeyboardNavigation.TabNavigation="None"> <Menu DockPanel.Dock="Top" Name="mainMenu" Height="23" KeyboardNavigation.TabNavigation="None">
<MenuItem Header="{x:Static properties:Resources._File}" /> <MenuItem Header="{x:Static properties:Resources._File}" Tag="_File" />
<!-- contents of file menu are added using MEF --> <!-- contents of file menu are added using MEF -->
<MenuItem Header="{x:Static properties:Resources._View}"> <MenuItem Header="{x:Static properties:Resources._View}" Tag="_View">
<MenuItem Header="{x:Static properties:Resources.Show_publiconlyTypesMembers}" IsCheckable="True" IsChecked="{Binding Workspace.ActiveTabPage.FilterSettings.ApiVisPublicOnly}" /> <MenuItem Header="{x:Static properties:Resources.Show_publiconlyTypesMembers}" IsCheckable="True" IsChecked="{Binding Workspace.ActiveTabPage.FilterSettings.ApiVisPublicOnly}" />
<MenuItem Header="{x:Static properties:Resources.Show_internalTypesMembers}" IsCheckable="True" IsChecked="{Binding Workspace.ActiveTabPage.FilterSettings.ApiVisPublicAndInternal}" /> <MenuItem Header="{x:Static properties:Resources.Show_internalTypesMembers}" IsCheckable="True" IsChecked="{Binding Workspace.ActiveTabPage.FilterSettings.ApiVisPublicAndInternal}" />
<MenuItem Header="{x:Static properties:Resources.Show_allTypesAndMembers}" IsCheckable="True" IsChecked="{Binding Workspace.ActiveTabPage.FilterSettings.ApiVisAll}" /> <MenuItem Header="{x:Static properties:Resources.Show_allTypesAndMembers}" IsCheckable="True" IsChecked="{Binding Workspace.ActiveTabPage.FilterSettings.ApiVisAll}" />
@ -145,7 +145,7 @@
<MenuItem Header="中文" IsCheckable="True" IsChecked="{Binding SessionSettings.CurrentCulture, Converter={StaticResource cultureSelectionConverter}, ConverterParameter=zh-Hans}" /> <MenuItem Header="中文" IsCheckable="True" IsChecked="{Binding SessionSettings.CurrentCulture, Converter={StaticResource cultureSelectionConverter}, ConverterParameter=zh-Hans}" />
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem Header="{x:Static properties:Resources._Window}" /> <MenuItem Header="{x:Static properties:Resources._Window}" Tag="_Window" />
</Menu> </Menu>
<!-- ToolBar --> <!-- ToolBar -->
<ToolBar <ToolBar

103
ILSpy/MainWindow.xaml.cs

@ -146,7 +146,7 @@ namespace ICSharpCode.ILSpy
filterSettings = dock.ActiveTabPage.FilterSettings; filterSettings = dock.ActiveTabPage.FilterSettings;
filterSettings.PropertyChanged += filterSettings_PropertyChanged; filterSettings.PropertyChanged += filterSettings_PropertyChanged;
var windowMenuItem = mainMenu.Items.OfType<MenuItem>().First(m => GetResourceString(m.Header as string) == Properties.Resources._Window); var windowMenuItem = mainMenu.Items.OfType<MenuItem>().First(m => (string)m.Tag == Properties.Resources._Window);
foreach (MenuItem menuItem in windowMenuItem.Items.OfType<MenuItem>()) foreach (MenuItem menuItem in windowMenuItem.Items.OfType<MenuItem>())
{ {
if (menuItem.IsCheckable && menuItem.Tag is TabPageModel) if (menuItem.IsCheckable && menuItem.Tag is TabPageModel)
@ -241,48 +241,93 @@ namespace ICSharpCode.ILSpy
void InitMainMenu() void InitMainMenu()
{ {
var mainMenuCommands = App.ExportProvider.GetExports<ICommand, IMainMenuCommandMetadata>("MainMenuCommand"); var mainMenuCommands = App.ExportProvider.GetExports<ICommand, IMainMenuCommandMetadata>("MainMenuCommand");
foreach (var topLevelMenu in mainMenuCommands.OrderBy(c => c.Metadata.MenuOrder).GroupBy(c => GetResourceString(c.Metadata.Menu))) // Start by constructing the individual flat menus
{ var parentMenuItems = new Dictionary<string, MenuItem>();
var topLevelMenuItem = mainMenu.Items.OfType<MenuItem>().FirstOrDefault(m => GetResourceString(m.Header as string) == topLevelMenu.Key); var menuGroups = mainMenuCommands.OrderBy(c => c.Metadata.MenuOrder).GroupBy(c => c.Metadata.ParentMenuID);
foreach (var category in topLevelMenu.GroupBy(c => GetResourceString(c.Metadata.MenuCategory))) foreach (var menu in menuGroups)
{
// Get or add the target menu item and add all items grouped by menu category
var parentMenuItem = GetOrAddParentMenuItem(menu.Key, menu.Key);
foreach (var category in menu.GroupBy(c => c.Metadata.MenuCategory))
{ {
if (topLevelMenuItem == null) if (parentMenuItem.Items.Count > 0)
{
topLevelMenuItem = new MenuItem();
topLevelMenuItem.Header = GetResourceString(topLevelMenu.Key);
mainMenu.Items.Add(topLevelMenuItem);
}
else if (topLevelMenuItem.Items.Count > 0)
{ {
topLevelMenuItem.Items.Add(new Separator()); parentMenuItem.Items.Add(new Separator() { Tag = category.Key });
} }
foreach (var entry in category) foreach (var entry in category)
{ {
MenuItem menuItem = new MenuItem(); if (menuGroups.Any(g => g.Key == entry.Metadata.MenuID))
menuItem.Command = CommandWrapper.Unwrap(entry.Value); {
if (!string.IsNullOrEmpty(GetResourceString(entry.Metadata.Header))) var menuItem = GetOrAddParentMenuItem(entry.Metadata.MenuID, entry.Metadata.Header);
// replace potential dummy text with real name
menuItem.Header = GetResourceString(entry.Metadata.Header); menuItem.Header = GetResourceString(entry.Metadata.Header);
if (!string.IsNullOrEmpty(entry.Metadata.MenuIcon)) parentMenuItem.Items.Add(menuItem);
}
else
{ {
menuItem.Icon = new Image { MenuItem menuItem = new MenuItem();
Width = 16, menuItem.Command = CommandWrapper.Unwrap(entry.Value);
Height = 16, menuItem.Tag = entry.Metadata.MenuID;
Source = Images.Load(entry.Value, entry.Metadata.MenuIcon) menuItem.Header = GetResourceString(entry.Metadata.Header);
}; if (!string.IsNullOrEmpty(entry.Metadata.MenuIcon))
{
menuItem.Icon = new Image {
Width = 16,
Height = 16,
Source = Images.Load(entry.Value, entry.Metadata.MenuIcon)
};
}
menuItem.IsEnabled = entry.Metadata.IsEnabled;
menuItem.InputGestureText = entry.Metadata.InputGestureText;
parentMenuItem.Items.Add(menuItem);
} }
}
}
}
foreach (var (key, item) in parentMenuItems)
{
if (item.Parent == null)
{
mainMenu.Items.Add(item);
}
}
menuItem.IsEnabled = entry.Metadata.IsEnabled; MenuItem GetOrAddParentMenuItem(string menuID, string resourceKey)
menuItem.InputGestureText = entry.Metadata.InputGestureText; {
topLevelMenuItem.Items.Add(menuItem); if (!parentMenuItems.TryGetValue(menuID, out var parentMenuItem))
{
var topLevelMenuItem = mainMenu.Items.OfType<MenuItem>().FirstOrDefault(m => (string)m.Tag == menuID);
if (topLevelMenuItem == null)
{
parentMenuItem = new MenuItem();
parentMenuItem.Header = GetResourceString(resourceKey);
parentMenuItem.Tag = menuID;
parentMenuItems.Add(menuID, parentMenuItem);
}
else
{
parentMenuItems.Add(menuID, topLevelMenuItem);
parentMenuItem = topLevelMenuItem;
} }
} }
return parentMenuItem;
} }
} }
internal static string GetResourceString(string key) internal static string GetResourceString(string key)
{ {
var str = !string.IsNullOrEmpty(key) ? Properties.Resources.ResourceManager.GetString(key) : null; if (string.IsNullOrEmpty(key))
return string.IsNullOrEmpty(key) || string.IsNullOrEmpty(str) ? key : str; {
return null;
}
string value = Properties.Resources.ResourceManager.GetString(key);
if (!string.IsNullOrEmpty(value))
{
return value;
}
return key;
} }
#endregion #endregion
@ -310,7 +355,7 @@ namespace ICSharpCode.ILSpy
private void InitWindowMenu() private void InitWindowMenu()
{ {
var windowMenuItem = mainMenu.Items.OfType<MenuItem>().First(m => GetResourceString(m.Header as string) == Properties.Resources._Window); var windowMenuItem = mainMenu.Items.OfType<MenuItem>().First(m => (string)m.Tag == Properties.Resources._Window);
Separator separatorBeforeTools, separatorBeforeDocuments; Separator separatorBeforeTools, separatorBeforeDocuments;
windowMenuItem.Items.Add(separatorBeforeTools = new Separator()); windowMenuItem.Items.Add(separatorBeforeTools = new Separator());
windowMenuItem.Items.Add(separatorBeforeDocuments = new Separator()); windowMenuItem.Items.Add(separatorBeforeDocuments = new Separator());
@ -472,7 +517,7 @@ namespace ICSharpCode.ILSpy
static void TabPageChanged(object sender, PropertyChangedEventArgs e) static void TabPageChanged(object sender, PropertyChangedEventArgs e)
{ {
var windowMenuItem = Instance.mainMenu.Items.OfType<MenuItem>().First(m => GetResourceString(m.Header as string) == Properties.Resources._Window); var windowMenuItem = Instance.mainMenu.Items.OfType<MenuItem>().First(m => (string)m.Tag == nameof(Properties.Resources._Window));
foreach (MenuItem menuItem in windowMenuItem.Items.OfType<MenuItem>()) foreach (MenuItem menuItem in windowMenuItem.Items.OfType<MenuItem>())
{ {
if (menuItem.IsCheckable && menuItem.Tag == sender) if (menuItem.IsCheckable && menuItem.Tag == sender)

2
ILSpy/Options/OptionsDialog.xaml.cs

@ -121,7 +121,7 @@ namespace ICSharpCode.ILSpy.Options
public int Order { get; set; } public int Order { get; set; }
} }
[ExportMainMenuCommand(Menu = nameof(Resources._View), Header = nameof(Resources._Options), MenuCategory = nameof(Resources.Options), MenuOrder = 999)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._View), Header = nameof(Resources._Options), MenuCategory = nameof(Resources.Options), MenuOrder = 999)]
sealed class ShowOptionsCommand : SimpleCommand sealed class ShowOptionsCommand : SimpleCommand
{ {
public override void Execute(object parameter) public override void Execute(object parameter)

2
TestPlugin/MainMenuCommand.cs

@ -10,7 +10,7 @@ namespace TestPlugin
// Header: text on the menu item // Header: text on the menu item
// MenuCategory: optional, used for grouping related menu items together. A separator is added between different groups. // MenuCategory: optional, used for grouping related menu items together. A separator is added between different groups.
// MenuOrder: controls the order in which the items appear (items are sorted by this value) // MenuOrder: controls the order in which the items appear (items are sorted by this value)
[ExportMainMenuCommand(Menu = "_File", MenuIcon = "Clear.png", Header = "_Clear List", MenuCategory = "Open", MenuOrder = 1.5)] [ExportMainMenuCommand(ParentMenuID = "_File", MenuIcon = "Clear.png", Header = "_Clear List", MenuCategory = "Open", MenuOrder = 1.5)]
// ToolTip: the tool tip // ToolTip: the tool tip
// ToolbarIcon: The icon. Must be embedded as "Resource" (WPF-style resource) in the same assembly as the command type. // ToolbarIcon: The icon. Must be embedded as "Resource" (WPF-style resource) in the same assembly as the command type.
// ToolbarCategory: optional, used for grouping related toolbar items together. A separator is added between different groups. // ToolbarCategory: optional, used for grouping related toolbar items together. A separator is added between different groups.

Loading…
Cancel
Save