diff --git a/.gitignore b/.gitignore index 77f173e..6f3b8be 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ bld/ [Oo]ut/ [Ll]og/ [Ll]ogs/ +.claude/ # Visual Studio 2015/2017 cache/options directory .vs/ diff --git a/CSharpCodeAnalyst/Areas/SearchArea/SearchItemViewModel.cs b/CSharpCodeAnalyst/Areas/SearchArea/SearchItemViewModel.cs index cb9608a..b8a9eed 100644 --- a/CSharpCodeAnalyst/Areas/SearchArea/SearchItemViewModel.cs +++ b/CSharpCodeAnalyst/Areas/SearchArea/SearchItemViewModel.cs @@ -1,6 +1,8 @@ using System.ComponentModel; using System.Diagnostics; +using System.Windows.Media.Imaging; using Contracts.Graph; +using CSharpCodeAnalyst.Common; namespace CSharpCodeAnalyst.SearchArea; @@ -11,6 +13,8 @@ public class SearchItemViewModel : INotifyPropertyChanged public string Type { get; set; } = string.Empty; public string FullPath { get; set; } = string.Empty; public CodeElement? CodeElement { get; set; } + + public BitmapImage? Icon => CodeElement != null ? CodeElementIconMapper.GetIcon(CodeElement.ElementType) : null; public event PropertyChangedEventHandler? PropertyChanged; diff --git a/CSharpCodeAnalyst/Areas/TreeArea/TreeItemViewModel.cs b/CSharpCodeAnalyst/Areas/TreeArea/TreeItemViewModel.cs index 7cad5b9..0f81d48 100644 --- a/CSharpCodeAnalyst/Areas/TreeArea/TreeItemViewModel.cs +++ b/CSharpCodeAnalyst/Areas/TreeArea/TreeItemViewModel.cs @@ -1,7 +1,9 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; +using System.Windows.Media.Imaging; using Contracts.Graph; +using CSharpCodeAnalyst.Common; namespace CSharpCodeAnalyst.TreeArea; @@ -16,6 +18,8 @@ public class TreeItemViewModel : INotifyPropertyChanged public string? Type { get; set; } public CodeElement? CodeElement { get; set; } public ObservableCollection Children { get; set; } = []; + + public BitmapImage? Icon => CodeElement != null ? CodeElementIconMapper.GetIcon(CodeElement.ElementType) : null; public bool IsExpanded { diff --git a/CSharpCodeAnalyst/CSharpCodeAnalyst.csproj b/CSharpCodeAnalyst/CSharpCodeAnalyst.csproj index 0d43b7f..eb6d7df 100644 --- a/CSharpCodeAnalyst/CSharpCodeAnalyst.csproj +++ b/CSharpCodeAnalyst/CSharpCodeAnalyst.csproj @@ -59,6 +59,19 @@ + + + + + + + + + + + + + diff --git a/CSharpCodeAnalyst/Common/CodeElementIconMapper.cs b/CSharpCodeAnalyst/Common/CodeElementIconMapper.cs new file mode 100644 index 0000000..d4c675d --- /dev/null +++ b/CSharpCodeAnalyst/Common/CodeElementIconMapper.cs @@ -0,0 +1,48 @@ +using System.Windows.Media.Imaging; +using Contracts.Graph; + +namespace CSharpCodeAnalyst.Common; + +public static class CodeElementIconMapper +{ + private static readonly Dictionary _iconCache = new(); + + static CodeElementIconMapper() + { + InitializeIcons(); + } + + public static BitmapImage GetIcon(CodeElementType elementType) + { + return _iconCache.TryGetValue(elementType, out var icon) ? icon : _iconCache[CodeElementType.Other]; + } + + private static void InitializeIcons() + { + _iconCache[CodeElementType.Assembly] = LoadIcon("Assembly_16.png"); + _iconCache[CodeElementType.Namespace] = LoadIcon("Namespace_16.png"); + _iconCache[CodeElementType.Class] = LoadIcon("Class_16.png"); + _iconCache[CodeElementType.Interface] = LoadIcon("Interface_16.png"); + _iconCache[CodeElementType.Struct] = LoadIcon("Struct_16.png"); + _iconCache[CodeElementType.Method] = LoadIcon("Method_16.png"); + _iconCache[CodeElementType.Property] = LoadIcon("Property_16.png"); + _iconCache[CodeElementType.Delegate] = LoadIcon("Delegate_16.png"); + _iconCache[CodeElementType.Event] = LoadIcon("Event_16.png"); + _iconCache[CodeElementType.Enum] = LoadIcon("Enum_16.png"); + _iconCache[CodeElementType.Field] = LoadIcon("Field_16.png"); + _iconCache[CodeElementType.Record] = LoadIcon("Record_16.png"); + _iconCache[CodeElementType.Other] = LoadIcon("Other_16.png"); + } + + private static BitmapImage LoadIcon(string fileName) + { + var uri = new Uri($"pack://application:,,,/Resources/{fileName}"); + var bitmap = new BitmapImage(); + bitmap.BeginInit(); + bitmap.UriSource = uri; + bitmap.CacheOption = BitmapCacheOption.OnLoad; + bitmap.EndInit(); + bitmap.Freeze(); // Make it thread-safe and improve performance + return bitmap; + } +} \ No newline at end of file diff --git a/CSharpCodeAnalyst/MainViewModel.cs b/CSharpCodeAnalyst/MainViewModel.cs index bd1d008..039becb 100644 --- a/CSharpCodeAnalyst/MainViewModel.cs +++ b/CSharpCodeAnalyst/MainViewModel.cs @@ -39,7 +39,7 @@ namespace CSharpCodeAnalyst; internal class MainViewModel : INotifyPropertyChanged { - private const int INFO_PANEL_TAB_INDEX = 2; + private const int InfoPanelTabIndex = 2; private readonly int _maxDegreeOfParallelism; private readonly MessageBus _messaging; @@ -87,21 +87,21 @@ internal MainViewModel(MessageBus messaging, ApplicationSettings settings) _messaging = messaging; _gallery = new Gallery.Gallery(); SearchCommand = new DelegateCommand(Search); - LoadSolutionCommand = new DelegateCommand(LoadSolution); - ImportJdepsCommand = new DelegateCommand(ImportJdeps); - LoadProjectCommand = new DelegateCommand(LoadProject); - SaveProjectCommand = new DelegateCommand(SaveProject); - GraphClearCommand = new DelegateCommand(GraphClear); - GraphLayoutCommand = new DelegateCommand(GraphLayout); - ExportToDgmlCommand = new DelegateCommand(ExportToDgml); - ExportToSvgCommand = new DelegateCommand(ExportToSvg); - FindCyclesCommand = new DelegateCommand(FindCycles); - ShowGalleryCommand = new DelegateCommand(ShowGallery); - ExportToDsiCommand = new DelegateCommand(ExportToDsi); - ShowLegendCommand = new DelegateCommand(ShowLegend); - OpenFilterDialogCommand = new DelegateCommand(OpenFilterDialog); - OpenSettingsDialogCommand = new DelegateCommand(OpenSettingsDialog); - ExportToPngCommand = new DelegateCommand(ExportToPng); + LoadSolutionCommand = new DelegateCommand(OnLoadSolution); + ImportJdepsCommand = new DelegateCommand(OnImportJdeps); + LoadProjectCommand = new DelegateCommand(OnLoadProject); + SaveProjectCommand = new DelegateCommand(OnSaveProject); + GraphClearCommand = new DelegateCommand(OnGraphClear); + GraphLayoutCommand = new DelegateCommand(OnGraphLayout); + ExportToDgmlCommand = new DelegateCommand(OnExportToDgml); + ExportToSvgCommand = new DelegateCommand(OnExportToSvg); + FindCyclesCommand = new DelegateCommand(OnFindCycles); + ShowGalleryCommand = new DelegateCommand(OnShowGallery); + ExportToDsiCommand = new DelegateCommand(OnExportToDsi); + ShowLegendCommand = new DelegateCommand(OnShowLegend); + OpenFilterDialogCommand = new DelegateCommand(OnOpenFilterDialog); + OpenSettingsDialogCommand = new DelegateCommand(OnOpenSettingsDialog); + ExportToPngCommand = new DelegateCommand(OnExportToPng); CopyToExplorerGraphCommand = new DelegateCommand(CopyToExplorerGraph); @@ -262,7 +262,7 @@ public int SelectedLeftTabIndex { if (value == _selectedLeftTabIndex) return; _selectedLeftTabIndex = value; - InfoPanelViewModel.Hide(value != INFO_PANEL_TAB_INDEX); + InfoPanelViewModel.Hide(value != InfoPanelTabIndex); OnPropertyChanged(nameof(SelectedLeftTabIndex)); } } @@ -270,7 +270,7 @@ public int SelectedLeftTabIndex public event PropertyChangedEventHandler? PropertyChanged; - private void ShowGallery() + private void OnShowGallery() { if (_graphViewModel is null || _gallery is null || _codeGraph is null) { @@ -324,7 +324,7 @@ GraphSession AddSession(string name) } } - private void ShowLegend() + private void OnShowLegend() { if (_openedLegendDialog == null) { @@ -336,13 +336,13 @@ private void ShowLegend() } } - private void OpenFilterDialog() + private void OnOpenFilterDialog() { var filterDialog = new FilterDialog(_projectExclusionFilters); filterDialog.ShowDialog(); } - private void OpenSettingsDialog() + private void OnOpenSettingsDialog() { var settingsDialog = new SettingsDialog(_applicationSettings); settingsDialog.Owner = Application.Current.MainWindow; @@ -402,7 +402,7 @@ private void Search() /// /// Exports the whole project to dsi. /// - private async void ExportToDsi() + private async void OnExportToDsi() { if (_codeGraph is null) { @@ -455,7 +455,7 @@ private async void ExportToDsi() } } - private async void FindCycles() + private async void OnFindCycles() { if (_codeGraph is null) { @@ -488,12 +488,12 @@ private async void FindCycles() } } - private void GraphLayout() + private void OnGraphLayout() { _graphViewModel?.Layout(); } - private void GraphClear() + private void OnGraphClear() { _graphViewModel?.Clear(); } @@ -503,40 +503,9 @@ protected virtual void OnPropertyChanged(string propertyName) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } - private async Task LoadAndAnalyzeSolution(string solutionPath) - { - try - { - IsLoading = true; - - var (codeGraph, diagnostics) = await Task.Run(async () => await LoadAsync(solutionPath)); - - var failures = diagnostics.FormatFailures(); - if (string.IsNullOrEmpty(failures) is false) - { - var failureText = Strings.Parser_FailureHeader + failures; - MessageBox.Show(failureText, Strings.Error_Title, MessageBoxButton.OK, MessageBoxImage.Warning); - } - - LoadCodeGraph(codeGraph); - // Imported a new solution - _isSaved = false; - } - catch (Exception ex) - { - var message = string.Format(Strings.OperationFailed_Message, ex.Message); - MessageBox.Show(message, Strings.Error_Title, MessageBoxButton.OK, - MessageBoxImage.Error); - } - finally - { - IsCanvasHintsVisible = false; - IsLoading = false; - } - } - private async Task<(CodeGraph, IParserDiagnostics)> LoadAsync(string solutionPath) + private async Task<(CodeGraph, IParserDiagnostics)> ImportSolutionAsync(string solutionPath) { LoadMessage = "Loading ..."; var parser = new Parser(new ParserConfig(_projectExclusionFilters, _maxDegreeOfParallelism)); @@ -567,7 +536,7 @@ private void LoadCodeGraph(CodeGraph codeGraph) Metrics = outputs; } - private async void LoadSolution() + private async void OnLoadSolution() { var openFileDialog = new OpenFileDialog { @@ -581,10 +550,45 @@ private async void LoadSolution() } var solutionPath = openFileDialog.FileName; - await LoadAndAnalyzeSolution(solutionPath); + + try + { + IsLoading = true; + + var codeGraph = await LoadSolutionAsync(solutionPath); + LoadDefaultSettings(); + LoadCodeGraph(codeGraph); + _gallery = new Gallery.Gallery(); + _isSaved = false; + } + catch (Exception ex) + { + var message = string.Format(Strings.OperationFailed_Message, ex.Message); + MessageBox.Show(message, Strings.Error_Title, MessageBoxButton.OK, + MessageBoxImage.Error); + } + finally + { + IsCanvasHintsVisible = false; + IsLoading = false; + } } - private void ImportJdeps() + private async Task LoadSolutionAsync(string solutionPath) + { + var (codeGraph, diagnostics) = await Task.Run(async () => await ImportSolutionAsync(solutionPath)); + + var failures = diagnostics.FormatFailures(); + if (string.IsNullOrEmpty(failures) is false) + { + var failureText = Strings.Parser_FailureHeader + failures; + MessageBox.Show(failureText, Strings.Error_Title, MessageBoxButton.OK, MessageBoxImage.Warning); + } + + return codeGraph; + } + + private void OnImportJdeps() { var openFileDialog = new OpenFileDialog { @@ -629,7 +633,7 @@ private void OnProgress(object? sender, ParserProgressArg e) LoadMessage = e.Message; } - private void ExportToDgml() + private void OnExportToDgml() { if (_graphViewModel is null) { @@ -653,7 +657,7 @@ private void ExportToDgml() } - private void ExportToPng(FrameworkElement canvas) + private void OnExportToPng(FrameworkElement canvas) { if (_graphViewModel is null) { @@ -677,7 +681,7 @@ private void ExportToPng(FrameworkElement canvas) /// /// Not usable at the moment. It does not render subgraphs. /// - private void ExportToSvg() + private void OnExportToSvg() { if (_graphViewModel is null) { @@ -702,58 +706,31 @@ private void ExportToSvg() } - private void LoadProject() + private async void OnLoadProject() { - try + var openFileDialog = new OpenFileDialog { - var openFileDialog = new OpenFileDialog - { - Filter = "JSON files (*.json)|*.json", - Title = "Load Project" - }; - - if (openFileDialog.ShowDialog() != true) - { - return; - } - - var json = File.ReadAllText(openFileDialog.FileName); - var options = new JsonSerializerOptions - { - ReferenceHandler = ReferenceHandler.Preserve - }; - var projectData = JsonSerializer.Deserialize(json, options); - if (projectData is null) - { - throw new NullReferenceException(); - } - - var codeGraph = projectData.GetCodeGraph(); + Filter = "JSON files (*.json)|*.json", + Title = "Load Project" + }; + if (openFileDialog.ShowDialog() != true) + { + return; + } - // Load settings - if (GraphViewModel != null) - { - if (projectData.Settings.TryGetValue(nameof(GraphViewModel.ShowFlatGraph), out var showFlatGraph)) - { - GraphViewModel.ShowFlatGraph = bool.Parse(showFlatGraph); - } - - if (projectData.Settings.TryGetValue(nameof(GraphViewModel.ShowDataFlow), out var showFlow)) - { - GraphViewModel.ShowDataFlow = bool.Parse(showFlow); - } - } + try + { + LoadMessage = "Loading ..."; + IsLoading = true; - if (projectData.Settings.TryGetValue(nameof(ProjectExclusionRegExCollection), out var projectExcludeRegEx)) - { - _projectExclusionFilters.Initialize(projectExcludeRegEx, ";"); - } + var fileName = openFileDialog.FileName; + var (codeGraph, projectData) = await Task.Run(() => LoadProject(fileName)); + LoadSettings(projectData.Settings); LoadCodeGraph(codeGraph); _gallery = projectData.GetGallery(); - _isSaved = true; } catch (Exception ex) @@ -763,11 +740,63 @@ private void LoadProject() } finally { + LoadMessage = string.Empty; IsCanvasHintsVisible = false; + IsLoading = false; } } - private void SaveProject() + private void LoadDefaultSettings() + { + if (GraphViewModel != null) + { + GraphViewModel.ShowFlatGraph = false; + GraphViewModel.ShowDataFlow = false; + } + + _projectExclusionFilters.Initialize(_applicationSettings.DefaultProjectExcludeFilter, ";"); + } + + private void LoadSettings(Dictionary settings) + { + if (GraphViewModel != null) + { + if (settings.TryGetValue(nameof(GraphViewModel.ShowFlatGraph), out var showFlatGraph)) + { + GraphViewModel.ShowFlatGraph = bool.Parse(showFlatGraph); + } + + if (settings.TryGetValue(nameof(GraphViewModel.ShowDataFlow), out var showFlow)) + { + GraphViewModel.ShowDataFlow = bool.Parse(showFlow); + } + } + + if (settings.TryGetValue(nameof(ProjectExclusionRegExCollection), out var projectExcludeRegEx)) + { + _projectExclusionFilters.Initialize(projectExcludeRegEx, ";"); + } + } + + private (CodeGraph codeGraph, ProjectData projectData) LoadProject(string fileName) + { + var json = File.ReadAllText(fileName); + var options = new JsonSerializerOptions + { + ReferenceHandler = ReferenceHandler.Preserve + }; + var projectData = JsonSerializer.Deserialize(json, options); + if (projectData is null) + { + throw new NullReferenceException(); + } + + var codeGraph = projectData.GetCodeGraph(); + + return (codeGraph, projectData); + } + + private void OnSaveProject() { if (_codeGraph is null || _graphViewModel is null) { @@ -835,7 +864,7 @@ internal bool OnClosing() if (MessageBox.Show(Strings.Save_Message, Strings.Save_Title, MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes) { - SaveProject(); + OnSaveProject(); } } diff --git a/CSharpCodeAnalyst/MainWindow.xaml b/CSharpCodeAnalyst/MainWindow.xaml index 06e36ec..bfa674c 100644 --- a/CSharpCodeAnalyst/MainWindow.xaml +++ b/CSharpCodeAnalyst/MainWindow.xaml @@ -330,7 +330,7 @@ - + @@ -405,12 +405,21 @@ + + + + + + + + - + + + diff --git a/CSharpCodeAnalyst/Resources/Assembly_16.png b/CSharpCodeAnalyst/Resources/Assembly_16.png new file mode 100644 index 0000000..11e0c8b Binary files /dev/null and b/CSharpCodeAnalyst/Resources/Assembly_16.png differ diff --git a/CSharpCodeAnalyst/Resources/Class_16.png b/CSharpCodeAnalyst/Resources/Class_16.png new file mode 100644 index 0000000..0a70071 Binary files /dev/null and b/CSharpCodeAnalyst/Resources/Class_16.png differ diff --git a/CSharpCodeAnalyst/Resources/Delegate_16.png b/CSharpCodeAnalyst/Resources/Delegate_16.png new file mode 100644 index 0000000..0138201 Binary files /dev/null and b/CSharpCodeAnalyst/Resources/Delegate_16.png differ diff --git a/CSharpCodeAnalyst/Resources/Enum_16.png b/CSharpCodeAnalyst/Resources/Enum_16.png new file mode 100644 index 0000000..5a3ac8a Binary files /dev/null and b/CSharpCodeAnalyst/Resources/Enum_16.png differ diff --git a/CSharpCodeAnalyst/Resources/Event_16.png b/CSharpCodeAnalyst/Resources/Event_16.png new file mode 100644 index 0000000..01cbced Binary files /dev/null and b/CSharpCodeAnalyst/Resources/Event_16.png differ diff --git a/CSharpCodeAnalyst/Resources/Field_16.png b/CSharpCodeAnalyst/Resources/Field_16.png new file mode 100644 index 0000000..24edcd4 Binary files /dev/null and b/CSharpCodeAnalyst/Resources/Field_16.png differ diff --git a/CSharpCodeAnalyst/Resources/Interface_16.png b/CSharpCodeAnalyst/Resources/Interface_16.png new file mode 100644 index 0000000..fc9ea09 Binary files /dev/null and b/CSharpCodeAnalyst/Resources/Interface_16.png differ diff --git a/CSharpCodeAnalyst/Resources/Method_16.png b/CSharpCodeAnalyst/Resources/Method_16.png new file mode 100644 index 0000000..439c695 Binary files /dev/null and b/CSharpCodeAnalyst/Resources/Method_16.png differ diff --git a/CSharpCodeAnalyst/Resources/Namespace_16.png b/CSharpCodeAnalyst/Resources/Namespace_16.png new file mode 100644 index 0000000..662ea92 Binary files /dev/null and b/CSharpCodeAnalyst/Resources/Namespace_16.png differ diff --git a/CSharpCodeAnalyst/Resources/Other_16.png b/CSharpCodeAnalyst/Resources/Other_16.png new file mode 100644 index 0000000..f197819 Binary files /dev/null and b/CSharpCodeAnalyst/Resources/Other_16.png differ diff --git a/CSharpCodeAnalyst/Resources/Property_16.png b/CSharpCodeAnalyst/Resources/Property_16.png new file mode 100644 index 0000000..72ad52a Binary files /dev/null and b/CSharpCodeAnalyst/Resources/Property_16.png differ diff --git a/CSharpCodeAnalyst/Resources/Record_16.png b/CSharpCodeAnalyst/Resources/Record_16.png new file mode 100644 index 0000000..e38f0de Binary files /dev/null and b/CSharpCodeAnalyst/Resources/Record_16.png differ diff --git a/CSharpCodeAnalyst/Resources/Struct_16.png b/CSharpCodeAnalyst/Resources/Struct_16.png new file mode 100644 index 0000000..677c68c Binary files /dev/null and b/CSharpCodeAnalyst/Resources/Struct_16.png differ