#region ================== Copyright

// *****************************************************************************
// 
//  Copyright 2004, Coder's Lab
//  All rights reserved. The software and associated documentation 
//  supplied hereunder are the proprietary information of Coder's Lab
//  and are supplied subject to licence terms.
//  
//
//  You can use this control freely in your projects, but let me know if you
//  are using it so I can add you to a list of references. 
//
//  Email: ludwig.stuyck@coders-lab.be
//  Home page: http://www.coders-lab.be
//
//  History
//		18/07/2004	
//			- Control creation	
//		24/07/2004	
//			- Implemented rubberband selection; also combination keys work: 
//			  ctrl, shift, ctrl+shift	
//		25/08/2004	
//			- Rubberband selection temporary removed due to scrolling problems. 
//			- Renamed TreeViewSelectionMode property to SelectionMode.
//			- Renamed SelectionModes enumeration to TreeViewSelectionMode.
//			- Added MultiSelectSameParent selection mode.
//			- Added keyboard functionality.
//			- Enhanced selection drawing.
//			- Added SelectionBackColor property.	
//		02/09/2004	
//			- When shift/ctrl was pressed, treeview scrolled to last selected 
//			  node. Fixed.
//			- Moved TreeViewSelectionMode outside the TreeView class.
//			- BeforeSelect was fired multiple times, AfterSelect was never 
//			  fired. Fixed.
//			- Collapsing/Expanding node changed selection. This does not happen 
//			  anymore, except if a node that has selected descendants is 
//			  collapsed; then all descendants are unselected and the collapsed 
//			  node becomes selected.
//			- If in the BeforeSelect event, e.Cancel is set to true, then node 
//			  will not be selected
//			- SHIFT selection sometimes didn’t behave correctly. Fixed.
//		04/09/2004	
//			- SelectedNodes is no longer an array of tree nodes, but a 
//			  SelectedNodesCollection
//			- In the AfterSelect event, the SelectedNodes contained two tree 
//			  nodes; the old one and the new one. Fixed.
//		05/09/2004	
//			- Added Home, End, PgUp and PgDwn keys functionality	
//		08/10/2004
//			- SelectedNodeCollection renamed to NodeCollection
//			- Fixes by GKM
//
//		18/8/2005
//			- Added events BeforeDeselect and AfterDeselect
//		09/5/2007
//			- Added an InvokeRequired check to Flashnode()
//		16/5/2007
//			- Gave the document a consistant format
//			- Created a new event 'SelectionsChanged'
// 
// *****************************************************************************

#endregion

#region ================== Namespaces

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

#endregion

namespace CodeImp.DoomBuilder.Controls
{
	#region TreeViewSelectionMode enumeration

	/// <summary>
	/// Selection mode for the treeview.	
	/// </summary>
	/// <remarks>
	/// The Selection mode determines how treeview nodes can be selected.
	/// </remarks>
	public enum TreeViewSelectionMode
	{
		/// <summary>
		/// Only one node can be selected at a time.
		/// </summary>
		SingleSelect,
		/// <summary>
		/// Multiple nodes can be selected at the same time without restriction.
		/// </summary>
		MultiSelect,
		/// <summary>
		/// Multiple nodes that belong to the same root branch can be selected at the same time.
		/// </summary>
		MultiSelectSameRootBranch,
		/// <summary>
		/// Multiple nodes that belong to the same level can be selected at the same time.
		/// </summary>
		MultiSelectSameLevel,
		/// <summary>
		/// Multiple nodes that belong to the same level and same root branch can be selected at the same time.
		/// </summary>
		MultiSelectSameLevelAndRootBranch,
		/// <summary>
		/// Only nodes that belong to the same direct parent can be selected at the same time.
		/// </summary>
		MultiSelectSameParent
	}

	#endregion

	#region Delegates

	/// <summary>
	/// Delegate used for tree node events.
	/// </summary>
	public delegate void TreeNodeEventHandler(TreeNode tn);

	#endregion

	/// <summary>
	/// The TreeView control is a regular treeview with multi-selection capability.
	/// </summary>
	[ToolboxItem(true)]
	public class MultiSelectTreeview : BufferedTreeView
	{
		public event TreeViewEventHandler AfterDeselect;
		public event TreeViewEventHandler BeforeDeselect;
		public event EventHandler SelectionsChanged;

		protected void OnAfterDeselect(TreeNode tn)
		{
			if(AfterDeselect != null)
			{
				AfterDeselect(this, new TreeViewEventArgs(tn));
			}
		}

		protected void OnBeforeDeselect(TreeNode tn)
		{
			if(BeforeDeselect != null)
			{
				BeforeDeselect(this, new TreeViewEventArgs(tn));
			}
		}

		protected void OnSelectionsChanged()
		{
			if(blnSelectionChanged)
				if(SelectionsChanged != null)
				{
					SelectionsChanged(this, new EventArgs());
				}
		}

		#region Private variables

		/// <summary> 
		/// Required designer variable.
		/// </summary>
		private Container components;

		/// <summary>
		/// Used to make sure that SelectedNode can only be used from within this class.
		/// </summary>
		private bool blnInternalCall;

		/// <summary>
		/// Hashtable that contains all selected nodes.
		/// </summary>
		private Hashtable htblSelectedNodes = new Hashtable();

		/// <summary>
		/// Track whether the total SelectedNodes changed across multiple operations
		/// for SelectionsChanged event
		/// </summary>
		private bool blnSelectionChanged;

		/// <summary>
		/// Hashtable to preserve Node's original colors (colors can be set on the TreeView, or individual nodes)
		/// (GKM)
		/// </summary>
		private Hashtable htblSelectedNodesOrigColors = new Hashtable();

		/// <summary>
		/// Keeps track of node that has to be pu in edit mode.
		/// </summary>
		private TreeNode tnNodeToStartEditOn;

		/// <summary>
		/// Remembers whether mouse click on a node was single or double click.
		/// </summary>
		private bool blnWasDoubleClick;

		/// <summary>
		/// Keeps track of most recent selected node.
		/// </summary>
		private TreeNode tnMostRecentSelectedNode;

		/// <summary>
		/// Keeps track of the selection mirror point; this is the last selected node without SHIFT key pressed.
		/// It is used as the mirror node during SHIFT selection.
		/// </summary>
		private TreeNode tnSelectionMirrorPoint;

		/// <summary>
		/// Keeps track of the number of mouse clicks.
		/// </summary>
		private int intMouseClicks;

		/// <summary>
		/// Selection mode.
		/// </summary>
		private TreeViewSelectionMode selectionMode = TreeViewSelectionMode.SingleSelect;

		/// <summary>
		/// Backcolor for selected nodes.
		/// </summary>
		private Color selectionBackColor = SystemColors.Highlight;

		/// <summary>
		/// Keeps track whether a node click has been handled by the mouse down event. This is almost always the
		/// case, except when a selected node has been clicked again. Then, it will not be handled in the mouse
		/// down event because we might want to drag the node and if that's the case, node should not go in edit 
		/// mode.
		/// </summary>
		private bool blnNodeProcessedOnMouseDown;

		/// <summary>
		/// Holds node that needs to be flashed.
		/// </summary>
		private TreeNode tnToFlash;

		/// <summary>
		/// Keeps track of the first selected node when selection has begun with the keyboard.
		/// </summary>
		private TreeNode tnKeysStartNode;

		#endregion

		#region SelectedNode, SelectionMode, SelectionBackColor, SelectedNodes + events

		/// <summary>
		/// This property is for internal use only. Use SelectedNodes instead.
		/// </summary>
		public new TreeNode SelectedNode
		{
			get
			{
				if(!blnInternalCall)
				{
					throw new NotSupportedException("Use SelectedNodes instead of SelectedNode.");
				}
				else
				{
					return base.SelectedNode;
				}
			}
			set
			{
				if(!blnInternalCall)
				{
					throw new NotSupportedException("Use SelectedNodes instead of SelectedNode.");
				}
				else
				{
					base.SelectedNode = value;
				}
			}
		}

		/// <summary>
		/// Gets/sets selection mode.
		/// </summary>
		public TreeViewSelectionMode SelectionMode
		{
			get
			{
				return selectionMode;
			}
			set
			{
				selectionMode = value;
			}
		}

		/// <summary>
		/// Gets/sets backcolor for selected nodes.
		/// </summary>
		public Color SelectionBackColor
		{
			get
			{
				return selectionBackColor;
			}
			set
			{
				selectionBackColor = value;
			}
		}

		/// <summary>
		/// Gets selected nodes.
		/// </summary>
		public NodesCollection SelectedNodes
		{
			get
			{
				// Create a SelectedNodesCollection to return, and add event handlers to catch actions on it
				NodesCollection selectedNodesCollection = new NodesCollection();
				foreach(TreeNode tn in htblSelectedNodes.Values)
				{
					selectedNodesCollection.Add(tn);
				}

				selectedNodesCollection.TreeNodeAdded += new TreeNodeEventHandler(SelectedNodes_TreeNodeAdded);
				selectedNodesCollection.TreeNodeInserted += new TreeNodeEventHandler(SelectedNodes_TreeNodeInserted);
				selectedNodesCollection.TreeNodeRemoved += new TreeNodeEventHandler(SelectedNodes_TreeNodeRemoved);
				selectedNodesCollection.SelectedNodesCleared += new EventHandler(SelectedNodes_SelectedNodesCleared);

				return selectedNodesCollection;
			}
		}

		/// <summary>
		/// Occurs when a tree node is added to the SelectedNodes collection.
		/// </summary>
		/// <param name="tn">Tree node that was added.</param>
		private void SelectedNodes_TreeNodeAdded(TreeNode tn)
		{
			blnSelectionChanged = false;

			SelectNode(tn, true, TreeViewAction.Unknown);
			//ProcessNodeRange(null, tn, new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X,  Cursor.Position.Y, 0), Keys.None, TreeViewAction.ByKeyboard, false); 

			OnSelectionsChanged();
		}

		/// <summary>
		/// Occurs when a tree node is inserted to the SelectedNodes collection.
		/// </summary>
		/// <param name="tn">tree node that was inserted.</param>
		private void SelectedNodes_TreeNodeInserted(TreeNode tn)
		{
			blnSelectionChanged = false;

			SelectNode(tn, true, TreeViewAction.Unknown);

			OnSelectionsChanged();
		}

		/// <summary>
		/// Occurs when a tree node is removed from the SelectedNodes collection.
		/// </summary>
		/// <param name="tn">Tree node that was removed.</param>
		private void SelectedNodes_TreeNodeRemoved(TreeNode tn)
		{
			blnSelectionChanged = false;

			SelectNode(tn, false, TreeViewAction.Unknown);

			OnSelectionsChanged();
		}

		/// <summary>
		/// Occurs when the SelectedNodes collection was cleared.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void SelectedNodes_SelectedNodesCleared(object sender, EventArgs e)
		{
			blnSelectionChanged = false;

			UnselectAllNodes(TreeViewAction.Unknown);

			OnSelectionsChanged();
		}

		#endregion

		#region Node selection methods

		/// <summary>
		/// Unselects all selected nodes.
		/// </summary>
		/// <param name="tva">Specifies the action that caused the selection change.</param>
		private void UnselectAllNodes(TreeViewAction tva)
		{
			UnselectAllNodesExceptNode(null, tva);
		}

		/// <summary>
		/// Unselects all selected nodes that don't belong to the specified level.
		/// </summary>
		/// <param name="level">Node level.</param>
		/// <param name="tva">Specifies the action that caused the selection change.</param>
		private void UnselectAllNodesNotBelongingToLevel(int level, TreeViewAction tva)
		{
			// First, build list of nodes that need to be unselected
			List<TreeNode> arrNodesToDeselect = new List<TreeNode>(); //mxd
			foreach(TreeNode selectedTreeNode in htblSelectedNodes.Values)
			{
				if(GetNodeLevel(selectedTreeNode) != level)
				{
					arrNodesToDeselect.Add(selectedTreeNode);
				}
			}

			// Do the actual unselect
			foreach(TreeNode tnToDeselect in arrNodesToDeselect)
			{
				SelectNode(tnToDeselect, false, tva);
			}
		}

		/// <summary>
		/// Unselects all selected nodes that don't belong directly to the specified parent.
		/// </summary>
		/// <param name="parent">Parent node.</param>
		/// <param name="tva">Specifies the action that caused the selection change.</param>
		private void UnselectAllNodesNotBelongingDirectlyToParent(TreeNode parent, TreeViewAction tva)
		{
			// First, build list of nodes that need to be unselected
			List<TreeNode> arrNodesToDeselect = new List<TreeNode>(); //mxd
			foreach(TreeNode selectedTreeNode in htblSelectedNodes.Values)
			{
				if(selectedTreeNode.Parent != parent)
				{
					arrNodesToDeselect.Add(selectedTreeNode);
				}
			}

			// Do the actual unselect
			foreach(TreeNode tnToDeselect in arrNodesToDeselect)
			{
				SelectNode(tnToDeselect, false, tva);
			}
		}

		/// <summary>
		/// Unselects all selected nodes that don't belong directly or indirectly to the specified parent.
		/// </summary>
		/// <param name="parent">Parent node.</param>
		/// <param name="tva">Specifies the action that caused the selection change.</param>
		private void UnselectAllNodesNotBelongingToParent(TreeNode parent, TreeViewAction tva)
		{
			// First, build list of nodes that need to be unselected
			List<TreeNode> arrNodesToDeselect = new List<TreeNode>(); //mxd
			foreach(TreeNode selectedTreeNode in htblSelectedNodes.Values)
			{
				if(!IsChildOf(selectedTreeNode, parent))
				{
					arrNodesToDeselect.Add(selectedTreeNode);
				}
			}

			// Do the actual unselect
			foreach(TreeNode tnToDeselect in arrNodesToDeselect)
			{
				SelectNode(tnToDeselect, false, tva);
			}
		}

		/// <summary>
		/// Unselects all selected nodes, except for the specified node which should not be touched.
		/// </summary>
		/// <param name="nodeKeepSelected">Node not to touch.</param>
		/// <param name="tva">Specifies the action that caused the selection change.</param>
		private void UnselectAllNodesExceptNode(TreeNode nodeKeepSelected, TreeViewAction tva)
		{
			// First, build list of nodes that need to be unselected
			List<TreeNode> arrNodesToDeselect = new List<TreeNode>(); //mxd
			foreach(TreeNode selectedTreeNode in htblSelectedNodes.Values)
			{
				if(nodeKeepSelected == null)
				{
					arrNodesToDeselect.Add(selectedTreeNode);
				}
				else if((nodeKeepSelected != null) && (selectedTreeNode != nodeKeepSelected))
				{
					arrNodesToDeselect.Add(selectedTreeNode);
				}
			}

			// Do the actual unselect
			foreach(TreeNode tnToDeselect in arrNodesToDeselect)
			{
				SelectNode(tnToDeselect, false, tva);
			}
		}

		/// <summary>
		/// occurs when a node is about to be selected.
		/// </summary>
		/// <param name="e">TreeViewCancelEventArgs.</param>
		protected override void OnBeforeSelect(TreeViewCancelEventArgs e)
		{
			// We don't want the base TreeView to handle the selection, because it can only handle single selection. 
			// Instead, we'll handle the selection ourselves by keeping track of the selected nodes and drawing the 
			// selection ourselves.
			e.Cancel = true;
		}

		/// <summary>
		/// Determines whether the specified node is selected or not.
		/// </summary>
		/// <param name="tn">Node to check.</param>
		/// <returns>True if specified node is selected, false if not.</returns>
		private bool IsNodeSelected(TreeNode tn)
		{
			if(tn != null)
				return htblSelectedNodes.ContainsKey(tn.GetHashCode());
			return false;
		}

		private void PreserveNodeColors(TreeNode tn)
		{
			if(tn == null) return;

			if(htblSelectedNodesOrigColors.ContainsKey(tn.GetHashCode()))
			{
				//				Color[] color = (Color[])htblSelectedNodesOrigColors[tn.GetHashCode()];
				//				color[0]=tn.BackColor;
				//				color[1]=tn.ForeColor;
			}
			else
			{
				htblSelectedNodesOrigColors.Add(tn.GetHashCode(), new[] { tn.BackColor, tn.ForeColor });
			}
		}

		/// <summary>
		/// (Un)selects the specified node.
		/// </summary>
		/// <param name="tn">Node to (un)select.</param>
		/// <param name="select">True to select node, false to unselect node.</param>
		/// <param name="tva">Specifies the action that caused the selection change.</param>
		/// <returns>True if node was selected, false if not.</returns>
		private bool SelectNode(TreeNode tn, bool select, TreeViewAction tva)
		{
			bool blnSelected = false;

			if(tn == null)
				return false;

			if(select)
			{
				// Only try to select node if it was not already selected																		
				if(!IsNodeSelected(tn))
				{
					// Check if node selection is cancelled
					TreeViewCancelEventArgs tvcea = new TreeViewCancelEventArgs(tn, false, tva);
					base.OnBeforeSelect(tvcea);
					if(tvcea.Cancel)
					{
						// This node selection was cancelled!						
						return false;
					}

					PreserveNodeColors(tn);

					tn.BackColor = SelectionBackColor; // GKM moved from above
					tn.ForeColor = BackColor; // GKM moved from above									

					htblSelectedNodes.Add(tn.GetHashCode(), tn);
					blnSelected = true;
					blnSelectionChanged = true;

					base.OnAfterSelect(new TreeViewEventArgs(tn, tva));
				}

				tnMostRecentSelectedNode = tn;
			}
			else
			{
				// Only unselect node if it was selected
				if(IsNodeSelected(tn))
				{
					OnBeforeDeselect(tn);

					Color[] originalColors = (Color[])this.htblSelectedNodesOrigColors[tn.GetHashCode()];
					if(originalColors != null)
					{
						htblSelectedNodes.Remove(tn.GetHashCode());
						blnSelectionChanged = true;
						htblSelectedNodesOrigColors.Remove(tn.GetHashCode());

						// GKM - Restore original node colors
						tn.BackColor = originalColors[0]; // GKM - was BackColor;
						tn.ForeColor = originalColors[1]; // GKM - was ForeColor;
					}

					OnAfterDeselect(tn);
				}
			}

			return blnSelected;
		}

		/// <summary>
		/// Selects nodes within the specified range.
		/// </summary>
		/// <param name="startNode">Start node.</param>
		/// <param name="endNode">End Node.</param>
		/// <param name="tva">Specifies the action that caused the selection change.</param>
		private void SelectNodesInsideRange(TreeNode startNode, TreeNode endNode, TreeViewAction tva)
		{
			// Calculate start node and end node
			TreeNode firstNode, lastNode;
			if(startNode.Bounds.Y < endNode.Bounds.Y)
			{
				firstNode = startNode;
				lastNode = endNode;
			}
			else
			{
				firstNode = endNode;
				lastNode = startNode;
			}

			// Select each node in range
			SelectNode(firstNode, true, tva);
			TreeNode tnTemp = firstNode;
			while(tnTemp != lastNode)
			{
				tnTemp = tnTemp.NextVisibleNode;
				if(tnTemp != null)
				{
					SelectNode(tnTemp, true, tva);
				}
			}
			SelectNode(lastNode, true, tva);
		}

		/// <summary>
		/// Unselects nodes outside the specified range.
		/// </summary>
		/// <param name="startNode">Start node.</param>
		/// <param name="endNode">End node.</param>
		/// <param name="tva">Specifies the action that caused the selection change.</param>
		private void UnselectNodesOutsideRange(TreeNode startNode, TreeNode endNode, TreeViewAction tva)
		{
			// Calculate start node and end node
			TreeNode firstNode, lastNode;
			if(startNode.Bounds.Y < endNode.Bounds.Y)
			{
				firstNode = startNode;
				lastNode = endNode;
			}
			else
			{
				firstNode = endNode;
				lastNode = startNode;
			}

			// Unselect each node outside range
			TreeNode tnTemp = firstNode;
			while(tnTemp != null)
			{
				tnTemp = tnTemp.PrevVisibleNode;
				if(tnTemp != null)
				{
					SelectNode(tnTemp, false, tva);
				}
			}

			tnTemp = lastNode;
			while(tnTemp != null)
			{
				tnTemp = tnTemp.NextVisibleNode;
				if(tnTemp != null)
				{
					SelectNode(tnTemp, false, tva);
				}
			}
		}

		/// <summary>
		/// Recursively unselect node.
		/// </summary>
		/// <param name="tn">Node to recursively unselect.</param>
		/// <param name="tva">Specifies the action that caused the selection change.</param>
		private void UnselectNodesRecursively(TreeNode tn, TreeViewAction tva)
		{
			SelectNode(tn, false, tva);
			foreach(TreeNode child in tn.Nodes)
			{
				UnselectNodesRecursively(child, tva);
			}
		}

		#endregion

		#region Helper methods

		/// <summary>
		/// Determines whether a mouse click was inside the node bounds or outside the node bounds..
		/// </summary>
		/// <param name="tn">TreeNode to check.</param>
		/// <param name="e">MouseEventArgs.</param>
		/// <returns>True is mouse was clicked inside the node bounds, false if it was clicked ouside the node bounds.</returns>
		private static bool IsClickOnNode(TreeNode tn, MouseEventArgs e)
		{
			if(tn == null) return false;

			// GKM
			// Determine the rightmost position we'll process clicks (so that the click has to be on the node's bounds, 
			// like the .NET treeview
			int rightMostX = tn.Bounds.X + tn.Bounds.Width;
			return (tn != null && e.X < rightMostX); // GKM
		}

		/// <summary>
		/// Gets level of specified node.
		/// </summary>
		/// <param name="node">Node.</param>
		/// <returns>Level of node.</returns>
		public int GetNodeLevel(TreeNode node)
		{
			int level = 0;
			while((node = node.Parent) != null)
				level++;
			return level;
		}

		/// <summary>
		/// Determines whether the specified node is a child (indirect or direct) of the specified parent.
		/// </summary>
		/// <param name="child">Node to check.</param>
		/// <param name="parent">Parent node.</param>
		/// <returns>True if specified node is a direct or indirect child of parent node, false if not.</returns>
		private static bool IsChildOf(TreeNode child, TreeNode parent)
		{
			bool blnChild = false;

			TreeNode tnTemp = child;
			while(tnTemp != null)
			{
				if(tnTemp == parent)
				{
					blnChild = true;
					break;
				}
				else
				{
					tnTemp = tnTemp.Parent;
				}
			}

			return blnChild;
		}

		/// <summary>
		/// Gets root parent of specified node.
		/// </summary>
		/// <param name="child">Node.</param>
		/// <returns>Root parent of specified node.</returns>
		public TreeNode GetRootParent(TreeNode child)
		{
			TreeNode tnParent = child;

			while(tnParent.Parent != null)
			{
				tnParent = tnParent.Parent;
			}

			return tnParent;
		}

		/// <summary>
		/// Gets number of visible nodes.
		/// </summary>
		/// <returns>Number of visible nodes.</returns>
		private int GetNumberOfVisibleNodes()
		{
			int intCounter = 0;

			TreeNode tnTemp = this.Nodes[0];

			while(tnTemp != null)
			{
				if(tnTemp.IsVisible)
				{
					intCounter++;
				}

				tnTemp = tnTemp.NextVisibleNode;
			}

			return intCounter;
		}

		/// <summary>
		/// Gets last visible node.
		/// </summary>
		/// <returns>Last visible node.</returns>
		private TreeNode GetLastVisibleNode()
		{
			TreeNode tnTemp = this.Nodes[0];

			while(tnTemp.NextVisibleNode != null)
			{
				tnTemp = tnTemp.NextVisibleNode;
			}

			return tnTemp;
		}

		/// <summary>
		/// Gets next tree node(s), starting from the specified node and direction.
		/// </summary>
		/// <param name="start">Node to start from.</param>
		/// <param name="down">True to go down, false to go up.</param>
		/// <param name="intNumber">Number of nodes to go down or up.</param>
		/// <returns>Next node.</returns>
		private static TreeNode GetNextTreeNode(TreeNode start, bool down, int intNumber)
		{
			int intCounter = 0;
			TreeNode tnTemp = start;
			while(intCounter < intNumber)
			{
				if(down)
				{
					if(tnTemp.NextVisibleNode != null)
						tnTemp = tnTemp.NextVisibleNode;
					else
						break;
				}
				else
				{
					if(tnTemp.PrevVisibleNode != null)
						tnTemp = tnTemp.PrevVisibleNode;
					else
						break;
				}

				intCounter++;
			}

			return tnTemp;
		}

		/// <summary>
		/// makes focus rectangle visible or hides it.
		/// </summary>
		/// <param name="tn">Node to make focus rectangle (in)visible for.</param>
		/// <param name="visible">True to make focus rectangle visible, false to hide it.</param>
		private void SetFocusToNode(TreeNode tn, bool visible)
		{
			Graphics g = this.CreateGraphics();
			Rectangle rect = new Rectangle(tn.Bounds.X, tn.Bounds.Y, tn.Bounds.Width, tn.Bounds.Height);
			if(visible)
			{
				this.Invalidate(rect, false);
				Update();
				if(tn.BackColor != SelectionBackColor)
				{
					using(Pen p = new Pen(SelectionBackColor, 1)) g.DrawRectangle(p, rect);
				}
			}
			else
			{
				if(tn.BackColor != SelectionBackColor)
				{
					using(Pen p = new Pen(BackColor, 1))
					{
						g.DrawRectangle(p, tnMostRecentSelectedNode.Bounds.X, tnMostRecentSelectedNode.Bounds.Y, tnMostRecentSelectedNode.Bounds.Width, tnMostRecentSelectedNode.Bounds.Height);
					}
				}
				this.Invalidate(rect, false);
				Update();
			}
		}

		#endregion

		#region Dispose

		/// <summary> 
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose(bool disposing)
		{
			if(disposing)
			{
				if(components != null)
				{
					components.Dispose();
				}
			}
			base.Dispose(disposing);
		}

		#endregion

		#region Component Designer generated code

		/// <summary> 
		/// Required method for Designer support - do not modify 
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			components = new System.ComponentModel.Container();
			DoubleBuffered = true; //mxd
			SetStyle(ControlStyles.OptimizedDoubleBuffer, true); //mxd
		}

		#endregion

		#region OnMouseUp, OnMouseDown

		/// <summary>
		/// Occurs when mouse button is up after a click.
		/// </summary>
		/// <param name="e"></param>
		protected override void OnMouseUp(MouseEventArgs e)
		{
#if DEBUG
			try
			{
#endif
				if(!this.blnNodeProcessedOnMouseDown)
				{
					TreeNode tn = this.GetNodeAt(e.X, e.Y);

					// Mouse click has not been handled by the mouse down event, so do it here. This is the case when
					// a selected node was clicked again; in that case we handle that click here because in case the
					// user is dragging the node, we should not put it in edit mode.					

					if(IsClickOnNode(tn, e))
					{
						this.ProcessNodeRange(this.tnMostRecentSelectedNode, tn, e, Control.ModifierKeys, TreeViewAction.ByMouse, true);
					}
				}

				this.blnNodeProcessedOnMouseDown = false;

				base.OnMouseUp(e);
#if DEBUG
			}
			catch(Exception ex)
			{
				// GKM - Untrapped exceptions were killing me for debugging purposes.
				// It probably shouldn't be here permanently, but it was causing real trouble for me.
				MessageBox.Show(this, ex.ToString());
			}
#endif
		}

		private bool IsPlusMinusClicked(TreeNode tn, MouseEventArgs e)
		{
			int intNodeLevel = GetNodeLevel(tn);
			bool blnPlusMinusClicked = e.X < 20 + (intNodeLevel * 20);

			return blnPlusMinusClicked;
		}

		/// <summary>
		/// Occurs when mouse is down.
		/// </summary>
		/// <param name="e"></param>
		protected override void OnMouseDown(MouseEventArgs e)
		{
			tnKeysStartNode = null;

			// Store number of mouse clicks in OnMouseDown event, because here we also get e.Clicks = 2 when an item was doubleclicked
			// in OnMouseUp we seem to get always e.Clicks = 1, also when item is doubleclicked
			intMouseClicks = e.Clicks;

			TreeNode tn = this.GetNodeAt(e.X, e.Y);

			if(tn == null) return;

			// Preserve colors here, because if you do it later then it will already have selected colors 
			// Don't know why...!
			PreserveNodeColors(tn);

			// If +/- was clicked, we should not process the node.
			if(!IsPlusMinusClicked(tn, e))
			{
				// If mouse down on a node that is already selected, then we should process this node in the mouse up event, because we
				// might want to drag it and it should not be put in edit mode.
				// Also, only process node if click was in node's bounds.
				if((tn != null) && (IsClickOnNode(tn, e)) && (!IsNodeSelected(tn)))
				{
					// Flash node. In case the node selection is cancelled by the user, this gives the effect that it
					// was selected and unselected again.
					tnToFlash = tn;
					System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(FlashNode));
					t.Start();

					blnNodeProcessedOnMouseDown = true;

					ProcessNodeRange(tnMostRecentSelectedNode, tn, e, Control.ModifierKeys, TreeViewAction.ByMouse, true);
				}
			}

			base.OnMouseDown(e);
		}

		#endregion

		#region FlashNode, StartEdit

		/// <summary>
		/// Flashes node.
		/// </summary>
		private void FlashNode()
		{
			if(this.InvokeRequired)
			{
				this.Invoke(new MethodInvoker(FlashNode));
				return;
			}

			TreeNode tn = tnToFlash;
			// Only flash node is it's not yet selected
			if(!IsNodeSelected(tn))
			{
				tn.BackColor = SelectionBackColor;
				tn.ForeColor = this.BackColor;
				this.Invalidate();
				this.Refresh();
				Application.DoEvents();
				System.Threading.Thread.Sleep(200);
			}

			// If node is not selected yet, restore default colors to end flashing
			if(!IsNodeSelected(tn))
			{
				tn.BackColor = BackColor;
				tn.ForeColor = this.ForeColor;
			}
		}

		/// <summary>
		/// Starts edit on a node.
		/// </summary>
		private void StartEdit()
		{
			System.Threading.Thread.Sleep(200);
			if(!blnWasDoubleClick)
			{
				blnInternalCall = true;
				SelectedNode = tnNodeToStartEditOn;
				blnInternalCall = false;
				tnNodeToStartEditOn.BeginEdit();
			}
			else
			{
				blnWasDoubleClick = false;
			}
		}

		#endregion

		#region ProcessNodeRange

		/// <summary>
		/// Processes a node range.
		/// </summary>
		/// <param name="startNode">Start node of range.</param>
		/// <param name="endNode">End node of range.</param>
		/// <param name="e">MouseEventArgs.</param>
		/// <param name="keys">Keys.</param>
		/// <param name="tva">TreeViewAction.</param>
		/// <param name="allowStartEdit">True if node can go to edit mode, false if not.</param>
		private void ProcessNodeRange(TreeNode startNode, TreeNode endNode, MouseEventArgs e, Keys keys, TreeViewAction tva, bool allowStartEdit)
		{
			blnSelectionChanged = false; // prepare for OnSelectionsChanged

			if(e.Button == MouseButtons.Left)
			{
				blnWasDoubleClick = (intMouseClicks == 2);

				TreeNode tnTemp;
				int intNodeLevelStart;

				if(((keys & Keys.Control) == 0) && ((keys & Keys.Shift) == 0))
				{
					// CTRL and SHIFT not held down							
					tnSelectionMirrorPoint = endNode;
					int intNumberOfSelectedNodes = SelectedNodes.Count;

					// If it was a double click, select node and suspend further processing					
					if(blnWasDoubleClick)
					{
						base.OnMouseDown(e);
						return;
					}

					if(!IsPlusMinusClicked(endNode, e))
					{
						bool blnNodeWasSelected = IsNodeSelected(endNode);

						UnselectAllNodesExceptNode(endNode, tva);
						SelectNode(endNode, true, tva);


						if((blnNodeWasSelected) && (LabelEdit) && (allowStartEdit) && (!blnWasDoubleClick) && (intNumberOfSelectedNodes <= 1))
						{
							// Node should be put in edit mode					
							tnNodeToStartEditOn = endNode;
							System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(StartEdit));
							t.Start();
						}
					}
				}
				else if(((keys & Keys.Control) != 0) && ((keys & Keys.Shift) == 0))
				{
					// CTRL held down
					tnSelectionMirrorPoint = null;

					if(!IsNodeSelected(endNode))
					{
						switch(selectionMode)
						{
							case TreeViewSelectionMode.SingleSelect:
								UnselectAllNodesExceptNode(endNode, tva);
								break;

							case TreeViewSelectionMode.MultiSelectSameRootBranch:
								TreeNode tnAbsoluteParent2 = GetRootParent(endNode);
								UnselectAllNodesNotBelongingToParent(tnAbsoluteParent2, tva);
								break;

							case TreeViewSelectionMode.MultiSelectSameLevel:
								UnselectAllNodesNotBelongingToLevel(GetNodeLevel(endNode), tva);
								break;

							case TreeViewSelectionMode.MultiSelectSameLevelAndRootBranch:
								TreeNode tnAbsoluteParent = GetRootParent(endNode);
								UnselectAllNodesNotBelongingToParent(tnAbsoluteParent, tva);
								UnselectAllNodesNotBelongingToLevel(GetNodeLevel(endNode), tva);
								break;

							case TreeViewSelectionMode.MultiSelectSameParent:
								TreeNode tnParent = endNode.Parent;
								UnselectAllNodesNotBelongingDirectlyToParent(tnParent, tva);
								break;
						}

						SelectNode(endNode, true, tva);
					}
					else
					{
						SelectNode(endNode, false, tva);
					}
				}
				else if(((keys & Keys.Control) == 0) && ((keys & Keys.Shift) != 0))
				{
					// SHIFT pressed
					if(tnSelectionMirrorPoint == null)
					{
						tnSelectionMirrorPoint = startNode;
					}

					switch(selectionMode)
					{
						case TreeViewSelectionMode.SingleSelect:
							UnselectAllNodesExceptNode(endNode, tva);
							SelectNode(endNode, true, tva);
							break;

						case TreeViewSelectionMode.MultiSelectSameRootBranch:
							TreeNode tnAbsoluteParentStartNode = GetRootParent(startNode);
							tnTemp = startNode;
							// Check each visible node from startNode to endNode and select it if needed
							while((tnTemp != null) && (tnTemp != endNode))
							{
								if(startNode.Bounds.Y > endNode.Bounds.Y)
									tnTemp = tnTemp.PrevVisibleNode;
								else
									tnTemp = tnTemp.NextVisibleNode;
								if(tnTemp != null)
								{
									TreeNode tnAbsoluteParent = GetRootParent(tnTemp);
									if(tnAbsoluteParent == tnAbsoluteParentStartNode)
									{
										SelectNode(tnTemp, true, tva);
									}
								}
							}
							UnselectAllNodesNotBelongingToParent(tnAbsoluteParentStartNode, tva);
							UnselectNodesOutsideRange(tnSelectionMirrorPoint, endNode, tva);
							break;

						case TreeViewSelectionMode.MultiSelectSameLevel:
							intNodeLevelStart = GetNodeLevel(startNode);
							tnTemp = startNode;
							// Check each visible node from startNode to endNode and select it if needed
							while((tnTemp != null) && (tnTemp != endNode))
							{
								if(startNode.Bounds.Y > endNode.Bounds.Y)
									tnTemp = tnTemp.PrevVisibleNode;
								else
									tnTemp = tnTemp.NextVisibleNode;
								if(tnTemp != null)
								{
									int intNodeLevel = GetNodeLevel(tnTemp);
									if(intNodeLevel == intNodeLevelStart)
									{
										SelectNode(tnTemp, true, tva);
									}
								}
							}
							UnselectAllNodesNotBelongingToLevel(intNodeLevelStart, tva);
							UnselectNodesOutsideRange(tnSelectionMirrorPoint, endNode, tva);
							break;

						case TreeViewSelectionMode.MultiSelectSameLevelAndRootBranch:
							TreeNode tnAbsoluteParentStart = GetRootParent(startNode);
							intNodeLevelStart = GetNodeLevel(startNode);
							tnTemp = startNode;
							// Check each visible node from startNode to endNode and select it if needed
							while((tnTemp != null) && (tnTemp != endNode))
							{
								if(startNode.Bounds.Y > endNode.Bounds.Y)
									tnTemp = tnTemp.PrevVisibleNode;
								else
									tnTemp = tnTemp.NextVisibleNode;
								if(tnTemp != null)
								{
									int intNodeLevel = GetNodeLevel(tnTemp);
									TreeNode tnAbsoluteParent = GetRootParent(tnTemp);
									if((intNodeLevel == intNodeLevelStart) && (tnAbsoluteParent == tnAbsoluteParentStart))
									{
										SelectNode(tnTemp, true, tva);
									}
								}
							}
							UnselectAllNodesNotBelongingToParent(tnAbsoluteParentStart, tva);
							UnselectAllNodesNotBelongingToLevel(intNodeLevelStart, tva);
							UnselectNodesOutsideRange(tnSelectionMirrorPoint, endNode, tva);
							break;

						case TreeViewSelectionMode.MultiSelect:
							SelectNodesInsideRange(tnSelectionMirrorPoint, endNode, tva);
							UnselectNodesOutsideRange(tnSelectionMirrorPoint, endNode, tva);
							break;

						case TreeViewSelectionMode.MultiSelectSameParent:
							TreeNode tnParentStartNode = startNode.Parent;
							tnTemp = startNode;
							// Check each visible node from startNode to endNode and select it if needed
							while((tnTemp != null) && (tnTemp != endNode))
							{
								if(startNode.Bounds.Y > endNode.Bounds.Y)
									tnTemp = tnTemp.PrevVisibleNode;
								else
									tnTemp = tnTemp.NextVisibleNode;
								if(tnTemp != null)
								{
									TreeNode tnParent = tnTemp.Parent;
									if(tnParent == tnParentStartNode)
									{
										SelectNode(tnTemp, true, tva);
									}
								}
							}
							UnselectAllNodesNotBelongingDirectlyToParent(tnParentStartNode, tva);
							UnselectNodesOutsideRange(tnSelectionMirrorPoint, endNode, tva);
							break;
					}
				}
				else if(((keys & Keys.Control) != 0) && ((keys & Keys.Shift) != 0))
				{
					// SHIFT AND CTRL pressed
					switch(selectionMode)
					{
						case TreeViewSelectionMode.SingleSelect:
							UnselectAllNodesExceptNode(endNode, tva);
							SelectNode(endNode, true, tva);
							break;

						case TreeViewSelectionMode.MultiSelectSameRootBranch:
							TreeNode tnAbsoluteParentStartNode = GetRootParent(startNode);
							tnTemp = startNode;
							// Check each visible node from startNode to endNode and select it if needed
							while((tnTemp != null) && (tnTemp != endNode))
							{
								if(startNode.Bounds.Y > endNode.Bounds.Y)
									tnTemp = tnTemp.PrevVisibleNode;
								else
									tnTemp = tnTemp.NextVisibleNode;
								if(tnTemp != null)
								{
									TreeNode tnAbsoluteParent = GetRootParent(tnTemp);
									if(tnAbsoluteParent == tnAbsoluteParentStartNode)
									{
										SelectNode(tnTemp, true, tva);
									}
								}
							}
							UnselectAllNodesNotBelongingToParent(tnAbsoluteParentStartNode, tva);
							break;

						case TreeViewSelectionMode.MultiSelectSameLevel:
							intNodeLevelStart = GetNodeLevel(startNode);
							tnTemp = startNode;
							// Check each visible node from startNode to endNode and select it if needed
							while((tnTemp != null) && (tnTemp != endNode))
							{
								if(startNode.Bounds.Y > endNode.Bounds.Y)
									tnTemp = tnTemp.PrevVisibleNode;
								else
									tnTemp = tnTemp.NextVisibleNode;
								if(tnTemp != null)
								{
									int intNodeLevel = GetNodeLevel(tnTemp);
									if(intNodeLevel == intNodeLevelStart)
									{
										SelectNode(tnTemp, true, tva);
									}
								}
							}
							UnselectAllNodesNotBelongingToLevel(intNodeLevelStart, tva);
							break;

						case TreeViewSelectionMode.MultiSelectSameLevelAndRootBranch:
							TreeNode tnAbsoluteParentStart = GetRootParent(startNode);
							intNodeLevelStart = GetNodeLevel(startNode);
							tnTemp = startNode;
							// Check each visible node from startNode to endNode and select it if needed
							while((tnTemp != null) && (tnTemp != endNode))
							{
								if(startNode.Bounds.Y > endNode.Bounds.Y)
									tnTemp = tnTemp.PrevVisibleNode;
								else
									tnTemp = tnTemp.NextVisibleNode;
								if(tnTemp != null)
								{
									int intNodeLevel = GetNodeLevel(tnTemp);
									TreeNode tnAbsoluteParent = GetRootParent(tnTemp);
									if((intNodeLevel == intNodeLevelStart) && (tnAbsoluteParent == tnAbsoluteParentStart))
									{
										SelectNode(tnTemp, true, tva);
									}
								}
							}
							UnselectAllNodesNotBelongingToParent(tnAbsoluteParentStart, tva);
							UnselectAllNodesNotBelongingToLevel(intNodeLevelStart, tva);
							break;

						case TreeViewSelectionMode.MultiSelect:
							tnTemp = startNode;
							// Check each visible node from startNode to endNode and select it if needed
							while((tnTemp != null) && (tnTemp != endNode))
							{
								if(startNode.Bounds.Y > endNode.Bounds.Y)
									tnTemp = tnTemp.PrevVisibleNode;
								else
									tnTemp = tnTemp.NextVisibleNode;
								if(tnTemp != null)
								{
									SelectNode(tnTemp, true, tva);
								}
							}
							break;

						case TreeViewSelectionMode.MultiSelectSameParent:
							TreeNode tnParentStartNode = startNode.Parent;
							tnTemp = startNode;
							// Check each visible node from startNode to endNode and select it if needed
							while((tnTemp != null) && (tnTemp != endNode))
							{
								if(startNode.Bounds.Y > endNode.Bounds.Y)
									tnTemp = tnTemp.PrevVisibleNode;
								else
									tnTemp = tnTemp.NextVisibleNode;
								if(tnTemp != null)
								{
									TreeNode tnParent = tnTemp.Parent;
									if(tnParent == tnParentStartNode)
									{
										SelectNode(tnTemp, true, tva);
									}
								}
							}
							UnselectAllNodesNotBelongingDirectlyToParent(tnParentStartNode, tva);
							break;
					}
				}
			}
			else if(e.Button == MouseButtons.Right)
			{
				// if right mouse button clicked, clear selection and select right-clicked node
				if(!IsNodeSelected(endNode))
				{
					UnselectAllNodes(tva);
					SelectNode(endNode, true, tva);
				}
			}
			OnSelectionsChanged();
		}

		#endregion

		#region OnBeforeLabelEdit

		/// <summary>
		/// Occurs before node goes into edit mode.
		/// </summary>
		/// <param name="e"></param>
		protected override void OnBeforeLabelEdit(NodeLabelEditEventArgs e)
		{
			blnSelectionChanged = false; // prepare for OnSelectionsChanged

			// Make sure that it's the only selected node
			SelectNode(e.Node, true, TreeViewAction.ByMouse);
			UnselectAllNodesExceptNode(e.Node, TreeViewAction.ByMouse);

			OnSelectionsChanged();

			base.OnBeforeLabelEdit(e);
		}

		#endregion

		#region OnKeyDown

		/// <summary>
		/// occurs when a key is down.
		/// </summary>
		/// <param name="e"></param>
		protected override void OnKeyDown(KeyEventArgs e)
		{
			Keys kMod = Keys.None;
			switch(e.Modifiers)
			{
				case Keys.Shift:
				case Keys.Control:
				case Keys.Control | Keys.Shift:
					kMod = Keys.Shift;
					if(tnKeysStartNode == null)
						tnKeysStartNode = tnMostRecentSelectedNode;
					break;
				default:
					tnKeysStartNode = null;
					break;
			}

			int intNumber = 0;

			TreeNode tnNewlySelectedNodeWithKeys = null;
			switch(e.KeyCode)
			{
				case Keys.Down:
					tnNewlySelectedNodeWithKeys = tnMostRecentSelectedNode.NextVisibleNode;
					break;

				case Keys.Up:
					tnNewlySelectedNodeWithKeys = tnMostRecentSelectedNode.PrevVisibleNode;
					break;

				case Keys.Left:
					if(tnMostRecentSelectedNode.IsExpanded) 
						tnMostRecentSelectedNode.Collapse(); 
					else
						tnNewlySelectedNodeWithKeys = tnMostRecentSelectedNode.Parent; 
					break;

				case Keys.Right:
					if(!tnMostRecentSelectedNode.IsExpanded) 
						tnMostRecentSelectedNode.Expand(); 
					else 
						if(tnMostRecentSelectedNode.Nodes != null) 
							tnNewlySelectedNodeWithKeys = tnMostRecentSelectedNode.Nodes[0];
					break;

				case Keys.Home:
					tnNewlySelectedNodeWithKeys = this.Nodes[0];
					break;

				case Keys.End:
					tnNewlySelectedNodeWithKeys = GetLastVisibleNode();
					break;

				case Keys.PageDown:

					intNumber = GetNumberOfVisibleNodes();
					tnNewlySelectedNodeWithKeys = GetNextTreeNode(tnMostRecentSelectedNode, true, intNumber);
					break;

				case Keys.PageUp:

					intNumber = GetNumberOfVisibleNodes();
					tnNewlySelectedNodeWithKeys = GetNextTreeNode(tnMostRecentSelectedNode, false, intNumber);
					break;

				default:
					base.OnKeyDown(e); // GKM
					return;
			}

			if((tnNewlySelectedNodeWithKeys != null))
			{
				SetFocusToNode(tnMostRecentSelectedNode, false);
				ProcessNodeRange(tnKeysStartNode, tnNewlySelectedNodeWithKeys, new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0), kMod, TreeViewAction.ByKeyboard, false);
				tnMostRecentSelectedNode = tnNewlySelectedNodeWithKeys;
				SetFocusToNode(tnMostRecentSelectedNode, true);
			}

			// Ensure visibility
			if(tnMostRecentSelectedNode != null)
			{
				TreeNode tnToMakeVisible = null;
				switch(e.KeyCode)
				{
					case Keys.Down:
					case Keys.Right:
						tnToMakeVisible = GetNextTreeNode(tnMostRecentSelectedNode, true, 5);
						break;

					case Keys.Up:
					case Keys.Left:
						tnToMakeVisible = GetNextTreeNode(tnMostRecentSelectedNode, false, 5);
						break;

					case Keys.Home:
					case Keys.End:
						tnToMakeVisible = tnMostRecentSelectedNode;
						break;

					case Keys.PageDown:
						tnToMakeVisible = GetNextTreeNode(tnMostRecentSelectedNode, true, intNumber - 2);
						break;

					case Keys.PageUp:
						tnToMakeVisible = GetNextTreeNode(tnMostRecentSelectedNode, false, intNumber - 2);
						break;
				}

				if(tnToMakeVisible != null)
					tnToMakeVisible.EnsureVisible();
			}

			base.OnKeyDown(e);
		}

		#endregion

		#region OnAfterCollapse

		/// <summary>
		/// Occurs after a node is collapsed.
		/// </summary>
		/// <param name="e"></param>
		protected override void OnAfterCollapse(TreeViewEventArgs e)
		{
			blnSelectionChanged = false;

			// All child nodes should be deselected
			bool blnChildSelected = false;
			foreach(TreeNode tn in e.Node.Nodes)
			{
				if(IsNodeSelected(tn))
				{
					blnChildSelected = true;
				}
				UnselectNodesRecursively(tn, TreeViewAction.Collapse);
			}

			if(blnChildSelected)
			{
				SelectNode(e.Node, true, TreeViewAction.Collapse);
			}

			OnSelectionsChanged();

			base.OnAfterCollapse(e);
		}

		#endregion

		#region OnItemDrag

		/// <summary>
		/// Occurs when an item is being dragged.
		/// </summary>
		/// <param name="e"></param>
		protected override void OnItemDrag(ItemDragEventArgs e)
		{
			e = new ItemDragEventArgs(MouseButtons.Left, this.SelectedNodes);
			base.OnItemDrag(e);
		}

		#endregion

	}

	#region SelectedNodesCollection

	/// <summary>
	/// Collection of selected nodes.
	/// </summary>
	public class NodesCollection : CollectionBase
	{
		#region Events

		/// <summary>
		/// Event fired when a tree node has been added to the collection.
		/// </summary>
		internal event TreeNodeEventHandler TreeNodeAdded;

		/// <summary>
		/// Event fired when a tree node has been removed to the collection.
		/// </summary>
		internal event TreeNodeEventHandler TreeNodeRemoved;

		/// <summary>
		/// Event fired when a tree node has been inserted to the collection.
		/// </summary>
		internal event TreeNodeEventHandler TreeNodeInserted;

		/// <summary>
		/// Event fired the collection has been cleared.
		/// </summary>
		internal event EventHandler SelectedNodesCleared;

		#endregion

		#region CollectionBase members

		/// <summary>
		/// Gets tree node at specified index.
		/// </summary>
		public TreeNode this[int index]
		{
			get { return ((TreeNode)List[index]); }
		}

		/// <summary>
		/// Adds a tree node to the collection.
		/// </summary>
		/// <param name="treeNode">Tree node to add.</param>
		/// <returns>The position into which the new element was inserted.</returns>
		public int Add(TreeNode treeNode)
		{
			if(TreeNodeAdded != null)
				TreeNodeAdded(treeNode);

			return List.Add(treeNode);
		}

		/// <summary>
		/// Inserts a tree node at specified index.
		/// </summary>
		/// <param name="index">The position into which the new element has to be inserted.</param>
		/// <param name="treeNode">Tree node to insert.</param>
		public void Insert(int index, TreeNode treeNode)
		{
			if(TreeNodeInserted != null)
				TreeNodeInserted(treeNode);

			List.Add(treeNode);
		}

		/// <summary>
		/// Removed a tree node from the collection.
		/// </summary>
		/// <param name="treeNode">Tree node to remove.</param>
		public void Remove(TreeNode treeNode)
		{
			if(TreeNodeRemoved != null)
				TreeNodeRemoved(treeNode);

			List.Remove(treeNode);
		}

		/// <summary>
		/// Determines whether treenode belongs to the collection.
		/// </summary>
		/// <param name="treeNode">Tree node to check.</param>
		/// <returns>True if tree node belongs to the collection, false if not.</returns>
		public bool Contains(TreeNode treeNode)
		{
			return List.Contains(treeNode);
		}

		/// <summary>
		/// Gets index of tree node in the collection.
		/// </summary>
		/// <param name="treeNode">Tree node to get index of.</param>
		/// <returns>Index of tree node in the collection.</returns>
		public int IndexOf(TreeNode treeNode)
		{
			return List.IndexOf(treeNode);
		}

		#endregion

		#region OnClear

		/// <summary>
		/// Occurs when collection is being cleared.
		/// </summary>
		protected override void OnClear()
		{
			if(SelectedNodesCleared != null)
				SelectedNodesCleared(this, EventArgs.Empty);

			base.OnClear();
		}

		#endregion

	}

	#endregion
}
