Skip to content

Commit 7fd0f05

Browse files
committed
UI might work
1 parent 41f5fb0 commit 7fd0f05

File tree

2 files changed

+199
-87
lines changed

2 files changed

+199
-87
lines changed

Rubberduck.Core/CodeAnalysis/CodeMetrics/CodeMetricsViewModel.cs

Lines changed: 96 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,24 @@
44
using System;
55
using System.Linq;
66
using System.Collections.Generic;
7+
using System.Collections.ObjectModel;
8+
using Rubberduck.Navigation.CodeExplorer;
9+
using System.Windows;
10+
using Rubberduck.Navigation.Folders;
711

812
namespace Rubberduck.CodeAnalysis.CodeMetrics
913
{
1014
public class CodeMetricsViewModel : ViewModelBase, IDisposable
1115
{
1216
private readonly RubberduckParserState _state;
1317
private readonly ICodeMetricsAnalyst _analyst;
18+
private readonly FolderHelper _folderHelper;
1419

15-
public CodeMetricsViewModel(RubberduckParserState state, ICodeMetricsAnalyst analyst)
20+
public CodeMetricsViewModel(RubberduckParserState state, ICodeMetricsAnalyst analyst, FolderHelper folderHelper)
1621
{
1722
_state = state;
1823
_analyst = analyst;
24+
_folderHelper = folderHelper;
1925
_state.StateChanged += OnStateChanged;
2026
}
2127

@@ -43,49 +49,111 @@ private void UpdateData()
4349
IsBusy = true;
4450

4551
var metricResults = _analyst.GetMetrics(_state);
46-
47-
MetricResults = metricResults
48-
.GroupBy(r => r.Metric.Level)
49-
.ToDictionary(g => g.Key,
50-
levelGrouping => levelGrouping.GroupBy(r => r.Declaration)
51-
.ToDictionary(g => g.Key,
52-
declarationGrouping => declarationGrouping.ToDictionary(r => r.Metric)
53-
)
54-
);
55-
56-
metricsByLevel = metricResults.GroupBy(r => r.Metric.Level).ToDictionary(g => g.Key, g => g.Select(r => r.Metric).ToList());
57-
declarationsByLevel = metricResults.GroupBy(r => r.Metric.Level).ToDictionary(g => g.Key, g => g.Select(r => r.Declaration).ToList());
58-
declarationsByParent = metricResults.Select(r => r.Declaration).GroupBy(decl => decl.ParentDeclaration).ToDictionary(g => g.Key, g => g.ToList());
5952
resultsByDeclaration = metricResults.GroupBy(r => r.Declaration).ToDictionary(g => g.Key, g => g.ToList());
60-
53+
54+
if (Projects == null)
55+
{
56+
Projects = new ObservableCollection<CodeExplorerItemViewModel>();
57+
}
58+
59+
IsBusy = _state.Status != ParserState.Pending && _state.Status <= ParserState.ResolvedDeclarations;
60+
61+
var userDeclarations = _state.DeclarationFinder.AllUserDeclarations
62+
.GroupBy(declaration => declaration.ProjectId)
63+
.ToList();
64+
65+
if (userDeclarations.Any(
66+
grouping => grouping.All(declaration => declaration.DeclarationType != DeclarationType.Project)))
67+
{
68+
return;
69+
}
70+
71+
var newProjects = userDeclarations.Select(grouping =>
72+
new CodeExplorerProjectViewModel(_folderHelper,
73+
grouping.SingleOrDefault(declaration => declaration.DeclarationType == DeclarationType.Project),
74+
grouping)).ToList();
75+
76+
UpdateNodes(Projects, newProjects);
77+
78+
Projects = new ObservableCollection<CodeExplorerItemViewModel>(newProjects);
79+
6180
IsBusy = false;
6281
}
6382

83+
private void UpdateNodes(IEnumerable<CodeExplorerItemViewModel> oldList, IEnumerable<CodeExplorerItemViewModel> newList)
84+
{
85+
foreach (var item in newList)
86+
{
87+
CodeExplorerItemViewModel oldItem;
88+
89+
if (item is CodeExplorerCustomFolderViewModel)
90+
{
91+
oldItem = oldList.FirstOrDefault(i => i.Name == item.Name);
92+
}
93+
else
94+
{
95+
oldItem = oldList.FirstOrDefault(i =>
96+
item.QualifiedSelection != null && i.QualifiedSelection != null &&
97+
i.QualifiedSelection.Value.QualifiedName.ProjectId ==
98+
item.QualifiedSelection.Value.QualifiedName.ProjectId &&
99+
i.QualifiedSelection.Value.QualifiedName.ComponentName ==
100+
item.QualifiedSelection.Value.QualifiedName.ComponentName &&
101+
i.QualifiedSelection.Value.Selection == item.QualifiedSelection.Value.Selection);
102+
}
103+
104+
if (oldItem != null)
105+
{
106+
item.IsExpanded = oldItem.IsExpanded;
107+
item.IsSelected = oldItem.IsSelected;
108+
109+
if (oldItem.Items.Any() && item.Items.Any())
110+
{
111+
UpdateNodes(oldItem.Items, item.Items);
112+
}
113+
}
114+
}
115+
}
116+
64117
public void Dispose()
65118
{
66119
_state.StateChanged -= OnStateChanged;
67120
}
68121

69-
// TBD: use these dictionaries to populate the GridView
70-
private Dictionary<AggregationLevel, List<CodeMetric>> metricsByLevel;
71-
private Dictionary<AggregationLevel, List<Declaration>> declarationsByLevel;
72-
private Dictionary<Declaration, List<Declaration>> declarationsByParent;
73122
private Dictionary<Declaration, List<ICodeMetricResult>> resultsByDeclaration;
74-
public Dictionary<AggregationLevel, Dictionary<Declaration, Dictionary<CodeMetric, ICodeMetricResult>>>
75-
MetricResults { get; private set; }
76123

77-
//SelectedMetric = ModuleMetrics.Any(i => SelectedMetric.ModuleName == i.ModuleName)
78-
// ? ModuleMetrics.First(i => SelectedMetric.ModuleName == i.ModuleName)
79-
// : ModuleMetrics.FirstOrDefault();
124+
private CodeExplorerItemViewModel _selectedItem;
125+
public CodeExplorerItemViewModel SelectedItem
126+
{
127+
get => _selectedItem;
128+
set
129+
{
130+
_selectedItem = value;
131+
OnPropertyChanged();
132+
OnPropertyChanged(nameof(Metrics));
133+
}
134+
}
80135

81-
private Dictionary<CodeMetric, ICodeMetricResult> _selectedMetric;
82-
public Dictionary<CodeMetric, ICodeMetricResult> SelectedMetric
136+
private ObservableCollection<CodeExplorerItemViewModel> _projects;
137+
public ObservableCollection<CodeExplorerItemViewModel> Projects
83138
{
84-
get => _selectedMetric;
139+
get => _projects;
85140
set
86141
{
87-
_selectedMetric = value;
142+
_projects = new ObservableCollection<CodeExplorerItemViewModel>(value.OrderBy(o => o.NameWithSignature));
143+
88144
OnPropertyChanged();
145+
OnPropertyChanged(nameof(TreeViewVisibility));
146+
}
147+
}
148+
149+
public Visibility TreeViewVisibility => Projects == null || Projects.Count == 0 ? Visibility.Collapsed : Visibility.Visible;
150+
151+
public ObservableCollection<ICodeMetricResult> Metrics
152+
{
153+
get
154+
{
155+
var results = resultsByDeclaration?.FirstOrDefault(f => f.Key == SelectedItem.GetSelectedDeclaration());
156+
return !results.HasValue || results.Value.Value == null ? new ObservableCollection<ICodeMetricResult>() : new ObservableCollection<ICodeMetricResult>(results.Value.Value);
89157
}
90158
}
91159

Rubberduck.Core/UI/CodeMetrics/CodeMetricsControl.xaml

Lines changed: 103 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
xmlns:codeMetrics="clr-namespace:Rubberduck.CodeAnalysis.CodeMetrics"
77
xmlns:controls="clr-namespace:Rubberduck.UI.Controls"
88
xmlns:converters="clr-namespace:Rubberduck.UI.Converters"
9+
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
910
ResxExtension.DefaultResxName="Rubberduck.Resources.RubberduckUI"
1011
Language="{UICulture}"
1112
Name="CodeMetrics"
@@ -151,6 +152,84 @@
151152
</LinearGradientBrush>
152153
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" />
153154
<SolidColorBrush x:Key="{x:Static SystemColors.ControlTextBrushKey}" Color="Black" />
155+
156+
<Style x:Key="ShinyTreeView"
157+
TargetType="{x:Type TreeViewItem}">
158+
<Setter Property="BorderThickness" Value="1.5"/>
159+
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
160+
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
161+
<Setter Property="Visibility" Value="{Binding IsVisible, Mode=OneWay, Converter={StaticResource BoolToVisibility}}" />
162+
<Setter Property="HorizontalAlignment" Value="Left" />
163+
<Style.Triggers>
164+
<Trigger Property="IsSelected" Value="True">
165+
<Setter Property="BorderBrush" Value="#adc6e5"/>
166+
</Trigger>
167+
<MultiTrigger>
168+
<MultiTrigger.Conditions>
169+
<Condition Property="IsSelected" Value="True"/>
170+
<Condition Property="IsSelectionActive" Value="False"/>
171+
</MultiTrigger.Conditions>
172+
<Setter Property="BorderBrush" Value="LightGray"/>
173+
</MultiTrigger>
174+
</Style.Triggers>
175+
<Style.Resources>
176+
<Style TargetType="Border">
177+
<Setter Property="CornerRadius" Value="2"/>
178+
</Style>
179+
</Style.Resources>
180+
</Style>
181+
182+
<Style x:Key="IconStyle" TargetType="Image">
183+
<Setter Property="Height" Value="16" />
184+
<Setter Property="Width" Value="16" />
185+
<Setter Property="Margin" Value="2,0,2,0" />
186+
<Setter Property="VerticalAlignment" Value="Top" />
187+
</Style>
188+
189+
<Style x:Key="TreeViewItemStyle" TargetType="TextBlock">
190+
<Setter Property="Text" Value="{Binding Name}" />
191+
<Setter Property="FontSize" Value="10" />
192+
<Setter Property="Margin" Value="2,0,2,0" />
193+
<Setter Property="VerticalAlignment" Value="Center" />
194+
<Setter Property="ToolTip" Value="{Binding Name}" />
195+
<Setter Property="ToolTipService.InitialShowDelay" Value="500" />
196+
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
197+
</Style>
198+
199+
<Style x:Key="TreeViewItemStyleWithSignatures" TargetType="TextBlock">
200+
<Setter Property="Text" Value="{Binding NameWithSignature}" />
201+
<Setter Property="FontSize" Value="10" />
202+
<Setter Property="Margin" Value="2,0,2,0" />
203+
<Setter Property="VerticalAlignment" Value="Center" />
204+
<Setter Property="ToolTip" Value="{Binding NameWithSignature}" />
205+
<Setter Property="ToolTipService.InitialShowDelay" Value="500" />
206+
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
207+
</Style>
208+
209+
<Style x:Key="TreeViewIconStyle" TargetType="Image" BasedOn="{StaticResource IconStyle}">
210+
<Setter Property="Source" Value="{Binding CollapsedIcon}" />
211+
<Style.Triggers>
212+
<!-- thanks to H.B. on http://stackoverflow.com/a/5797323/1188513 -->
213+
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}, Path=IsExpanded}" Value="True">
214+
<Setter Property="Source" Value="{Binding ExpandedIcon}" />
215+
</DataTrigger>
216+
</Style.Triggers>
217+
</Style>
218+
219+
<HierarchicalDataTemplate x:Key="CodeExplorerTemplate"
220+
DataType="codeExplorer:CodeExplorerProjectViewModel"
221+
ItemsSource="{Binding Items}">
222+
<StackPanel Orientation="Horizontal">
223+
<Image Style="{StaticResource TreeViewIconStyle}" />
224+
<TextBlock Style="{StaticResource TreeViewItemStyle}" Visibility="{Binding ElementName=DisplaySignatures, Path=IsChecked, Converter={StaticResource BoolToHiddenVisibility}}" />
225+
<TextBlock Style="{StaticResource TreeViewItemStyleWithSignatures}" Visibility="{Binding ElementName=DisplaySignatures, Path=IsChecked, Converter={StaticResource BoolToVisibility}}" />
226+
</StackPanel>
227+
</HierarchicalDataTemplate>
228+
229+
<Style x:Key="CodeExplorerTreeViewStyle" TargetType="TreeView">
230+
<Setter Property="ItemTemplate" Value="{StaticResource CodeExplorerTemplate}" />
231+
<Setter Property="ItemsSource" Value="{Binding Projects, UpdateSourceTrigger=PropertyChanged}" />
232+
</Style>
154233
</ResourceDictionary>
155234
</UserControl.Resources>
156235

@@ -164,65 +243,30 @@
164243
<RowDefinition Height="*" />
165244
</Grid.RowDefinitions>
166245

167-
<ListBox Name="ListBox"
168-
Grid.RowSpan="2"
169-
Grid.Column="0"
170-
ItemContainerStyle="{StaticResource PrettyListBoxItem}"
171-
ItemsSource="{Binding ModuleMetrics}"
172-
DisplayMemberPath="ModuleName"
173-
SelectedItem="{Binding SelectedMetric, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
174-
Margin="5,5,0,5" />
175-
176-
<WrapPanel Orientation="Horizontal" Grid.Column="1" Grid.Row="0">
177-
<StackPanel Orientation="Horizontal">
178-
<Label Content="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=CodeMetrics_Lines}" FontWeight="Bold" />
179-
<TextBlock Text="{Binding SelectedMetric.Result.Lines}" VerticalAlignment="Center" />
180-
</StackPanel>
181-
<StackPanel Orientation="Horizontal">
182-
<Label Content="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=CodeMetrics_Complexity}" FontWeight="Bold" />
183-
<TextBlock Text="{Binding SelectedMetric.Result.CyclomaticComplexity}" VerticalAlignment="Center" />
184-
</StackPanel>
185-
<StackPanel Orientation="Horizontal">
186-
<Label Content="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=CodeMetrics_Nesting}" FontWeight="Bold" />
187-
<TextBlock Text="{Binding SelectedMetric.Result.MaximumNesting}" VerticalAlignment="Center" />
188-
</StackPanel>
189-
</WrapPanel>
190-
191-
<DataGrid Grid.Column="1"
192-
Grid.Row="1"
193-
ItemsSource="{Binding SelectedMetric.MemberResults}"
194-
AutoGenerateColumns="False"
195-
IsReadOnly="True"
196-
CanUserReorderColumns="False"
197-
CanUserSortColumns="True"
198-
CanUserAddRows="False"
199-
CanUserDeleteRows="False"
200-
CanUserResizeColumns="True"
201-
CanUserResizeRows="False"
202-
HorizontalGridLinesBrush="Transparent"
203-
VerticalGridLinesBrush="Transparent"
204-
HeadersVisibility="Column"
205-
HorizontalScrollBarVisibility="Disabled"
206-
ItemContainerStyle="{StaticResource PrettifyRow}"
207-
ColumnHeaderHeight="22"
208-
SelectionMode="Single"
209-
Margin="5,5,0,5">
210-
<DataGrid.CellStyle>
211-
<Style TargetType="{x:Type DataGridCell}">
212-
<Setter Property="BorderThickness" Value="0" />
213-
<Setter Property="Background" Value="Transparent" />
214-
<Setter Property="Padding" Value="0" />
215-
<Setter Property="VerticalContentAlignment" Value="Stretch" />
216-
<Setter Property="VerticalAlignment" Value="Center" />
217-
</Style>
218-
</DataGrid.CellStyle>
219-
<DataGrid.Columns>
220-
<DataGridTextColumn Header="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=SearchResults_MemberName}" Binding="{Binding Path=Key, Mode=OneWay, Converter={StaticResource DeclarationToMemberSignatureConverter}}" Width="*"/>
221-
<DataGridTextColumn Header="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=CodeMetrics_Lines}" Binding="{Binding Path=Value.Lines, Mode=OneWay}" Width="auto"/>
222-
<DataGridTextColumn Header="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=CodeMetrics_Complexity}" Binding="{Binding Path=Value.CyclomaticComplexity, Mode=OneWay}" Width="auto"/>
223-
<DataGridTextColumn Header="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=CodeMetrics_Nesting}" Binding="{Binding Path=Value.MaximumNesting, Mode=OneWay}" Width="auto"/>
224-
</DataGrid.Columns>
225-
</DataGrid>
246+
<TreeView x:Name="ProjectTree"
247+
Grid.Column="0"
248+
Grid.RowSpan="2"
249+
Background="White"
250+
ItemContainerStyle="{StaticResource ShinyTreeView}"
251+
HorizontalContentAlignment="Stretch"
252+
Style="{StaticResource CodeExplorerTreeViewStyle}" BorderThickness="0,1"
253+
VirtualizingPanel.IsVirtualizing="False"
254+
Visibility="{Binding Path=TreeViewVisibility}">
255+
<i:Interaction.Behaviors>
256+
<controls:BindableSelectedItemBehavior SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
257+
</i:Interaction.Behaviors>
258+
</TreeView>
259+
260+
<ListView ItemsSource="{Binding Metrics}" Grid.Column="1" Grid.Row="1">
261+
<ListView.ItemTemplate>
262+
<DataTemplate>
263+
<StackPanel Orientation="Horizontal">
264+
<Label Content="{Binding Metric.Name}" FontWeight="Bold" />
265+
<TextBlock Text="{Binding Value}" VerticalAlignment="Center" />
266+
</StackPanel>
267+
</DataTemplate>
268+
</ListView.ItemTemplate>
269+
</ListView>
226270

227271
<controls:EmptyUIRefresh Grid.ColumnSpan="2" Grid.RowSpan="2" Visibility="{Binding EmptyUIRefreshMessageVisibility, Mode=OneWay, Converter={StaticResource BoolToVisibility}}" />
228272
<controls:BusyIndicator Grid.ColumnSpan="2" Grid.RowSpan="2" Width="120" Height="120" Visibility="{Binding IsBusy, Mode=OneWay, Converter={StaticResource BoolToVisibility}}" />

0 commit comments

Comments
 (0)