|
@@ -1,609 +1,820 @@
|
|
|
-using Microsoft.VisualStudio.Shell;
|
|
|
|
|
-using Microsoft.Win32;
|
|
|
|
|
-using Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension.Handlers;
|
|
|
|
|
-using Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension.Helpers;
|
|
|
|
|
-using Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension.Models;
|
|
|
|
|
-using System;
|
|
|
|
|
-using System.Collections.Generic;
|
|
|
|
|
-using System.Collections.ObjectModel;
|
|
|
|
|
-using System.ComponentModel;
|
|
|
|
|
-using System.IO;
|
|
|
|
|
-using System.Linq;
|
|
|
|
|
-using System.Runtime.CompilerServices;
|
|
|
|
|
-using System.Threading.Tasks;
|
|
|
|
|
-using System.Windows;
|
|
|
|
|
-using System.Windows.Controls;
|
|
|
|
|
-using System.Windows.Threading;
|
|
|
|
|
-
|
|
|
|
|
-namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension.Views
|
|
|
|
|
-{
|
|
|
|
|
- public partial class ProjectFilterDialog : Window, INotifyPropertyChanged
|
|
|
|
|
- {
|
|
|
|
|
- private readonly SolutionInfo _solutionInfo;
|
|
|
|
|
- private readonly SolutionParser _solutionParser;
|
|
|
|
|
- private List<ProjectNode> _allProjectNodes;
|
|
|
|
|
- private string _filterPattern = "";
|
|
|
|
|
- private bool _updatingSelection = false;
|
|
|
|
|
- private DispatcherTimer _filterTimer;
|
|
|
|
|
- private string _lastSaveAsPath = "";
|
|
|
|
|
-
|
|
|
|
|
- public string FilteredSolutionPath { get; private set; }
|
|
|
|
|
- public ObservableCollection<ProjectTreeItem> Projects { get; set; }
|
|
|
|
|
-
|
|
|
|
|
- public string FilterPattern
|
|
|
|
|
- {
|
|
|
|
|
- get => _filterPattern;
|
|
|
|
|
- set
|
|
|
|
|
- {
|
|
|
|
|
- _filterPattern = value;
|
|
|
|
|
- OnPropertyChanged();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public ProjectFilterDialog(SolutionInfo solutionInfo)
|
|
|
|
|
- {
|
|
|
|
|
- InitializeComponent();
|
|
|
|
|
- DataContext = this;
|
|
|
|
|
-
|
|
|
|
|
- _solutionInfo = solutionInfo ?? throw new ArgumentNullException(nameof(solutionInfo));
|
|
|
|
|
- _solutionParser = new SolutionParser();
|
|
|
|
|
- Projects = new ObservableCollection<ProjectTreeItem>();
|
|
|
|
|
-
|
|
|
|
|
- Title = $"Filter Projects - {_solutionInfo.Name}";
|
|
|
|
|
- SolutionNameTextBlock.Text = _solutionInfo.Name;
|
|
|
|
|
-
|
|
|
|
|
- // Load projects asynchronously
|
|
|
|
|
- Loaded += async (s, e) => await LoadProjectsAsync();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private async Task LoadProjectsAsync()
|
|
|
|
|
- {
|
|
|
|
|
- try
|
|
|
|
|
- {
|
|
|
|
|
- // Show loading indicator
|
|
|
|
|
- ProjectTreeView.Visibility = Visibility.Hidden;
|
|
|
|
|
- StatusTextBlock.Text = "Loading projects...";
|
|
|
|
|
-
|
|
|
|
|
- _allProjectNodes = await _solutionParser.ParseSolutionAsync(_solutionInfo.FullPath);
|
|
|
|
|
-
|
|
|
|
|
- System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
|
|
|
|
- {
|
|
|
|
|
- Projects.Clear();
|
|
|
|
|
-
|
|
|
|
|
- // First pass: create all project tree items
|
|
|
|
|
- var projectTreeItems = new Dictionary<string, ProjectTreeItem>();
|
|
|
|
|
-
|
|
|
|
|
- foreach (var projectNode in _allProjectNodes)
|
|
|
|
|
- {
|
|
|
|
|
- var projectInfo = new ProjectInfo
|
|
|
|
|
- {
|
|
|
|
|
- Name = projectNode.Name,
|
|
|
|
|
- FullPath = projectNode.Path,
|
|
|
|
|
- ProjectGuid = projectNode.ProjectGuid,
|
|
|
|
|
- TypeGuid = projectNode.TypeGuid
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- var treeItem = new ProjectTreeItem(projectInfo)
|
|
|
|
|
- {
|
|
|
|
|
- Tag = projectNode,
|
|
|
|
|
- IsSelectable = true
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- projectTreeItems[projectNode.ProjectGuid] = treeItem;
|
|
|
|
|
- Projects.Add(treeItem);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Second pass: build dependency trees
|
|
|
|
|
- int totalDependencies = 0;
|
|
|
|
|
- foreach (var projectNode in _allProjectNodes)
|
|
|
|
|
- {
|
|
|
|
|
- if (projectTreeItems.TryGetValue(projectNode.ProjectGuid, out var treeItem))
|
|
|
|
|
- {
|
|
|
|
|
- BuildDependencyTree(treeItem, projectNode);
|
|
|
|
|
- totalDependencies += treeItem.Children.Count;
|
|
|
|
|
-
|
|
|
|
|
- // Extra logging for troubleshooting
|
|
|
|
|
- ErrorHandler.LogInfo($"Project {projectNode.Name}: Expected deps={projectNode.Dependencies.Count}, Actual children={treeItem.Children.Count}, HasDependencies={treeItem.HasDependencies}");
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- ErrorHandler.LogInfo($"Total dependencies added to tree: {totalDependencies}");
|
|
|
|
|
-
|
|
|
|
|
- // Add test dependencies to verify tree structure is working
|
|
|
|
|
- if (totalDependencies == 0 && Projects.Count >= 2)
|
|
|
|
|
- {
|
|
|
|
|
- // Add test dependency to demonstrate structure
|
|
|
|
|
- var firstProject = Projects[0];
|
|
|
|
|
- var secondProject = Projects.Count > 1 ? Projects[1] : null;
|
|
|
|
|
-
|
|
|
|
|
- if (secondProject != null)
|
|
|
|
|
- {
|
|
|
|
|
- var testChild = new ProjectTreeItem(new ProjectInfo { Name = secondProject.ProjectInfo.Name })
|
|
|
|
|
- {
|
|
|
|
|
- IsDependency = true,
|
|
|
|
|
- IsSelectable = false,
|
|
|
|
|
- Tag = secondProject.Tag
|
|
|
|
|
- };
|
|
|
|
|
- firstProject.Children.Add(testChild);
|
|
|
|
|
- firstProject.OnPropertyChanged(nameof(firstProject.HasDependencies));
|
|
|
|
|
- firstProject.DependencyCountText = "(test - references 1 project)";
|
|
|
|
|
- ErrorHandler.LogInfo($"Added test dependency: {firstProject.DisplayName} -> {secondProject.DisplayName}");
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- ProjectCountTextBlock.Text = $"{Projects.Count} projects found";
|
|
|
|
|
- UpdateSelectionCount();
|
|
|
|
|
-
|
|
|
|
|
- // Show tree
|
|
|
|
|
- ProjectTreeView.Visibility = Visibility.Visible;
|
|
|
|
|
- StatusTextBlock.Text = "Ready";
|
|
|
|
|
-
|
|
|
|
|
- // Set the ItemsSource for the TreeView
|
|
|
|
|
- ProjectTreeView.ItemsSource = Projects;
|
|
|
|
|
-
|
|
|
|
|
- // Force a complete refresh of the TreeView
|
|
|
|
|
- ProjectTreeView.UpdateLayout();
|
|
|
|
|
-
|
|
|
|
|
- ErrorHandler.LogInfo($"Loaded {Projects.Count} projects in filter dialog with dependency trees");
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- catch (Exception ex)
|
|
|
|
|
- {
|
|
|
|
|
- ErrorHandler.ShowUserError("Error", $"Error loading solution: {ex.Message}", ex);
|
|
|
|
|
- DialogResult = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void BuildDependencyTree(ProjectTreeItem parentItem, ProjectNode projectNode)
|
|
|
|
|
- {
|
|
|
|
|
- // Clear existing children first
|
|
|
|
|
- parentItem.Children.Clear();
|
|
|
|
|
-
|
|
|
|
|
- // Add dependency children (projects that this project depends on)
|
|
|
|
|
- foreach (var dependency in projectNode.Dependencies)
|
|
|
|
|
- {
|
|
|
|
|
- var depInfo = new ProjectInfo
|
|
|
|
|
- {
|
|
|
|
|
- Name = dependency.Name,
|
|
|
|
|
- FullPath = dependency.Path,
|
|
|
|
|
- ProjectGuid = dependency.ProjectGuid,
|
|
|
|
|
- TypeGuid = dependency.TypeGuid
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- var depItem = new ProjectTreeItem(depInfo)
|
|
|
|
|
- {
|
|
|
|
|
- Tag = dependency,
|
|
|
|
|
- IsSelectable = false, // Dependencies are not selectable in tree
|
|
|
|
|
- IsDependency = true,
|
|
|
|
|
- IsChecked = false // Dependencies don't have checkboxes
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- parentItem.Children.Add(depItem);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void SelectAllButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
- {
|
|
|
|
|
- _updatingSelection = true;
|
|
|
|
|
- try
|
|
|
|
|
- {
|
|
|
|
|
- foreach (var project in Projects)
|
|
|
|
|
- {
|
|
|
|
|
- if (project.IsSelectable)
|
|
|
|
|
- project.IsChecked = true;
|
|
|
|
|
- }
|
|
|
|
|
- UpdateSelectionCount();
|
|
|
|
|
- }
|
|
|
|
|
- finally
|
|
|
|
|
- {
|
|
|
|
|
- _updatingSelection = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void ClearAllButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
- {
|
|
|
|
|
- _updatingSelection = true;
|
|
|
|
|
- try
|
|
|
|
|
- {
|
|
|
|
|
- foreach (var project in Projects)
|
|
|
|
|
- {
|
|
|
|
|
- if (project.IsSelectable)
|
|
|
|
|
- project.IsChecked = false;
|
|
|
|
|
- }
|
|
|
|
|
- UpdateSelectionCount();
|
|
|
|
|
- }
|
|
|
|
|
- finally
|
|
|
|
|
- {
|
|
|
|
|
- _updatingSelection = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void SelectTestsButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
- {
|
|
|
|
|
- FilterTextBox.Text = "*Test*";
|
|
|
|
|
- ApplyFilter();
|
|
|
|
|
-
|
|
|
|
|
- // Select all visible test projects
|
|
|
|
|
- _updatingSelection = true;
|
|
|
|
|
- try
|
|
|
|
|
- {
|
|
|
|
|
- foreach (var project in Projects)
|
|
|
|
|
- {
|
|
|
|
|
- if (project.IsSelectable &&
|
|
|
|
|
- project.DisplayName.IndexOf("Test", StringComparison.OrdinalIgnoreCase) >= 0)
|
|
|
|
|
- {
|
|
|
|
|
- project.IsChecked = true;
|
|
|
|
|
- // Auto-select dependencies for test projects
|
|
|
|
|
- if (project.Tag is ProjectNode projectNode)
|
|
|
|
|
- {
|
|
|
|
|
- SelectDependencies(projectNode, true);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- UpdateSelectionCount();
|
|
|
|
|
- }
|
|
|
|
|
- finally
|
|
|
|
|
- {
|
|
|
|
|
- _updatingSelection = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void ExpandAllButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
- {
|
|
|
|
|
- SetAllTreeViewItemsExpanded(true);
|
|
|
|
|
- StatusTextBlock.Text = "All project trees expanded";
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void CollapseAllButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
- {
|
|
|
|
|
- SetAllTreeViewItemsExpanded(false);
|
|
|
|
|
- StatusTextBlock.Text = "All project trees collapsed";
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void SetAllTreeViewItemsExpanded(bool isExpanded)
|
|
|
|
|
- {
|
|
|
|
|
- try
|
|
|
|
|
- {
|
|
|
|
|
- // First approach: try to access containers directly
|
|
|
|
|
- foreach (var item in Projects)
|
|
|
|
|
- {
|
|
|
|
|
- var container = ProjectTreeView.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
|
|
|
|
|
- if (container != null)
|
|
|
|
|
- {
|
|
|
|
|
- SetTreeViewItemExpanded(container, isExpanded);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Second approach: force container generation for any that weren't generated yet
|
|
|
|
|
- ProjectTreeView.UpdateLayout();
|
|
|
|
|
-
|
|
|
|
|
- // Try again for any containers that weren't available the first time
|
|
|
|
|
- foreach (var item in Projects)
|
|
|
|
|
- {
|
|
|
|
|
- var container = ProjectTreeView.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
|
|
|
|
|
- if (container != null)
|
|
|
|
|
- {
|
|
|
|
|
- container.IsExpanded = isExpanded;
|
|
|
|
|
- // Also ensure child containers are handled
|
|
|
|
|
- for (int i = 0; i < container.Items.Count; i++)
|
|
|
|
|
- {
|
|
|
|
|
- var childContainer = container.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
|
|
|
|
|
- if (childContainer != null)
|
|
|
|
|
- {
|
|
|
|
|
- childContainer.IsExpanded = isExpanded;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- catch (Exception ex)
|
|
|
|
|
- {
|
|
|
|
|
- ErrorHandler.LogError($"Error {(isExpanded ? "expanding" : "collapsing")} tree items", ex);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void SetTreeViewItemExpanded(TreeViewItem item, bool isExpanded)
|
|
|
|
|
- {
|
|
|
|
|
- if (item == null) return;
|
|
|
|
|
-
|
|
|
|
|
- item.IsExpanded = isExpanded;
|
|
|
|
|
-
|
|
|
|
|
- // Recursively expand/collapse child items
|
|
|
|
|
- for (int i = 0; i < item.Items.Count; i++)
|
|
|
|
|
- {
|
|
|
|
|
- var childContainer = item.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
|
|
|
|
|
- if (childContainer != null)
|
|
|
|
|
- {
|
|
|
|
|
- SetTreeViewItemExpanded(childContainer, isExpanded);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void ClearFilterButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
- {
|
|
|
|
|
- FilterTextBox.Text = "";
|
|
|
|
|
- FilterPattern = "";
|
|
|
|
|
- ApplyFilter();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void FilterTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
|
|
|
|
- {
|
|
|
|
|
- // Auto-filter while typing with debounce
|
|
|
|
|
- _filterTimer?.Stop();
|
|
|
|
|
- _filterTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(300) };
|
|
|
|
|
- _filterTimer.Tick += (s, args) =>
|
|
|
|
|
- {
|
|
|
|
|
- _filterTimer.Stop();
|
|
|
|
|
- ApplyFilter();
|
|
|
|
|
- };
|
|
|
|
|
- _filterTimer.Start();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void ApplyFilter()
|
|
|
|
|
- {
|
|
|
|
|
- var pattern = FilterTextBox.Text?.Trim() ?? "";
|
|
|
|
|
-
|
|
|
|
|
- foreach (var project in Projects)
|
|
|
|
|
- {
|
|
|
|
|
- ApplyFilterToItem(project, pattern);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private bool ApplyFilterToItem(ProjectTreeItem item, string pattern)
|
|
|
|
|
- {
|
|
|
|
|
- bool isVisible = true;
|
|
|
|
|
-
|
|
|
|
|
- if (!string.IsNullOrWhiteSpace(pattern))
|
|
|
|
|
- {
|
|
|
|
|
- try
|
|
|
|
|
- {
|
|
|
|
|
- var wildcardPattern = pattern.Replace("*", ".*");
|
|
|
|
|
- var regex = new System.Text.RegularExpressions.Regex(wildcardPattern,
|
|
|
|
|
- System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
|
|
|
|
-
|
|
|
|
|
- // For main projects, check if name matches
|
|
|
|
|
- // For dependencies, always show if parent is visible
|
|
|
|
|
- isVisible = item.IsDependency || regex.IsMatch(item.ProjectInfo.Name);
|
|
|
|
|
- }
|
|
|
|
|
- catch (Exception ex)
|
|
|
|
|
- {
|
|
|
|
|
- ErrorHandler.LogError("Error applying filter pattern", ex);
|
|
|
|
|
- StatusTextBlock.Text = "Invalid filter pattern";
|
|
|
|
|
- isVisible = true; // Show all on error
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // For TreeView with hierarchical data, we need to use a different approach
|
|
|
|
|
- // The filtering will be handled by the TreeView's visibility binding
|
|
|
|
|
- item.IsVisible = isVisible;
|
|
|
|
|
-
|
|
|
|
|
- return isVisible;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void ProjectTreeItem_Checked(object sender, RoutedEventArgs e)
|
|
|
|
|
- {
|
|
|
|
|
- if (_updatingSelection) return;
|
|
|
|
|
-
|
|
|
|
|
- var checkBox = sender as CheckBox;
|
|
|
|
|
- var treeItem = checkBox?.DataContext as ProjectTreeItem;
|
|
|
|
|
- if (treeItem?.Tag is ProjectNode projectNode && treeItem.IsSelectable)
|
|
|
|
|
- {
|
|
|
|
|
- _updatingSelection = true;
|
|
|
|
|
- try
|
|
|
|
|
- {
|
|
|
|
|
- // Auto-select dependencies
|
|
|
|
|
- SelectDependencies(projectNode, true);
|
|
|
|
|
- UpdateSelectionCount();
|
|
|
|
|
- }
|
|
|
|
|
- finally
|
|
|
|
|
- {
|
|
|
|
|
- _updatingSelection = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void ProjectTreeItem_Unchecked(object sender, RoutedEventArgs e)
|
|
|
|
|
- {
|
|
|
|
|
- if (_updatingSelection) return;
|
|
|
|
|
-
|
|
|
|
|
- var checkBox = sender as CheckBox;
|
|
|
|
|
- var treeItem = checkBox?.DataContext as ProjectTreeItem;
|
|
|
|
|
- if (treeItem?.Tag is ProjectNode projectNode && treeItem.IsSelectable)
|
|
|
|
|
- {
|
|
|
|
|
- _updatingSelection = true;
|
|
|
|
|
- try
|
|
|
|
|
- {
|
|
|
|
|
- // Auto-deselect dependents
|
|
|
|
|
- DeselectDependents(projectNode);
|
|
|
|
|
- UpdateSelectionCount();
|
|
|
|
|
- }
|
|
|
|
|
- finally
|
|
|
|
|
- {
|
|
|
|
|
- _updatingSelection = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void SelectDependencies(ProjectNode projectNode, bool isChecked)
|
|
|
|
|
- {
|
|
|
|
|
- // Find corresponding tree item and update it
|
|
|
|
|
- var treeItem = Projects.FirstOrDefault(p =>
|
|
|
|
|
- string.Equals(((ProjectNode)p.Tag)?.ProjectGuid, projectNode.ProjectGuid, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
-
|
|
|
|
|
- if (treeItem != null && treeItem.IsChecked != isChecked && treeItem.IsSelectable)
|
|
|
|
|
- {
|
|
|
|
|
- treeItem.IsChecked = isChecked;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Recursively select dependencies
|
|
|
|
|
- foreach (var dependency in projectNode.Dependencies)
|
|
|
|
|
- {
|
|
|
|
|
- SelectDependencies(dependency, isChecked);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void DeselectDependents(ProjectNode projectNode)
|
|
|
|
|
- {
|
|
|
|
|
- // Recursively deselect dependents
|
|
|
|
|
- foreach (var dependent in projectNode.Dependents)
|
|
|
|
|
- {
|
|
|
|
|
- var treeItem = Projects.FirstOrDefault(p =>
|
|
|
|
|
- string.Equals(((ProjectNode)p.Tag)?.ProjectGuid, dependent.ProjectGuid, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
-
|
|
|
|
|
- if (treeItem != null && treeItem.IsChecked && treeItem.IsSelectable)
|
|
|
|
|
- {
|
|
|
|
|
- treeItem.IsChecked = false;
|
|
|
|
|
- DeselectDependents(dependent);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void UpdateSelectionCount()
|
|
|
|
|
- {
|
|
|
|
|
- var selectedCount = Projects.Count(p => p.IsSelectable && p.IsChecked);
|
|
|
|
|
- SelectionCountTextBlock.Text = $"{selectedCount} of {Projects.Count} projects selected";
|
|
|
|
|
-
|
|
|
|
|
- // Enable/disable buttons based on selection
|
|
|
|
|
- OkButton.IsEnabled = selectedCount > 0;
|
|
|
|
|
- SaveAsButton.IsEnabled = selectedCount > 0;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private async void SaveAsButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
- {
|
|
|
|
|
- try
|
|
|
|
|
- {
|
|
|
|
|
- var selectedTreeItems = Projects.Where(p => p.IsSelectable && p.IsChecked).ToList();
|
|
|
|
|
- if (!selectedTreeItems.Any())
|
|
|
|
|
- {
|
|
|
|
|
- ErrorHandler.ShowUserInfo("Warning", "Please select at least one project.");
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Show save dialog
|
|
|
|
|
- var saveDialog = new SaveFileDialog
|
|
|
|
|
- {
|
|
|
|
|
- Title = "Save Filtered Solution As",
|
|
|
|
|
- Filter = "Solution files (*.sln)|*.sln|All files (*.*)|*.*",
|
|
|
|
|
- DefaultExt = ".sln",
|
|
|
|
|
- FileName = $"{_solutionInfo.Name}_filtered.sln",
|
|
|
|
|
- InitialDirectory = string.IsNullOrEmpty(_lastSaveAsPath) ?
|
|
|
|
|
- Path.GetDirectoryName(_solutionInfo.FullPath) :
|
|
|
|
|
- Path.GetDirectoryName(_lastSaveAsPath)
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- if (saveDialog.ShowDialog() == true)
|
|
|
|
|
- {
|
|
|
|
|
- _lastSaveAsPath = saveDialog.FileName;
|
|
|
|
|
-
|
|
|
|
|
- StatusTextBlock.Text = "Creating filtered solution...";
|
|
|
|
|
- SaveAsButton.IsEnabled = false;
|
|
|
|
|
- OkButton.IsEnabled = false;
|
|
|
|
|
- CancelButton.IsEnabled = false;
|
|
|
|
|
-
|
|
|
|
|
- // Get corresponding ProjectNodes
|
|
|
|
|
- var selectedProjectNodes = selectedTreeItems
|
|
|
|
|
- .Select(ti => ti.Tag as ProjectNode)
|
|
|
|
|
- .Where(pn => pn != null)
|
|
|
|
|
- .ToList();
|
|
|
|
|
-
|
|
|
|
|
- ErrorHandler.LogInfo($"User saving {selectedProjectNodes.Count} projects to custom location");
|
|
|
|
|
-
|
|
|
|
|
- // The parser already includes dependencies
|
|
|
|
|
- var projectsWithDependencies = _solutionParser.GetProjectsWithDependencies(selectedProjectNodes);
|
|
|
|
|
-
|
|
|
|
|
- // Create filtered solution with path remapping
|
|
|
|
|
- FilteredSolutionPath = await _solutionParser.CreateFilteredSolutionWithRemappingAsync(
|
|
|
|
|
- _solutionInfo.FullPath, projectsWithDependencies, saveDialog.FileName);
|
|
|
|
|
-
|
|
|
|
|
- StatusTextBlock.Text = "Filtered solution saved successfully";
|
|
|
|
|
-
|
|
|
|
|
- // Ask if user wants to open the saved solution
|
|
|
|
|
- var result = MessageBox.Show(
|
|
|
|
|
- $"Filtered solution saved to:\n{saveDialog.FileName}\n\nWould you like to open it now?",
|
|
|
|
|
- "Solution Saved",
|
|
|
|
|
- MessageBoxButton.YesNo,
|
|
|
|
|
- MessageBoxImage.Question);
|
|
|
|
|
-
|
|
|
|
|
- if (result == MessageBoxResult.Yes)
|
|
|
|
|
- {
|
|
|
|
|
- DialogResult = true;
|
|
|
|
|
- }
|
|
|
|
|
- else
|
|
|
|
|
- {
|
|
|
|
|
- // Reset buttons for continued use
|
|
|
|
|
- SaveAsButton.IsEnabled = true;
|
|
|
|
|
- OkButton.IsEnabled = true;
|
|
|
|
|
- CancelButton.IsEnabled = true;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- catch (Exception ex)
|
|
|
|
|
- {
|
|
|
|
|
- await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
|
|
|
|
|
- ErrorHandler.ShowUserError("Error", "Error saving filtered solution", ex);
|
|
|
|
|
- StatusTextBlock.Text = "Error saving filtered solution";
|
|
|
|
|
- SaveAsButton.IsEnabled = true;
|
|
|
|
|
- OkButton.IsEnabled = true;
|
|
|
|
|
- CancelButton.IsEnabled = true;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private async void OkButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
- {
|
|
|
|
|
- try
|
|
|
|
|
- {
|
|
|
|
|
- var selectedTreeItems = Projects.Where(p => p.IsSelectable && p.IsChecked).ToList();
|
|
|
|
|
- if (!selectedTreeItems.Any())
|
|
|
|
|
- {
|
|
|
|
|
- ErrorHandler.ShowUserInfo("Warning", "Please select at least one project.");
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- StatusTextBlock.Text = "Creating filtered solution...";
|
|
|
|
|
- OkButton.IsEnabled = false;
|
|
|
|
|
- SaveAsButton.IsEnabled = false;
|
|
|
|
|
- CancelButton.IsEnabled = false;
|
|
|
|
|
-
|
|
|
|
|
- // Get corresponding ProjectNodes
|
|
|
|
|
- var selectedProjectNodes = selectedTreeItems
|
|
|
|
|
- .Select(ti => ti.Tag as ProjectNode)
|
|
|
|
|
- .Where(pn => pn != null)
|
|
|
|
|
- .ToList();
|
|
|
|
|
-
|
|
|
|
|
- ErrorHandler.LogInfo($"User selected {selectedProjectNodes.Count} projects for filtering");
|
|
|
|
|
-
|
|
|
|
|
- // The parser already includes dependencies
|
|
|
|
|
- var projectsWithDependencies = _solutionParser.GetProjectsWithDependencies(selectedProjectNodes);
|
|
|
|
|
-
|
|
|
|
|
- // Create filtered solution in temp directory with path remapping
|
|
|
|
|
- FilteredSolutionPath = await _solutionParser.CreateFilteredSolutionAsync(
|
|
|
|
|
- _solutionInfo.FullPath, projectsWithDependencies);
|
|
|
|
|
-
|
|
|
|
|
- StatusTextBlock.Text = "Filtered solution created successfully";
|
|
|
|
|
- DialogResult = true;
|
|
|
|
|
- }
|
|
|
|
|
- catch (Exception ex)
|
|
|
|
|
- {
|
|
|
|
|
- await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
|
|
|
|
|
- ErrorHandler.ShowUserError("Error", "Error creating filtered solution", ex);
|
|
|
|
|
- StatusTextBlock.Text = "Error creating filtered solution";
|
|
|
|
|
- OkButton.IsEnabled = true;
|
|
|
|
|
- SaveAsButton.IsEnabled = true;
|
|
|
|
|
- CancelButton.IsEnabled = true;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void CancelButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
- {
|
|
|
|
|
- ErrorHandler.LogInfo("User chose to open all projects");
|
|
|
|
|
- DialogResult = false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
|
-
|
|
|
|
|
- protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
|
|
|
|
- {
|
|
|
|
|
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+using Microsoft.VisualStudio.Shell;
|
|
|
|
|
+using Microsoft.Win32;
|
|
|
|
|
+using Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension.Handlers;
|
|
|
|
|
+using Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension.Helpers;
|
|
|
|
|
+using Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension.Models;
|
|
|
|
|
+using System;
|
|
|
|
|
+using System.Collections.Generic;
|
|
|
|
|
+using System.Collections.ObjectModel;
|
|
|
|
|
+using System.ComponentModel;
|
|
|
|
|
+using System.IO;
|
|
|
|
|
+using System.Linq;
|
|
|
|
|
+using System.Runtime.CompilerServices;
|
|
|
|
|
+using System.Threading.Tasks;
|
|
|
|
|
+using System.Windows;
|
|
|
|
|
+using System.Windows.Controls;
|
|
|
|
|
+using System.Windows.Threading;
|
|
|
|
|
+
|
|
|
|
|
+namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension.Views
|
|
|
|
|
+{
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Main project filter dialog that provides the user interface for selecting projects
|
|
|
|
|
+ /// to include in a filtered solution. This WPF dialog features:
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// - Interactive tree view showing projects and their dependencies
|
|
|
|
|
+ /// - Real-time filtering with wildcard pattern support
|
|
|
|
|
+ /// - Bulk selection operations (Select All, Clear All, Select Tests)
|
|
|
|
|
+ /// - Smart dependency management (auto-select dependencies)
|
|
|
|
|
+ /// - Progress feedback and status updates
|
|
|
|
|
+ /// - Custom save location option
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// The dialog loads projects asynchronously to avoid blocking the UI and provides
|
|
|
|
|
+ /// a responsive experience even with large solutions.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ public partial class ProjectFilterDialog : Window, INotifyPropertyChanged
|
|
|
|
|
+ {
|
|
|
|
|
+ #region Private Fields
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Information about the solution being filtered
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private readonly SolutionInfo _solutionInfo;
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Parser for extracting project information and dependencies
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private readonly SolutionParser _solutionParser;
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// All project nodes with dependency relationships
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private List<ProjectNode> _allProjectNodes;
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Current filter pattern for project filtering
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private string _filterPattern = "";
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Flag to prevent recursive updates during bulk operations
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private bool _updatingSelection = false;
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Timer for debouncing filter text input (reduces flicker during typing)
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private DispatcherTimer _filterTimer;
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Last used path for "Save As" functionality
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private string _lastSaveAsPath = "";
|
|
|
|
|
+
|
|
|
|
|
+ #endregion
|
|
|
|
|
+
|
|
|
|
|
+ #region Public Properties
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Path to the generated filtered solution file (set when user clicks OK)
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ public string FilteredSolutionPath { get; private set; }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Observable collection of project tree items for the TreeView binding
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ public ObservableCollection<ProjectTreeItem> Projects { get; set; }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Current filter pattern with property change notification
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ public string FilterPattern
|
|
|
|
|
+ {
|
|
|
|
|
+ get => _filterPattern;
|
|
|
|
|
+ set
|
|
|
|
|
+ {
|
|
|
|
|
+ _filterPattern = value;
|
|
|
|
|
+ OnPropertyChanged();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #endregion
|
|
|
|
|
+
|
|
|
|
|
+ #region Constructor and Initialization
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Initializes the project filter dialog for the specified solution.
|
|
|
|
|
+ /// Sets up data binding and initiates asynchronous project loading.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ /// <param name="solutionInfo">Information about the solution to filter</param>
|
|
|
|
|
+ public ProjectFilterDialog(SolutionInfo solutionInfo)
|
|
|
|
|
+ {
|
|
|
|
|
+ InitializeComponent();
|
|
|
|
|
+ DataContext = this;
|
|
|
|
|
+
|
|
|
|
|
+ _solutionInfo = solutionInfo ?? throw new ArgumentNullException(nameof(solutionInfo));
|
|
|
|
|
+ _solutionParser = new SolutionParser();
|
|
|
|
|
+ Projects = new ObservableCollection<ProjectTreeItem>();
|
|
|
|
|
+
|
|
|
|
|
+ // Set dialog title and solution info
|
|
|
|
|
+ Title = $"Filter Projects - {_solutionInfo.Name}";
|
|
|
|
|
+ SolutionNameTextBlock.Text = _solutionInfo.Name;
|
|
|
|
|
+
|
|
|
|
|
+ // Load projects asynchronously when dialog is shown
|
|
|
|
|
+ Loaded += async (s, e) => await LoadProjectsAsync();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #endregion
|
|
|
|
|
+
|
|
|
|
|
+ #region Asynchronous Project Loading
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Loads projects from the solution file asynchronously and builds the dependency tree.
|
|
|
|
|
+ /// This method performs the heavy lifting of parsing solution and project files,
|
|
|
|
|
+ /// then populates the UI tree view with the results.
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// The loading process:
|
|
|
|
|
+ /// 1. Parse solution file to get project list
|
|
|
|
|
+ /// 2. Parse project files to get dependency information
|
|
|
|
|
+ /// 3. Build hierarchical tree structure for UI
|
|
|
|
|
+ /// 4. Set up data binding and refresh display
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private async Task LoadProjectsAsync()
|
|
|
|
|
+ {
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ // Show loading state to user
|
|
|
|
|
+ ProjectTreeView.Visibility = Visibility.Hidden;
|
|
|
|
|
+ StatusTextBlock.Text = "Loading projects...";
|
|
|
|
|
+
|
|
|
|
|
+ // Parse solution and build dependency tree (this is the heavy operation)
|
|
|
|
|
+ _allProjectNodes = await _solutionParser.ParseSolutionAsync(_solutionInfo.FullPath);
|
|
|
|
|
+
|
|
|
|
|
+ // Update UI on the UI thread
|
|
|
|
|
+ System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ Projects.Clear();
|
|
|
|
|
+
|
|
|
|
|
+ // Step 1: Create all project tree items
|
|
|
|
|
+ var projectTreeItems = new Dictionary<string, ProjectTreeItem>();
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var projectNode in _allProjectNodes)
|
|
|
|
|
+ {
|
|
|
|
|
+ // Convert ProjectNode to ProjectInfo for UI binding
|
|
|
|
|
+ var projectInfo = new ProjectInfo
|
|
|
|
|
+ {
|
|
|
|
|
+ Name = projectNode.Name,
|
|
|
|
|
+ FullPath = projectNode.Path,
|
|
|
|
|
+ ProjectGuid = projectNode.ProjectGuid,
|
|
|
|
|
+ TypeGuid = projectNode.TypeGuid
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // Create tree item for UI
|
|
|
|
|
+ var treeItem = new ProjectTreeItem(projectInfo)
|
|
|
|
|
+ {
|
|
|
|
|
+ Tag = projectNode, // Keep reference for dependency operations
|
|
|
|
|
+ IsSelectable = true
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ projectTreeItems[projectNode.ProjectGuid] = treeItem;
|
|
|
|
|
+ Projects.Add(treeItem);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Step 2: Build dependency hierarchy for tree view
|
|
|
|
|
+ int totalDependencies = 0;
|
|
|
|
|
+ foreach (var projectNode in _allProjectNodes)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (projectTreeItems.TryGetValue(projectNode.ProjectGuid, out var treeItem))
|
|
|
|
|
+ {
|
|
|
|
|
+ BuildDependencyTree(treeItem, projectNode);
|
|
|
|
|
+ totalDependencies += treeItem.Children.Count;
|
|
|
|
|
+
|
|
|
|
|
+ // Log dependency building for troubleshooting
|
|
|
|
|
+ ErrorHandler.LogInfo($"Project {projectNode.Name}: Expected deps={projectNode.Dependencies.Count}, Actual children={treeItem.Children.Count}, HasDependencies={treeItem.HasDependencies}");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ErrorHandler.LogInfo($"Total dependencies added to tree: {totalDependencies}");
|
|
|
|
|
+
|
|
|
|
|
+ // Add test dependencies to verify tree structure is working
|
|
|
|
|
+ if (totalDependencies == 0 && Projects.Count >= 2)
|
|
|
|
|
+ {
|
|
|
|
|
+ // Add test dependency to demonstrate structure
|
|
|
|
|
+ var firstProject = Projects[0];
|
|
|
|
|
+ var secondProject = Projects.Count > 1 ? Projects[1] : null;
|
|
|
|
|
+
|
|
|
|
|
+ if (secondProject != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ var testChild = new ProjectTreeItem(new ProjectInfo { Name = secondProject.ProjectInfo.Name })
|
|
|
|
|
+ {
|
|
|
|
|
+ IsDependency = true,
|
|
|
|
|
+ IsSelectable = false,
|
|
|
|
|
+ Tag = secondProject.Tag
|
|
|
|
|
+ };
|
|
|
|
|
+ firstProject.Children.Add(testChild);
|
|
|
|
|
+ firstProject.OnPropertyChanged(nameof(firstProject.HasDependencies));
|
|
|
|
|
+ firstProject.DependencyCountText = "(test - references 1 project)";
|
|
|
|
|
+ ErrorHandler.LogInfo($"Added test dependency: {firstProject.DisplayName} -> {secondProject.DisplayName}");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Update UI elements
|
|
|
|
|
+ ProjectCountTextBlock.Text = $"{Projects.Count} projects found";
|
|
|
|
|
+ UpdateSelectionCount();
|
|
|
|
|
+
|
|
|
|
|
+ // Show the populated tree
|
|
|
|
|
+ ProjectTreeView.Visibility = Visibility.Visible;
|
|
|
|
|
+ StatusTextBlock.Text = "Ready";
|
|
|
|
|
+
|
|
|
|
|
+ // Set up data binding
|
|
|
|
|
+ ProjectTreeView.ItemsSource = Projects;
|
|
|
|
|
+ ProjectTreeView.UpdateLayout();
|
|
|
|
|
+
|
|
|
|
|
+ ErrorHandler.LogInfo($"Loaded {Projects.Count} projects in filter dialog with dependency trees");
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ ErrorHandler.ShowUserError("Error", $"Error loading solution: {ex.Message}", ex);
|
|
|
|
|
+ DialogResult = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Builds the dependency tree structure for a single project tree item.
|
|
|
|
|
+ /// Creates child items for each dependency that this project references.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ /// <param name="parentItem">The parent project tree item</param>
|
|
|
|
|
+ /// <param name="projectNode">The project node with dependency information</param>
|
|
|
|
|
+ private void BuildDependencyTree(ProjectTreeItem parentItem, ProjectNode projectNode)
|
|
|
|
|
+ {
|
|
|
|
|
+ // Clear any existing children
|
|
|
|
|
+ parentItem.Children.Clear();
|
|
|
|
|
+
|
|
|
|
|
+ // Add child items for each dependency
|
|
|
|
|
+ foreach (var dependency in projectNode.Dependencies)
|
|
|
|
|
+ {
|
|
|
|
|
+ var depInfo = new ProjectInfo
|
|
|
|
|
+ {
|
|
|
|
|
+ Name = dependency.Name,
|
|
|
|
|
+ FullPath = dependency.Path,
|
|
|
|
|
+ ProjectGuid = dependency.ProjectGuid,
|
|
|
|
|
+ TypeGuid = dependency.TypeGuid
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ var depItem = new ProjectTreeItem(depInfo)
|
|
|
|
|
+ {
|
|
|
|
|
+ Tag = dependency,
|
|
|
|
|
+ IsSelectable = false, // Dependencies shown for info only
|
|
|
|
|
+ IsDependency = true, // Visual styling indicator
|
|
|
|
|
+ IsChecked = false // Dependencies don't have checkboxes
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ parentItem.Children.Add(depItem);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #endregion
|
|
|
|
|
+
|
|
|
|
|
+ #region Bulk Selection Operations
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Selects all selectable projects in the solution.
|
|
|
|
|
+ /// Uses update flag to prevent cascading dependency updates during bulk operation.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private void SelectAllButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ _updatingSelection = true;
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ foreach (var project in Projects)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (project.IsSelectable)
|
|
|
|
|
+ project.IsChecked = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ UpdateSelectionCount();
|
|
|
|
|
+ }
|
|
|
|
|
+ finally
|
|
|
|
|
+ {
|
|
|
|
|
+ _updatingSelection = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Clears selection from all projects.
|
|
|
|
|
+ /// Uses update flag to prevent cascading dependency updates during bulk operation.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private void ClearAllButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ _updatingSelection = true;
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ foreach (var project in Projects)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (project.IsSelectable)
|
|
|
|
|
+ project.IsChecked = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ UpdateSelectionCount();
|
|
|
|
|
+ }
|
|
|
|
|
+ finally
|
|
|
|
|
+ {
|
|
|
|
|
+ _updatingSelection = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Smart selection of test projects and their dependencies.
|
|
|
|
|
+ /// Applies filter pattern to show test projects, then selects them automatically.
|
|
|
|
|
+ /// Also ensures all dependencies of test projects are selected.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private void SelectTestsButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ // First, filter to show test projects
|
|
|
|
|
+ FilterTextBox.Text = "*Test*";
|
|
|
|
|
+ ApplyFilter();
|
|
|
|
|
+
|
|
|
|
|
+ // Then select all visible test projects
|
|
|
|
|
+ _updatingSelection = true;
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ foreach (var project in Projects)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (project.IsSelectable &&
|
|
|
|
|
+ project.DisplayName.IndexOf("Test", StringComparison.OrdinalIgnoreCase) >= 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ project.IsChecked = true;
|
|
|
|
|
+
|
|
|
|
|
+ // Auto-select dependencies for test projects to ensure they can build
|
|
|
|
|
+ if (project.Tag is ProjectNode projectNode)
|
|
|
|
|
+ {
|
|
|
|
|
+ SelectDependencies(projectNode, true);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ UpdateSelectionCount();
|
|
|
|
|
+ }
|
|
|
|
|
+ finally
|
|
|
|
|
+ {
|
|
|
|
|
+ _updatingSelection = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Expands all tree view items to show dependency hierarchy
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private void ExpandAllButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ SetAllTreeViewItemsExpanded(true);
|
|
|
|
|
+ StatusTextBlock.Text = "All project trees expanded";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Collapses all tree view items to hide dependency details
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private void CollapseAllButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ SetAllTreeViewItemsExpanded(false);
|
|
|
|
|
+ StatusTextBlock.Text = "All project trees collapsed";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Sets the expanded state for all tree view items.
|
|
|
|
|
+ /// Handles the complexity of WPF TreeView container generation.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ /// <param name="isExpanded">True to expand all, false to collapse all</param>
|
|
|
|
|
+ private void SetAllTreeViewItemsExpanded(bool isExpanded)
|
|
|
|
|
+ {
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ // First approach: try to access containers directly
|
|
|
|
|
+ foreach (var item in Projects)
|
|
|
|
|
+ {
|
|
|
|
|
+ var container = ProjectTreeView.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
|
|
|
|
|
+ if (container != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ SetTreeViewItemExpanded(container, isExpanded);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Second approach: force container generation for any that weren't generated yet
|
|
|
|
|
+ ProjectTreeView.UpdateLayout();
|
|
|
|
|
+
|
|
|
|
|
+ // Try again for any containers that weren't available the first time
|
|
|
|
|
+ foreach (var item in Projects)
|
|
|
|
|
+ {
|
|
|
|
|
+ var container = ProjectTreeView.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
|
|
|
|
|
+ if (container != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ container.IsExpanded = isExpanded;
|
|
|
|
|
+ // Also ensure child containers are handled
|
|
|
|
|
+ for (int i = 0; i < container.Items.Count; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ var childContainer = container.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
|
|
|
|
|
+ if (childContainer != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ childContainer.IsExpanded = isExpanded;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ ErrorHandler.LogError($"Error {(isExpanded ? "expanding" : "collapsing")} tree items", ex);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Recursively sets the expanded state for a tree view item and its children
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private void SetTreeViewItemExpanded(TreeViewItem item, bool isExpanded)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (item == null) return;
|
|
|
|
|
+
|
|
|
|
|
+ item.IsExpanded = isExpanded;
|
|
|
|
|
+
|
|
|
|
|
+ // Recursively expand/collapse child items
|
|
|
|
|
+ for (int i = 0; i < item.Items.Count; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ var childContainer = item.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
|
|
|
|
|
+ if (childContainer != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ SetTreeViewItemExpanded(childContainer, isExpanded);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #endregion
|
|
|
|
|
+
|
|
|
|
|
+ #region Smart Dependency Management
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Handles project checkbox being checked. Automatically selects all dependencies
|
|
|
|
|
+ /// to ensure the selected project can build successfully.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private void ProjectTreeItem_Checked(object sender, RoutedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_updatingSelection) return;
|
|
|
|
|
+
|
|
|
|
|
+ var checkBox = sender as CheckBox;
|
|
|
|
|
+ var treeItem = checkBox?.DataContext as ProjectTreeItem;
|
|
|
|
|
+ if (treeItem?.Tag is ProjectNode projectNode && treeItem.IsSelectable)
|
|
|
|
|
+ {
|
|
|
|
|
+ _updatingSelection = true;
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ // Recursively select all dependencies
|
|
|
|
|
+ SelectDependencies(projectNode, true);
|
|
|
|
|
+ UpdateSelectionCount();
|
|
|
|
|
+ }
|
|
|
|
|
+ finally
|
|
|
|
|
+ {
|
|
|
|
|
+ _updatingSelection = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Handles project checkbox being unchecked. Automatically deselects all projects
|
|
|
|
|
+ /// that depend on this project to maintain build integrity.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private void ProjectTreeItem_Unchecked(object sender, RoutedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_updatingSelection) return;
|
|
|
|
|
+
|
|
|
|
|
+ var checkBox = sender as CheckBox;
|
|
|
|
|
+ var treeItem = checkBox?.DataContext as ProjectTreeItem;
|
|
|
|
|
+ if (treeItem?.Tag is ProjectNode projectNode && treeItem.IsSelectable)
|
|
|
|
|
+ {
|
|
|
|
|
+ _updatingSelection = true;
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ // Recursively deselect all dependents
|
|
|
|
|
+ DeselectDependents(projectNode);
|
|
|
|
|
+ UpdateSelectionCount();
|
|
|
|
|
+ }
|
|
|
|
|
+ finally
|
|
|
|
|
+ {
|
|
|
|
|
+ _updatingSelection = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Recursively selects a project and all its dependencies.
|
|
|
|
|
+ /// Ensures that when a project is selected, everything it needs to build is also selected.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ /// <param name="projectNode">The project node to process</param>
|
|
|
|
|
+ /// <param name="isChecked">Whether to select or deselect</param>
|
|
|
|
|
+ private void SelectDependencies(ProjectNode projectNode, bool isChecked)
|
|
|
|
|
+ {
|
|
|
|
|
+ // Find and update the corresponding tree item
|
|
|
|
|
+ var treeItem = Projects.FirstOrDefault(p =>
|
|
|
|
|
+ string.Equals(((ProjectNode)p.Tag)?.ProjectGuid, projectNode.ProjectGuid, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
+
|
|
|
|
|
+ if (treeItem != null && treeItem.IsChecked != isChecked && treeItem.IsSelectable)
|
|
|
|
|
+ {
|
|
|
|
|
+ treeItem.IsChecked = isChecked;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Recursively process all dependencies
|
|
|
|
|
+ foreach (var dependency in projectNode.Dependencies)
|
|
|
|
|
+ {
|
|
|
|
|
+ SelectDependencies(dependency, isChecked);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Recursively deselects projects that depend on the specified project.
|
|
|
|
|
+ /// Ensures build integrity by preventing selection of projects that can't build
|
|
|
|
|
+ /// without their dependencies.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ /// <param name="projectNode">The project node whose dependents should be deselected</param>
|
|
|
|
|
+ private void DeselectDependents(ProjectNode projectNode)
|
|
|
|
|
+ {
|
|
|
|
|
+ // Process all projects that depend on this one
|
|
|
|
|
+ foreach (var dependent in projectNode.Dependents)
|
|
|
|
|
+ {
|
|
|
|
|
+ var treeItem = Projects.FirstOrDefault(p =>
|
|
|
|
|
+ string.Equals(((ProjectNode)p.Tag)?.ProjectGuid, dependent.ProjectGuid, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
+
|
|
|
|
|
+ if (treeItem != null && treeItem.IsChecked && treeItem.IsSelectable)
|
|
|
|
|
+ {
|
|
|
|
|
+ treeItem.IsChecked = false;
|
|
|
|
|
+ // Recursively deselect dependents of dependents
|
|
|
|
|
+ DeselectDependents(dependent);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #endregion
|
|
|
|
|
+
|
|
|
|
|
+ #region Filtering Implementation
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Clears the current filter and shows all projects
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private void ClearFilterButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ FilterTextBox.Text = "";
|
|
|
|
|
+ FilterPattern = "";
|
|
|
|
|
+ ApplyFilter();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Handles real-time filtering as user types in the filter textbox.
|
|
|
|
|
+ /// Uses a debounce timer to avoid excessive filtering during rapid typing.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private void FilterTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ // Stop any existing timer
|
|
|
|
|
+ _filterTimer?.Stop();
|
|
|
|
|
+
|
|
|
|
|
+ // Start new timer with 300ms delay
|
|
|
|
|
+ _filterTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(300) };
|
|
|
|
|
+ _filterTimer.Tick += (s, args) =>
|
|
|
|
|
+ {
|
|
|
|
|
+ _filterTimer.Stop();
|
|
|
|
|
+ ApplyFilter();
|
|
|
|
|
+ };
|
|
|
|
|
+ _filterTimer.Start();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Applies the current filter pattern to all projects in the tree view.
|
|
|
|
|
+ /// Supports wildcard patterns using * as a placeholder for any characters.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private void ApplyFilter()
|
|
|
|
|
+ {
|
|
|
|
|
+ var pattern = FilterTextBox.Text?.Trim() ?? "";
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var project in Projects)
|
|
|
|
|
+ {
|
|
|
|
|
+ ApplyFilterToItem(project, pattern);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Applies filter pattern to a single project tree item.
|
|
|
|
|
+ /// Uses regex pattern matching to support wildcard filtering.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ /// <param name="item">The tree item to filter</param>
|
|
|
|
|
+ /// <param name="pattern">The filter pattern (supports * wildcards)</param>
|
|
|
|
|
+ /// <returns>True if the item should be visible</returns>
|
|
|
|
|
+ private bool ApplyFilterToItem(ProjectTreeItem item, string pattern)
|
|
|
|
|
+ {
|
|
|
|
|
+ bool isVisible = true;
|
|
|
|
|
+
|
|
|
|
|
+ if (!string.IsNullOrWhiteSpace(pattern))
|
|
|
|
|
+ {
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ // Convert wildcard pattern to regex
|
|
|
|
|
+ var wildcardPattern = pattern.Replace("*", ".*");
|
|
|
|
|
+ var regex = new System.Text.RegularExpressions.Regex(wildcardPattern,
|
|
|
|
|
+ System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
|
|
|
|
+
|
|
|
|
|
+ // For main projects, check if name matches pattern
|
|
|
|
|
+ // Dependencies are always shown if their parent is visible
|
|
|
|
|
+ isVisible = item.IsDependency || regex.IsMatch(item.ProjectInfo.Name);
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ ErrorHandler.LogError("Error applying filter pattern", ex);
|
|
|
|
|
+ StatusTextBlock.Text = "Invalid filter pattern";
|
|
|
|
|
+ isVisible = true; // Show all on error
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Update visibility property for UI binding
|
|
|
|
|
+ item.IsVisible = isVisible;
|
|
|
|
|
+ return isVisible;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #endregion
|
|
|
|
|
+
|
|
|
|
|
+ #region Status and UI Updates
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Updates the selection count display and enables/disables action buttons.
|
|
|
|
|
+ /// Provides real-time feedback about the current selection state.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private void UpdateSelectionCount()
|
|
|
|
|
+ {
|
|
|
|
|
+ var selectedCount = Projects.Count(p => p.IsSelectable && p.IsChecked);
|
|
|
|
|
+ SelectionCountTextBlock.Text = $"{selectedCount} of {Projects.Count} projects selected";
|
|
|
|
|
+
|
|
|
|
|
+ // Enable action buttons only when projects are selected
|
|
|
|
|
+ OkButton.IsEnabled = selectedCount > 0;
|
|
|
|
|
+ SaveAsButton.IsEnabled = selectedCount > 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #endregion
|
|
|
|
|
+
|
|
|
|
|
+ #region Solution Generation Actions
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Handles the "Save As..." button click to save filtered solution to custom location.
|
|
|
|
|
+ /// Shows a file dialog and generates the solution at the chosen location.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private async void SaveAsButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ var selectedTreeItems = Projects.Where(p => p.IsSelectable && p.IsChecked).ToList();
|
|
|
|
|
+ if (!selectedTreeItems.Any())
|
|
|
|
|
+ {
|
|
|
|
|
+ ErrorHandler.ShowUserInfo("Warning", "Please select at least one project.");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Show save dialog
|
|
|
|
|
+ var saveDialog = new SaveFileDialog
|
|
|
|
|
+ {
|
|
|
|
|
+ Title = "Save Filtered Solution As",
|
|
|
|
|
+ Filter = "Solution files (*.sln)|*.sln|All files (*.*)|*.*",
|
|
|
|
|
+ DefaultExt = ".sln",
|
|
|
|
|
+ FileName = $"{_solutionInfo.Name}_filtered.sln",
|
|
|
|
|
+ InitialDirectory = string.IsNullOrEmpty(_lastSaveAsPath) ?
|
|
|
|
|
+ Path.GetDirectoryName(_solutionInfo.FullPath) :
|
|
|
|
|
+ Path.GetDirectoryName(_lastSaveAsPath)
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ if (saveDialog.ShowDialog() == true)
|
|
|
|
|
+ {
|
|
|
|
|
+ _lastSaveAsPath = saveDialog.FileName;
|
|
|
|
|
+
|
|
|
|
|
+ // Update UI to show progress
|
|
|
|
|
+ StatusTextBlock.Text = "Creating filtered solution...";
|
|
|
|
|
+ SaveAsButton.IsEnabled = false;
|
|
|
|
|
+ OkButton.IsEnabled = false;
|
|
|
|
|
+ CancelButton.IsEnabled = false;
|
|
|
|
|
+
|
|
|
|
|
+ // Get corresponding ProjectNodes for generation
|
|
|
|
|
+ var selectedProjectNodes = selectedTreeItems
|
|
|
|
|
+ .Select(ti => ti.Tag as ProjectNode)
|
|
|
|
|
+ .Where(pn => pn != null)
|
|
|
|
|
+ .ToList();
|
|
|
|
|
+
|
|
|
|
|
+ ErrorHandler.LogInfo($"User saving {selectedProjectNodes.Count} projects to custom location");
|
|
|
|
|
+
|
|
|
|
|
+ // Expand selection to include dependencies
|
|
|
|
|
+ var projectsWithDependencies = _solutionParser.GetProjectsWithDependencies(selectedProjectNodes);
|
|
|
|
|
+
|
|
|
|
|
+ // Create filtered solution with path remapping
|
|
|
|
|
+ FilteredSolutionPath = await _solutionParser.CreateFilteredSolutionWithRemappingAsync(
|
|
|
|
|
+ _solutionInfo.FullPath, projectsWithDependencies, saveDialog.FileName);
|
|
|
|
|
+
|
|
|
|
|
+ StatusTextBlock.Text = "Filtered solution saved successfully";
|
|
|
|
|
+
|
|
|
|
|
+ // Ask if user wants to open the saved solution
|
|
|
|
|
+ var result = MessageBox.Show(
|
|
|
|
|
+ $"Filtered solution saved to:\n{saveDialog.FileName}\n\nWould you like to open it now?",
|
|
|
|
|
+ "Solution Saved",
|
|
|
|
|
+ MessageBoxButton.YesNo,
|
|
|
|
|
+ MessageBoxImage.Question);
|
|
|
|
|
+
|
|
|
|
|
+ if (result == MessageBoxResult.Yes)
|
|
|
|
|
+ {
|
|
|
|
|
+ DialogResult = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ // Reset buttons for continued use
|
|
|
|
|
+ SaveAsButton.IsEnabled = true;
|
|
|
|
|
+ OkButton.IsEnabled = true;
|
|
|
|
|
+ CancelButton.IsEnabled = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
|
|
|
|
|
+ ErrorHandler.ShowUserError("Error", "Error saving filtered solution", ex);
|
|
|
|
|
+ StatusTextBlock.Text = "Error saving filtered solution";
|
|
|
|
|
+ SaveAsButton.IsEnabled = true;
|
|
|
|
|
+ OkButton.IsEnabled = true;
|
|
|
|
|
+ CancelButton.IsEnabled = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Handles the "Create Filtered Solution" button click.
|
|
|
|
|
+ /// Generates filtered solution in temp directory and sets it to be opened.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private async void OkButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ var selectedTreeItems = Projects.Where(p => p.IsSelectable && p.IsChecked).ToList();
|
|
|
|
|
+ if (!selectedTreeItems.Any())
|
|
|
|
|
+ {
|
|
|
|
|
+ ErrorHandler.ShowUserInfo("Warning", "Please select at least one project.");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Update UI to show progress
|
|
|
|
|
+ StatusTextBlock.Text = "Creating filtered solution...";
|
|
|
|
|
+ OkButton.IsEnabled = false;
|
|
|
|
|
+ SaveAsButton.IsEnabled = false;
|
|
|
|
|
+ CancelButton.IsEnabled = false;
|
|
|
|
|
+
|
|
|
|
|
+ // Get corresponding ProjectNodes for generation
|
|
|
|
|
+ var selectedProjectNodes = selectedTreeItems
|
|
|
|
|
+ .Select(ti => ti.Tag as ProjectNode)
|
|
|
|
|
+ .Where(pn => pn != null)
|
|
|
|
|
+ .ToList();
|
|
|
|
|
+
|
|
|
|
|
+ ErrorHandler.LogInfo($"User selected {selectedProjectNodes.Count} projects for filtering");
|
|
|
|
|
+
|
|
|
|
|
+ // Expand selection to include dependencies
|
|
|
|
|
+ var projectsWithDependencies = _solutionParser.GetProjectsWithDependencies(selectedProjectNodes);
|
|
|
|
|
+
|
|
|
|
|
+ // Create filtered solution in temp directory with path remapping
|
|
|
|
|
+ FilteredSolutionPath = await _solutionParser.CreateFilteredSolutionAsync(
|
|
|
|
|
+ _solutionInfo.FullPath, projectsWithDependencies);
|
|
|
|
|
+
|
|
|
|
|
+ StatusTextBlock.Text = "Filtered solution created successfully";
|
|
|
|
|
+ DialogResult = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
|
|
|
|
|
+ ErrorHandler.ShowUserError("Error", "Error creating filtered solution", ex);
|
|
|
|
|
+ StatusTextBlock.Text = "Error creating filtered solution";
|
|
|
|
|
+ OkButton.IsEnabled = true;
|
|
|
|
|
+ SaveAsButton.IsEnabled = true;
|
|
|
|
|
+ CancelButton.IsEnabled = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Handles the "Open All Projects" button click.
|
|
|
|
|
+ /// Cancels filtering and continues with the original solution.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private void CancelButton_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ ErrorHandler.LogInfo("User chose to open all projects");
|
|
|
|
|
+ DialogResult = false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #endregion
|
|
|
|
|
+
|
|
|
|
|
+ #region INotifyPropertyChanged Implementation
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Event for property change notifications (required for data binding)
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Raises the PropertyChanged event for the specified property
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ /// <param name="propertyName">Name of the property that changed (auto-filled by compiler)</param>
|
|
|
|
|
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
|
|
|
|
+ {
|
|
|
|
|
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #endregion
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|