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 13 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 @@ -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
{
public bool IsVisible(SharpTreeNode[] selectedNodes)
public bool IsVisible(TextViewContext context)
{
return selectedNodes.All(
return context.SelectedTreeNodes != null && context.SelectedTreeNodes.All(
delegate (SharpTreeNode n) {
AssemblyTreeNode a = n as AssemblyTreeNode;
if (a == null)
@ -194,15 +194,17 @@ namespace ICSharpCode.ILSpy.Debugger.Commands @@ -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) {
AssemblyTreeNode n = selectedNodes[0] as AssemblyTreeNode;
AssemblyTreeNode n = context.SelectedTreeNodes[0] as AssemblyTreeNode;
if (DebuggerSettings.Instance.AskForArguments) {
var window = new ExecuteProcessWindow { Owner = MainWindow.Instance,

93
ILSpy/ContextMenuEntry.cs

@ -19,17 +19,49 @@ @@ -19,17 +19,49 @@
using System;
using System.ComponentModel.Composition;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using ICSharpCode.ILSpy.TextView;
using ICSharpCode.TreeView;
namespace ICSharpCode.ILSpy
{
public interface IContextMenuEntry
{
bool IsVisible(SharpTreeNode[] selectedNodes);
bool IsEnabled(SharpTreeNode[] selectedNodes);
void Execute(SharpTreeNode[] selectedNodes);
bool IsVisible(TextViewContext context);
bool IsEnabled(TextViewContext context);
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
@ -61,23 +93,31 @@ namespace ICSharpCode.ILSpy @@ -61,23 +93,31 @@ namespace ICSharpCode.ILSpy
/// <summary>
/// Enables extensible context menu support for the specified tree view.
/// </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;
// Context menu is shown only when the ContextMenu property is not null before the
// ContextMenuOpening event handler is called.
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 DecompilerTextView textView;
[ImportMany(typeof(IContextMenuEntry))]
Lazy<IContextMenuEntry, IContextMenuEntryMetadata>[] entries = null;
private ContextMenuProvider(SharpTreeView treeView)
ContextMenuProvider(SharpTreeView treeView, DecompilerTextView textView = null)
{
this.treeView = treeView;
this.textView = textView;
App.CompositionContainer.ComposeParts(this);
}
@ -88,12 +128,34 @@ namespace ICSharpCode.ILSpy @@ -88,12 +128,34 @@ namespace ICSharpCode.ILSpy
e.Handled = true; // don't show the menu
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)) {
bool needSeparatorForCategory = true;
foreach (var entryPair in category) {
IContextMenuEntry entry = entryPair.Value;
if (entry.IsVisible(selectedNodes)) {
if (entry.IsVisible(context)) {
if (needSeparatorForCategory && menu.Items.Count > 0) {
menu.Items.Add(new Separator());
needSeparatorForCategory = false;
@ -107,22 +169,15 @@ namespace ICSharpCode.ILSpy @@ -107,22 +169,15 @@ namespace ICSharpCode.ILSpy
Source = Images.LoadImage(entry, entryPair.Metadata.Icon)
};
}
if (entryPair.Value.IsEnabled(selectedNodes)) {
menuItem.Click += delegate
{
entry.Execute(selectedNodes);
};
if (entryPair.Value.IsEnabled(context)) {
menuItem.Click += delegate { entry.Execute(context); };
} else
menuItem.IsEnabled = false;
menu.Items.Add(menuItem);
}
}
}
if (menu.Items.Count > 0)
treeView.ContextMenu = menu;
else
// hide the context menu.
e.Handled = true;
return menu.Items.Count > 0;
}
}
}

2
ILSpy/Languages/CSharpLanguage.cs

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

2
ILSpy/MainWindow.xaml.cs

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

2
ILSpy/TextView/AvalonEditTextOutput.cs

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

11
ILSpy/TextView/DecompilerTextView.cs

@ -182,7 +182,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -182,7 +182,7 @@ namespace ICSharpCode.ILSpy.TextView
TextViewPosition? position = textEditor.TextArea.TextView.GetPosition(e.GetPosition(textEditor.TextArea.TextView) + textEditor.TextArea.TextView.ScrollOffset);
if (position == null)
return;
int offset = textEditor.Document.GetOffset(position.Value);
int offset = textEditor.Document.GetOffset(position.Value.Location);
ReferenceSegment seg = referenceElementGenerator.References.FindSegmentsContaining(offset).FirstOrDefault();
if (seg == null)
return;
@ -761,6 +761,15 @@ namespace ICSharpCode.ILSpy.TextView @@ -761,6 +761,15 @@ namespace ICSharpCode.ILSpy.TextView
}
#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()
{
if (decompiledNodes == null)

38
ILSpy/TreeNodes/Analyzer/AnalyzeContextMenuEntry.cs

@ -18,22 +18,27 @@ @@ -18,22 +18,27 @@
using System;
using System.Linq;
using System.Windows;
using ICSharpCode.TreeView;
using Mono.Cecil;
namespace ICSharpCode.ILSpy.TreeNodes.Analyzer
{
[ExportContextMenuEntry(Header = "Analyze", Icon = "images/Search.png")]
[ExportContextMenuEntryAttribute(Header = "Analyze", Icon = "images/Search.png")]
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
|| node.Member is FieldDefinition
|| node.Member is MethodDefinition
@ -45,20 +50,29 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer @@ -45,20 +50,29 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer
return true;
}
public void Execute(SharpTreeNode[] selectedNodes)
public void Execute(TextViewContext context)
{
// TODO: figure out when equivalent nodes are already present
// and focus those instead.
foreach (IMemberTreeNode node in selectedNodes) {
Analyze(node.Member);
var segment = context.Reference;
if (context.SelectedTreeNodes != null) {
// TODO: figure out when equivalent nodes are already present
// 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)
{
TypeDefinition type = member as TypeDefinition;
TypeDefinition type = null;
if (member is TypeReference)
type = ((TypeReference)member).Resolve();
if (type != null)
AnalyzerTreeView.Instance.Show(new AnalyzedTypeTreeNode(type));
AnalyzerTreeView.Instance.Show(new AnalyzedTypeTreeNode(type.Resolve()));
FieldDefinition field = member as FieldDefinition;
if (field != null)
AnalyzerTreeView.Instance.Show(new AnalyzedFieldTreeNode(field));

16
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -255,22 +255,26 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -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
{
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;
}
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();
}
}

16
TestPlugin/ContextMenuCommand.cs

@ -11,22 +11,24 @@ using Mono.Cecil; @@ -11,22 +11,24 @@ using Mono.Cecil;
namespace TestPlugin
{
[ExportContextMenuEntry(Header = "_Save Assembly")]
[ExportContextMenuEntryAttribute(Header = "_Save Assembly")]
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;
if (asm != null) {
SaveFileDialog dlg = new SaveFileDialog();

Loading…
Cancel
Save