// Nova.Studio - a GUI test framework for the Nova.CodeDOM C# object model library.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using Microsoft.Win32;
using Nova.CodeDOM;
using Nova.Resolving;
using Nova.Test;
using Nova.UI;
using Nova.Utilities;
namespace Nova.Studio
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
#region /* FIELDS */
// The currently displayed solution
protected SolutionVM _solutionVM;
// Special solution for miscellaneous files
protected Solution _miscellaneousFilesSolution;
// Special tree node for miscellaneous files
protected TreeViewItem _miscellaneousFilesTreeItem;
// True if busy (loading, parsing, resolving, etc)
protected bool _busy;
protected Stopwatch _busyStopWatch = new Stopwatch();
// Status bar related
protected readonly DispatcherTimer _statusTimer; // 4Hz status update timer
protected int _messageCount;
protected int _resultsCount;
protected int _heapUsage;
protected int _maxHeapUsage;
protected double _tabControlMinHeight;
// Last solution and configuration, for re-loading when saving '.nuo' fails
protected string _lastSolutionFileName;
protected string _lastSolutionActiveConfiguration;
protected string _lastSolutionActivePlatform;
#endregion
#region /* CONSTANTS */
public const string MiscellaneousFiles = "Miscellaneous Files";
#endregion
#region /* CONSTRUCTORS */
public MainWindow()
{
InitializeComponent();
// Setup callback for logging class to display logs in the UI
Log.SetLogWriteLineCallback(AddLogEntry);
// Force a reference to CodeObject, so that any .config settings are read
CodeObject.ForceReference();
// Enumerate system fonts
foreach (FontFamily font in Fonts.SystemFontFamilies)
comboBoxFonts.Items.Add(font.Source);
comboBoxFonts.SelectedItem = CodeRenderer.CodeFontFamily.Source;
textBoxFontSize.Text = CodeRenderer.CodeFontSize.ToString();
// Command initialization
BindCommands(this);
InputBindings.Add(new InputBinding(ApplicationCommands.Close, new KeyGesture(Key.F4, ModifierKeys.Control)));
foreach (Type type in Assembly.GetAssembly(typeof(CodeObjectVM)).GetTypes())
{
MethodInfo method = type.GetMethod("BindCommands", BindingFlags.Public | BindingFlags.Static);
if (method != null)
method.Invoke(null, new object[] { this });
}
// Setup callbacks
CodeRenderer.ReRenderAll = ReRenderCurrentTab;
// Setup status update timer at 4Hz
_statusTimer = new DispatcherTimer(new TimeSpan(0, 0, 0, 0, 250), DispatcherPriority.Normal, OnUpdateStatus, Dispatcher.CurrentDispatcher);
_statusTimer.Start();
Log.WriteLine("MainWindow initialization complete.");
}
protected void Shutdown()
{
_statusTimer.Stop();
Application.Current.Shutdown();
}
#endregion
#region /* PROPERTIES */
public SolutionVM SolutionVM
{
get { return _solutionVM; }
set
{
_solutionVM = value;
// Bind the messages of the solution to the list view (support calls from non-UI threads)
if (listViewMessages.Dispatcher.CheckAccess())
BindMessages();
else
listViewOutput.Dispatcher.Invoke(new Action(BindMessages));
}
}
public bool Busy
{
get { return _busy; }
set
{
_busy = value;
// Force the CanExecute status of all commands to update without having to click
// somewhere in the UI first (so toolbar buttons update immediately).
CommandManager.InvalidateRequerySuggested();
}
}
#endregion
#region /* MAIN MENU & TOOL BARS */
protected void CanAlwaysExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = !_busy;
}
// Keep the open file dialog around so that it remembers the last directory used
protected OpenFileDialog _openFileDialog;
protected void OnFileOpen(object sender, ExecutedRoutedEventArgs e)
{
if (_openFileDialog == null)
{
_openFileDialog = new OpenFileDialog
{
Filter = "All C# Files (*.sln;*.csproj;*.cs)|*.sln;*.csproj;*.cs|"
+ "Solution Files (*.sln)|*.sln|"
+ "C# Project Files (*.csproj)|*.csproj|"
+ "C# Files (*.cs)|*.cs|All Files (*.*)|*.*"
};
}
_openFileDialog.FileName = null;
if (_openFileDialog.ShowDialog() == true)
{
string fileName = _openFileDialog.FileName;
switch (Path.GetExtension(fileName))
{
case Solution.SolutionFileExtension:
OpenSolution(fileName);
break;
case Project.CSharpProjectFileExtension:
OpenProject(fileName);
break;
default:
OpenIndividualFile(fileName);
break;
}
}
}
protected void CanCloseSolutionExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = (treeViewSolutionFiles.Items.Count > 0 && !_busy);
}
protected void OnCloseSolution(object sender, ExecutedRoutedEventArgs e)
{
CloseSolution();
}
protected void CloseSolution()
{
// Remove all tabs (including any that aren't actually linked to the solution tree, but that's OK)
tabControlFiles.Items.Clear();
// Clear the solution tree and other controls
WPFUtil.Clear(treeViewSolutionFiles);
_miscellaneousFilesTreeItem = null;
WPFUtil.Clear(listViewOutput);
listViewMessages.ItemsSource = null;
tabItemOutput.IsSelected = true;
SetConfigurationPlatform();
// Unload the solution
if (_solutionVM != null)
{
_solutionVM.Unload();
_solutionVM = null;
}
if (_miscellaneousFilesSolution != null)
_miscellaneousFilesSolution.Unload();
OnUpdateStatus(null, null);
}
protected void OpenSolution(string fileName)
{
Busy = true;
CloseSolution();
WPFUtil.ExecuteActionOnThread(this, OpenSolutionThread, fileName, OpenFileCompleted);
}
protected void OpenSolutionThread(string fileName)
{
// Clear any saved config for a previous solution (otherwise, we'll re-use it just in case saving the '.nuo' file failed)
if (fileName != _lastSolutionFileName)
{
_lastSolutionActiveConfiguration = null;
_lastSolutionActivePlatform = null;
}
Solution.Load(fileName, _lastSolutionActiveConfiguration, _lastSolutionActivePlatform, LoadOptions.Complete, LoadStatusUpdate);
}
protected void OpenFileCompleted()
{
treeViewSolutionFiles.InvalidateVisual();
DisplayResolveResults();
ShowProgressBar(null);
OnUpdateStatus(null, null);
Busy = false;
}
protected void OpenProject(string fileName)
{
Busy = true;
CloseSolution();
WPFUtil.ExecuteActionOnThread(this, OpenProjectFileThread, fileName, OpenFileCompleted);
}
protected void OpenProjectFileThread(string fileName)
{
Project.Load(fileName, LoadOptions.Complete, LoadStatusUpdate);
}
protected void LoadStatusUpdate(LoadStatus loadStatus, CodeObject codeObject)
{
switch (loadStatus)
{
case LoadStatus.ObjectCreated:
treeViewSolutionFiles.Dispatcher.Invoke(new Action<CodeObject>(AddItem), codeObject);
break;
case LoadStatus.ObjectAnnotated:
treeViewSolutionFiles.Dispatcher.Invoke(new Action<CodeObject>(UpdateItem), codeObject);
break;
case LoadStatus.SolutionLoaded:
buttonActiveConfigurationPlatform.Dispatcher.Invoke(new Action(SetConfigurationPlatform));
break;
case LoadStatus.ProjectParsed:
treeViewSolutionFiles.Dispatcher.Invoke(new Action<Project>(ProjectParsed), codeObject);
break;
case LoadStatus.ProjectsLoaded:
treeViewSolutionFiles.Dispatcher.Invoke(new Action(UpdateWebsiteProjectReferences));
break;
default:
progressBarStatus.Dispatcher.Invoke(new Action<string>(ShowProgressBar), loadStatus.ToString());
break;
}
}
protected void ProjectParsed(Project project)
{
// The project was opened directly - display the final active configuration/platform
if (_solutionVM.Solution.IsNew)
SetConfigurationPlatform();
}
protected void OpenIndividualFile(string fileName, Project project)
{
// First, search for the file in the existing solution, and open it if found
TreeViewItem fileItem = FindFileNameInTree(treeViewSolutionFiles.Items, fileName);
if (fileItem != null)
SelectFileItemAndOpenTab(fileItem, true);
else
{
// Add the file to the miscellaneous files
fileItem = AddMiscellaneousFile(fileName, project);
SelectTreeItem(fileItem);
CodeUnit codeUnit = ((CodeUnitVM)fileItem.Tag).CodeUnit;
ParseResolveCodeUnit(codeUnit);
}
}
protected void OpenIndividualFile(string fileName)
{
OpenIndividualFile(fileName, null);
}
protected void ParseResolveCodeUnit(CodeUnit codeUnit)
{
Busy = true;
WPFUtil.ExecuteActionOnThread(this, ParseResolveCodeUnitThread, codeUnit, ParseResolveCodeUnitCompleted);
}
protected void ParseResolveCodeUnitThread(CodeUnit codeUnit)
{
codeUnit.ParseResolveLog(LoadOptions.Complete, LoadStatusUpdate);
}
protected void ParseResolveCodeUnitCompleted()
{
OpenFileCompleted();
OpenItem((TreeViewItem)treeViewSolutionFiles.SelectedItem, false);
}
protected TabItem OpenCodeUnit(CodeUnit codeUnit, bool resolve)
{
TabItem tabItem = null;
TreeViewItem fileItem = FindCodeObjectInTree(treeViewSolutionFiles.Items, codeUnit);
if (fileItem != null)
tabItem = SelectFileItemAndOpenTab(fileItem, resolve);
return tabItem;
}
protected TabItem SelectFileItemAndOpenTab(TreeViewItem fileItem, bool resolve)
{
SelectTreeItem(fileItem);
// If a tab already exists, give it focus, otherwise open the file
foreach (TabItem tabItem in tabControlFiles.Items)
{
if (((TabInfo)tabItem.Tag).TreeViewItem == fileItem)
{
tabItem.IsSelected = true;
return tabItem;
}
}
return OpenItem(fileItem, resolve);
}
protected void Reload()
{
// Save the config settings locally, in case writing the '.nuo' file failed (such as due to authorization)
_lastSolutionFileName = _solutionVM.Solution.FileName;
_lastSolutionActiveConfiguration = _solutionVM.Solution.ActiveConfiguration;
_lastSolutionActivePlatform = _solutionVM.Solution.ActivePlatform;
object rootItemTag = ((TreeViewItem)treeViewSolutionFiles.Items[0]).Tag;
if (rootItemTag is SolutionVM)
OpenSolution(((SolutionVM)rootItemTag).Solution.FileName);
else if (rootItemTag is ProjectVM)
OpenProject(((ProjectVM)rootItemTag).Project.FileName);
}
protected void CanCloseExecute(object sender, CanExecuteRoutedEventArgs e)
{
TabItem tabItem = e.Parameter as TabItem;
if (tabItem != null)
{
TabControl tabControl = tabItem.Parent as TabControl;
e.CanExecute = (tabControl == tabControlFiles && tabControlFiles != null);
}
else
{
// Called from Menu
if (tabControlFiles.SelectedIndex >= 0)
e.CanExecute = (tabControlFiles != null && tabControlFiles.SelectedItem != null && !_busy);
}
}
protected void OnFileClose(object sender, ExecutedRoutedEventArgs e)
{
TabItem tabItem = e.Parameter as TabItem;
if (tabItem != null)
{
TabControl tabControl = tabItem.Parent as TabControl;
if (tabControl != null)
tabControl.Items.Remove(tabItem);
}
else
{
// If we had no parameter, close the selected code window (closing from Menu)
if (tabControlFiles.SelectedIndex >= 0)
tabControlFiles.Items.RemoveAt(tabControlFiles.SelectedIndex);
}
}
protected void CanSaveExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = (tabControlFiles != null && tabControlFiles.SelectedItem != null && !_busy);
}
protected void CanSaveAllExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = (_solutionVM != null && !_busy);
}
protected void OnFileSave(object sender, ExecutedRoutedEventArgs e)
{
SaveTabItem((TabItem)tabControlFiles.SelectedItem);
}
protected void OnFileSaveAs(object sender, ExecutedRoutedEventArgs e)
{
TabItem tabItem = (TabItem)tabControlFiles.SelectedItem;
CodeObject codeObject = ((TabInfo)tabItem.Tag).CodeVM.CodeObject;
if (codeObject is CodeUnit)
{
CodeUnit codeUnit = (CodeUnit)codeObject;
SaveFileDialog saveFileDialog = new SaveFileDialog { FileName = CodeUnit.GetSaveFileName(codeUnit.FileName) };
if (saveFileDialog.ShowDialog() == true)
{
string fileName = saveFileDialog.FileName;
codeUnit.SaveAs(fileName);
}
}
}
protected void OnFileSaveAll(object sender, ExecutedRoutedEventArgs e)
{
// Save all open tabs
foreach (TabItem tabItem in tabControlFiles.Items)
SaveTabItem(tabItem);
}
protected void SaveTabItem(TabItem tabItem)
{
CodeObject codeObject = ((TabInfo)tabItem.Tag).CodeVM.CodeObject;
if (codeObject is CodeUnit)
((CodeUnit)codeObject).Save();
}
protected void Exit_Click(object sender, RoutedEventArgs e)
{
Shutdown();
}
protected void CanFindExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = (_solutionVM != null && !_busy);
}
protected void CanConfigurationManagerExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = (treeViewSolutionFiles.Items.Count > 0 && !_busy);
}
protected void OnConfigurationManager(object sender, ExecutedRoutedEventArgs e)
{
ConfigurationManager configurationManager = new ConfigurationManager(_solutionVM.Solution) { Owner = this };
if (configurationManager.ShowDialog() == true)
{
// Save the configuration settings in the '.nuo' file
_solutionVM.Solution.SaveUserOptions();
// Re-load the solution (or project) so that it's parsed with the proper compiler directive symbols,
// and generated files are loaded from the proper output directory.
Reload();
}
}
protected void OnAbout(object sender, ExecutedRoutedEventArgs e)
{
AboutWindow aboutWindow = new AboutWindow { Owner = this };
aboutWindow.ShowDialog();
}
/// <summary>
/// Get the <see cref="CodeUnit"/> of the active tab or tree selection.
/// </summary>
public CodeUnit GetCurrentCodeUnit()
{
CodeUnit codeUnit = null;
TabItem tabItem = tabControlFiles.SelectedItem as TabItem;
if (tabItem != null)
{
CodeUnitVM codeUnitVM = ((TabInfo)tabItem.Tag).CodeVM as CodeUnitVM;
if (codeUnitVM != null)
codeUnit = codeUnitVM.CodeUnit;
if (codeUnit == null)
{
TreeViewItem treeViewItem = treeViewSolutionFiles.SelectedItem as TreeViewItem;
if (treeViewItem != null && treeViewItem.Tag is CodeUnitVM)
codeUnit = ((CodeUnitVM)treeViewItem.Tag).CodeUnit;
}
}
return codeUnit;
}
/// <summary>
/// Get the <see cref="Project"/> of the active tab or tree selection.
/// </summary>
public Project GetCurrentProject()
{
Project project = null;
CodeUnit codeUnit = GetCurrentCodeUnit();
if (codeUnit != null)
{
project = codeUnit.Project;
if (project == null)
{
TreeViewItem treeViewItem = treeViewSolutionFiles.SelectedItem as TreeViewItem;
while (treeViewItem != null)
{
if (treeViewItem.Tag is ProjectVM)
{
project = ((ProjectVM)treeViewItem.Tag).Project;
break;
}
treeViewItem = VisualTreeHelper.GetParent(treeViewItem) as TreeViewItem;
}
}
}
return project;
}
#endregion
#region /* MAIN TAB CONTROL */
protected void tabControlFiles_Loaded(object sender, RoutedEventArgs e)
{
App app = (App)Application.Current;
if (app.Arguments.Length > 0)
{
switch (app.Arguments[0])
{
case "manualtests":
{
// Run simple manual tests
AddTestTab("Simple", ManualTests.GenerateSimpleTest);
AddTestTab("Method", ManualTests.GenerateMethodTest);
// Use a separate project for this test, with special references
string baseDirectory = FileUtil.GetBaseDirectory();
string testOutput = FileUtil.RemoveLastDirectory(baseDirectory) + @"\Nova.Test\bin\x86\Debug\";
Project project = GetMiscellaneousProject("FullTest", new AssemblyReference("Nova.CodeDOM", Environment.CurrentDirectory),
new AssemblyReference("Nova.Test.Library", testOutput));
AddTestTab("FullTest", ManualTests.GenerateFullTest, project, true);
if (SolutionVM.CodeAnnotations != null && SolutionVM.CodeAnnotations.Count > 0)
tabItemMessages.IsSelected = true;
break;
}
case "fulltest":
{
// Use a separate project for this test, with special references
string testProject = FileUtil.RemoveLastDirectory(FileUtil.GetBaseDirectory()) + @"\Nova.Test\";
string testOutput = testProject + @"\bin\x86\Debug\";
Project project = GetMiscellaneousProject("FullTest", new AssemblyReference("Nova.CodeDOM", Environment.CurrentDirectory),
new AssemblyReference("Nova.Test.Library", testOutput));
OpenIndividualFile(FileUtil.CombineAndNormalizePath(testProject, "FullTest.cs"), project);
break;
}
case "selftest":
{
// Search for & load the Nova.sln file
string rootDirectory = FileUtil.RemoveLastDirectory(FileUtil.GetBaseDirectory());
OpenSolution(FileUtil.CombineAndNormalizePath(rootDirectory, "Nova.sln"));
break;
}
}
}
}
private void AddTestTab(string testName, Func<string, Project, CodeObject> generateTest, Project project, bool parse)
{
// Manually generate code
CodeObject code = null;
try
{
code = generateTest(testName, project);
}
catch (Exception ex)
{
Log.Exception(ex, "generating test '" + testName + "'");
}
// Add the code under a new tab in the tab control
AddMiscellaneousCode(testName, code, true);
// Create a test tab with a re-parsed version of FullTest
if (parse && code != null)
{
testName += "-Parsed";
string testOutput = FileUtil.RemoveLastDirectory(FileUtil.GetBaseDirectory()) + @"\Nova.Test\bin\x86\Debug\";
project = GetMiscellaneousProject(testName, new AssemblyReference("Nova.CodeDOM", Environment.CurrentDirectory),
new AssemblyReference("Nova.Test.Library", testOutput));
project.Solution = CreateMiscellaneousSolution();
CodeUnit codeUnit = new CodeUnit(testName, code.AsText(), project);
ParseItem(codeUnit);
AddMiscellaneousCode(testName, codeUnit, true);
}
}
private void AddTestTab(string testName, Func<string, Project, CodeObject> generateTest)
{
AddTestTab(testName, generateTest, null, false);
}
protected TabItem AddTab(string name, CodeObjectVM codeObjectVM, TreeViewItem treeViewItem)
{
RemoveTab(codeObjectVM);
string description = (codeObjectVM is CodeUnitVM ? ((CodeUnitVM)codeObjectVM).CodeUnit.FileName : name);
// Use a TextBlock for the Header to prevent special handling of any underscores
TabItem tabItem = new TabItem { Header = new TextBlock { Text = name, ToolTip = description }, Tag = new TabInfo(codeObjectVM, treeViewItem) };
RenderTab(tabItem);
tabControlFiles.Items.Insert(0, tabItem);
tabControlFiles.SelectedIndex = 0;
return tabItem;
}
protected void RemoveTab(CodeObjectVM codeObjectVM)
{
foreach (TabItem tabItem in tabControlFiles.Items)
{
if (((TabInfo)tabItem.Tag).CodeVM == codeObjectVM)
{
tabControlFiles.Items.Remove(tabItem);
break;
}
}
}
protected void RenderTab(TabItem tabItem)
{
if (tabItem != null)
{
CodeObjectVM codeVM = ((TabInfo)tabItem.Tag).CodeVM;
CodeWindow codeWindow = new CodeWindow(((TextBlock)tabItem.Header).Text, codeVM);
if (_tabControlMinHeight == 0)
{
tabControlFiles.Measure(new Size(double.MaxValue, double.MaxValue));
_tabControlMinHeight = tabControlFiles.DesiredSize.Height - 4; // Approx size of client area
}
codeWindow.Render(tabControlFiles.ActualHeight - _tabControlMinHeight);
tabItem.Content = codeWindow;
}
}
protected void ReRenderCurrentTab()
{
RenderTab((TabItem)tabControlFiles.SelectedItem);
}
protected void tabControlFiles_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
TabItem tabItem = (TabItem)tabControlFiles.SelectedItem;
if (tabItem != null)
{
TreeViewItem treeViewItem = ((TabInfo)tabItem.Tag).TreeViewItem;
if (treeViewItem != null)
SelectTreeItem(treeViewItem);
}
}
protected void ResetTabToolTipTimer()
{
TabItem tabItem = (TabItem)tabControlFiles.SelectedItem;
if (tabItem != null)
((CodeWindow)tabItem.Content).ResetToolTipTimer();
}
protected void CloseTabToolTip()
{
TabItem tabItem = (TabItem)tabControlFiles.SelectedItem;
if (tabItem != null)
((CodeWindow)tabItem.Content).CloseToolTip();
}
public CodeWindow GetCurrentCodeWindow()
{
TabItem tabItem = (TabItem)tabControlFiles.SelectedItem;
return (tabItem != null ? (CodeWindow)tabItem.Content : null);
}
#region /* TAB INFO CLASS */
public class TabInfo
{
public CodeObjectVM CodeVM;
public TreeViewItem TreeViewItem;
public TabInfo(CodeObjectVM codeVM, TreeViewItem treeViewItem)
{
CodeVM = codeVM;
TreeViewItem = treeViewItem;
}
}
#endregion
#endregion
#region /* SOLUTION TREE */
protected string GetSolutionDescription(int projectCount)
{
string description = "Solution '" + _solutionVM.Solution.Name + "'";
if (projectCount > 0)
description += " (" + projectCount + " project" + (projectCount != 1 ? "s" : "") + ")";
return description;
}
private static int TotalProjects;
protected void AddItem(CodeObject codeObject)
{
if (codeObject is Solution)
{
Solution solution = (Solution)codeObject;
SolutionVM = new SolutionVM(solution);
// Ignore if this is a dummy solution for a directly-loaded project
if (!solution.IsNew)
{
TreeViewItem solutionItem = AddTreeItem(treeViewSolutionFiles, GetSolutionDescription(0), solution);
solutionItem.IsExpanded = true;
}
TotalProjects = 0;
}
else if (codeObject is Project)
{
Project project = (Project)codeObject;
ItemsControl parentItem = (treeViewSolutionFiles.Items.Count == 0 ? treeViewSolutionFiles : (ItemsControl)treeViewSolutionFiles.Items[0]);
string solutionFolders = project.GetSolutionFolders();
TreeViewItem projectItem = AddProjectItem(parentItem, solutionFolders, project);
if (parentItem != treeViewSolutionFiles)
((TreeViewItem)treeViewSolutionFiles.Items[0]).Header = GetSolutionDescription(_solutionVM.Solution.Projects.Count + 1);
// For up to 3 projects, expand them, but if more than 3, collapse them all
if (TotalProjects == 0 || parentItem == treeViewSolutionFiles)
TotalProjects = Enumerable.Count(SolutionVM.Solution.ProjectEntries, delegate(Solution.ProjectEntry projectEntry) { return !projectEntry.IsFolder; });
if (TotalProjects <= 3)
projectItem.IsExpanded = true;
}
else if (codeObject is Reference)
{
Reference reference = (Reference)codeObject;
if (!reference.IsHidden)
{
TreeViewItem projectItem = FindProjectItem((Project)reference.Parent);
TreeViewItem references = FindReferencesFolder(projectItem);
AddTreeItem(references, reference.ShortName, reference);
}
}
else if (codeObject is CodeUnit)
{
CodeUnit codeUnit = (CodeUnit)codeObject;
Project project = (Project)codeUnit.Parent;
TreeViewItem projectItem = FindProjectItem(project);
string projectRoot = project.GetDirectory();
if (!string.IsNullOrEmpty(projectRoot)) projectRoot += @"\";
AddFileItem(projectItem, projectRoot, codeUnit);
}
}
protected void UpdateItem(CodeObject codeObject)
{
if (codeObject is Solution)
UpdateTreeItem(treeViewSolutionFiles, GetSolutionDescription(0), _solutionVM.Solution);
else if (codeObject is Project)
{
Project project = (Project)codeObject;
TreeViewItem projectItem = FindProjectItem(project);
ItemsControl parent = projectItem.Parent as ItemsControl;
UpdateTreeItem(parent, project.GetDescription(), project);
}
else if (codeObject is Reference)
{
Reference reference = (Reference)codeObject;
if (!reference.IsHidden)
{
TreeViewItem projectItem = FindProjectItem((Project)reference.Parent);
TreeViewItem references = FindReferencesFolder(projectItem);
UpdateTreeItem(references, reference.ShortName, reference);
}
}
else if (codeObject is CodeUnit)
{
CodeUnit codeUnit = (CodeUnit)codeObject;
Project project = (Project)codeUnit.Parent;
TreeViewItem projectItem = FindProjectItem(project);
string projectRoot = project.GetDirectory();
if (!string.IsNullOrEmpty(projectRoot)) projectRoot += @"\";
UpdateTreeItem(projectItem, projectRoot, codeUnit);
}
}
protected static TreeViewItem AddProjectItem(ItemsControl itemsControl, string solutionFolders, Project project)
{
// If there's a folder, call this routine recursively, otherwise add the file
if (!string.IsNullOrEmpty(solutionFolders))
{
int index = solutionFolders.IndexOf('\\');
string folderName = (index >= 0 ? solutionFolders.Substring(0, index) : solutionFolders);
string remainder = (index >= 0 ? solutionFolders.Substring(index + 1) : null);
TreeViewItem folderItem = FindTreeItem(itemsControl, folderName, null, out index);
if (folderItem == null)
{
folderItem = InsertTreeItem(itemsControl, index, folderName, null, folderName);
folderItem.IsExpanded = true; // Expand all solution folders by default
}
return AddProjectItem(folderItem, remainder, project);
}
//string desc = project.GetDescription() + " (" + files + " file" + (files != 1 ? "s" : "") + ")";
return AddTreeItem(itemsControl, project.GetDescription(), project);
}
protected static void AddFileItem(ItemsControl itemsControl, string rootPath, CodeUnit codeUnit)
{
string relativePath = Path.GetDirectoryName(codeUnit.FileName) + @"\";
if (relativePath.StartsWith(rootPath))
relativePath = relativePath.Substring(rootPath.Length);
// If there's a subdirectory, call this routine recursively, otherwise add the file
if (!string.IsNullOrEmpty(relativePath))
{
string folderName = relativePath.Substring(0, relativePath.IndexOf('\\'));
string newRoot = rootPath + folderName + @"\";
int index;
TreeViewItem folderItem = FindTreeItem(itemsControl, folderName, null, out index);
if (folderItem == null)
folderItem = InsertTreeItem(itemsControl, index, folderName, null, newRoot);
AddFileItem(folderItem, newRoot, codeUnit);
}
else
AddTreeItem(itemsControl, codeUnit.Name, codeUnit);
}
protected void UpdateWebsiteProjectReferences()
{
// Update any project references for any website projects where the names have been
// resolved from GUIDs to actual project names.
if (treeViewSolutionFiles.Items.Count > 0)
{
TreeViewItem solutionItem = (TreeViewItem)treeViewSolutionFiles.Items[0];
if (solutionItem.Tag is SolutionVM)
UpdateWebsiteProjectReferences(solutionItem);
}
}
private void UpdateWebsiteProjectReferences(TreeViewItem parentItem)
{
foreach (TreeViewItem childItem in parentItem.Items)
{
if (childItem.Tag is ProjectVM)
{
ProjectVM projectVM = (ProjectVM)childItem.Tag;
if (projectVM.Project.IsWebSiteProject)
{
TreeViewItem references = FindReferencesFolder(childItem);
foreach (ReferenceVM referenceVM in projectVM.References)
{
if (referenceVM is ProjectReferenceVM)
{
Reference reference = referenceVM.Reference;
RemoveTreeItem(references, reference);
AddTreeItem(references, reference.ShortName, reference);
}
}
TreeViewItem referencesItem = (TreeViewItem)childItem.Items[0];
if ((string)referencesItem.Header == Project.ReferencesFolder)
{
foreach (TreeViewItem referenceItem in referencesItem.Items)
{
ProjectReferenceVM projectReferenceVM = referenceItem.Tag as ProjectReferenceVM;
if (projectReferenceVM != null)
referenceItem.Header = projectReferenceVM.ProjectReference.ShortName;
}
}
}
}
else if (childItem.Tag == null)
UpdateWebsiteProjectReferences(childItem);
}
}
public Solution CreateMiscellaneousSolution()
{
// Create the miscellaneous files solution
if (_miscellaneousFilesSolution == null)
_miscellaneousFilesSolution = new Solution(MiscellaneousFiles) { IsGenerated = true };
if (SolutionVM == null)
SolutionVM = new SolutionVM(_miscellaneousFilesSolution);
return _miscellaneousFilesSolution;
}
public Project CreateMiscellaneousProject(string name, params Reference[] references)
{
// Create the miscellaneous files solution
Solution solution = CreateMiscellaneousSolution();
// Create a miscellaneous project file, and add typical references
Project project = solution.CreateProject(name);
project.IsGenerated = true;
project.AddDefaultAssemblyReferences();
project.AddAssemblyReference("System.Configuration");
project.AddAssemblyReference("PresentationCore");
project.AddAssemblyReference("PresentationFramework");
project.AddAssemblyReference("UIAutomationProvider");
project.AddAssemblyReference("WindowsBase");
foreach (Reference reference in references)
project.References.Add(reference);
project.LoadReferencedAssembliesAndTypes(true);
return project;
}
public Project GetMiscellaneousProject(string name, params Reference[] references)
{
Project project;
// Create the miscellaneous files project and tree item if it doesn't exist yet
if (_miscellaneousFilesTreeItem == null)
{
project = CreateMiscellaneousProject(MiscellaneousFiles);
_miscellaneousFilesTreeItem = AddTreeItem(treeViewSolutionFiles, MiscellaneousFiles, project);
_miscellaneousFilesTreeItem.IsExpanded = true;
}
// Return either the miscellaneous files project or a separate hidden individual project
if (name == MiscellaneousFiles)
project = ((ProjectVM)_miscellaneousFilesTreeItem.Tag).Project;
else
project = CreateMiscellaneousProject(MiscellaneousFiles + " " + name, references);
return project;
}
public bool IsMiscellaneousProject(Project project)
{
return (_miscellaneousFilesTreeItem != null && project == ((ProjectVM)_miscellaneousFilesTreeItem.Tag).Project);
}
public TreeViewItem AddMiscellaneousFile(string fileName, Project project)
{
// Use any specified project, otherwise always create separate projects for miscellaneous files to prevent conflicts
if (project == null)
project = GetMiscellaneousProject(Path.GetFileName(fileName));
// Create a CodeUnit for the file, and add it to the tree
CodeUnit codeUnit = new CodeUnit(fileName, project);
return AddTreeItem(_miscellaneousFilesTreeItem, codeUnit.Name, codeUnit);
}
public TreeViewItem AddMiscellaneousCode(string name, CodeObject codeObject, bool open)
{
// If miscellaneous code is added without a project, use the shared one
if (codeObject.Parent == null)
codeObject.Parent = GetMiscellaneousProject(MiscellaneousFiles);
// Add the in-memory code fragment to the tree
TreeViewItem treeViewItem = AddTreeItem(_miscellaneousFilesTreeItem, name, codeObject);
if (open)
{
SelectTreeItem(treeViewItem);
OpenItem(treeViewItem, false);
}
return treeViewItem;
}
protected static TreeViewItem AddTreeItem(ItemsControl itemsControl, string name, CodeObject codeObject)
{
// Use dummy empty-string tooltips for CodeObjects - they will be generated when opened
string toolTip = codeObject != null ? "" : null;
// Insert at the bottom if it's the special Miscellaneous Files project
if (name == MiscellaneousFiles)
return InsertTreeItem(itemsControl, itemsControl.Items.Count, name, codeObject, toolTip);
int index;
FindTreeItem(itemsControl, name, codeObject, out index);
return InsertTreeItem(itemsControl, index, name, codeObject, toolTip);
}
protected static TreeViewItem InsertTreeItem(ItemsControl itemsControl, int index, string name, CodeObject codeObject, object toolTip)
{
TreeViewItem treeViewItem = new TreeViewItem { Header = name, ToolTip = toolTip, Tag = CodeObjectVM.CreateVM(codeObject, null) };
treeViewItem.ToolTipOpening += treeViewSolutionFiles_ToolTipOpening;
itemsControl.Items.Insert(index, treeViewItem);
SetTreeItemColorBasedOnMessages(treeViewItem);
return treeViewItem;
}
protected static void UpdateTreeItem(ItemsControl itemsControl, string name, CodeObject codeObject)
{
int index;
TreeViewItem treeViewItem = FindTreeItem(itemsControl, name, codeObject, out index);
if (treeViewItem != null)
{
SetTreeItemColorBasedOnMessages(treeViewItem);
CodeObjectVM codeObjectVM = treeViewItem.Tag as CodeObjectVM;
if (codeObjectVM != null)
codeObjectVM.UpdateAnnotations();
}
}
/// <summary>
/// Find a tree item by name using a binary search (assumes the items are sorted alphabetically).
/// </summary>
/// <param name="itemsControl">The items control to be searched.</param>
/// <param name="name">The name to be found.</param>
/// <param name="codeObject">The associated code object (used to search for folders vs files at the root level of a project).</param>
/// <param name="index">The index of the matched node, or the insert location of the missing node.</param>
/// <returns>The TreeViewItem if found, otherwise null.</returns>
protected static TreeViewItem FindTreeItem(ItemsControl itemsControl, string name, CodeObject codeObject, out int index)
{
index = 0;
ItemCollection items = itemsControl.Items;
int low = 0, high = items.Count - 1;
// Do some special sort handling if we're finding at the root level of a Project
if (itemsControl.Tag is ProjectVM)
{
// Special handling for the References and Properties folders, which always "stick" at the top
if (codeObject == null)
{
if (name == Project.ReferencesFolder)
return ((high >= 0 && (string)((TreeViewItem)items[0]).Header == Project.ReferencesFolder) ? (TreeViewItem)items[0] : null);
if (name == Project.PropertiesFolder)
{
index = (items.Count > 0 && (string)((TreeViewItem)items[0]).Header == Project.ReferencesFolder ? 1 : 0);
return ((index <= high && (string)((TreeViewItem)items[index]).Header == Project.PropertiesFolder) ? (TreeViewItem)items[index] : null);
}
}
if (high >= 0 && (string)((TreeViewItem)items[0]).Header == Project.ReferencesFolder)
++low;
if (low <= high && (string)((TreeViewItem)items[low]).Header == Project.PropertiesFolder)
++low;
if (low > high)
{
index = low;
return null;
}
}
// Special handling for folders vs files (all folders appear first)
if (codeObject == null)
{
// If we're finding a folder, ignore any files at the end of the collection
while (high >= low && ((TreeViewItem)items[high]).Tag is CodeObjectVM)
--high;
if (high < low)
{
index = low;
return null;
}
}
else
{
// If we're finding a file, ignore any folders at the beginning of the collection
while (low <= high && ((TreeViewItem)items[low]).Tag == null)
++low;
if (low > high)
{
index = low;
return null;
}
}
// Do a binary search for the item
TreeViewItem found = null;
while (high >= low)
{
index = low + (high - low) / 2;
int result = string.Compare(name, (string)((TreeViewItem)items[index]).Header, true);
if (result < 0)
high = index - 1;
else if (result > 0)
{
low = index + 1;
if (low > high)
{
index = low;
break;
}
}
else
{
found = (TreeViewItem)items[index];
break;
}
}
return found;
}
protected TreeViewItem FindProjectItem(Project project)
{
if (treeViewSolutionFiles.Items.Count > 0)
{
// Look at the root node
TreeViewItem rootNode = (TreeViewItem)treeViewSolutionFiles.Items[0];
if (rootNode.Tag is ProjectVM)
{
if (((ProjectVM)rootNode.Tag).Project == project)
return rootNode;
}
else
return FindProjectInTree(rootNode, project);
}
return null;
}
protected static TreeViewItem FindProjectInTree(TreeViewItem item, Project project)
{
// Look in the current tree node
int index;
TreeViewItem projectItem = FindTreeItem(item, project.GetDescription(), project, out index);
if (projectItem != null)
return projectItem;
// Recursively look in any folders
foreach (TreeViewItem child in item.Items)
{
if (child.Tag == null)
{
projectItem = FindProjectInTree(child, project);
if (projectItem != null)
return projectItem;
}
}
return null;
}
protected TreeViewItem FindReferencesFolder(TreeViewItem projectItem)
{
int index;
TreeViewItem references = FindTreeItem(projectItem, Project.ReferencesFolder, null, out index);
if (references == null)
references = InsertTreeItem(projectItem, 0, Project.ReferencesFolder, null, "");
return references;
}
protected static TreeViewItem FindFileNameInTree(ItemCollection items, string fileName)
{
foreach (TreeViewItem child in items)
{
if (child.Tag is CodeUnitVM && ((CodeUnitVM)child.Tag).CodeUnit.FileName == fileName)
return child;
if (child.HasItems)
{
TreeViewItem found = FindFileNameInTree(child.Items, fileName);
if (found != null)
return found;
}
}
return null;
}
protected static TreeViewItem FindCodeObjectInTree<T>(ItemCollection items, T codeObject) where T : CodeObject
{
foreach (TreeViewItem child in items)
{
if (child.Tag is CodeObjectVM && ((CodeObjectVM)child.Tag).CodeObject == codeObject)
return child;
if (child.HasItems)
{
TreeViewItem found = FindCodeObjectInTree(child.Items, codeObject);
if (found != null)
return found;
}
}
return null;
}
protected static void RemoveTreeItem(ItemsControl itemControl, CodeObject codeObject)
{
ItemCollection items = itemControl.Items;
for (int i = 0; i < items.Count; ++i)
{
object tag = ((TreeViewItem)items[i]).Tag;
if (tag is CodeObjectVM && ((CodeObjectVM)tag).CodeObject == codeObject)
{
items.RemoveAt(i);
break;
}
}
}
public static void SetTreeItemColorBasedOnMessages(TreeViewItem treeViewItem)
{
CodeObjectVM codeObjectVM = treeViewItem.Tag as CodeObjectVM;
if (codeObjectVM != null)
{
treeViewItem.Foreground = codeObjectVM.GetMessageBrush(Brushes.Black, CodeObjectVM.RenderFlags.None);
if (treeViewItem.Foreground != Brushes.Black)
treeViewItem.BringIntoView();
}
}
protected void SelectTreeItem(TreeViewItem treeViewItem)
{
treeViewItem.IsSelected = true;
treeViewItem.BringIntoView();
treeViewSolutionFiles.UpdateLayout();
treeViewItem.BringIntoView();
}
#region /* SOLUTION TREE EVENTS */
protected static void treeViewSolutionFiles_ToolTipOpening(object sender, ToolTipEventArgs e)
{
TreeViewItem item = sender as TreeViewItem;
if (item != null)
{
// Generate tooltips dynamically for CodeObjectVMs in the solution tree
if (item.Tag is CodeObjectVM)
{
StackPanel panel = new StackPanel();
CodeRenderer renderer = new CodeRenderer(panel);
CodeObjectVM codeObjectVM = (CodeObjectVM)item.Tag;
// Create a new VM to render the tooltip, because the height/width will differ for a description
CodeObjectVM.CreateVM(codeObjectVM.CodeObject, codeObjectVM.ParentVM, true).RenderToolTip(renderer);
item.ToolTip = panel;
}
else if ((string)item.Header == Project.ReferencesFolder && item.Parent is TreeViewItem && ((TreeViewItem)item.Parent).Tag is ProjectVM)
item.ToolTip = item.Items.Count + " reference" + (item.Items.Count != 1 ? "s" : "");
}
}
protected void treeViewSolutionFiles_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
ContextMenu contextMenu = null;
TreeViewItem item = (TreeViewItem)treeViewSolutionFiles.SelectedItem;
if (item != null)
{
object tag = item.Tag;
contextMenu = new ContextMenu();
// Add context specific menu items here
if (tag is SolutionVM)
{
WPFUtil.AddMenuItemCommand(contextMenu, CloseSolutionCommand, this);
WPFUtil.AddSeparator(contextMenu);
}
// Add general menu items
bool isRenderable = (tag is CodeObjectVM && ((CodeObjectVM)tag).CodeObject.IsRenderable);
bool hasChildren = item.HasItems;
bool hasRenderableChildren = (hasChildren && !(item.Header is string && (string)item.Header == Project.ReferencesFolder
&& item.Parent is TreeViewItem && ((TreeViewItem)item.Parent).Tag is ProjectVM));
if (hasRenderableChildren || isRenderable)
{
WPFUtil.AddMenuItem(contextMenu, hasRenderableChildren ? "Open All" : "Open", miOpen_Click, !_busy);
WPFUtil.AddMenuItem(contextMenu, hasRenderableChildren ? "Resolve All" : "Resolve", miResolve_Click, !_busy);
WPFUtil.AddMenuItem(contextMenu, hasRenderableChildren ? "Save All" : "Save", miSave_Click, !_busy);
WPFUtil.AddMenuItem(contextMenu, hasRenderableChildren ? "Diff All" : "Diff", miDiff_Click, !_busy);
}
if (hasChildren)
{
if (contextMenu.Items.Count > 0)
WPFUtil.AddSeparator(contextMenu);
WPFUtil.AddMenuItem(contextMenu, "Expand All", miExpandAll_Click, !_busy);
WPFUtil.AddMenuItem(contextMenu, "Collapse All", miCollapseAll_Click, !_busy);
}
if (contextMenu.Items.Count == 0)
contextMenu = null;
else
contextMenu.IsOpen = true;
}
((FrameworkElement)sender).ContextMenu = contextMenu;
e.Handled = true;
}
protected void treeViewSolutionFiles_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
// Give focus to an item if right-clicked
TreeViewItem item = sender as TreeViewItem;
if (item != null)
{
item.Focus();
e.Handled = true;
}
}
protected static void ExecuteActionOnTree(TreeViewItem treeViewItem, Action<TreeViewItem, object, Action<TreeViewItem>> action, object parameter,
string actionName, Action<TreeViewItem, object> complete, object completeParameter)
{
if (treeViewItem == null)
return;
TreeAction treeAction = new TreeAction(treeViewItem, action, parameter, actionName, TreeActionComplete, complete, completeParameter);
treeAction.Start();
}
protected static void TreeActionComplete(TreeViewItem treeViewItem, object parameter1, object parameter2)
{
if (parameter1 != null)
((Action<TreeViewItem, object>)parameter1)(treeViewItem, parameter2);
}
protected TabItem OpenItem(TreeViewItem treeViewItem, bool resolve)
{
TabItem tabItem = null;
if (treeViewItem != null)
{
object obj = treeViewItem.Tag;
if (obj is CodeObjectVM)
{
CodeObjectVM codeObjectVM = (CodeObjectVM)obj;
if (codeObjectVM.CodeObject.IsRenderable)
{
if (resolve)
ResolveItem(treeViewItem, null);
tabItem = AddTab((string)treeViewItem.Header, codeObjectVM, treeViewItem);
}
}
}
return tabItem;
}
protected void OpenItemAction(TreeViewItem treeViewItem, object parameter, Action<TreeViewItem> completed)
{
OpenItem(treeViewItem, false);
completed(treeViewItem);
}
protected void miOpen_Click(object sender, RoutedEventArgs e)
{
ExecuteActionOnTree(treeViewSolutionFiles.SelectedItem as TreeViewItem, OpenItemAction, null, null, null, null);
}
protected void treeViewSolutionFiles_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
// Double-clicking on a file opens it
OpenItem(treeViewSolutionFiles.SelectedItem as TreeViewItem, false);
}
protected void ParseItemAction(TreeViewItem treeViewItem, object parameter, Action<TreeViewItem> completed)
{
// Only parse CodeUnits here (solutions, projects, and references are parsed when loaded)
object obj = treeViewItem.Tag;
if (obj is CodeUnitVM)
WPFUtil.ExecuteActionOnThread(this, ParseItemThread, ((CodeUnitVM)obj).CodeUnit, completed, treeViewItem);
}
private static void ParseItemThread(CodeUnit codeUnit)
{
codeUnit.Parse();
}
protected void ParseComplete(TreeViewItem treeViewItem, object parameter)
{
if (Unrecognized.Count > 0)
Log.WriteLine("UNRECOGNIZED OBJECT COUNT: " + Unrecognized.Count);
ShowProgressBar(null);
if (parameter != null)
((Action<TreeViewItem>)parameter)(treeViewItem);
else
Busy = false;
}
public void ParseItem(CodeUnit codeUnit)
{
Busy = true;
ShowProgressBar("Parsing");
WPFUtil.ScrollToTop(listViewMessages);
Unrecognized.Count = 0;
codeUnit.Parse();
codeUnit.Resolve();
ParseComplete(null, null);
}
protected void ResolveItemAction(TreeViewItem treeViewItem, object parameter, Action<TreeViewItem> completed)
{
// Only resolve CodeUnits here (solutions, projects, and references are resolved when loaded)
object obj = treeViewItem.Tag;
if (obj is CodeUnitVM)
WPFUtil.ExecuteActionOnThread(this, ResolveItemThread, ((CodeUnitVM)obj).CodeUnit, completed, treeViewItem);
}
private static void ResolveItemThread(CodeUnit codeUnit)
{
codeUnit.Resolve();
}
protected void ResolveItem(TreeViewItem treeViewItem, Action<TreeViewItem> complete)
{
Busy = true;
ShowProgressBar("Resolving");
WPFUtil.ScrollToTop(listViewMessages);
Resolver.ResolveAttempts = Resolver.ResolveFailures = 0;
ExecuteActionOnTree(treeViewItem, ResolveItemAction, null, "Resolved", ResolveComplete, complete);
}
protected void ResolveComplete(TreeViewItem treeViewItem, object parameter)
{
Log.DetailWriteLine(string.Format("ResolveAttempts = {0:N0} / ResolveFailures = {1:N0}", Resolver.ResolveAttempts, Resolver.ResolveFailures));
ShowProgressBar(null);
if (parameter != null)
((Action<TreeViewItem>)parameter)(treeViewItem);
else
{
DisplayResolveResults();
Busy = false;
}
}
protected void DisplayResolveResults()
{
// Set the output tab focus based on any messages
bool hasMessages = (SolutionVM != null && SolutionVM.CodeAnnotations != null && SolutionVM.CodeAnnotations.Count > 0);
if (hasMessages)
tabItemMessages.IsSelected = true;
else
tabItemOutput.IsSelected = true;
}
protected void miResolve_Click(object sender, RoutedEventArgs e)
{
ResolveItem(treeViewSolutionFiles.SelectedItem as TreeViewItem, null);
}
protected void SaveItemAction(TreeViewItem treeViewItem, object parameter, Action<TreeViewItem> completed)
{
object obj = treeViewItem.Tag;
if (obj is CodeObjectVM)
{
CodeObject codeObject = ((CodeObjectVM)obj).CodeObject;
if (codeObject is IFile)
((IFile)codeObject).Save();
}
completed(treeViewItem);
}
protected void miSave_Click(object sender, RoutedEventArgs e)
{
Busy = true;
ShowProgressBar("Saving");
ExecuteActionOnTree(treeViewSolutionFiles.SelectedItem as TreeViewItem, SaveItemAction, null, "Saved", SaveComplete, null);
}
protected void SaveComplete(TreeViewItem treeViewItem, object parameter)
{
ShowProgressBar(null);
Busy = false;
}
protected void DiffItemAction(TreeViewItem treeViewItem, object parameter, Action<TreeViewItem> completed)
{
object obj = treeViewItem.Tag;
if (obj is CodeObjectVM)
{
CodeObject codeObject = ((CodeObjectVM)obj).CodeObject;
// Process Solutions, Projects, and files, but ignore generated projects (Miscellaneous Files)
if (codeObject is IFile && !(codeObject is Project && IsMiscellaneousProject((Project)codeObject)))
{
string diffProgram = System.Configuration.ConfigurationManager.AppSettings["DiffProgram"];
if (!string.IsNullOrEmpty(diffProgram))
{
// Save the file into a TEMP file and diff with the original
IFile file = (IFile)codeObject;
string tempFile = Path.GetTempFileName();
file.SaveAs(tempFile);
Process.Start(diffProgram, "\"" + file.FileName + "\" \"" + tempFile + "\"");
}
}
}
completed(treeViewItem);
}
protected void miDiff_Click(object sender, RoutedEventArgs e)
{
Busy = true;
ExecuteActionOnTree(treeViewSolutionFiles.SelectedItem as TreeViewItem, DiffItemAction, null, null, DiffComplete, null);
}
protected void DiffComplete(TreeViewItem treeViewItem, object parameter)
{
ShowProgressBar(null);
Busy = false;
}
public static string GetDescriptionAttributeValue(MemberInfo thisMemberInfo)
{
DescriptionAttribute[] attributes = (DescriptionAttribute[])thisMemberInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
return (attributes.Length > 0 ? attributes[0].Description : null);
}
protected void miExpandAll_Click(object sender, RoutedEventArgs e)
{
ExpandCollapseAll(true);
}
protected void miCollapseAll_Click(object sender, RoutedEventArgs e)
{
ExpandCollapseAll(false);
}
protected void ExpandCollapseAll(bool expand)
{
TreeViewItem item = treeViewSolutionFiles.SelectedItem as TreeViewItem;
if (item != null && item.HasItems)
ExpandCollapseAll(item, expand);
}
protected void ExpandCollapseAll(TreeViewItem treeViewItem, bool expand)
{
treeViewItem.IsExpanded = expand;
foreach (TreeViewItem childItem in treeViewItem.Items)
{
if (childItem.HasItems)
ExpandCollapseAll(childItem, expand);
}
}
protected void ExpandCollapseAllProjects(bool expand)
{
if (treeViewSolutionFiles.Items.Count > 0)
{
// Look at the root node
TreeViewItem rootNode = (TreeViewItem)treeViewSolutionFiles.Items[0];
if (rootNode.Tag is ProjectVM)
rootNode.IsExpanded = expand;
else
{
// Look at all child projects in the solution
foreach (TreeViewItem childItem in rootNode.Items)
{
if (childItem.Tag is ProjectVM)
childItem.IsExpanded = expand;
else if (childItem.Tag == null)
{
foreach (TreeViewItem grandChildItem in childItem.Items)
{
if (grandChildItem.Tag is ProjectVM)
grandChildItem.IsExpanded = expand;
}
}
}
}
}
}
#endregion
#endregion
#region /* ACTIVE CONFIGURATION/PLATFORM */
private void SetConfigurationPlatform()
{
buttonActiveConfigurationPlatform.Content = (_solutionVM != null ? _solutionVM.Solution.ActiveConfiguration + " - " + _solutionVM.Solution.ActivePlatform : "");
buttonActiveConfigurationPlatform.IsEnabled = (_solutionVM != null);
}
#endregion
#region /* FONT SELECTION */
protected void comboBoxFonts_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string fontName = comboBoxFonts.SelectedItem.ToString();
if (CodeRenderer.CodeFontFamily.Source != fontName)
{
CodeRenderer.CodeFontFamily = new FontFamily(comboBoxFonts.SelectedItem.ToString());
ReRenderCurrentTab();
}
}
protected void textBoxFontSize_TextChanged(object sender, TextChangedEventArgs e)
{
double fontSize = StringUtil.ParseDouble(textBoxFontSize.Text);
if (CodeRenderer.CodeFontSize != fontSize)
{
CodeRenderer.CodeFontSize = fontSize;
ReRenderCurrentTab();
}
}
#endregion
#region /* OUTPUT WINDOWS */
protected void AddLogEntry(string message, string toolTip)
{
if (listViewOutput.Dispatcher.CheckAccess())
AddLogEntryLocal(message, toolTip);
else
listViewOutput.Dispatcher.Invoke(new Action<string, string>(AddLogEntryLocal), message, toolTip);
}
protected void AddLogEntryLocal(string message, string toolTip)
{
// Add the log entry to the list view
ListViewItem entry = new ListViewItem { Content = message, ToolTip = toolTip };
//entry.new String[] { DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.ffff"), message });
listViewOutput.Items.Add(entry);
while (listViewOutput.Items.Count > 9999)
listViewOutput.Items.RemoveAt(0);
//if (!menuViewFreeze.Checked)
WPFUtil.ShowLastItem(listViewOutput);
}
protected void tabControlOutput_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.Source == tabControlOutput)
{
// Select the last line in the Output window if its tab was brought to the front
if (tabItemOutput.IsSelected && listViewOutput.Items.Count > 0)
WPFUtil.ShowLastItem(listViewOutput);
}
}
protected void BindMessages()
{
listViewMessages.ItemsSource = (_solutionVM != null ? _solutionVM.CodeAnnotations : null);
}
protected void listViewMessages_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
// Double-clicking on an error navigates to the error location
CodeAnnotation error = (CodeAnnotation)listViewMessages.SelectedItem;
if (error != null)
{
CodeObject errorParent = error.Annotation.Parent;
if (errorParent is Solution)
{
// Handle errors on the solution
SelectTreeItem((TreeViewItem)treeViewSolutionFiles.Items[0]);
}
else if (errorParent is Project)
{
// Handle errors on a project
TreeViewItem projectItem = FindCodeObjectInTree(treeViewSolutionFiles.Items, (Project)errorParent);
if (projectItem != null)
SelectTreeItem(projectItem);
}
else if (errorParent is Reference)
{
// Handle errors on project references
Reference reference = (Reference)errorParent;
TreeViewItem projectItem = FindCodeObjectInTree(treeViewSolutionFiles.Items, (Project)reference.Parent);
if (projectItem != null)
{
// Find and select the reference
TreeViewItem referencesItem = (TreeViewItem)projectItem.Items[0];
if (referencesItem != null)
{
foreach (TreeViewItem referenceItem in referencesItem.Items)
{
if (((ReferenceVM)referenceItem.Tag).Reference == reference)
SelectTreeItem(referenceItem);
}
}
}
}
else if (error.CodeUnit != null)
{
// Open the CodeUnit in a tab (or find any existing tab), and select the CodeObject
Annotation annotation = error.Annotation;
SelectCodeObject(error.CodeUnit, annotation is Message ? annotation.Parent : annotation);
}
}
}
protected void SelectCodeObject(CodeUnit codeUnit, CodeObject codeObject)
{
// Open the CodeUnit (or find the tab if already open). Note that the same physical file might be included
// in more than one project, in which case different CodeUnit objects will exist so that different compiler
// directives can product different parse trees.
TabItem tabItem = OpenCodeUnit(codeUnit, false);
// Select the UI object corresponding to the code object
if (tabItem != null)
((CodeWindow)tabItem.Content).SelectCodeObject(codeObject);
}
/// <summary>
/// Select the specified <see cref="CodeObject"/>, opening the containing <see cref="CodeUnit"/> first if necessary.
/// </summary>
/// <param name="codeObject"></param>
public void SelectCodeObject(CodeObject codeObject)
{
if (codeObject != null)
{
CodeUnit codeUnit = codeObject.FindParent<CodeUnit>();
if (codeUnit != null)
SelectCodeObject(codeUnit, codeObject);
}
}
#endregion
#region /* STATUS BAR */
protected void ShowProgressBar(string status)
{
textBlockStatus.Text = status;
progressBarStatus.Visibility = (status != null ? Visibility.Visible : Visibility.Hidden);
}
protected void UpdateMessageCount(int messageCount)
{
tabItemMessages.Header = new TextBlock { Text = "Messages (" + messageCount + ")" };
_messageCount = messageCount;
}
protected void UpdateHeapUsage(int heapUsage)
{
btnHeapSize.Content = heapUsage + " MB";
_heapUsage = heapUsage;
if (heapUsage > _maxHeapUsage)
{
_maxHeapUsage = heapUsage;
btnHeapSize.ToolTip = "Max: " + _maxHeapUsage;
}
}
private static int LowerFrequency;
protected void OnUpdateStatus(object sender, EventArgs e)
{
// Update the message count on the Messages tab
ObservableCollectionMT<CodeAnnotation> messages = listViewMessages.ItemsSource as ObservableCollectionMT<CodeAnnotation>;
int messageCount = (messages != null ? messages.Count : 0);
if (messageCount != _messageCount)
UpdateMessageCount(messageCount);
// Update the heap size
if (++LowerFrequency >= 4)
{
btnHeapSize_Click(null, null);
LowerFrequency = 0;
}
}
protected void btnHeapSize_Click(object sender, RoutedEventArgs e)
{
int heapUsage = (int)(GC.GetTotalMemory(false) / (1024 * 1024));
if (heapUsage != _heapUsage)
UpdateHeapUsage(heapUsage);
}
#endregion
#region /* MAINWINDOW EVENTS */
private void Window_MouseMove(object sender, MouseEventArgs e)
{
ResetTabToolTipTimer();
}
private void Window_Deactivated(object sender, EventArgs e)
{
CloseTabToolTip();
}
#endregion
#region /* COMMANDS */
public static readonly RoutedCommand CloseSolutionCommand = new RoutedCommand("Close Solution", typeof(MainWindow));
public static readonly RoutedCommand SaveAllCommand = new RoutedCommand("Save All", typeof(MainWindow));
public static readonly RoutedCommand ConfigurationManagerCommand = new RoutedCommand("Configuration Manager", typeof(MainWindow));
public static readonly RoutedCommand AboutCommand = new RoutedCommand("About", typeof(MainWindow));
public void BindCommands(Window window)
{
WPFUtil.AddCommandBinding(window, CloseSolutionCommand, OnCloseSolution, CanCloseSolutionExecute);
WPFUtil.AddCommandBinding(window, SaveAllCommand, OnFileSaveAll, CanSaveAllExecute);
WPFUtil.AddCommandBinding(window, ConfigurationManagerCommand, OnConfigurationManager, CanConfigurationManagerExecute);
WPFUtil.AddCommandBinding(window, AboutCommand, OnAbout, CanAlwaysExecute);
}
#endregion
}
#region /* TREE ACTION CLASS */
/// <summary>
/// This class is used to execute an action on a tree of objects (such as files).
/// </summary>
public class TreeAction
{
public TreeViewItem StartTreeItem;
public Action<TreeViewItem, object, Action<TreeViewItem>> Action;
public object ActionParameter;
public string DoneDescription;
public Action<TreeViewItem, object, object> Complete;
public object CompleteParameter1;
public object CompleteParameter2;
protected int _busyCount;
protected Stopwatch _stopWatch;
public TreeAction(TreeViewItem startTreeItem, Action<TreeViewItem, object, Action<TreeViewItem>> action, object actionParameter,
string doneDescription, Action<TreeViewItem, object, object> complete, object completeParameter1, object completeParameter2)
{
StartTreeItem = startTreeItem;
Action = action;
ActionParameter = actionParameter;
DoneDescription = doneDescription;
Complete = complete;
CompleteParameter1 = completeParameter1;
CompleteParameter2 = completeParameter2;
}
public void Start()
{
_stopWatch = new Stopwatch();
_stopWatch.Start();
PerformActionOnTree(StartTreeItem);
}
protected void PerformActionOnTree(TreeViewItem treeViewItem)
{
if (treeViewItem != null)
{
object obj = treeViewItem.Tag;
// Queue the action for (same thread) execution when the UI is idle
if (obj is CodeObjectVM && !(obj is ProjectVM && ((MainWindow)Application.Current.MainWindow).IsMiscellaneousProject(((ProjectVM)obj).Project)))
{
++_busyCount;
treeViewItem.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action<TreeViewItem>(PerformAction), treeViewItem);
}
// Process children items
if (treeViewItem.HasItems)
{
if (obj is SolutionVM)
{
// Special handling for solutions in order to process projects in dependency order
Dictionary<Project, TreeViewItem> itemDictionary = Enumerable.ToDictionary<TreeViewItem, Project>(Enumerable.Cast<TreeViewItem>(treeViewItem.Items), delegate(TreeViewItem item) { return ((ProjectVM)item.Tag).Project; });
IEnumerable<TreeViewItem> itemsInDependentOrder = Enumerable.Select<Project, TreeViewItem>(((SolutionVM)obj).Solution.ProjectsInDependentOrder, delegate(Project project) { return itemDictionary[project]; });
foreach (TreeViewItem orderedItem in itemsInDependentOrder)
PerformActionOnTree(orderedItem);
}
else
{
foreach (TreeViewItem childItem in treeViewItem.Items)
PerformActionOnTree(childItem);
}
}
}
}
protected void PerformAction(TreeViewItem treeViewItem)
{
// Perform the action on the tree item
Action(treeViewItem, ActionParameter, ActionCompleted);
}
private void ActionCompleted(TreeViewItem treeViewItem)
{
MainWindow.SetTreeItemColorBasedOnMessages(treeViewItem);
if (_busyCount > 0)
{
// Decrement the busy count
--_busyCount;
// Check if we're done
if (_busyCount == 0)
{
// If a description was provided, then log the results
if (DoneDescription != null)
{
Log.WriteLine(DoneDescription + " '" + StartTreeItem.Header + "', elapsed time: " + _stopWatch.Elapsed.TotalSeconds.ToString("N3"));
_stopWatch.Reset();
}
// Call the completion routine
if (Complete != null)
Complete(StartTreeItem, CompleteParameter1, CompleteParameter2);
}
}
}
}
#endregion
}