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 @@ |
|||||||
|
// <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 @@ |
|||||||
|
<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 @@ |
|||||||
|
// <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