// Copyright (c) 2007, SlickEdit, Inc
// Email: info@slickedit.com
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE. IT CAN BE DISTRIBUTED FREE OF CHARGE AS LONG AS THIS HEADER
// REMAINS UNCHANGED.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Xml.Serialization;
namespace CPBrowser
{
/// <summary>
/// This user control implements the "Downloaded Projects" sidebar control. It
/// shows the directories in the download root directory, and all projects
/// and solution files contained in each respectively. This is where the user
/// can manage their downloads.
/// </summary>
public partial class ProjectTreeView : UserControl
{
// OnClose declaration
public delegate void ClosedDelegate(object sender, EventArgs e);
public event ClosedDelegate OnClosed;
// the reference back to the hosting CPBrowserCtl object
private CPBrowserCtl m_cpBrowserCtl = null;
/// <summary>
/// Constructor.
/// </summary>
public ProjectTreeView()
{
InitializeComponent();
}
/// <summary>
/// Initializes the instance of this control. The tree control is also
/// populated with the user's downloaded project information.
/// </summary>
/// <param name="cpBrowserCtl">The host for this control.</param>
public void Initialize(CPBrowserCtl cpBrowserCtl)
{
m_cpBrowserCtl = cpBrowserCtl;
PopulateTree();
}
/// <summary>
/// The download root directory is traversed and each sub-directory is added to
/// the tree.
/// </summary>
private void PopulateTree()
{
try
{
// disable rendering of the tree
ProjectTreeCtl.BeginUpdate();
// clear the tree
ProjectTreeCtl.Nodes.Clear();
// load any serialized project info
LoadProjects();
// get all sub-directories beneath the download root
string[] directoryNames = Directory.GetDirectories(m_cpBrowserCtl.ProjectLoader.DownloadsRoot);
foreach (string projectDir in directoryNames)
{
ProjectEntry projectEntry = new ProjectEntry(projectDir, "");
AddProjectToTree(projectEntry);
}
// initially, collapse everything
ProjectTreeCtl.CollapseAll();
}
catch { }
finally
{
// enable rendering of the tree
ProjectTreeCtl.EndUpdate();
}
}
/// <summary>
/// Deserializes the "projects.xml" file in the download root directory, if it
/// exists.
/// </summary>
private void LoadProjects()
{
FileStream fs = null;
StreamReader sr = null;
try
{
// create the XML serializer
XmlSerializer xmlSerializer = new XmlSerializer(typeof(List<ProjectEntry>));
string fileName = Path.Combine(m_cpBrowserCtl.ProjectLoader.DownloadsRoot, "projects.xml");
// make sure the file exists
if (File.Exists(fileName) == false)
return;
// create a Stream Reader to read the file contents
fs = new FileStream(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read);
sr = new StreamReader(fs);
// now deserialize it into a populated object
List<ProjectEntry> projects = (List<ProjectEntry>)xmlSerializer.Deserialize(sr);
foreach (ProjectEntry projectEntry in projects)
{
AddProjectToTree(projectEntry);
}
}
catch { }
finally
{
// free the resources
if (sr != null)
sr.Close();
if (fs != null)
fs.Close();
}
}
/// <summary>
/// Serializes the list of ProjectEntry objects to the "projects.xml" file in
/// the download root directory.
/// </summary>
private void SaveProjects()
{
StreamWriter sw = null;
try
{
// create the XML serializer
XmlSerializer xmlSerializer = new XmlSerializer(typeof(List<ProjectEntry>));
List<ProjectEntry> projects = new List<ProjectEntry>();
string fileName = Path.Combine(m_cpBrowserCtl.ProjectLoader.DownloadsRoot, "projects.xml");
// create the list of project entries
foreach (TreeNode node in ProjectTreeCtl.Nodes)
{
// all top level nodes should be project entry nodes
if (node.Tag is ProjectEntry)
projects.Add((ProjectEntry)node.Tag);
}
// create the StreamWriter object
sw = new StreamWriter(fileName);
// serialize the object
xmlSerializer.Serialize(sw, projects);
}
catch { }
finally
{
// free the resources
if (sw != null)
sw.Close();
}
}
/// <summary>
/// Takes a project directory and traverses it and its subdirectories for
/// project or solution files. The root directory is added to the tree view and
/// any found project or solution files are added as children.
/// </summary>
/// <param name="projectDir">The directory to add to the tree.</param>
public void AddProjectToTree(ProjectEntry project)
{
// make sure the project folder exists
if (Directory.Exists(project.RootFolder) == false)
return;
// get the innermost folder name
string name = Path.GetFileName(project.RootFolder);
TreeNode projectFolderNode = null;
// first, see if the project directory already exists in the tree. If so, just reuse
// that node and repopulate it
foreach (TreeNode node in ProjectTreeCtl.Nodes)
{
if (String.Compare(node.Text, name, true) == 0)
{
// we've found a matching project folder node
projectFolderNode = node;
// clean out it's child nodes, we'll repopulate them
projectFolderNode.Nodes.Clear();
break;
}
}
// if we didn't find a project folder node, then create one
if (projectFolderNode == null)
{
// add an entry to the tree for this
projectFolderNode = new TreeNode(name);
projectFolderNode.ImageKey = "Folder";
projectFolderNode.SelectedImageKey = "Folder";
// store the project entry object in the tag
projectFolderNode.Tag = project;
ProjectTreeCtl.Nodes.Add(projectFolderNode);
}
// now find any solutions in that directory (or subdirectories)
List<string> solutionFiles = new List<string>();
List<string> projectFiles = new List<string>();
List<string> exeFiles = new List<string>();
// now find any solutions or projects in that directory (or subdirectories)
m_cpBrowserCtl.ProjectLoader.GetSignificantFiles(project.RootFolder, ref solutionFiles, ref projectFiles, ref exeFiles);
foreach (string fileName in solutionFiles)
{
name = Path.GetFileName(fileName);
TreeNode projectSolutionNode = new TreeNode(name);
projectSolutionNode.ImageKey = GetImageKeyForFile(fileName);
projectSolutionNode.SelectedImageKey = GetImageKeyForFile(fileName);
// store the file name in the tag
projectSolutionNode.Tag = fileName;
projectFolderNode.Nodes.Add(projectSolutionNode);
}
foreach (string fileName in projectFiles)
{
name = Path.GetFileName(fileName);
TreeNode projectSolutionNode = new TreeNode(name);
projectSolutionNode.ImageKey = GetImageKeyForFile(fileName);
projectSolutionNode.SelectedImageKey = GetImageKeyForFile(fileName);
// store the file name in the tag
projectSolutionNode.Tag = fileName;
projectFolderNode.Nodes.Add(projectSolutionNode);
}
foreach (string fileName in exeFiles)
{
name = Path.GetFileName(fileName);
TreeNode exeSolutionNode = new TreeNode(name);
exeSolutionNode.ImageKey = GetImageKeyForFile(fileName);
exeSolutionNode.SelectedImageKey = GetImageKeyForFile(fileName);
// store the exe name in the tag
exeSolutionNode.Tag = fileName;
projectFolderNode.Nodes.Add(exeSolutionNode);
}
// select and expand the directory node
ProjectTreeCtl.SelectedNode = projectFolderNode;
projectFolderNode.Expand();
// save the projects
SaveProjects();
}
/// <summary>
/// gets the extension of the file and determines if the icon for that extension
/// has been looked up yet. If so, then that image is returned. If not, then
/// the Windows shell is queried for the icon for that file type. That icon is
/// added to the image list and is returned to the caller.
/// </summary>
/// <param name="filename">The file name to get the icon for.</param>
/// <returns>The image associated with that file type.</returns>
private string GetImageKeyForFile(string filename)
{
try
{
// if the image list already contains the icon for this extension, just return it
string extension = Path.GetExtension(filename);
if (ImageListCtl.Images.ContainsKey(extension) == false)
{
Win32.SHFILEINFO infoSmall = new Win32.SHFILEINFO(true);
int cbFileInfo = Marshal.SizeOf(infoSmall);
Win32.SHGFI flagsSmall = Win32.SHGFI.Icon | Win32.SHGFI.SmallIcon | Win32.SHGFI.UseFileAttributes;
int smallOK = Win32.SHGetFileInfo(filename, 256, out infoSmall, (uint)cbFileInfo, flagsSmall);
if (smallOK == 1)
{
Icon icoSmall = System.Drawing.Icon.FromHandle(infoSmall.hIcon);
ImageListCtl.Images.Add(extension, icoSmall);
}
}
return extension;
}
catch
{
// if we run into a problem getting the icon, just return the key for the generic one
return "File";
}
}
/// <summary>
/// Event handler to detect delete key presses. If the user hits delete and a
/// directory node is selected, then the directory is deleted. If a file is
/// selected, then that file is deleted.
/// </summary>
private void ProjectTreeCtl_KeyUp(object sender, KeyEventArgs e)
{
// check for a delete key
if (e.KeyCode == Keys.Delete)
DeleteSelectedNode();
}
/// <summary>
/// Deletes the currently selected node and any files in the corresponding
/// directory under My Code Project Downloads
/// </summary>
private void DeleteSelectedNode()
{
// get the selected node
TreeNode selectedNode = ProjectTreeCtl.SelectedNode;
if (selectedNode == null)
return;
string path = "";
// check if this is a project entry or not
if (selectedNode.Tag is ProjectEntry)
{
path = ((ProjectEntry)selectedNode.Tag).RootFolder;
// ask if the user wants to remove the directory
string msg = "Are you sure you want to delete the '" + selectedNode.Text + "' directory?";
DialogResult dr = MessageBox.Show(msg, "Code Project Browser", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (dr == DialogResult.Yes)
{
try
{
// delete the directory
Directory.Delete(path, true);
// remove the node from the tree
selectedNode.Remove();
}
catch (Exception ex)
{
msg = "Unable to delete the directory: " + ex.Message;
MessageBox.Show(msg, "Code Project Browser", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
else
{
path = (string)selectedNode.Tag;
// ask if the user wants to remove the file
string msg = "Are you sure you want to delete this file?";
DialogResult dr = MessageBox.Show(msg, "Code Project Browser", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (dr == DialogResult.Yes)
{
try
{
// delete the directory
File.Delete(path);
// remove the node from the tree
selectedNode.Remove();
}
catch (Exception ex)
{
msg = "Unable to delete the file: " + ex.Message;
MessageBox.Show(msg, "Code Project Browser", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
}
}
}
}
/// <summary>
/// Event handler for when the user clicks a directory, project or solution node
/// in the tree, to load it in Visual Studio.
/// </summary>
private void ProjectTreeCtl_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
{
// get the full path
string path = "";
// check if this is a project entry or not
if (e.Node.Tag is ProjectEntry)
{
path = ((ProjectEntry)e.Node.Tag).RootFolder;
m_cpBrowserCtl.ProjectLoader.LoadProjectOrSolutionFromDirectory(path);
}
else
{
path = (string)e.Node.Tag;
// if it's an executable, then launch it
if (m_cpBrowserCtl.ProjectLoader.IsExecutableFile(path) == true)
{
// try/catch here, Process.Start requires full trust, which the user might not have
try
{
Process.Start(path);
}
catch (Exception ex)
{
MessageBox.Show("Error running program: " + ex.Message, "Code Project Browser", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
else
{
m_cpBrowserCtl.ProjectLoader.OpenProjectOrSolution(path);
}
}
}
/// <summary>
/// Event handler for the CloseButton's Click event, which causes this sidebar
/// pane object to raise the OnClose event.
/// </summary>
private void CloseButtonCtl_Click(object sender, EventArgs e)
{
// raise the OnClose event
if (OnClosed != null)
OnClosed(this, new EventArgs());
}
/// <summary>
/// Event handler for the menu to delete an articles downloaded files.
/// </summary>
private void MenuDelete_Click(object sender, EventArgs e)
{
DeleteSelectedNode();
}
private void ContextMenuCtl_Opening(object sender, CancelEventArgs e)
{
// get the selected node
TreeNode selectedNode = ProjectTreeCtl.SelectedNode;
if (selectedNode == null)
return;
// check to see if the selected node has an article URL associated with it
bool menusVisible = false;
bool articleMenusEnabled = false;
if (selectedNode.Tag is ProjectEntry)
{
ProjectEntry projectEntry = (ProjectEntry)selectedNode.Tag;
articleMenusEnabled = (projectEntry.Url != "");
menusVisible = true;
}
// set the menu visibility
MenuGoToArticle.Visible = menusVisible;
MenuGoToArticle.Enabled = articleMenusEnabled;
MenuOpenSeperateBrowser.Visible = menusVisible;
MenuOpenSeperateBrowser.Enabled = articleMenusEnabled;
}
/// <summary>
/// This handler needs to be implemented to capture the selected node when right
/// clicking invokes the context menu. When the right click occurrs, the
/// previously selected node is still reported. This function sets the selected
/// node when the right click happens.
/// </summary>
private void ProjectTreeCtl_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
TreeNode clickedNode = ProjectTreeCtl.GetNodeAt(e.Location);
if (clickedNode != null)
ProjectTreeCtl.SelectedNode = clickedNode;
}
}
/// <summary>
/// Event handler to browse the project's article
/// </summary>
private void MenuGoToArticle_Click(object sender, EventArgs e)
{
// get the selected node
TreeNode selectedNode = ProjectTreeCtl.SelectedNode;
if (selectedNode == null)
return;
// check to see if the selected node has an article URL associated with it
if (selectedNode.Tag is ProjectEntry)
{
ProjectEntry projectEntry = (ProjectEntry)selectedNode.Tag;
if (projectEntry.Url != "")
m_cpBrowserCtl.WebBrowserCtl.Navigate(projectEntry.Url);
}
}
/// <summary>
/// Event handler to browse the project directory
/// </summary>
private void MenuBrowseDirectory_Click(object sender, EventArgs e)
{
// get the selected node
TreeNode selectedNode = ProjectTreeCtl.SelectedNode;
if (selectedNode == null)
return;
string path = "";
// check to see if the selected node has an article URL associated with it
if (selectedNode.Tag is ProjectEntry)
path = ((ProjectEntry)selectedNode.Tag).RootFolder;
else
path = Path.GetDirectoryName((string)selectedNode.Tag);
// spawn the file explorer to that folder
if (Directory.Exists(path) == true)
Process.Start(path);
}
/// <summary>
/// Event handler to open the article's page in a seperate browser
/// </summary>
private void MenuOpenSeperateBrowser_Click(object sender, EventArgs e)
{
// get the selected node
TreeNode selectedNode = ProjectTreeCtl.SelectedNode;
if (selectedNode == null)
return;
// check to see if the selected node has an article URL associated with it
if (selectedNode.Tag is ProjectEntry)
{
ProjectEntry projectEntry = (ProjectEntry)selectedNode.Tag;
if (projectEntry.Url != "")
{
// spawn the default browser to that URL
Process.Start(projectEntry.Url);
}
}
}
}
}