1111using Microsoft . Msagl . Drawing ;
1212using Microsoft . Msagl . WpfGraphControl ;
1313using Color = Microsoft . Msagl . Drawing . Color ;
14+ using Node = Microsoft . Msagl . Drawing . Node ;
1415
1516namespace CSharpCodeAnalyst . GraphArea ;
1617
@@ -20,12 +21,13 @@ namespace CSharpCodeAnalyst.GraphArea;
2021/// Dependencies of the same type (i.e a method Calls another multiple times) are handled
2122/// in the parser. In this case the dependency holds all source references.
2223/// </summary>
23- internal class DependencyGraphViewer : IDependencyGraphViewer , IDependencyGraphBinding , INotifyPropertyChanged
24+ internal partial class DependencyGraphViewer : IDependencyGraphViewer , IDependencyGraphBinding , INotifyPropertyChanged
2425{
2526 private readonly List < IContextCommand > _contextCommands = [ ] ;
2627 private readonly MsaglBuilder _msaglBuilder ;
2728 private readonly IPublisher _publisher ;
28- private readonly LinkedList < CodeGraph > _undoStack = new ( ) ;
29+
30+ private readonly LinkedList < UndoState > _undoStack = new ( ) ;
2931
3032 private readonly int _undoStackSize = 10 ;
3133
@@ -45,6 +47,7 @@ internal class DependencyGraphViewer : IDependencyGraphViewer, IDependencyGraphB
4547 private IViewerEdge ? _lastHighlightedEdge ;
4648
4749 private GraphViewer ? _msaglViewer ;
50+ private PresentationState _presentationState = new ( ) ;
4851
4952 private RenderOption _renderOption = new DefaultRenderOptions ( ) ;
5053 private bool _showFlatGraph ;
@@ -77,20 +80,13 @@ public void ShowFlatGraph(bool value)
7780 RefreshGraph ( ) ;
7881 }
7982
80- /// <summary>
81- /// Adding an existing element or dependency is prevented.
82- /// Note from the originalCodeElement we don't add parent or children.
83- /// We just use this information to integrate the node into the existing canvas.
84- /// </summary>
85- public void AddToGraph ( IEnumerable < CodeElement > originalCodeElements , IEnumerable < Dependency > newDependencies )
83+ private void AddToGraphInternal ( IEnumerable < CodeElement > originalCodeElements , IEnumerable < Dependency > newDependencies )
8684 {
8785 if ( _msaglViewer is null )
8886 {
8987 return ;
9088 }
9189
92- PushUndo ( ) ;
93-
9490 IntegrateNewFromOriginal ( originalCodeElements ) ;
9591
9692 // Add dependencies we explicitly requested.
@@ -104,25 +100,56 @@ public void AddToGraph(IEnumerable<CodeElement> originalCodeElements, IEnumerabl
104100 RefreshGraph ( ) ;
105101 }
106102
103+ /// <summary>
104+ /// Adding an existing element or dependency is prevented.
105+ /// Note from the originalCodeElement we don't add parent or children.
106+ /// We just use this information to integrate the node into the existing canvas.
107+ /// </summary>
108+ public void AddToGraph ( IEnumerable < CodeElement > originalCodeElements , IEnumerable < Dependency > newDependencies )
109+ {
110+ if ( _msaglViewer is null )
111+ {
112+ return ;
113+ }
114+
115+ PushUndo ( ) ;
116+ AddToGraphInternal ( originalCodeElements , newDependencies ) ;
117+ }
118+
107119 public void AddContextCommand ( IContextCommand command )
108120 {
109121 _contextCommands . Add ( command ) ;
110122 }
111123
112- public void Clear ( )
124+ private void Clear ( bool withUndoStack )
113125 {
114126 if ( _msaglViewer is null )
115127 {
116128 return ;
117129 }
118130
119131 _clonedCodeGraph = new CodeGraph ( ) ;
120- _undoStack . Clear ( ) ;
132+
133+ if ( withUndoStack )
134+ {
135+ ClearUndo ( ) ;
136+ }
137+
138+ // Nothing collapsed by default
139+ _presentationState = new PresentationState ( ) ;
121140 RefreshGraph ( ) ;
122141 }
142+ public void Clear ( )
143+ {
144+ Clear ( true ) ;
145+ }
123146
147+ private void ClearUndo ( )
148+ {
149+ _undoStack . Clear ( ) ;
150+ }
124151
125- public void Reset ( )
152+ public void Layout ( )
126153 {
127154 //_msaglViewer?.SetInitialTransform();
128155 RefreshGraph ( ) ;
@@ -187,19 +214,36 @@ public void ShowGlobalContextMenu()
187214 globalContextMenu . IsOpen = true ;
188215 }
189216
217+
218+
190219 public bool Undo ( )
191220 {
192221 if ( _undoStack . Any ( ) is false )
193222 {
194223 return false ;
195224 }
196225
197- _clonedCodeGraph = _undoStack . First ( ) ;
226+ var state = _undoStack . First ( ) ;
198227 _undoStack . RemoveFirst ( ) ;
228+
229+ _clonedCodeGraph = state . CodeGraph ;
230+ _presentationState = state . PresentationState ;
231+
199232 RefreshGraph ( ) ;
200233 return true ;
201234 }
202235
236+ public void ImportCycleGroup ( List < CodeElement > codeElements , List < Dependency > dependencies )
237+ {
238+ PushUndo ( ) ;
239+ Clear ( false ) ;
240+
241+ // Everything is collapsed by default. This allows to import large graphs.
242+ var defaultState = codeElements . Where ( c => c . Children . Any ( ) ) . ToDictionary ( c => c . Id , c => true ) ;
243+ _presentationState = new PresentationState ( defaultState ) ;
244+ AddToGraphInternal ( codeElements , dependencies ) ;
245+ }
246+
203247
204248 public event PropertyChangedEventHandler ? PropertyChanged ;
205249
@@ -211,7 +255,8 @@ private void PushUndo()
211255 _undoStack . RemoveLast ( ) ;
212256 }
213257
214- _undoStack . AddFirst ( _clonedCodeGraph . Clone ( null , null ) ) ;
258+ var state = new UndoState ( _clonedCodeGraph . Clone ( null , null ) , _presentationState . Clone ( ) ) ;
259+ _undoStack . AddFirst ( state ) ;
215260 }
216261
217262 private void ClearEdgeColoring ( )
@@ -264,7 +309,8 @@ private void RefreshGraph()
264309 {
265310 if ( _msaglViewer != null )
266311 {
267- var graph = _msaglBuilder . CreateGraphFromCodeStructure ( _clonedCodeGraph , _showFlatGraph ) ;
312+ var graph = _msaglBuilder . CreateGraphFromCodeStructure ( _clonedCodeGraph , _presentationState ,
313+ _showFlatGraph ) ;
268314
269315 _renderOption . Apply ( graph ) ;
270316 _msaglViewer . Graph = graph ;
@@ -400,18 +446,38 @@ bool IsCtrlPressed()
400446 var node = clickedObject . Node ;
401447 var contextMenu = new ContextMenu ( ) ;
402448
403- var addParentMenuItem = new MenuItem { Header = "Add parent" } ;
404- addParentMenuItem . Click += ( _ , _ ) => AddParentRequest ( node ) ;
449+ MenuItem item = null ;
450+ if ( node . UserData is CodeElement codeElement )
451+ {
452+ if ( _presentationState . IsCollapsed ( codeElement . Id ) &&
453+ codeElement . Children . Any ( ) )
454+ {
455+ item = new MenuItem { Header = "Expand" } ;
456+ item . Click += ( _ , _ ) => Expand ( codeElement . Id ) ;
457+ contextMenu . Items . Add ( item ) ;
458+ }
405459
406- var findInTreeMenuItem = new MenuItem { Header = "Find in Tree" } ;
407- findInTreeMenuItem . Click += ( _ , _ ) => FindInTree ( node ) ;
460+ if ( ! _presentationState . IsCollapsed ( codeElement . Id ) &&
461+ codeElement . Children . Any ( ) )
462+ {
463+ item = new MenuItem { Header = "Collapse" } ;
464+ item . Click += ( _ , _ ) => Collapse ( codeElement . Id ) ;
465+ contextMenu . Items . Add ( item ) ;
466+ }
467+ }
468+
469+ item = new MenuItem { Header = "Delete Node" } ;
470+ item . Click += ( _ , _ ) => DeleteNode ( node ) ;
471+ contextMenu . Items . Add ( item ) ;
472+
473+ item = new MenuItem { Header = "Find in Tree" } ;
474+ item . Click += ( _ , _ ) => FindInTree ( node ) ;
475+ contextMenu . Items . Add ( item ) ;
408476
409- var deleteMenuItem = new MenuItem { Header = "Delete Node" } ;
410- deleteMenuItem . Click += ( _ , _ ) => DeleteNode ( node ) ;
477+ item = new MenuItem { Header = "Add parent" } ;
478+ item . Click += ( _ , _ ) => AddParentRequest ( node ) ;
479+ contextMenu . Items . Add ( item ) ;
411480
412- contextMenu . Items . Add ( deleteMenuItem ) ;
413- contextMenu . Items . Add ( findInTreeMenuItem ) ;
414- contextMenu . Items . Add ( addParentMenuItem ) ;
415481 contextMenu . Items . Add ( new Separator ( ) ) ;
416482 var lastItemIsSeparator = true ;
417483
@@ -451,6 +517,21 @@ bool IsCtrlPressed()
451517 }
452518 }
453519
520+ private void Collapse ( string id )
521+ {
522+ PushUndo ( ) ;
523+ _presentationState . SetCollapsedState ( id , true ) ;
524+ RefreshGraph ( ) ;
525+ }
526+
527+ private void Expand ( string id )
528+ {
529+ PushUndo ( ) ;
530+ _presentationState . SetCollapsedState ( id , false ) ;
531+
532+ RefreshGraph ( ) ;
533+ }
534+
454535 private void DeleteAllMarkedElements ( )
455536 {
456537 if ( _msaglViewer is null )
0 commit comments