Click here to Skip to main content
15,881,172 members
Articles / Programming Languages / C#

Resolving Symbolic References in a CodeDOM (Part 7)

Rate me:
Please Sign up or sign in to vote.
4.75/5 (6 votes)
2 Dec 2012CDDL12 min read 19.4K   509   14  
Resolving symbolic references in a CodeDOM.
// 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
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)


Written By
Software Developer (Senior)
United States United States
I've been writing software since the late 70's, currently focusing mainly on C#.NET. I also like to travel around the world, and I own a Chocolate Factory (sadly, none of my employees are oompa loompas).

Comments and Discussions