Browse Source

refactored ContextMenu API to allow opening a context menu in both tree view and text view. closes #215

pull/348/head
Siegfried Pammer 14 years ago
parent
commit
bfda87c43b
  1. 16
      Debugger/ILSpy.Debugger/Commands/DebuggerCommands.cs
  2. 93
      ILSpy/ContextMenuEntry.cs
  3. 2
      ILSpy/Languages/CSharpLanguage.cs
  4. 2
      ILSpy/MainWindow.xaml.cs
  5. 2
      ILSpy/TextView/AvalonEditTextOutput.cs
  6. 11
      ILSpy/TextView/DecompilerTextView.cs
  7. 38
      ILSpy/TreeNodes/Analyzer/AnalyzeContextMenuEntry.cs
  8. 16
      ILSpy/TreeNodes/AssemblyTreeNode.cs
  9. 16
      TestPlugin/ContextMenuCommand.cs

16
Debugger/ILSpy.Debugger/Commands/DebuggerCommands.cs

@ -179,12 +179,12 @@ namespace ICSharpCode.ILSpy.Debugger.Commands
} }
} }
[ExportContextMenuEntry(Header = "_Debug Assembly", Icon = "Images/application-x-executable.png")] [ExportContextMenuEntryAttribute(Header = "_Debug Assembly", Icon = "Images/application-x-executable.png")]
internal sealed class DebugExecutableNodeCommand : DebuggerCommand, IContextMenuEntry internal sealed class DebugExecutableNodeCommand : DebuggerCommand, IContextMenuEntry
{ {
public bool IsVisible(SharpTreeNode[] selectedNodes) public bool IsVisible(TextViewContext context)
{ {
return selectedNodes.All( return context.SelectedTreeNodes != null && context.SelectedTreeNodes.All(
delegate (SharpTreeNode n) { delegate (SharpTreeNode n) {
AssemblyTreeNode a = n as AssemblyTreeNode; AssemblyTreeNode a = n as AssemblyTreeNode;
if (a == null) if (a == null)
@ -194,15 +194,17 @@ namespace ICSharpCode.ILSpy.Debugger.Commands
}); });
} }
public bool IsEnabled(SharpTreeNode[] selectedNodes) public bool IsEnabled(TextViewContext context)
{ {
return selectedNodes.Length == 1; return context.SelectedTreeNodes != null && context.SelectedTreeNodes.Length == 1;
} }
public void Execute(SharpTreeNode[] selectedNodes) public void Execute(TextViewContext context)
{ {
if (context.SelectedTreeNodes == null)
return;
if (!CurrentDebugger.IsDebugging) { if (!CurrentDebugger.IsDebugging) {
AssemblyTreeNode n = selectedNodes[0] as AssemblyTreeNode; AssemblyTreeNode n = context.SelectedTreeNodes[0] as AssemblyTreeNode;
if (DebuggerSettings.Instance.AskForArguments) { if (DebuggerSettings.Instance.AskForArguments) {
var window = new ExecuteProcessWindow { Owner = MainWindow.Instance, var window = new ExecuteProcessWindow { Owner = MainWindow.Instance,

93
ILSpy/ContextMenuEntry.cs

@ -19,17 +19,49 @@
using System; using System;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.Linq; using System.Linq;
using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using ICSharpCode.ILSpy.TextView;
using ICSharpCode.TreeView; using ICSharpCode.TreeView;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
public interface IContextMenuEntry public interface IContextMenuEntry
{ {
bool IsVisible(SharpTreeNode[] selectedNodes); bool IsVisible(TextViewContext context);
bool IsEnabled(SharpTreeNode[] selectedNodes); bool IsEnabled(TextViewContext context);
void Execute(SharpTreeNode[] selectedNodes); void Execute(TextViewContext context);
}
public class TextViewContext
{
/// <summary>
/// Returns the selected nodes in the tree view.
/// Returns null, if context menu does not belong to a tree view.
/// </summary>
public SharpTreeNode[] SelectedTreeNodes { get; private set; }
/// <summary>
/// Returns the text view the context menu is assigned to.
/// Returns null, if context menu is not assigned to a text view.
/// </summary>
public DecompilerTextView TextView { get; private set; }
/// <summary>
/// Returns the reference the mouse cursor is currently hovering above.
/// Returns null, if there was no reference found.
/// </summary>
public ReferenceSegment Reference { get; private set; }
public static TextViewContext Create(SharpTreeNode[] selectedTreeNodes = null, DecompilerTextView textView = null)
{
var reference = textView != null ? textView.GetReferenceSegmentAtMousePosition() : null;
return new TextViewContext {
SelectedTreeNodes = selectedTreeNodes,
TextView = textView,
Reference = reference
};
}
} }
public interface IContextMenuEntryMetadata public interface IContextMenuEntryMetadata
@ -61,23 +93,31 @@ namespace ICSharpCode.ILSpy
/// <summary> /// <summary>
/// Enables extensible context menu support for the specified tree view. /// Enables extensible context menu support for the specified tree view.
/// </summary> /// </summary>
public static void Add(SharpTreeView treeView) public static void Add(SharpTreeView treeView, DecompilerTextView textView = null)
{ {
var provider = new ContextMenuProvider(treeView); var provider = new ContextMenuProvider(treeView, textView);
treeView.ContextMenuOpening += provider.treeView_ContextMenuOpening; treeView.ContextMenuOpening += provider.treeView_ContextMenuOpening;
// Context menu is shown only when the ContextMenu property is not null before the // Context menu is shown only when the ContextMenu property is not null before the
// ContextMenuOpening event handler is called. // ContextMenuOpening event handler is called.
treeView.ContextMenu = new ContextMenu(); 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();
}
} }
readonly SharpTreeView treeView; readonly SharpTreeView treeView;
readonly DecompilerTextView textView;
[ImportMany(typeof(IContextMenuEntry))] [ImportMany(typeof(IContextMenuEntry))]
Lazy<IContextMenuEntry, IContextMenuEntryMetadata>[] entries = null; Lazy<IContextMenuEntry, IContextMenuEntryMetadata>[] entries = null;
private ContextMenuProvider(SharpTreeView treeView) ContextMenuProvider(SharpTreeView treeView, DecompilerTextView textView = null)
{ {
this.treeView = treeView; this.treeView = treeView;
this.textView = textView;
App.CompositionContainer.ComposeParts(this); App.CompositionContainer.ComposeParts(this);
} }
@ -88,12 +128,34 @@ namespace ICSharpCode.ILSpy
e.Handled = true; // don't show the menu e.Handled = true; // don't show the menu
return; return;
} }
ContextMenu menu = new ContextMenu(); TextViewContext context = TextViewContext.Create(selectedNodes);
ContextMenu menu;
if (ShowContextMenu(context, out menu))
treeView.ContextMenu = menu;
else
// hide the context menu.
e.Handled = true;
}
void textView_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
TextViewContext context = TextViewContext.Create(textView: textView);
ContextMenu menu;
if (ShowContextMenu(context, out menu))
textView.ContextMenu = menu;
else
// hide the context menu.
e.Handled = true;
}
bool ShowContextMenu(TextViewContext context, out ContextMenu menu)
{
menu = new ContextMenu();
foreach (var category in entries.OrderBy(c => c.Metadata.Order).GroupBy(c => c.Metadata.Category)) { foreach (var category in entries.OrderBy(c => c.Metadata.Order).GroupBy(c => c.Metadata.Category)) {
bool needSeparatorForCategory = true; bool needSeparatorForCategory = true;
foreach (var entryPair in category) { foreach (var entryPair in category) {
IContextMenuEntry entry = entryPair.Value; IContextMenuEntry entry = entryPair.Value;
if (entry.IsVisible(selectedNodes)) { if (entry.IsVisible(context)) {
if (needSeparatorForCategory && menu.Items.Count > 0) { if (needSeparatorForCategory && menu.Items.Count > 0) {
menu.Items.Add(new Separator()); menu.Items.Add(new Separator());
needSeparatorForCategory = false; needSeparatorForCategory = false;
@ -107,22 +169,15 @@ namespace ICSharpCode.ILSpy
Source = Images.LoadImage(entry, entryPair.Metadata.Icon) Source = Images.LoadImage(entry, entryPair.Metadata.Icon)
}; };
} }
if (entryPair.Value.IsEnabled(selectedNodes)) { if (entryPair.Value.IsEnabled(context)) {
menuItem.Click += delegate menuItem.Click += delegate { entry.Execute(context); };
{
entry.Execute(selectedNodes);
};
} else } else
menuItem.IsEnabled = false; menuItem.IsEnabled = false;
menu.Items.Add(menuItem); menu.Items.Add(menuItem);
} }
} }
} }
if (menu.Items.Count > 0) return menu.Items.Count > 0;
treeView.ContextMenu = menu;
else
// hide the context menu.
e.Handled = true;
} }
} }
} }

2
ILSpy/Languages/CSharpLanguage.cs

@ -230,7 +230,6 @@ namespace ICSharpCode.ILSpy
return "x86"; return "x86";
else else
return "AnyCPU (64-bit preferred)"; return "AnyCPU (64-bit preferred)";
break;
case TargetArchitecture.AMD64: case TargetArchitecture.AMD64:
return "x64"; return "x64";
case TargetArchitecture.IA64: case TargetArchitecture.IA64:
@ -250,7 +249,6 @@ namespace ICSharpCode.ILSpy
return "x86"; return "x86";
else else
return "AnyCPU"; return "AnyCPU";
break;
case TargetArchitecture.AMD64: case TargetArchitecture.AMD64:
return "x64"; return "x64";
case TargetArchitecture.IA64: case TargetArchitecture.IA64:

2
ILSpy/MainWindow.xaml.cs

@ -90,7 +90,7 @@ namespace ICSharpCode.ILSpy
InitMainMenu(); InitMainMenu();
InitToolbar(); InitToolbar();
ContextMenuProvider.Add(treeView); ContextMenuProvider.Add(treeView, decompilerTextView);
this.Loaded += new RoutedEventHandler(MainWindow_Loaded); this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
} }

2
ILSpy/TextView/AvalonEditTextOutput.cs

@ -33,7 +33,7 @@ namespace ICSharpCode.ILSpy.TextView
/// <summary> /// <summary>
/// A text segment that references some object. Used for hyperlinks in the editor. /// A text segment that references some object. Used for hyperlinks in the editor.
/// </summary> /// </summary>
sealed class ReferenceSegment : TextSegment public sealed class ReferenceSegment : TextSegment
{ {
public object Reference; public object Reference;
public bool IsLocal; public bool IsLocal;

11
ILSpy/TextView/DecompilerTextView.cs

@ -182,7 +182,7 @@ namespace ICSharpCode.ILSpy.TextView
TextViewPosition? position = textEditor.TextArea.TextView.GetPosition(e.GetPosition(textEditor.TextArea.TextView) + textEditor.TextArea.TextView.ScrollOffset); TextViewPosition? position = textEditor.TextArea.TextView.GetPosition(e.GetPosition(textEditor.TextArea.TextView) + textEditor.TextArea.TextView.ScrollOffset);
if (position == null) if (position == null)
return; return;
int offset = textEditor.Document.GetOffset(position.Value); int offset = textEditor.Document.GetOffset(position.Value.Location);
ReferenceSegment seg = referenceElementGenerator.References.FindSegmentsContaining(offset).FirstOrDefault(); ReferenceSegment seg = referenceElementGenerator.References.FindSegmentsContaining(offset).FirstOrDefault();
if (seg == null) if (seg == null)
return; return;
@ -761,6 +761,15 @@ namespace ICSharpCode.ILSpy.TextView
} }
#endregion #endregion
internal ReferenceSegment GetReferenceSegmentAtMousePosition()
{
TextViewPosition? position = textEditor.TextArea.TextView.GetPosition(Mouse.GetPosition(textEditor.TextArea.TextView) + textEditor.TextArea.TextView.ScrollOffset);
if (position == null)
return null;
int offset = textEditor.Document.GetOffset(position.Value.Location);
return referenceElementGenerator.References.FindSegmentsContaining(offset).FirstOrDefault();
}
public DecompilerTextViewState GetState() public DecompilerTextViewState GetState()
{ {
if (decompiledNodes == null) if (decompiledNodes == null)

38
ILSpy/TreeNodes/Analyzer/AnalyzeContextMenuEntry.cs

@ -18,22 +18,27 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Windows;
using ICSharpCode.TreeView; using ICSharpCode.TreeView;
using Mono.Cecil; using Mono.Cecil;
namespace ICSharpCode.ILSpy.TreeNodes.Analyzer namespace ICSharpCode.ILSpy.TreeNodes.Analyzer
{ {
[ExportContextMenuEntry(Header = "Analyze", Icon = "images/Search.png")] [ExportContextMenuEntryAttribute(Header = "Analyze", Icon = "images/Search.png")]
internal sealed class AnalyzeContextMenuEntry : IContextMenuEntry internal sealed class AnalyzeContextMenuEntry : IContextMenuEntry
{ {
public bool IsVisible(SharpTreeNode[] selectedNodes) public bool IsVisible(TextViewContext context)
{ {
return selectedNodes.All(n => n is IMemberTreeNode); if (context.SelectedTreeNodes == null)
return context.Reference != null && context.Reference.Reference is MemberReference;
return context.SelectedTreeNodes.All(n => n is IMemberTreeNode);
} }
public bool IsEnabled(SharpTreeNode[] selectedNodes) public bool IsEnabled(TextViewContext context)
{ {
foreach (IMemberTreeNode node in selectedNodes) { if (context.SelectedTreeNodes == null)
return context.Reference != null && context.Reference.Reference is MemberReference;
foreach (IMemberTreeNode node in context.SelectedTreeNodes) {
if (!(node.Member is TypeDefinition if (!(node.Member is TypeDefinition
|| node.Member is FieldDefinition || node.Member is FieldDefinition
|| node.Member is MethodDefinition || node.Member is MethodDefinition
@ -45,20 +50,29 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer
return true; return true;
} }
public void Execute(SharpTreeNode[] selectedNodes) public void Execute(TextViewContext context)
{ {
// TODO: figure out when equivalent nodes are already present var segment = context.Reference;
// and focus those instead. if (context.SelectedTreeNodes != null) {
foreach (IMemberTreeNode node in selectedNodes) { // TODO: figure out when equivalent nodes are already present
Analyze(node.Member); // and focus those instead.
foreach (IMemberTreeNode node in context.SelectedTreeNodes) {
Analyze(node.Member);
}
} else if (segment != null && segment.Reference is MemberReference) {
if (segment.Reference is MemberReference)
Analyze((MemberReference)segment.Reference);
// TODO: implement support for other references: ParameterReference, etc.
} }
} }
public static void Analyze(MemberReference member) public static void Analyze(MemberReference member)
{ {
TypeDefinition type = member as TypeDefinition; TypeDefinition type = null;
if (member is TypeReference)
type = ((TypeReference)member).Resolve();
if (type != null) if (type != null)
AnalyzerTreeView.Instance.Show(new AnalyzedTypeTreeNode(type)); AnalyzerTreeView.Instance.Show(new AnalyzedTypeTreeNode(type.Resolve()));
FieldDefinition field = member as FieldDefinition; FieldDefinition field = member as FieldDefinition;
if (field != null) if (field != null)
AnalyzerTreeView.Instance.Show(new AnalyzedFieldTreeNode(field)); AnalyzerTreeView.Instance.Show(new AnalyzedFieldTreeNode(field));

16
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -255,22 +255,26 @@ namespace ICSharpCode.ILSpy.TreeNodes
} }
} }
[ExportContextMenuEntry(Header = "_Remove", Icon = "images/Delete.png")] [ExportContextMenuEntryAttribute(Header = "_Remove", Icon = "images/Delete.png")]
sealed class RemoveAssembly : IContextMenuEntry sealed class RemoveAssembly : IContextMenuEntry
{ {
public bool IsVisible(SharpTreeNode[] selectedNodes) public bool IsVisible(TextViewContext context)
{ {
return selectedNodes.All(n => n is AssemblyTreeNode); if (context.SelectedTreeNodes == null)
return false;
return context.SelectedTreeNodes.All(n => n is AssemblyTreeNode);
} }
public bool IsEnabled(SharpTreeNode[] selectedNodes) public bool IsEnabled(TextViewContext context)
{ {
return true; return true;
} }
public void Execute(SharpTreeNode[] selectedNodes) public void Execute(TextViewContext context)
{ {
foreach (var node in selectedNodes) { if (context.SelectedTreeNodes == null)
return;
foreach (var node in context.SelectedTreeNodes) {
node.Delete(); node.Delete();
} }
} }

16
TestPlugin/ContextMenuCommand.cs

@ -11,22 +11,24 @@ using Mono.Cecil;
namespace TestPlugin namespace TestPlugin
{ {
[ExportContextMenuEntry(Header = "_Save Assembly")] [ExportContextMenuEntryAttribute(Header = "_Save Assembly")]
public class SaveAssembly : IContextMenuEntry public class SaveAssembly : IContextMenuEntry
{ {
public bool IsVisible(SharpTreeNode[] selectedNodes) public bool IsVisible(TextViewContext context)
{ {
return selectedNodes.All(n => n is AssemblyTreeNode); return context.SelectedTreeNodes != null && context.SelectedTreeNodes.All(n => n is AssemblyTreeNode);
} }
public bool IsEnabled(SharpTreeNode[] selectedNodes) public bool IsEnabled(TextViewContext context)
{ {
return selectedNodes.Length == 1; return context.SelectedTreeNodes != null && context.SelectedTreeNodes.Length == 1;
} }
public void Execute(SharpTreeNode[] selectedNodes) public void Execute(TextViewContext context)
{ {
AssemblyTreeNode node = (AssemblyTreeNode)selectedNodes[0]; if (context.SelectedTreeNodes == null)
return;
AssemblyTreeNode node = (AssemblyTreeNode)context.SelectedTreeNodes[0];
AssemblyDefinition asm = node.LoadedAssembly.AssemblyDefinition as AssemblyDefinition; AssemblyDefinition asm = node.LoadedAssembly.AssemblyDefinition as AssemblyDefinition;
if (asm != null) { if (asm != null) {
SaveFileDialog dlg = new SaveFileDialog(); SaveFileDialog dlg = new SaveFileDialog();

Loading…
Cancel
Save