Browse Source
Attemp to move Object graph visualizer to Pad - will stay in separate topmost Window until AbstractPadContent.IsVisible works properly. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4708 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61shortcuts
15 changed files with 494 additions and 243 deletions
@ -0,0 +1,80 @@
@@ -0,0 +1,80 @@
|
||||
// <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 ICSharpCode.Core; |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Windows; |
||||
using Debugger; |
||||
using Debugger.AddIn.Visualizers.Graph; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.Gui.Pads |
||||
{ |
||||
/// <summary>
|
||||
/// Description of ObjectGraphPad.
|
||||
/// </summary>
|
||||
public class ObjectGraphPad : DebuggerPad |
||||
{ |
||||
Process debuggedProcess; |
||||
ObjectGraphControl objectGraphControl; |
||||
static ObjectGraphPad instance; |
||||
|
||||
public ObjectGraphPad() |
||||
{ |
||||
instance = this; |
||||
} |
||||
|
||||
/// <remarks>Always check if Instance is null, might be null if pad is not opened!</remarks>
|
||||
public static ObjectGraphPad Instance { |
||||
get { return instance; } |
||||
} |
||||
|
||||
protected override void InitializeComponents() |
||||
{ |
||||
objectGraphControl = new ObjectGraphControl(); |
||||
} |
||||
|
||||
public override object Control { |
||||
get { |
||||
return objectGraphControl; |
||||
} |
||||
} |
||||
|
||||
public override void RefreshPad() |
||||
{ |
||||
// BUG: if pad window is undocked and floats standalone, IsVisible == false (so pad won't refresh)
|
||||
// REQUEST: need to refresh pad
|
||||
if (!this.IsVisible) |
||||
{ |
||||
LoggingService.Info("skipped refresh"); |
||||
return; |
||||
} |
||||
if (debuggedProcess == null || debuggedProcess.IsRunning || debuggedProcess.SelectedStackFrame == null) { |
||||
this.objectGraphControl.Clear(); |
||||
return; |
||||
} |
||||
this.objectGraphControl.Refresh(); |
||||
} |
||||
|
||||
protected override void SelectProcess(Process process) |
||||
{ |
||||
if (debuggedProcess != null) { |
||||
debuggedProcess.Paused -= debuggedProcess_Paused; |
||||
} |
||||
debuggedProcess = process; |
||||
if (debuggedProcess != null) { |
||||
debuggedProcess.Paused += debuggedProcess_Paused; |
||||
} |
||||
RefreshPad(); |
||||
} |
||||
|
||||
void debuggedProcess_Paused(object sender, ProcessEventArgs e) |
||||
{ |
||||
RefreshPad(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
<UserControl x:Class="Debugger.AddIn.Visualizers.Graph.ObjectGraphControl" |
||||
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" |
||||
xmlns:controls="clr-namespace:Debugger.AddIn.Visualizers.Controls"> |
||||
<Grid> |
||||
<Grid.RowDefinitions> |
||||
<RowDefinition Height="Auto"></RowDefinition> |
||||
<RowDefinition></RowDefinition> |
||||
</Grid.RowDefinitions> |
||||
<StackPanel Grid.Row="0" Grid.Column="0" Orientation="Horizontal" Background="AliceBlue" VerticalAlignment="Top"> |
||||
|
||||
<StackPanel.Resources> |
||||
<Style TargetType="TextBlock"> |
||||
<Setter Property="Margin" Value="0 0 8 0" /> |
||||
</Style> |
||||
<Style TargetType="Button"> |
||||
<Setter Property="Margin" Value="8 0 0 0" /> |
||||
<Setter Property="Padding" Value="8 0 8 0" /> |
||||
</Style> |
||||
</StackPanel.Resources> |
||||
|
||||
<Border Margin="3" Padding="3"> |
||||
<StackPanel Orientation="Vertical"> |
||||
<StackPanel Orientation="Horizontal"> |
||||
<TextBlock VerticalAlignment="Center">Expression:</TextBlock> |
||||
<TextBox Name="txtExpression" VerticalAlignment="Center" Width="100"></TextBox> |
||||
<Button Click="Inspect_Button_Click">Inspect</Button> |
||||
<TextBlock Margin="12 0 6 0" VerticalAlignment="Center">Layout:</TextBlock> |
||||
<ComboBox Name="cmbLayoutDirection" Width="150" ItemsSource="{Binding Path=EnumValues}" SelectedValue="{Binding Path=SelectedEnumValue}" SelectedValuePath="EnumValue" DisplayMemberPath="DisplayValue"></ComboBox> |
||||
</StackPanel> |
||||
<StackPanel Orientation="Horizontal" Name="pnlError" Visibility="Collapsed" Margin="0 6 0 0"> |
||||
<TextBlock Margin="0 0 4 0" Name="lblError" FontStyle="Italic">Error: </TextBlock> |
||||
<TextBlock Margin="0 0 0 0" Name="txtError" FontStyle="Italic"></TextBlock> |
||||
</StackPanel> |
||||
</StackPanel> |
||||
</Border> |
||||
|
||||
<Border Margin="3" Padding="3"> |
||||
<StackPanel Orientation="Horizontal"> |
||||
|
||||
</StackPanel> |
||||
</Border> |
||||
</StackPanel> |
||||
|
||||
<controls:DragScrollViewer x:Name="scroller" Margin="0 4 0 0" Grid.Row="1" Grid.Column="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"> |
||||
<Canvas HorizontalAlignment="Left" VerticalAlignment="Top" Name="canvas" Margin="4"> |
||||
</Canvas> |
||||
</controls:DragScrollViewer> |
||||
</Grid> |
||||
</UserControl> |
@ -0,0 +1,228 @@
@@ -0,0 +1,228 @@
|
||||
// <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.ComponentModel; |
||||
using System.Linq; |
||||
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 Debugger.AddIn.Visualizers.Graph.Layout; |
||||
using ICSharpCode.SharpDevelop.Debugging; |
||||
using ICSharpCode.SharpDevelop.Services; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph |
||||
{ |
||||
/// <summary>
|
||||
/// Interaction logic for ObjectGraphControl.xaml
|
||||
/// </summary>
|
||||
public partial class ObjectGraphControl : UserControl |
||||
{ |
||||
private WindowsDebugger debuggerService; |
||||
private EnumViewModel<LayoutDirection> layoutViewModel; |
||||
private ObjectGraph objectGraph; |
||||
private ObjectGraphBuilder objectGraphBuilder; |
||||
|
||||
private PositionedGraph oldPosGraph; |
||||
private PositionedGraph currentPosGraph; |
||||
private GraphDrawer graphDrawer; |
||||
private Layout.TreeLayouter layouter; |
||||
|
||||
/// <summary> Long-lived map telling which graph nodes and content nodes the user expanded. </summary>
|
||||
private Expanded expanded = new Expanded(); |
||||
|
||||
public ObjectGraphControl() |
||||
{ |
||||
InitializeComponent(); |
||||
|
||||
debuggerService = DebuggerService.CurrentDebugger as WindowsDebugger; |
||||
if (debuggerService == null) |
||||
throw new ApplicationException("Only windows debugger is currently supported"); |
||||
|
||||
this.layoutViewModel = new EnumViewModel<LayoutDirection>(); |
||||
this.layoutViewModel.PropertyChanged += new PropertyChangedEventHandler(layoutViewModel_PropertyChanged); |
||||
this.cmbLayoutDirection.DataContext = this.layoutViewModel; |
||||
|
||||
this.layouter = new TreeLayouter(); |
||||
this.graphDrawer = new GraphDrawer(this.canvas); |
||||
} |
||||
|
||||
public void Clear() |
||||
{ |
||||
txtExpression.Text = string.Empty; |
||||
} |
||||
|
||||
public void Refresh() |
||||
{ |
||||
refreshGraph(); |
||||
} |
||||
|
||||
public string ShownExpression |
||||
{ |
||||
get { |
||||
return this.txtExpression.Text; |
||||
} |
||||
set { |
||||
if (value != this.txtExpression.Text) { |
||||
this.txtExpression.Text = value; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// ObjectGraph visualizer caches UI controls, this clears the cache.
|
||||
/// </summary>
|
||||
public void ClearUIControlCache() |
||||
{ |
||||
NodeControlCache.Instance.Clear(); |
||||
} |
||||
|
||||
private void Inspect_Button_Click(object sender, RoutedEventArgs e) |
||||
{ |
||||
this.Refresh(); |
||||
} |
||||
|
||||
void refreshGraph() |
||||
{ |
||||
clearErrorMessage(); |
||||
if (string.IsNullOrEmpty(txtExpression.Text)) |
||||
{ |
||||
this.graphDrawer.ClearCanvas(); |
||||
return; |
||||
} |
||||
if (debuggerService.IsProcessRunning) // TODO "Process not paused" exception still occurs
|
||||
{ |
||||
showErrorMessage("Cannot inspect when the process is running."); |
||||
return; |
||||
} |
||||
bool graphBuiltOk = true; |
||||
try |
||||
{ |
||||
this.objectGraph = rebuildGraph(txtExpression.Text); |
||||
} |
||||
catch(DebuggerVisualizerException ex) |
||||
{ |
||||
graphBuiltOk = false; |
||||
showErrorMessage(ex.Message); |
||||
} |
||||
catch(Debugger.GetValueException ex) |
||||
{ |
||||
graphBuiltOk = false; |
||||
showErrorMessage("Cannot evaluate: " + ex.Message); |
||||
} |
||||
if (graphBuiltOk) |
||||
{ |
||||
layoutGraph(this.objectGraph); |
||||
} |
||||
else |
||||
{ |
||||
this.graphDrawer.ClearCanvas(); |
||||
} |
||||
} |
||||
|
||||
ObjectGraph rebuildGraph(string expression) |
||||
{ |
||||
this.objectGraphBuilder = new ObjectGraphBuilder(debuggerService); |
||||
ICSharpCode.Core.LoggingService.Debug("Debugger visualizer: Building graph for expression: " + txtExpression.Text); |
||||
return this.objectGraphBuilder.BuildGraphForExpression(expression, this.expanded.Expressions); |
||||
} |
||||
|
||||
void layoutGraph(ObjectGraph graph) |
||||
{ |
||||
if (this.oldPosGraph != null) |
||||
{ |
||||
foreach (var oldNode in this.oldPosGraph.Nodes) |
||||
{ |
||||
// controls from old graph would be garbage collected, reuse them
|
||||
NodeControlCache.Instance.ReturnForReuse(oldNode.NodeVisualControl); |
||||
} |
||||
} |
||||
this.oldPosGraph = this.currentPosGraph; |
||||
ICSharpCode.Core.LoggingService.Debug("Debugger visualizer: Calculating graph layout"); |
||||
this.currentPosGraph = this.layouter.CalculateLayout(graph, layoutViewModel.SelectedEnumValue, this.expanded); |
||||
ICSharpCode.Core.LoggingService.Debug("Debugger visualizer: Graph layout done"); |
||||
registerExpandCollapseEvents(this.currentPosGraph); |
||||
|
||||
var graphDiff = new GraphMatcher().MatchGraphs(oldPosGraph, currentPosGraph); |
||||
ICSharpCode.Core.LoggingService.Debug("Debugger visualizer: starting graph animation"); |
||||
this.graphDrawer.StartAnimation(oldPosGraph, currentPosGraph, graphDiff); |
||||
//this.graphDrawer.Draw(this.currentPosGraph); // buggy layout with NodeControlCache
|
||||
} |
||||
|
||||
void layoutViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) |
||||
{ |
||||
if (e.PropertyName == "SelectedEnumValue") // TODO special event for enum value change
|
||||
{ |
||||
if (this.objectGraph != null) |
||||
{ |
||||
layoutGraph(this.objectGraph); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void clearErrorMessage() |
||||
{ |
||||
this.pnlError.Visibility = Visibility.Collapsed; |
||||
} |
||||
|
||||
void showErrorMessage(string message) |
||||
{ |
||||
this.txtError.Text = message; |
||||
this.pnlError.Visibility = Visibility.Visible; |
||||
//MessageBox.Show(ex.Message, "Exception", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} |
||||
|
||||
void registerExpandCollapseEvents(PositionedGraph posGraph) |
||||
{ |
||||
foreach (var node in posGraph.Nodes) |
||||
{ |
||||
node.PropertyExpanded += new EventHandler<PositionedPropertyEventArgs>(node_PropertyExpanded); |
||||
node.PropertyCollapsed += new EventHandler<PositionedPropertyEventArgs>(node_PropertyCollapsed); |
||||
node.ContentNodeExpanded += new EventHandler<ContentNodeEventArgs>(node_ContentNodeExpanded); |
||||
node.ContentNodeCollapsed += new EventHandler<ContentNodeEventArgs>(node_ContentNodeCollapsed); |
||||
} |
||||
} |
||||
|
||||
void node_ContentNodeExpanded(object sender, ContentNodeEventArgs e) |
||||
{ |
||||
expanded.ContentNodes.SetExpanded(e.Node); |
||||
layoutGraph(this.objectGraph); |
||||
} |
||||
|
||||
void node_ContentNodeCollapsed(object sender, ContentNodeEventArgs e) |
||||
{ |
||||
expanded.ContentNodes.SetCollapsed(e.Node); |
||||
layoutGraph(this.objectGraph); |
||||
} |
||||
|
||||
void node_PropertyExpanded(object sender, PositionedPropertyEventArgs e) |
||||
{ |
||||
// remember this property is expanded (for later graph rebuilds)
|
||||
expanded.Expressions.SetExpanded(e.Property.Expression); |
||||
|
||||
// add edge (+ possibly nodes) to underlying object graph (no need to fully rebuild)
|
||||
// TODO can add more nodes if they are expanded - now this adds always one node
|
||||
e.Property.ObjectGraphProperty.TargetNode = this.objectGraphBuilder.ObtainNodeForExpression(e.Property.Expression); |
||||
layoutGraph(this.objectGraph); |
||||
} |
||||
|
||||
void node_PropertyCollapsed(object sender, PositionedPropertyEventArgs e) |
||||
{ |
||||
// remember this property is collapsed (for later graph rebuilds)
|
||||
expanded.Expressions.SetCollapsed(e.Property.Expression); |
||||
|
||||
// just remove edge from underlying object graph (no need to fully rebuild)
|
||||
e.Property.ObjectGraphProperty.TargetNode = null; |
||||
layoutGraph(this.objectGraph); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue