Skip to content

Commit 090be5d

Browse files
authored
Merge pull request #2162 from Hosch250/Issue2053
Implement collapse/expand all subnodes for CE
2 parents c3a0206 + 0eb936c commit 090be5d

File tree

10 files changed

+225
-23
lines changed

10 files changed

+225
-23
lines changed

RetailCoder.VBE/Navigation/CodeExplorer/CodeExplorerItemViewModel.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,17 @@ protected set
171171
}
172172
}
173173

174-
public bool IsExpanded { get; set; }
174+
private bool _isExpanded;
175+
public bool IsExpanded
176+
{
177+
get { return _isExpanded; }
178+
set
179+
{
180+
_isExpanded = value;
181+
OnPropertyChanged();
182+
}
183+
}
184+
175185
public bool IsSelected { get; set; }
176186

177187
public abstract string Name { get; }

RetailCoder.VBE/Navigation/CodeExplorer/CodeExplorerViewModel.cs

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,35 +33,38 @@ public CodeExplorerViewModel(FolderHelper folderHelper, RubberduckParserState st
3333
_refreshCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), param => _state.OnParseRequested(this),
3434
param => !IsBusy && _state.IsDirty());
3535

36-
_navigateCommand = commands.OfType<UI.CodeExplorer.Commands.NavigateCommand>().FirstOrDefault();
36+
_navigateCommand = commands.OfType<UI.CodeExplorer.Commands.NavigateCommand>().SingleOrDefault();
3737

38-
_addTestModuleCommand = commands.OfType<UI.CodeExplorer.Commands.AddTestModuleCommand>().FirstOrDefault();
39-
_addStdModuleCommand = commands.OfType<AddStdModuleCommand>().FirstOrDefault();
40-
_addClassModuleCommand = commands.OfType<AddClassModuleCommand>().FirstOrDefault();
41-
_addUserFormCommand = commands.OfType<AddUserFormCommand>().FirstOrDefault();
38+
_addTestModuleCommand = commands.OfType<UI.CodeExplorer.Commands.AddTestModuleCommand>().SingleOrDefault();
39+
_addStdModuleCommand = commands.OfType<AddStdModuleCommand>().SingleOrDefault();
40+
_addClassModuleCommand = commands.OfType<AddClassModuleCommand>().SingleOrDefault();
41+
_addUserFormCommand = commands.OfType<AddUserFormCommand>().SingleOrDefault();
4242

43-
_openDesignerCommand = commands.OfType<OpenDesignerCommand>().FirstOrDefault();
44-
_openProjectPropertiesCommand = commands.OfType<OpenProjectPropertiesCommand>().FirstOrDefault();
45-
_renameCommand = commands.OfType<RenameCommand>().FirstOrDefault();
46-
_indenterCommand = commands.OfType<IndentCommand>().FirstOrDefault();
43+
_openDesignerCommand = commands.OfType<OpenDesignerCommand>().SingleOrDefault();
44+
_openProjectPropertiesCommand = commands.OfType<OpenProjectPropertiesCommand>().SingleOrDefault();
45+
_renameCommand = commands.OfType<RenameCommand>().SingleOrDefault();
46+
_indenterCommand = commands.OfType<IndentCommand>().SingleOrDefault();
4747

48-
_findAllReferencesCommand = commands.OfType<UI.CodeExplorer.Commands.FindAllReferencesCommand>().FirstOrDefault();
49-
_findAllImplementationsCommand = commands.OfType<UI.CodeExplorer.Commands.FindAllImplementationsCommand>().FirstOrDefault();
48+
_findAllReferencesCommand = commands.OfType<UI.CodeExplorer.Commands.FindAllReferencesCommand>().SingleOrDefault();
49+
_findAllImplementationsCommand = commands.OfType<UI.CodeExplorer.Commands.FindAllImplementationsCommand>().SingleOrDefault();
5050

51-
_importCommand = commands.OfType<ImportCommand>().FirstOrDefault();
52-
_exportCommand = commands.OfType<ExportCommand>().FirstOrDefault();
53-
_externalRemoveCommand = commands.OfType<RemoveCommand>().FirstOrDefault();
51+
_collapseAllSubnodesCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteCollapseNodes);
52+
_expandAllSubnodesCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteExpandNodes);
53+
54+
_importCommand = commands.OfType<ImportCommand>().SingleOrDefault();
55+
_exportCommand = commands.OfType<ExportCommand>().SingleOrDefault();
56+
_externalRemoveCommand = commands.OfType<RemoveCommand>().SingleOrDefault();
5457
if (_externalRemoveCommand != null)
5558
{
5659
_removeCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteRemoveComand, _externalRemoveCommand.CanExecute);
5760
}
5861

59-
_printCommand = commands.OfType<PrintCommand>().FirstOrDefault();
62+
_printCommand = commands.OfType<PrintCommand>().SingleOrDefault();
6063

61-
_commitCommand = commands.OfType<CommitCommand>().FirstOrDefault();
62-
_undoCommand = commands.OfType<UndoCommand>().FirstOrDefault();
64+
_commitCommand = commands.OfType<CommitCommand>().SingleOrDefault();
65+
_undoCommand = commands.OfType<UndoCommand>().SingleOrDefault();
6366

64-
_copyResultsCommand = commands.OfType<CopyResultsCommand>().FirstOrDefault();
67+
_copyResultsCommand = commands.OfType<CopyResultsCommand>().SingleOrDefault();
6568

6669
_setNameSortCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), param =>
6770
{
@@ -409,6 +412,33 @@ private void SetErrorState(CodeExplorerItemViewModel itemNode, VBComponent compo
409412
}
410413
}
411414

415+
private void ExecuteCollapseNodes(object parameter)
416+
{
417+
var node = parameter as CodeExplorerItemViewModel;
418+
if (node == null) { return; }
419+
420+
SwitchNodeState(node, false);
421+
}
422+
423+
private void ExecuteExpandNodes(object parameter)
424+
{
425+
var node = parameter as CodeExplorerItemViewModel;
426+
if (node == null) { return; }
427+
428+
SwitchNodeState(node, true);
429+
}
430+
431+
private void SwitchNodeState(CodeExplorerItemViewModel node, bool expandedState)
432+
{
433+
node.IsExpanded = expandedState;
434+
435+
foreach (var item in node.Items)
436+
{
437+
item.IsExpanded = expandedState;
438+
SwitchNodeState(item, expandedState);
439+
}
440+
}
441+
412442
private readonly CommandBase _refreshCommand;
413443
public CommandBase RefreshCommand { get { return _refreshCommand; } }
414444

@@ -445,6 +475,12 @@ private void SetErrorState(CodeExplorerItemViewModel itemNode, VBComponent compo
445475
private readonly CommandBase _findAllImplementationsCommand;
446476
public CommandBase FindAllImplementationsCommand { get { return _findAllImplementationsCommand; } }
447477

478+
private readonly CommandBase _collapseAllSubnodesCommand;
479+
public CommandBase CollapseAllSubnodesCommand { get { return _collapseAllSubnodesCommand; } }
480+
481+
private readonly CommandBase _expandAllSubnodesCommand;
482+
public CommandBase ExpandAllSubnodesCommand { get { return _expandAllSubnodesCommand; } }
483+
448484
private readonly CommandBase _importCommand;
449485
public CommandBase ImportCommand { get { return _importCommand; } }
450486

647 Bytes
Loading

RetailCoder.VBE/Resources/folder.png

476 Bytes
Loading

RetailCoder.VBE/Rubberduck.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,6 +1278,8 @@
12781278
<Resource Include="Resources\Custom\PNG\CollapseUp.png" />
12791279
<Resource Include="Resources\Custom\PNG\ObjectLibrary.png" />
12801280
<Content Include="Resources\Custom\mit_license.txt" />
1281+
<Resource Include="Resources\folder-open.png" />
1282+
<Resource Include="Resources\folder.png" />
12811283
<Content Include="Resources\Rubberduck\RD-AboutWindow.png" />
12821284
<Content Include="Resources\Rubberduck\RD-InstallBanner.bmp" />
12831285
<Content Include="Resources\Rubberduck\RD-InstallWindow.bmp" />

RetailCoder.VBE/UI/CodeExplorer/CodeExplorerControl.xaml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
d:DesignHeight="300" d:DesignWidth="300" d:DataContext="{d:DesignInstance codeExplorer:CodeExplorerViewModel}">
1616
<UserControl.Resources>
1717
<BitmapImage x:Key="RefreshImage" UriSource="../../Resources/arrow-circle-double.png" />
18+
<BitmapImage x:Key="CollaseNodesImage" UriSource="../../Resources/folder.png" />
19+
<BitmapImage x:Key="ExpandNodesImage" UriSource="../../Resources/folder-open.png" />
1820
<BitmapImage x:Key="UndoImage" UriSource="../../Resources/arrow-circle-left.png" />
1921
<BitmapImage x:Key="PrintImage" UriSource="../../Resources/printer.png" />
2022

2123
<BooleanToVisibilityConverter x:Key="BoolToVisibility"/>
2224
<converters:BoolToHiddenVisibilityConverter x:Key="BoolToHiddenVisibility" />
23-
<converters:InvertBoolValueConverter x:Key="InvertBoolValue" />
2425

2526
<Style x:Key="MenuItemStyle" TargetType="{x:Type MenuItem}">
2627
<Setter Property="OverridesDefaultStyle" Value="True"/>
@@ -404,6 +405,21 @@
404405
Command="{Binding IndenterCommand}"
405406
CommandParameter="{Binding SelectedItem}" />
406407
<Separator />
408+
<MenuItem Header="{Resx ResxName=Rubberduck.UI.RubberduckUI, Key=CodeExplorer_CollapseSubnodesToolTip}"
409+
Command="{Binding CollapseAllSubnodesCommand}"
410+
CommandParameter="{Binding SelectedItem}">
411+
<MenuItem.Icon>
412+
<Image Source="{StaticResource CollaseNodesImage}" />
413+
</MenuItem.Icon>
414+
</MenuItem>
415+
<MenuItem Header="{Resx ResxName=Rubberduck.UI.RubberduckUI, Key=CodeExplorer_ExpandSubnodesToolTip}"
416+
Command="{Binding ExpandAllSubnodesCommand}"
417+
CommandParameter="{Binding SelectedItem}">
418+
<MenuItem.Icon>
419+
<Image Source="{StaticResource ExpandNodesImage}" />
420+
</MenuItem.Icon>
421+
</MenuItem>
422+
<Separator />
407423
<MenuItem Header="{Resx ResxName=Rubberduck.UI.RubberduckUI, Key=CodeExplorer_Import}"
408424
Command="{Binding ImportCommand}"
409425
CommandParameter="{Binding SelectedItem}" />
@@ -697,6 +713,7 @@
697713
IsCheckable="True" />
698714
</MenuItem>
699715
</Menu>
716+
700717
<ToggleButton Name="DisplaySignatures" ToolTip="{Resx ResxName=Rubberduck.UI.RubberduckUI, Key=CodeExplorer_ShowSignaturesToolTip}" IsChecked="True">
701718
<Image Height="16" Source="../../Resources/Custom/PNG/DisplayFullSignature.png" />
702719
</ToggleButton>

RetailCoder.VBE/UI/RubberduckUI.Designer.cs

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

RetailCoder.VBE/UI/RubberduckUI.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,4 +1769,10 @@ All our stargazers, likers &amp; followers, for the warm fuzzies
17691769
<data name="HotkeyDescription_RefactorEncapsulateField" xml:space="preserve">
17701770
<value>Refactor / Encapsulate Field</value>
17711771
</data>
1772+
<data name="CodeExplorer_CollapseSubnodesToolTip" xml:space="preserve">
1773+
<value>Collapse node and all child nodes</value>
1774+
</data>
1775+
<data name="CodeExplorer_ExpandSubnodesToolTip" xml:space="preserve">
1776+
<value>Expand node and all child nodes</value>
1777+
</data>
17721778
</root>

Rubberduck.sln

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio 2013
4-
VisualStudioVersion = 12.0.40629.0
3+
# Visual Studio 14
4+
VisualStudioVersion = 14.0.25420.1
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rubberduck", "RetailCoder.VBE\Rubberduck.csproj", "{20589DE8-432E-4359-9232-69EB070B7185}"
77
ProjectSection(ProjectDependencies) = postProject

RubberduckTests/CodeExplorer/CodeExplorerTests.cs

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
using Rubberduck.UI;
1616
using Rubberduck.UI.CodeExplorer.Commands;
1717
using Rubberduck.UI.Command;
18-
using Rubberduck.UnitTesting;
1918
using Rubberduck.VBEditor.VBEHost;
2019
using Rubberduck.VBEditor.Extensions;
2120
using RubberduckTests.Mocks;
@@ -955,6 +954,120 @@ Sub Bar()
955954
Assert.AreEqual(expectedCode, module.Lines());
956955
}
957956

957+
[TestMethod]
958+
public void ExpandAllNodes()
959+
{
960+
var inputCode =
961+
@"Sub Foo()
962+
End Sub";
963+
964+
var builder = new MockVbeBuilder();
965+
VBComponent component;
966+
var vbe = builder.BuildFromSingleStandardModule(inputCode, out component);
967+
var mockHost = new Mock<IHostApplication>();
968+
mockHost.SetupAllProperties();
969+
970+
var state = new RubberduckParserState(new Mock<ISinks>().Object);
971+
var vm = new CodeExplorerViewModel(new FolderHelper(state, GetDelimiterConfigLoader()), state, new List<CommandBase>());
972+
973+
var parser = MockParser.Create(vbe.Object, state);
974+
parser.Parse(new CancellationTokenSource());
975+
if (parser.State.Status >= ParserState.Error) { Assert.Inconclusive("Parser Error"); }
976+
977+
vm.SelectedItem = vm.Projects.Single();
978+
vm.ExpandAllSubnodesCommand.Execute(vm.SelectedItem);
979+
980+
Assert.IsTrue(vm.Projects.Single().IsExpanded);
981+
Assert.IsTrue(vm.Projects.Single().Items.Single().IsExpanded);
982+
Assert.IsTrue(vm.Projects.Single().Items.Single().Items.Single().IsExpanded);
983+
Assert.IsTrue(vm.Projects.Single().Items.Single().Items.Single().Items.Single().IsExpanded);
984+
}
985+
986+
[TestMethod]
987+
public void ExpandAllNodes_StartingWithSubnode()
988+
{
989+
var builder = new MockVbeBuilder();
990+
var project = builder.ProjectBuilder("Proj", vbext_ProjectProtection.vbext_pp_none)
991+
.AddComponent("Comp1", vbext_ComponentType.vbext_ct_ClassModule, @"'@Folder ""Foo""")
992+
.AddComponent("Comp2", vbext_ComponentType.vbext_ct_ClassModule, @"'@Folder ""Bar""")
993+
.Build();
994+
var vbe = builder.AddProject(project).Build();
995+
var mockHost = new Mock<IHostApplication>();
996+
mockHost.SetupAllProperties();
997+
998+
var state = new RubberduckParserState(new Mock<ISinks>().Object);
999+
var vm = new CodeExplorerViewModel(new FolderHelper(state, GetDelimiterConfigLoader()), state, new List<CommandBase>());
1000+
1001+
var parser = MockParser.Create(vbe.Object, state);
1002+
parser.Parse(new CancellationTokenSource());
1003+
if (parser.State.Status >= ParserState.Error) { Assert.Inconclusive("Parser Error"); }
1004+
1005+
vm.Projects.Single().Items.Last().IsExpanded = false;
1006+
1007+
vm.SelectedItem = vm.Projects.Single().Items.First();
1008+
vm.ExpandAllSubnodesCommand.Execute(vm.SelectedItem);
1009+
1010+
Assert.IsTrue(vm.Projects.Single().Items.First().IsExpanded);
1011+
Assert.IsFalse(vm.Projects.Single().Items.Last().IsExpanded);
1012+
}
1013+
1014+
[TestMethod]
1015+
public void CollapseAllNodes()
1016+
{
1017+
var inputCode =
1018+
@"Sub Foo()
1019+
End Sub";
1020+
1021+
var builder = new MockVbeBuilder();
1022+
VBComponent component;
1023+
var vbe = builder.BuildFromSingleStandardModule(inputCode, out component);
1024+
var mockHost = new Mock<IHostApplication>();
1025+
mockHost.SetupAllProperties();
1026+
1027+
var state = new RubberduckParserState(new Mock<ISinks>().Object);
1028+
var vm = new CodeExplorerViewModel(new FolderHelper(state, GetDelimiterConfigLoader()), state, new List<CommandBase>());
1029+
1030+
var parser = MockParser.Create(vbe.Object, state);
1031+
parser.Parse(new CancellationTokenSource());
1032+
if (parser.State.Status >= ParserState.Error) { Assert.Inconclusive("Parser Error"); }
1033+
1034+
vm.SelectedItem = vm.Projects.Single();
1035+
vm.CollapseAllSubnodesCommand.Execute(vm.SelectedItem);
1036+
1037+
Assert.IsFalse(vm.Projects.Single().IsExpanded);
1038+
Assert.IsFalse(vm.Projects.Single().Items.Single().IsExpanded);
1039+
Assert.IsFalse(vm.Projects.Single().Items.Single().Items.Single().IsExpanded);
1040+
Assert.IsFalse(vm.Projects.Single().Items.Single().Items.Single().Items.Single().IsExpanded);
1041+
}
1042+
1043+
[TestMethod]
1044+
public void CollapseAllNodes_StartingWithSubnode()
1045+
{
1046+
var builder = new MockVbeBuilder();
1047+
var project = builder.ProjectBuilder("Proj", vbext_ProjectProtection.vbext_pp_none)
1048+
.AddComponent("Comp1", vbext_ComponentType.vbext_ct_ClassModule, @"'@Folder ""Foo""")
1049+
.AddComponent("Comp2", vbext_ComponentType.vbext_ct_ClassModule, @"'@Folder ""Bar""")
1050+
.Build();
1051+
var vbe = builder.AddProject(project).Build();
1052+
var mockHost = new Mock<IHostApplication>();
1053+
mockHost.SetupAllProperties();
1054+
1055+
var state = new RubberduckParserState(new Mock<ISinks>().Object);
1056+
var vm = new CodeExplorerViewModel(new FolderHelper(state, GetDelimiterConfigLoader()), state, new List<CommandBase>());
1057+
1058+
var parser = MockParser.Create(vbe.Object, state);
1059+
parser.Parse(new CancellationTokenSource());
1060+
if (parser.State.Status >= ParserState.Error) { Assert.Inconclusive("Parser Error"); }
1061+
1062+
vm.Projects.Single().Items.Last().IsExpanded = true;
1063+
1064+
vm.SelectedItem = vm.Projects.Single().Items.First();
1065+
vm.CollapseAllSubnodesCommand.Execute(vm.SelectedItem);
1066+
1067+
Assert.IsFalse(vm.Projects.Single().Items.First().IsExpanded);
1068+
Assert.IsTrue(vm.Projects.Single().Items.Last().IsExpanded);
1069+
}
1070+
9581071
[TestMethod]
9591072
public void CompareByName_ReturnsZeroForIdenticalNodes()
9601073
{

0 commit comments

Comments
 (0)