Browse Source
git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4049 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61shortcuts
33 changed files with 1720 additions and 1 deletions
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníèek" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph |
||||
{ |
||||
/// <summary>
|
||||
/// Description of DebuggerVisualizerException.
|
||||
/// </summary>
|
||||
public class DebuggerVisualizerException : ApplicationException |
||||
{ |
||||
public DebuggerVisualizerException() |
||||
: base() |
||||
{ |
||||
} |
||||
|
||||
public DebuggerVisualizerException(string message) |
||||
: base(message) |
||||
{ |
||||
} |
||||
|
||||
public DebuggerVisualizerException(string message, System.Exception innerException) |
||||
: base(message, innerException) |
||||
{ |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
using System; |
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System.Collections.Generic; |
||||
using System.Text; |
||||
using Debugger.AddIn.Visualizers.Graph; |
||||
using System.Windows.Controls; |
||||
using System.Windows.Media; |
||||
using System.Windows; |
||||
using Debugger.AddIn.Visualizers.Graph.Drawing; |
||||
using Debugger.AddIn.Visualizers.Graph.Layout; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph |
||||
{ |
||||
/// <summary>
|
||||
/// Draws ObjectGraph on a Canvas.
|
||||
/// </summary>
|
||||
public class GraphDrawer |
||||
{ |
||||
private ObjectGraph graph; |
||||
|
||||
public GraphDrawer(ObjectGraph graph) |
||||
{ |
||||
this.graph = graph; |
||||
} |
||||
|
||||
public static void Draw(PositionedGraph posGraph, Canvas canvas) |
||||
{ |
||||
canvas.Children.Clear(); |
||||
|
||||
foreach (PositionedNode node in posGraph.Nodes) |
||||
{ |
||||
canvas.Children.Add(node.NodeVisualControl); |
||||
Canvas.SetLeft(node.NodeVisualControl, node.Left); |
||||
Canvas.SetTop(node.NodeVisualControl, node.Top); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
<UserControl x:Class="Debugger.AddIn.Visualizers.Graph.Drawing.NodeControl" |
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
xmlns:dropShadow="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"> |
||||
<dropShadow:SystemDropShadowChrome> |
||||
<Border Name="border1" BorderBrush="Black" BorderThickness="1" Padding="04,04,04,04" |
||||
Canvas.Left="20" Canvas.Top="20"> |
||||
<Border.Background> |
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> |
||||
<GradientStop Color="#ddeeff" Offset="0.0" /> |
||||
<GradientStop Color="White" Offset="0.4" /> |
||||
<GradientStop Color="White" Offset="0.8" /> |
||||
</LinearGradientBrush> |
||||
</Border.Background> |
||||
<Grid Name="propertyGrid"> |
||||
<Grid.RowDefinitions> |
||||
</Grid.RowDefinitions> |
||||
<Grid.ColumnDefinitions> |
||||
<ColumnDefinition Width="Auto" /> |
||||
<ColumnDefinition Width="Auto" /> |
||||
</Grid.ColumnDefinitions> |
||||
</Grid> |
||||
</Border> |
||||
</dropShadow:SystemDropShadowChrome> |
||||
</UserControl> |
||||
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Text; |
||||
using System.Windows; |
||||
using System.Windows.Controls; |
||||
using System.Windows.Data; |
||||
using System.Windows.Documents; |
||||
using System.Windows.Input; |
||||
using System.Windows.Media; |
||||
using System.Windows.Media.Imaging; |
||||
using System.Windows.Navigation; |
||||
using System.Windows.Shapes; |
||||
using Debugger.AddIn.Visualizers.Graph; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Drawing |
||||
{ |
||||
/// <summary>
|
||||
/// UserControl used to display ObjectNode.
|
||||
/// </summary>
|
||||
public partial class NodeControl : UserControl |
||||
{ |
||||
public NodeControl() |
||||
{ |
||||
InitializeComponent(); |
||||
} |
||||
|
||||
private ObjectNode node; |
||||
/// <summary>
|
||||
/// ObjectNode that this control displays.
|
||||
/// </summary>
|
||||
public ObjectNode GraphNode |
||||
{ |
||||
get |
||||
{ |
||||
return node; |
||||
} |
||||
set |
||||
{ |
||||
node = value; |
||||
int row = 0; |
||||
// dynamically create TextBlocks and insert them to the 2-column propertyGrid
|
||||
foreach (var property in node.Properties) |
||||
{ |
||||
propertyGrid.RowDefinitions.Add(new RowDefinition()); |
||||
|
||||
TextBlock txtName = createTextBlock(property.Name); |
||||
propertyGrid.Children.Add(txtName); |
||||
Grid.SetRow(txtName, row); |
||||
Grid.SetColumn(txtName, 0); |
||||
|
||||
TextBlock txtValue = createTextBlock(property.Value); |
||||
propertyGrid.Children.Add(txtValue); |
||||
Grid.SetRow(txtValue, row); |
||||
Grid.SetColumn(txtValue, 1); |
||||
|
||||
row++; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Creates TextBlock with given text.
|
||||
/// </summary>
|
||||
private TextBlock createTextBlock(string text) |
||||
{ |
||||
TextBlock newTextblock = new TextBlock(); |
||||
newTextblock.Text = text; |
||||
newTextblock.Padding = new Thickness(4); |
||||
return newTextblock; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// Edge with position information.
|
||||
/// </summary>
|
||||
public class PositionedEdge : NamedEdge<PositionedNode> |
||||
{ |
||||
} |
||||
} |
||||
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// Graph with positioned nodes and edges.
|
||||
/// </summary>
|
||||
public class PositionedGraph |
||||
{ |
||||
internal List<PositionedNode> nodes = new List<PositionedNode>(); |
||||
|
||||
/// <summary>
|
||||
/// All nodes in the graph.
|
||||
/// </summary>
|
||||
public IEnumerable<PositionedNode> Nodes |
||||
{ |
||||
get { return nodes; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// All edges in the graph.
|
||||
/// </summary>
|
||||
public IEnumerable<PositionedEdge> Edges |
||||
{ |
||||
get |
||||
{ |
||||
foreach (PositionedNode node in this.Nodes) |
||||
{ |
||||
foreach (PositionedEdge edge in node.Edges) |
||||
{ |
||||
yield return edge; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using Debugger.AddIn.Visualizers.Graph.Drawing; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// Node with position information.
|
||||
/// </summary>
|
||||
public class PositionedNode |
||||
{ |
||||
public PositionedNode(NodeControl nodeVisualControl) |
||||
{ |
||||
this.nodeVisualControl = nodeVisualControl; |
||||
} |
||||
|
||||
public double Left { get; set; } |
||||
public double Top { get; set; } |
||||
public double Width |
||||
{ |
||||
get { return NodeVisualControl.DesiredSize.Width; } |
||||
} |
||||
public double Height |
||||
{ |
||||
get { return NodeVisualControl.DesiredSize.Height; } |
||||
} |
||||
|
||||
private NodeControl nodeVisualControl; |
||||
/// <summary>
|
||||
/// Visual control to be shown for this node.
|
||||
/// </summary>
|
||||
public NodeControl NodeVisualControl |
||||
{ |
||||
get |
||||
{ |
||||
return this.nodeVisualControl; |
||||
} |
||||
} |
||||
|
||||
public virtual IEnumerable<PositionedEdge> Edges |
||||
{ |
||||
get |
||||
{ |
||||
return new PositionedEdge[]{}; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// Direction of tree layout.
|
||||
/// </summary>
|
||||
public enum LayoutDirection |
||||
{ |
||||
TopBottom, |
||||
LeftRight |
||||
} |
||||
} |
||||
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// Description of TreeEdge.
|
||||
/// </summary>
|
||||
public class TreeEdge : PositionedEdge |
||||
{ |
||||
} |
||||
} |
||||
@ -0,0 +1,122 @@
@@ -0,0 +1,122 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníèek" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Windows; |
||||
using Debugger.AddIn.Visualizers.Graph.Drawing; |
||||
using System.Linq; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// Calculates layout of <see cref="ObjectGraph" />, producing <see cref="PositionedGraph" />.
|
||||
/// </summary>
|
||||
public class TreeLayouter |
||||
{ |
||||
private static readonly double horizNodeMargin = 20; |
||||
private static readonly double vertNodeMargin = 20; |
||||
|
||||
private LayoutDirection layoutDirection = LayoutDirection.TopBottom; |
||||
|
||||
PositionedGraph resultGraph = null; |
||||
|
||||
Dictionary<ObjectNode, TreeNode> treeNodeFor = new Dictionary<ObjectNode, TreeNode>(); |
||||
Dictionary<ObjectNode, object> seenNodes = new Dictionary<ObjectNode, object>(); |
||||
|
||||
public TreeLayouter() |
||||
{ |
||||
} |
||||
|
||||
private TreeNode createTreeNode(NodeControl nodeVisualControl) |
||||
{ |
||||
return TreeNode.Create(this.layoutDirection, nodeVisualControl); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Calculates layout for given <see cref="ObjectGraph" />.
|
||||
/// </summary>
|
||||
/// <param name="objectGraph"></param>
|
||||
/// <returns></returns>
|
||||
public PositionedGraph CalculateLayout(ObjectGraph objectGraph, LayoutDirection direction) |
||||
{ |
||||
resultGraph = new PositionedGraph(); |
||||
layoutDirection = direction; |
||||
|
||||
var seenNodes = new Dictionary<ObjectNode, object>(); |
||||
|
||||
TreeNode tree = buildTreeRecursive(objectGraph.Root); |
||||
calculateNodePosRecursive(tree, 0, 0); |
||||
|
||||
return resultGraph; |
||||
} |
||||
|
||||
private TreeNode buildTreeRecursive(ObjectNode objectGraphNode) |
||||
{ |
||||
seenNodes.Add(objectGraphNode, null); |
||||
|
||||
NodeControl nodeVisualControl = new NodeControl(); |
||||
nodeVisualControl.GraphNode = objectGraphNode; |
||||
nodeVisualControl.Measure(new Size(500, 500)); |
||||
|
||||
TreeNode newTreeNode = createTreeNode(nodeVisualControl); |
||||
newTreeNode.HorizontalMargin = horizNodeMargin; |
||||
newTreeNode.VerticalMargin = vertNodeMargin; |
||||
resultGraph.nodes.Add(newTreeNode); |
||||
treeNodeFor[objectGraphNode] = newTreeNode; |
||||
|
||||
double subtreeSize = 0; |
||||
foreach (ObjectEdge edge in objectGraphNode.Edges) |
||||
{ |
||||
ObjectNode neighbor = edge.TargetNode; |
||||
if (seenNodes.ContainsKey(neighbor)) |
||||
{ |
||||
newTreeNode.AdditionalNeighbors.Add(new TreeEdge { Name = edge.Name, SourceNode = newTreeNode, TargetNode = treeNodeFor[neighbor]}); |
||||
} |
||||
else |
||||
{ |
||||
TreeNode newChild = buildTreeRecursive(neighbor); |
||||
newTreeNode.ChildEdges.Add(new TreeEdge { Name = edge.Name, SourceNode = newTreeNode, TargetNode = newChild}); |
||||
|
||||
subtreeSize += newChild.SubtreeSize; |
||||
} |
||||
} |
||||
subtreeSize = Math.Max(newTreeNode.LateralSize + newTreeNode.LateralMargin, subtreeSize); |
||||
newTreeNode.SubtreeSize = subtreeSize; |
||||
|
||||
return newTreeNode; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Given SubtreeSize for each node, positions the nodes, in a left-to-right or top-to-bottom fashion.
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <param name="lateralStart"></param>
|
||||
/// <param name="mainStart"></param>
|
||||
private void calculateNodePosRecursive(TreeNode node, double lateralStart, double mainStart) |
||||
{ |
||||
// center this node
|
||||
double subtreeSize = node.Childs.Sum(child => child.SubtreeSize); |
||||
double center = node.Childs.Count() == 0 ? 0 : 0.5 * (subtreeSize - (node.LateralSize + node.LateralMargin)); |
||||
|
||||
// design alternatives
|
||||
// node.MainPos += center; // used this
|
||||
// Adapt(node).PosLateral += center; // TreeNodeAdapterLR + TreeNodeAdapterTB
|
||||
// SetMainPos(node, GetMainPos(node) + 10) // TreeNodeAdapterLR + TreeNodeAdapterTB, no creation
|
||||
|
||||
node.LateralCoord += lateralStart + center; |
||||
node.MainCoord = mainStart; |
||||
|
||||
double childLateral = lateralStart; |
||||
double childsMainFixed = node.MainCoord + node.MainSize + node.MainMargin; |
||||
foreach (TreeNode child in node.Childs) |
||||
{ |
||||
calculateNodePosRecursive(child, childLateral, childsMainFixed); |
||||
childLateral += child.SubtreeSize; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,89 @@
@@ -0,0 +1,89 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using Debugger.AddIn.Visualizers.Graph.Drawing; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// Abstract ancestor of TreeNodeLR and TreeNodeTB.
|
||||
/// There are 2 dimensions - "main" and "lateral".
|
||||
/// Main dimension is the dimension in which the graph depth grows (vertical when TB, horizontal when LR).
|
||||
/// Lateral dimension is the other dimension. Siblings are placed next to each other in Lateral dimension.
|
||||
/// </summary>
|
||||
public abstract class TreeNode : PositionedNode |
||||
{ |
||||
public static TreeNode Create(LayoutDirection direction, NodeControl nodeVisualControl) |
||||
{ |
||||
switch (direction) { |
||||
case LayoutDirection.TopBottom: return new TreeNodeTB(nodeVisualControl); |
||||
case LayoutDirection.LeftRight: return new TreeNodeLR(nodeVisualControl); |
||||
default: throw new DebuggerVisualizerException("Unsupported layout direction: " + direction.ToString()); |
||||
} |
||||
} |
||||
|
||||
public double HorizontalMargin { get; set; } |
||||
public double VerticalMargin { get; set; } |
||||
|
||||
protected TreeNode(NodeControl nodeVisualControl) : base(nodeVisualControl) |
||||
{ |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Width or height of the subtree.
|
||||
/// </summary>
|
||||
public double SubtreeSize { get; set; } |
||||
|
||||
public abstract double MainCoord { get; set; } |
||||
public abstract double LateralCoord { get; set; } |
||||
|
||||
public abstract double MainSize { get; } |
||||
public abstract double LateralSize { get; } |
||||
|
||||
public abstract double MainMargin { get; } |
||||
public abstract double LateralMargin { get; } |
||||
|
||||
private List<TreeEdge> childs = new List<TreeEdge>(); |
||||
public List<TreeEdge> ChildEdges |
||||
{ |
||||
get |
||||
{ |
||||
return childs; |
||||
} |
||||
} |
||||
|
||||
public IEnumerable<TreeNode> Childs |
||||
{ |
||||
get |
||||
{ |
||||
foreach (TreeEdge childEdge in ChildEdges) |
||||
yield return (TreeNode)childEdge.TargetNode; |
||||
} |
||||
} |
||||
|
||||
private List<TreeEdge> additionalNeighbors = new List<TreeEdge>(); |
||||
public List<TreeEdge> AdditionalNeighbors |
||||
{ |
||||
get |
||||
{ |
||||
return additionalNeighbors; |
||||
} |
||||
} |
||||
|
||||
public override IEnumerable<PositionedEdge> Edges |
||||
{ |
||||
get |
||||
{ |
||||
foreach (TreeEdge childEdge in ChildEdges) |
||||
yield return childEdge; |
||||
foreach (TreeEdge neighborEdge in AdditionalNeighbors) |
||||
yield return neighborEdge; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using Debugger.AddIn.Visualizers.Graph.Drawing; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// Description of TreeNodeLR.
|
||||
/// </summary>
|
||||
public class TreeNodeLR: TreeNode |
||||
{ |
||||
public TreeNodeLR(NodeControl nodeControl) : base(nodeControl) |
||||
{ |
||||
} |
||||
|
||||
public override double MainSize { get { return this.Width; } } |
||||
public override double LateralSize { get { return this.Height ; } } |
||||
|
||||
public override double MainCoord { get { return this.Left; } set { this.Left = value; } } |
||||
public override double LateralCoord { get { return this.Top; } set { this.Top = value; } } |
||||
|
||||
public override double MainMargin { get { return this.HorizontalMargin; } } |
||||
public override double LateralMargin { get { return this.VerticalMargin; } } |
||||
} |
||||
} |
||||
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using Debugger.AddIn.Visualizers.Graph.Drawing; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// Description of TreeNodeTB.
|
||||
/// </summary>
|
||||
public class TreeNodeTB : TreeNode |
||||
{ |
||||
public TreeNodeTB(NodeControl nodeControl) : base(nodeControl) |
||||
{ |
||||
} |
||||
|
||||
public override double MainSize { get { return this.Height; } } |
||||
public override double LateralSize { get { return this.Width; } } |
||||
|
||||
public override double MainCoord { get { return this.Top; } set { this.Top = value; } } |
||||
public override double LateralCoord { get { return this.Left; } set { this.Left = value; } } |
||||
|
||||
public override double MainMargin { get { return this.VerticalMargin; } } |
||||
public override double LateralMargin { get { return this.HorizontalMargin; } } |
||||
} |
||||
} |
||||
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph |
||||
{ |
||||
/// <summary>
|
||||
/// Named edge connecting 2 objects of same type.
|
||||
/// </summary>
|
||||
public class NamedEdge<TSourceTarget> : NamedEdge<TSourceTarget, TSourceTarget> |
||||
{ |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Named edge connecting 2 objects of arbitrary types.
|
||||
/// </summary>
|
||||
public class NamedEdge<TSource, TTarget> |
||||
{ |
||||
/// <summary>
|
||||
/// Name of the edge.
|
||||
/// </summary>
|
||||
public string Name { get; set; } |
||||
|
||||
/// <summary>
|
||||
/// Target node of the edge.
|
||||
/// </summary>
|
||||
public TTarget TargetNode { get; set; } |
||||
|
||||
/// <summary>
|
||||
/// Source node of the edge.
|
||||
/// </summary>
|
||||
public TSource SourceNode { get; set; } |
||||
} |
||||
} |
||||
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Text; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph |
||||
{ |
||||
/// <summary>
|
||||
/// Named edge in the <see cref="ObjectGraph" />.
|
||||
/// </summary>
|
||||
public class ObjectEdge : NamedEdge<ObjectNode> |
||||
{ |
||||
} |
||||
} |
||||
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Text; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph |
||||
{ |
||||
/// <summary>
|
||||
/// Object graph built by <see cref="ObjectGraphBuilder"/>
|
||||
/// </summary>
|
||||
public class ObjectGraph |
||||
{ |
||||
/// <summary>
|
||||
/// Root of the graph.
|
||||
/// </summary>
|
||||
public ObjectNode Root { get; internal set; } |
||||
|
||||
/// <summary>
|
||||
/// Adds node to the graph.
|
||||
/// </summary>
|
||||
/// <param name="node">node to be added</param>
|
||||
internal void AddNode(ObjectNode node) |
||||
{ |
||||
_nodes.Add(node); |
||||
} |
||||
|
||||
private List<ObjectNode> _nodes = new List<ObjectNode>(); |
||||
/// <summary>
|
||||
/// All nodes in the graph.
|
||||
/// </summary>
|
||||
public IEnumerable<ObjectNode> Nodes |
||||
{ |
||||
get { return _nodes; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// All edges in the graph.
|
||||
/// </summary>
|
||||
public IEnumerable<ObjectEdge> Edges |
||||
{ |
||||
get |
||||
{ |
||||
foreach (ObjectNode node in this.Nodes) |
||||
{ |
||||
foreach (ObjectEdge edge in node.Edges) |
||||
{ |
||||
yield return edge; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,259 @@
@@ -0,0 +1,259 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníèek" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using ICSharpCode.SharpDevelop.Services; |
||||
using Debugger; |
||||
using Debugger.MetaData; |
||||
using Debugger.Wrappers.CorDebug; |
||||
using Debugger.Expressions; |
||||
using Debugger.AddIn.Visualizers.Graph.Utils; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph |
||||
{ |
||||
// The object graph building starts with given expression and recursively
|
||||
// explores all its members.
|
||||
//
|
||||
// Important part of the algorithm is finding if we already have a node
|
||||
// for given value - to detect loops and shared references correctly.
|
||||
// This is done using the following algorithm:
|
||||
//
|
||||
// getNodeForValue(value)
|
||||
// get the hashCode for the value
|
||||
// find if there is already a node with this hashCode (in O(1))
|
||||
// if not, we can be sure we have not seen this value yet
|
||||
// if yes, it might be different object with the same hashCode -> compare addresses
|
||||
//
|
||||
// 'different object with the same hashCode' are possible - my question on stackoverflow:
|
||||
// http://stackoverflow.com/questions/750947/-net-unique-object-identifier
|
||||
//
|
||||
// this way, the whole graph building is O(n) in the size of the resulting graph
|
||||
// It tries to call as few Expression.Evaluate as possible, since Expression.Evaluate is expensive (5ms) -
|
||||
// this is a prototype and the speed can be I believe greatly improved for future versions.
|
||||
|
||||
/// <summary>
|
||||
/// Builds <see cref="ObjectGraph" /> for given string expression.
|
||||
/// </summary>
|
||||
public class ObjectGraphBuilder |
||||
{ |
||||
/// <summary>
|
||||
/// The underlying debugger service used for getting expression values.
|
||||
/// </summary>
|
||||
private WindowsDebugger _debuggerService; |
||||
/// <summary>
|
||||
/// The resulting object graph.
|
||||
/// </summary>
|
||||
private ObjectGraph _resultGraph; |
||||
|
||||
/// <summary>
|
||||
/// Given hash code, lookup already existing node(s) with this hash code.
|
||||
/// </summary>
|
||||
private Lookup<string, ObjectNode> objectNodesForHashCode = new Lookup<string, ObjectNode>(); |
||||
|
||||
/// <summary>
|
||||
/// Binding flags for getting member expressions.
|
||||
/// </summary>
|
||||
private readonly Debugger.MetaData.BindingFlags _bindingFlags = |
||||
BindingFlags.Public | BindingFlags.Instance | BindingFlags.Field | BindingFlags.GetProperty; |
||||
|
||||
/// <summary>
|
||||
/// Creates ObjectGraphBuilder.
|
||||
/// </summary>
|
||||
/// <param name="debuggerService">Debugger service.</param>
|
||||
public ObjectGraphBuilder(WindowsDebugger debuggerService) |
||||
{ |
||||
_debuggerService = debuggerService; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Builds full object graph for given string expression.
|
||||
/// </summary>
|
||||
/// <param name="expression">Expression valid in the program being debugged (eg. variable name)</param>
|
||||
/// <returns>Object graph</returns>
|
||||
public ObjectGraph BuildGraphForExpression(string expression) |
||||
{ |
||||
if (string.IsNullOrEmpty(expression)) |
||||
{ |
||||
throw new DebuggerVisualizerException("Expression cannot be empty."); |
||||
} |
||||
|
||||
_resultGraph = new ObjectGraph(); |
||||
|
||||
// empty graph for null expression
|
||||
if (!_debuggerService.GetValueFromName(expression).IsNull) |
||||
{ |
||||
_resultGraph.Root = buildGraphRecursive(_debuggerService.GetValueFromName(expression).GetPermanentReference()); |
||||
} |
||||
|
||||
return _resultGraph; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Builds the subgraph representing given value.
|
||||
/// </summary>
|
||||
/// <param name="rootValue">The Value for which the subgraph will be built.</param>
|
||||
/// <returns>ObjectNode representing the value + all recursive members.</returns>
|
||||
private ObjectNode buildGraphRecursive(Value rootValue) |
||||
{ |
||||
ObjectNode thisNode = createNewNode(rootValue); |
||||
|
||||
/*string[] memberValues = rootValue.GetMemberValuesAsString(_bindingFlags); |
||||
foreach (string memberValue in memberValues) |
||||
{ |
||||
//Value memberValuePerm = memberValue.GetPermanentReference();
|
||||
|
||||
}*/ |
||||
|
||||
|
||||
foreach(Expression memberExpr in rootValue.Expression.AppendObjectMembers(rootValue.Type, _bindingFlags)) |
||||
{ |
||||
checkIsOfSupportedType(memberExpr); |
||||
|
||||
string memberName = memberExpr.CodeTail; |
||||
if (isOfAtomicType(memberExpr)) |
||||
{ |
||||
// atomic members are added to the list of node's "properties"
|
||||
string memberValueAsString = memberExpr.Evaluate(_debuggerService.DebuggedProcess).AsString; |
||||
thisNode.AddProperty(memberName, memberValueAsString); |
||||
} |
||||
else |
||||
{ |
||||
if (!isNull(memberExpr)) |
||||
{ |
||||
// for object members, edges are added
|
||||
|
||||
Value memberValue = getPermanentReference(memberExpr); |
||||
|
||||
// if node for memberValue already exists, only add edge to it (so loops etc. are solved correctly)
|
||||
ObjectNode targetNode = getNodeForValue(memberValue); |
||||
if (targetNode == null) |
||||
{ |
||||
// if no node for memberValue exists, build the subgraph for the value
|
||||
targetNode = buildGraphRecursive(memberValue); |
||||
} |
||||
// add the edge
|
||||
thisNode.AddNamedEdge(targetNode, memberName); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return thisNode; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Creates new node for the value.
|
||||
/// </summary>
|
||||
/// <param name="permanentReference">Value, has to be valid.</param>
|
||||
/// <returns>New empty object node representing the value.</returns>
|
||||
private ObjectNode createNewNode(Value permanentReference) |
||||
{ |
||||
ObjectNode newNode = new ObjectNode(); |
||||
|
||||
_resultGraph.AddNode(newNode); |
||||
// remember this node's hashcode for quick lookup
|
||||
objectNodesForHashCode.Add(invokeGetHashCode(permanentReference), newNode); |
||||
|
||||
// permanent reference to the object this node represents is useful for graph building,
|
||||
// and matching nodes in animations
|
||||
newNode.PermanentReference = permanentReference; |
||||
|
||||
return newNode; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Finds node that represents the same instance as given value.
|
||||
/// </summary>
|
||||
/// <param name="value">Valid value representing an instance.</param>
|
||||
/// <returns></returns>
|
||||
private ObjectNode getNodeForValue(Value value) |
||||
{ |
||||
string objectHashCode = invokeGetHashCode(value); |
||||
// are there any nodes with the same hash code?
|
||||
LookupValueCollection<ObjectNode> nodesWithSameHashCode = objectNodesForHashCode[objectHashCode]; |
||||
if (nodesWithSameHashCode == null) |
||||
{ |
||||
return null; |
||||
} |
||||
else |
||||
{ |
||||
// if there is a node with same hash code, check if it has also the same address
|
||||
// (hash codes are not uniqe - http://stackoverflow.com/questions/750947/-net-unique-object-identifier)
|
||||
ulong objectAddress = getObjectValue(value); |
||||
ObjectNode nodeWithSameAddress = nodesWithSameHashCode.Find( |
||||
node => { return objectAddress == getObjectValue(node.PermanentReference); } ); |
||||
return nodeWithSameAddress; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Invokes GetHashCode on given value.
|
||||
/// </summary>
|
||||
/// <param name="value">Valid value.</param>
|
||||
/// <returns>Hash code of the object in the debugee.</returns>
|
||||
private string invokeGetHashCode(Value value) |
||||
{ |
||||
MethodInfo method = value.Type.GetMember("GetHashCode", BindingFlags.Method | BindingFlags.IncludeSuperType) as MethodInfo; |
||||
string hashCode = value.InvokeMethod(method, null).AsString; |
||||
return hashCode; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Checks whether given expression's type is supported by the graph builder.
|
||||
/// </summary>
|
||||
/// <param name="expr">Expression to be checked.</param>
|
||||
private void checkIsOfSupportedType(Expression expr) |
||||
{ |
||||
DebugType typeOfValue = expr.Evaluate(_debuggerService.DebuggedProcess).Type; |
||||
if (typeOfValue.IsArray) |
||||
{ |
||||
// arrays will be supported of course in the final version
|
||||
throw new DebuggerVisualizerException("Arrays are not supported yet"); |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Checks whether given expression's type is atomic - atomic values will be added to node's property list.
|
||||
/// </summary>
|
||||
/// <param name="expr">Expression.</param>
|
||||
/// <returns>True if expression's type is atomic, False otherwise.</returns>
|
||||
private bool isOfAtomicType(Expression expr) |
||||
{ |
||||
DebugType typeOfValue = expr.Evaluate(_debuggerService.DebuggedProcess).Type; |
||||
return (!typeOfValue.IsClass) || typeOfValue.IsString; |
||||
} |
||||
|
||||
#region helpers
|
||||
|
||||
private Value getPermanentReference(Expression expr) |
||||
{ |
||||
return expr.Evaluate(_debuggerService.DebuggedProcess).GetPermanentReference(); |
||||
} |
||||
|
||||
private bool isNull(Expression expr) |
||||
{ |
||||
return expr.Evaluate(_debuggerService.DebuggedProcess).IsNull; |
||||
} |
||||
|
||||
private DebugType getType(Expression expr) |
||||
{ |
||||
return expr.Evaluate(_debuggerService.DebuggedProcess).Type; |
||||
} |
||||
|
||||
private ulong getObjectAddress(Expression expr) |
||||
{ |
||||
return getObjectValue(expr.Evaluate(_debuggerService.DebuggedProcess)); |
||||
} |
||||
|
||||
private ulong getObjectValue(Value val) |
||||
{ |
||||
ICorDebugReferenceValue refVal = val.CorValue.CastTo<ICorDebugReferenceValue>(); |
||||
return refVal.Value; |
||||
} |
||||
|
||||
#endregion
|
||||
} |
||||
} |
||||
@ -0,0 +1,57 @@
@@ -0,0 +1,57 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Text; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph |
||||
{ |
||||
/// <summary>
|
||||
/// Node in the <see cref="ObjectGraph" />.
|
||||
/// </summary>
|
||||
public class ObjectNode |
||||
{ |
||||
/// <summary>
|
||||
/// Additional info useful for internal algorithms, not to be visible to the user.
|
||||
/// </summary>
|
||||
internal Debugger.Value PermanentReference { get; set; } |
||||
|
||||
private List<ObjectEdge> _edges = new List<ObjectEdge>(); |
||||
/// <summary>
|
||||
/// Outgoing edges.
|
||||
/// </summary>
|
||||
public IEnumerable<ObjectEdge> Edges |
||||
{ |
||||
get { return _edges; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Adds outgoing edge.
|
||||
/// </summary>
|
||||
internal void AddNamedEdge(ObjectNode targetNode, string edgeName) |
||||
{ |
||||
_edges.Add(new ObjectEdge { Name = edgeName, SourceNode = this, TargetNode = targetNode }); |
||||
} |
||||
|
||||
private ObjectPropertyCollection _properties = new ObjectPropertyCollection(); |
||||
/// <summary>
|
||||
/// Properties representing atomic string values as part of the node.
|
||||
/// </summary>
|
||||
public ObjectPropertyCollection Properties |
||||
{ |
||||
get { return _properties; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Adds string property.
|
||||
/// </summary>
|
||||
internal void AddProperty(string name, string value) |
||||
{ |
||||
_properties.Add(new ObjectProperty { Name = name, Value = value }); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Text; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph |
||||
{ |
||||
/// <summary>
|
||||
/// Primitive property (int, string, etc.) of an object, in string form.
|
||||
/// </summary>
|
||||
public class ObjectProperty |
||||
{ |
||||
/// <summary>
|
||||
/// e.g. "Age"
|
||||
/// </summary>
|
||||
public string Name { get; set; } |
||||
/// <summary>
|
||||
/// e.g. "19"
|
||||
/// </summary>
|
||||
public string Value { get; set; } |
||||
} |
||||
} |
||||
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Text; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph |
||||
{ |
||||
/// <summary>
|
||||
/// Collection of object primitive properties.
|
||||
/// </summary>
|
||||
public class ObjectPropertyCollection : List<ObjectProperty> |
||||
{ |
||||
} |
||||
} |
||||
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníèek" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Text; |
||||
using System.Windows.Forms; |
||||
using ICSharpCode.Core; |
||||
using ICSharpCode.SharpDevelop; |
||||
using ICSharpCode.SharpDevelop.Gui; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph |
||||
{ |
||||
/// <summary>
|
||||
/// ViewContent of the visualizer.
|
||||
/// </summary>
|
||||
public class ObjectGraphVisualizerViewContent : AbstractViewContent |
||||
{ |
||||
VisualizerWinFormsControl control = new VisualizerWinFormsControl(); |
||||
|
||||
/// <summary>
|
||||
/// The <see cref="System.Windows.Forms.Control"/> representing the view.
|
||||
/// </summary>
|
||||
public override object Control |
||||
{ |
||||
get |
||||
{ |
||||
return control; |
||||
} |
||||
} |
||||
|
||||
public ObjectGraphVisualizerViewContent() |
||||
{ |
||||
this.TitleName = "Object graph visualizer"; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníèek" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Text; |
||||
using System.Windows.Forms; |
||||
using ICSharpCode.Core; |
||||
using ICSharpCode.SharpDevelop; |
||||
using ICSharpCode.SharpDevelop.Gui; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph |
||||
{ |
||||
/// <summary>
|
||||
/// Command in the tools menu for showing the object graph visualizer.
|
||||
/// </summary>
|
||||
public class ShowObjectGraphVisualizerCommand : AbstractMenuCommand |
||||
{ |
||||
public override void Run() |
||||
{ |
||||
VisualizerWPFWindow window = new VisualizerWPFWindow(); |
||||
window.Topmost = true; |
||||
// fix non-editable TextBox bug
|
||||
//System.Windows.Forms.Integration.ElementHost.EnableModelessKeyboardInterop(window);
|
||||
window.Show(); |
||||
//WorkbenchSingleton.Workbench.ShowView(new DebuggerVisualizerViewContent());
|
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníèek" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Utils |
||||
{ |
||||
/// <summary>
|
||||
/// Same like Dictionary, but can store multiple values for one key.
|
||||
/// </summary>
|
||||
public class Lookup<TKey, TValue> |
||||
{ |
||||
private Dictionary<TKey, LookupValueCollection<TValue>> _dictionary; |
||||
|
||||
public Lookup() |
||||
{ |
||||
_dictionary = new Dictionary<TKey, LookupValueCollection<TValue>>(); |
||||
} |
||||
|
||||
public LookupValueCollection<TValue> this[TKey key] |
||||
{ |
||||
get |
||||
{ |
||||
LookupValueCollection<TValue> values = null; |
||||
if (_dictionary.TryGetValue(key, out values)) |
||||
{ |
||||
return values; |
||||
} |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
public void Add(TKey key, TValue value) |
||||
{ |
||||
LookupValueCollection<TValue> values = null;; |
||||
if (!_dictionary.TryGetValue(key, out values)) |
||||
{ |
||||
values = new LookupValueCollection<TValue>(); |
||||
_dictionary.Add(key, values); |
||||
} |
||||
values.Add(value); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníèek" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Utils |
||||
{ |
||||
/// <summary>
|
||||
/// A collection of values for one key in the Lookup class.
|
||||
/// </summary>
|
||||
public class LookupValueCollection<TValue> : List<TValue> |
||||
{ |
||||
} |
||||
} |
||||
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
<UserControl x:Class="Debugger.AddIn.Visualizers.Graph.VisualizerWPFControl" |
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="300" Width="300"> |
||||
<StackPanel> |
||||
<StackPanel Orientation="Horizontal"> |
||||
<TextBlock VerticalAlignment="Center" Padding="4">Expression:</TextBlock> |
||||
<TextBox VerticalAlignment="Center" Margin="4" Width="100"></TextBox> |
||||
<Button Padding="4" Margin="4">Inspect</Button> |
||||
</StackPanel> |
||||
<Canvas></Canvas> |
||||
</StackPanel> |
||||
</UserControl> |
||||
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Text; |
||||
using System.Windows; |
||||
using System.Windows.Controls; |
||||
using System.Windows.Data; |
||||
using System.Windows.Documents; |
||||
using System.Windows.Input; |
||||
using System.Windows.Media; |
||||
using System.Windows.Media.Imaging; |
||||
using System.Windows.Navigation; |
||||
using System.Windows.Shapes; |
||||
|
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph |
||||
{ |
||||
/// <summary>
|
||||
/// Interaction logic for VisualizerWPFControl.xaml
|
||||
/// </summary>
|
||||
public partial class VisualizerWPFControl : UserControl |
||||
{ |
||||
public VisualizerWPFControl() |
||||
{ |
||||
InitializeComponent(); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
<Window x:Class="Debugger.AddIn.Visualizers.Graph.VisualizerWPFWindow" |
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
xmlns:dv="clr-namespace:Debugger.AddIn.Visualizers.Graph" |
||||
Title="Object graph visualizer" Height="400" Width="600"> |
||||
<StackPanel> |
||||
<StackPanel> |
||||
<StackPanel Orientation="Horizontal"> |
||||
<TextBlock VerticalAlignment="Center" Padding="4">Expression:</TextBlock> |
||||
<TextBox Name="txtExpression" VerticalAlignment="Center" Margin="4" Width="100"></TextBox> |
||||
<Button Padding="4" Margin="4" Click="Inspect_Button_Click">Inspect</Button> |
||||
</StackPanel> |
||||
<Canvas Name="canvas"> |
||||
</Canvas> |
||||
</StackPanel> |
||||
</StackPanel> |
||||
</Window> |
||||
@ -0,0 +1,97 @@
@@ -0,0 +1,97 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Text; |
||||
using System.Windows; |
||||
using System.Windows.Controls; |
||||
using System.Windows.Data; |
||||
using System.Windows.Documents; |
||||
using System.Windows.Input; |
||||
using System.Windows.Media; |
||||
using System.Windows.Media.Imaging; |
||||
using System.Windows.Shapes; |
||||
|
||||
using ICSharpCode.SharpDevelop.Debugging; |
||||
using ICSharpCode.SharpDevelop.Services; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph |
||||
{ |
||||
/// <summary>
|
||||
/// Interaction logic for VisualizerWPFWindow.xaml
|
||||
/// </summary>
|
||||
public partial class VisualizerWPFWindow : Window |
||||
{ |
||||
WindowsDebugger _debuggerService = null; |
||||
|
||||
public VisualizerWPFWindow() |
||||
{ |
||||
InitializeComponent(); |
||||
|
||||
_debuggerService = DebuggerService.CurrentDebugger as WindowsDebugger; |
||||
if (_debuggerService == null) |
||||
throw new ApplicationException("Only windows debugger is currently supported"); |
||||
|
||||
_debuggerService.IsProcessRunningChanged += new EventHandler(debuggerService_IsProcessRunningChanged); |
||||
_debuggerService.DebugStopped += new EventHandler(_debuggerService_DebugStopped); |
||||
} |
||||
|
||||
public void debuggerService_IsProcessRunningChanged(object sender, EventArgs e) |
||||
{ |
||||
// on step or breakpoint hit
|
||||
if (!_debuggerService.IsProcessRunning) |
||||
{ |
||||
refreshGraph(); |
||||
} |
||||
} |
||||
|
||||
public void _debuggerService_DebugStopped(object sender, EventArgs e) |
||||
{ |
||||
_debuggerService.IsProcessRunningChanged -= new EventHandler(debuggerService_IsProcessRunningChanged); |
||||
this.Close(); |
||||
} |
||||
|
||||
private void Inspect_Button_Click(object sender, RoutedEventArgs e) |
||||
{ |
||||
refreshGraph(); |
||||
} |
||||
|
||||
void refreshGraph() |
||||
{ |
||||
ObjectGraphBuilder graphBuilder = new ObjectGraphBuilder(_debuggerService); |
||||
ObjectGraph graph = null; |
||||
|
||||
try |
||||
{ |
||||
graph = graphBuilder.BuildGraphForExpression(txtExpression.Text); |
||||
} |
||||
catch(DebuggerVisualizerException ex) |
||||
{ |
||||
guiHandleException(ex); |
||||
return; |
||||
} |
||||
catch(Debugger.GetValueException ex) |
||||
{ |
||||
guiHandleException(ex); |
||||
return; |
||||
} |
||||
|
||||
Layout.TreeLayouter layouter = new Layout.TreeLayouter(); |
||||
Layout.PositionedGraph posGraph = layouter.CalculateLayout(graph, Layout.LayoutDirection.LeftRight); |
||||
|
||||
GraphDrawer.Draw(posGraph, canvas); |
||||
|
||||
//GraphDrawer drawer = new GraphDrawer(graph);
|
||||
//drawer.Draw(canvas);
|
||||
} |
||||
|
||||
void guiHandleException(System.Exception ex) |
||||
{ |
||||
MessageBox.Show(ex.Message, "Exception", MessageBoxButton.OK, MessageBoxImage.Error); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
|
||||
namespace Debugger.AddIn.Visualizers.Graph |
||||
{ |
||||
partial class VisualizerWinFormsControl |
||||
{ |
||||
/// <summary>
|
||||
/// Designer variable used to keep track of non-visual components.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null; |
||||
|
||||
/// <summary>
|
||||
/// Disposes resources used by the control.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) |
||||
{ |
||||
if (disposing) { |
||||
if (components != null) { |
||||
components.Dispose(); |
||||
} |
||||
} |
||||
base.Dispose(disposing); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// This method is required for Windows Forms designer support.
|
||||
/// Do not change the method contents inside the source code editor. The Forms designer might
|
||||
/// not be able to load this method if it was changed manually.
|
||||
/// </summary>
|
||||
private void InitializeComponent() |
||||
{ |
||||
this.components = new System.ComponentModel.Container(); |
||||
this.txtExpression = new System.Windows.Forms.TextBox(); |
||||
this.btnInspect = new System.Windows.Forms.Button(); |
||||
this.lblInfo = new System.Windows.Forms.Label(); |
||||
this.lblExpression = new System.Windows.Forms.Label(); |
||||
this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); |
||||
this.SuspendLayout(); |
||||
//
|
||||
// txtExpression
|
||||
//
|
||||
this.txtExpression.Location = new System.Drawing.Point(88, 13); |
||||
this.txtExpression.Name = "txtExpression"; |
||||
this.txtExpression.Size = new System.Drawing.Size(131, 20); |
||||
this.txtExpression.TabIndex = 0; |
||||
this.toolTip1.SetToolTip(this.txtExpression, "Expression (e.g. variable name) to be inspected"); |
||||
//
|
||||
// btnInspect
|
||||
//
|
||||
this.btnInspect.Location = new System.Drawing.Point(225, 10); |
||||
this.btnInspect.Name = "btnInspect"; |
||||
this.btnInspect.Size = new System.Drawing.Size(75, 23); |
||||
this.btnInspect.TabIndex = 1; |
||||
this.btnInspect.Text = "Inspect"; |
||||
this.btnInspect.UseVisualStyleBackColor = true; |
||||
this.btnInspect.Click += new System.EventHandler(this.BtnInspectClick); |
||||
//
|
||||
// lblInfo
|
||||
//
|
||||
this.lblInfo.Location = new System.Drawing.Point(11, 58); |
||||
this.lblInfo.Name = "lblInfo"; |
||||
this.lblInfo.Size = new System.Drawing.Size(349, 23); |
||||
this.lblInfo.TabIndex = 2; |
||||
this.lblInfo.Text = "< Result >"; |
||||
//
|
||||
// lblExpression
|
||||
//
|
||||
this.lblExpression.Location = new System.Drawing.Point(11, 14); |
||||
this.lblExpression.Name = "lblExpression"; |
||||
this.lblExpression.Size = new System.Drawing.Size(71, 23); |
||||
this.lblExpression.TabIndex = 4; |
||||
this.lblExpression.Text = "Expression:"; |
||||
//
|
||||
// VisualizerWinFormsControl
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); |
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; |
||||
this.Controls.Add(this.lblExpression); |
||||
this.Controls.Add(this.lblInfo); |
||||
this.Controls.Add(this.btnInspect); |
||||
this.Controls.Add(this.txtExpression); |
||||
this.Name = "VisualizerWinFormsControl"; |
||||
this.Size = new System.Drawing.Size(525, 426); |
||||
this.ResumeLayout(false); |
||||
this.PerformLayout(); |
||||
} |
||||
private System.Windows.Forms.Button btnInspect; |
||||
private System.Windows.Forms.ToolTip toolTip1; |
||||
private System.Windows.Forms.Label lblExpression; |
||||
private System.Windows.Forms.Label lblInfo; |
||||
private System.Windows.Forms.TextBox txtExpression; |
||||
} |
||||
} |
||||
@ -0,0 +1,84 @@
@@ -0,0 +1,84 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníèek" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.ComponentModel; |
||||
using System.Drawing; |
||||
using System.Windows.Forms; |
||||
using System.Linq; |
||||
|
||||
using ICSharpCode.SharpDevelop.Debugging; |
||||
using ICSharpCode.SharpDevelop.Services; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph |
||||
{ |
||||
/// <summary>
|
||||
/// Windows forms control making up the object graph visualizer user interface/
|
||||
/// </summary>
|
||||
public partial class VisualizerWinFormsControl : UserControl |
||||
{ |
||||
WindowsDebugger _debuggerService = null; |
||||
|
||||
public VisualizerWinFormsControl() |
||||
{ |
||||
//
|
||||
// The InitializeComponent() call is required for Windows Forms designer support.
|
||||
//
|
||||
InitializeComponent(); |
||||
|
||||
_debuggerService = DebuggerService.CurrentDebugger as WindowsDebugger; |
||||
if (_debuggerService == null) |
||||
throw new ApplicationException("Only windows debugger is currently supported"); |
||||
|
||||
_debuggerService.IsProcessRunningChanged += new EventHandler(debuggerService_IsProcessRunningChanged); |
||||
} |
||||
|
||||
public void debuggerService_IsProcessRunningChanged(object sender, EventArgs e) |
||||
{ |
||||
// on step, breakpoint hit
|
||||
if (!_debuggerService.IsProcessRunning) |
||||
{ |
||||
refreshGraph(); |
||||
} |
||||
} |
||||
|
||||
void BtnInspectClick(object sender, EventArgs e) |
||||
{ |
||||
refreshGraph(); |
||||
} |
||||
|
||||
void refreshGraph() |
||||
{ |
||||
ObjectGraphBuilder graphBuilder = new ObjectGraphBuilder(_debuggerService); |
||||
ObjectGraph graph = null; |
||||
|
||||
try |
||||
{ |
||||
graph = graphBuilder.BuildGraphForExpression(txtExpression.Text); |
||||
} |
||||
catch(DebuggerVisualizerException ex) |
||||
{ |
||||
guiHandleException(ex); |
||||
return; |
||||
} |
||||
catch(Debugger.GetValueException ex) |
||||
{ |
||||
guiHandleException(ex); |
||||
|
||||
return; |
||||
} |
||||
|
||||
// just a simple message for checking the graph is build ok, will be replaced by graph drawing of course
|
||||
lblInfo.Text = string.Format("Done. Number of graph nodes: {0}, number of edges: {1}", graph.Nodes.Count(), graph.Edges.Count()); |
||||
} |
||||
|
||||
void guiHandleException(System.Exception ex) |
||||
{ |
||||
lblInfo.Text = "< Result >"; |
||||
MessageBox.Show(ex.Message, "Exception", MessageBoxButtons.OK, MessageBoxIcon.Information); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,120 @@
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<root> |
||||
<!-- |
||||
Microsoft ResX Schema |
||||
|
||||
Version 2.0 |
||||
|
||||
The primary goals of this format is to allow a simple XML format |
||||
that is mostly human readable. The generation and parsing of the |
||||
various data types are done through the TypeConverter classes |
||||
associated with the data types. |
||||
|
||||
Example: |
||||
|
||||
... ado.net/XML headers & schema ... |
||||
<resheader name="resmimetype">text/microsoft-resx</resheader> |
||||
<resheader name="version">2.0</resheader> |
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> |
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> |
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> |
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> |
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> |
||||
<value>[base64 mime encoded serialized .NET Framework object]</value> |
||||
</data> |
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> |
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> |
||||
<comment>This is a comment</comment> |
||||
</data> |
||||
|
||||
There are any number of "resheader" rows that contain simple |
||||
name/value pairs. |
||||
|
||||
Each data row contains a name, and value. The row also contains a |
||||
type or mimetype. Type corresponds to a .NET class that support |
||||
text/value conversion through the TypeConverter architecture. |
||||
Classes that don't support this are serialized and stored with the |
||||
mimetype set. |
||||
|
||||
The mimetype is used for serialized objects, and tells the |
||||
ResXResourceReader how to depersist the object. This is currently not |
||||
extensible. For a given mimetype the value must be set accordingly: |
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format |
||||
that the ResXResourceWriter will generate, however the reader can |
||||
read any of the formats listed below. |
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64 |
||||
value : The object must be serialized with |
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter |
||||
: and then encoded with base64 encoding. |
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64 |
||||
value : The object must be serialized with |
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter |
||||
: and then encoded with base64 encoding. |
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64 |
||||
value : The object must be serialized into a byte array |
||||
: using a System.ComponentModel.TypeConverter |
||||
: and then encoded with base64 encoding. |
||||
--> |
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> |
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> |
||||
<xsd:element name="root" msdata:IsDataSet="true"> |
||||
<xsd:complexType> |
||||
<xsd:choice maxOccurs="unbounded"> |
||||
<xsd:element name="metadata"> |
||||
<xsd:complexType> |
||||
<xsd:sequence> |
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" /> |
||||
</xsd:sequence> |
||||
<xsd:attribute name="name" use="required" type="xsd:string" /> |
||||
<xsd:attribute name="type" type="xsd:string" /> |
||||
<xsd:attribute name="mimetype" type="xsd:string" /> |
||||
<xsd:attribute ref="xml:space" /> |
||||
</xsd:complexType> |
||||
</xsd:element> |
||||
<xsd:element name="assembly"> |
||||
<xsd:complexType> |
||||
<xsd:attribute name="alias" type="xsd:string" /> |
||||
<xsd:attribute name="name" type="xsd:string" /> |
||||
</xsd:complexType> |
||||
</xsd:element> |
||||
<xsd:element name="data"> |
||||
<xsd:complexType> |
||||
<xsd:sequence> |
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> |
||||
</xsd:sequence> |
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> |
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> |
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> |
||||
<xsd:attribute ref="xml:space" /> |
||||
</xsd:complexType> |
||||
</xsd:element> |
||||
<xsd:element name="resheader"> |
||||
<xsd:complexType> |
||||
<xsd:sequence> |
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
||||
</xsd:sequence> |
||||
<xsd:attribute name="name" type="xsd:string" use="required" /> |
||||
</xsd:complexType> |
||||
</xsd:element> |
||||
</xsd:choice> |
||||
</xsd:complexType> |
||||
</xsd:element> |
||||
</xsd:schema> |
||||
<resheader name="resmimetype"> |
||||
<value>text/microsoft-resx</value> |
||||
</resheader> |
||||
<resheader name="version"> |
||||
<value>2.0</value> |
||||
</resheader> |
||||
<resheader name="reader"> |
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
||||
</resheader> |
||||
<resheader name="writer"> |
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
||||
</resheader> |
||||
</root> |
||||
Loading…
Reference in new issue