/*********************************************************************************************
 * Copyright 2005 - Volian Enterprises, Inc. All rights reserved.
 * Volian Enterprises - Proprietary Information - DO NOT COPY OR DISTRIBUTE
 * ------------------------------------------------------------------------------
 * $Workfile: TreeViewMultiSelect.cs $     $Revision: 1 $
 * $Author: Kathy $   $Date: 3/08/05 1:45p $
 *
 * $History: TreeViewMultiSelect.cs $
 * 
 * *****************  Version 1  *****************
 * User: Kathy        Date: 3/08/05    Time: 1:45p
 * Created in $/LibSource/GUI_Utils
 * Approval
 * 
 * *****************  Version 1  *****************
 * User: Kathy        Date: 3/08/05    Time: 1:40p
 * Created in $/LibSource/GUI_Utils/GUI_Utils
 * 
 * *****************  Version 1  *****************
 * User: Kathy        Date: 3/08/05    Time: 1:33p
 * Created in $/LibSource/GUI_Utils/GUI_Utils
 * 
 * *****************  Version 1  *****************
 * User: Kathy        Date: 3/08/05    Time: 1:32p
 * Created in $/LibSource/GUI_Utils/GUI_Utils
 * 
 * *****************  Version 1  *****************
 * User: Kathy        Date: 3/08/05    Time: 1:30p
 * Created in $/GUI_Utils/GUI_Utils
 *********************************************************************************************/
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
namespace MultiSelectTreeView
{
	/// 
	/// Summary description for MultiSelectTreeView.
	/// The MultiSelectTreeView inherits from System.Windows.Forms.TreeView to 
	/// allow user to select multiple nodes.
	/// The underlying comctl32 TreeView doesn't support multiple selection.
	/// Hence this MultiSelectTreeView listens for the BeforeSelect && AfterSelect
	/// events to dynamically change the BackColor of the individual treenodes to
	/// denote selection. 
	/// It then adds the TreeNode to the internal arraylist of currently
	/// selectedNodes after validation checks.
	/// 
	/// The MultiSelectTreeView supports
	///		1) Select + Control will add the current node to list of SelectedNodes
	///		2) Select + Shift  will add the current node and all the nodes between the two 
	///			(if the start node and end node is at the same level)
	///		3) Control + A when the MultiSelectTreeView has focus will select all Nodes.
	///		
	/// 
	/// 
	public class MultiSelectTreeView : System.Windows.Forms.TreeView
	{
		/// 
		///  This is private member which caches the last treenode user clicked
		/// 
		private TreeNode lastNode;
		private TreeNode ParentNode;
		private int SelectNodes_ImageId;
		/// 
		///  This is private member stores the list of SelectedNodes
		/// 
		private	ArrayList selectedNodes;
		/// 
		///  This is private member which caches the first treenode user clicked
		/// 
		private TreeNode firstNode;
		/// 
		/// The constructor which initialises the MultiSelectTreeView.
		/// 
		public MultiSelectTreeView(int imgid)
		{
			SelectNodes_ImageId=imgid;
			selectedNodes = new ArrayList();
		}
		/// 
		/// The constructor which initialises the MultiSelectTreeView.
		/// 
		[
		Category("Selection"),
		Description("Gets or sets the selected nodes as ArrayList")
		]
		public ArrayList SelectedNodes
		{
			get
			{
				return selectedNodes;
			}
			set
			{
				DeselectNodes();
				selectedNodes.Clear();
				selectedNodes = value;
				SelectNodes();
			}
		}
		#region overrides
		/// 
		///		If the user has pressed "Control+A" keys then select all nodes.
		/// 
		/// 
		protected override void OnKeyUp(KeyEventArgs e)
		{
			base.OnKeyDown (e);
			bool Pressed = (e.Control && ((e.KeyData & Keys.A) == Keys.A));
			if (Pressed)
			{
			//	SelectAllNodes(this.Nodes);		//we won't allow selection of all.
			}
		}
		/// 
		///		This Function starts the multiple selection.
		/// 
		/// 
		protected override void OnBeforeSelect(TreeViewCancelEventArgs e)
		{
			base.OnBeforeSelect(e);
			//if (e.Node.SelectedImageIndex!=SelectNodes_ImageId)return;
			//if (selectedNodes.Count>1 && e.Node.Parent != ParentNode) return;
			//ParentNode=e.Node.Parent;
			//Check for the current keys press..	
			bool isControlPressed = (ModifierKeys==Keys.Control);
			bool isShiftPressed = (ModifierKeys==Keys.Shift);
			bool isRightMouse = (Control.MouseButtons==MouseButtons.Right);
			if (isRightMouse)return;
			//If control is pressed and the selectedNodes contains current Node
			//Deselect that node...
			//Remove from the selectedNodes Collection...
			if (isControlPressed && selectedNodes.Contains(e.Node))
			{
				DeselectNodes();
				selectedNodes.Remove( e.Node );
				SelectNodes();
				//MultiSelectTreeView has handled this event ....
				//Windows.Forms.TreeView should eat this event.
				e.Cancel = true;
				return;
			}
			
			//else (if Shift key is pressed)
			//Start the multiselection ...
            //Since Shift is pressed we would "SELECT" 
			///all nodes from first node - to last node
			lastNode = e.Node;
			//If Shift not pressed...
			//Remember this Node to be the Start Node .. in case user presses Shift to 
			//select multiple nodes.
			if (!isShiftPressed&&!isControlPressed&&(e.Node.ImageIndex==this.SelectNodes_ImageId)) 
				firstNode = e.Node;
		}
		/// 
		///		This function ends the multi selection. Also adds and removes the node to
		///		the selectedNodes depending upon the keys pressed.
		/// 
		/// 
		protected override void OnAfterSelect(TreeViewEventArgs e)
		{
			base.OnAfterSelect(e);
			//if (e.Node.SelectedImageIndex!=SelectNodes_ImageId)return;
			//if (selectedNodes.Count>1 && e.Node.Parent != ParentNode) return;
			ParentNode=e.Node.Parent;
			//Check for the current keys press..
			bool isControlPressed = (ModifierKeys==Keys.Control);
			bool isShiftPressed = (ModifierKeys==Keys.Shift);
			bool isRightMouse = (Control.MouseButtons==MouseButtons.Right);
			if (isRightMouse)return;
			if (isControlPressed)
			{
				if ((!selectedNodes.Contains(e.Node ))&& (e.Node.SelectedImageIndex==SelectNodes_ImageId) && (selectedNodes.Count==0 || e.Node.Parent == ParentNode)) 
				{
					//This is a new Node, so add it to the list.
					selectedNodes.Add(e.Node);
					ParentNode = e.Node.Parent;
					SelectNodes();
				}
				else  if (selectedNodes.Contains(e.Node))
				{
					//If control is pressed and the selectedNodes contains current Node
					//Deselect that node...
					//Remove from the selectedNodes Collection...
					DeselectNodes();
					selectedNodes.Remove( e.Node );
					if (selectedNodes.Count==0)ParentNode=null;
					SelectNodes();
				}
				else
				{
					//If control is pressed and this is not at the level of multi
					// select, clear selectetdNodes.
					if (selectedNodes!=null && selectedNodes.Count>0)
					{
						DeselectNodes();
						selectedNodes.Clear();
						ParentNode=null;
					}
					selectedNodes.Add( e.Node );
				}
			}
			else 
			{
				// SHIFT is pressed
				if (isShiftPressed && e.Node.SelectedImageIndex==SelectNodes_ImageId)
				{
					if (firstNode==null)
						firstNode=e.Node;
					else
					{
						//Start Looking for the start and end nodes to select all the nodes between them.				
						TreeNode uppernode = firstNode;
						TreeNode bottomnode = e.Node;
						//Check Parenting Upper ---> Bottom
						//Is Upper Node parent (direct or indirect) of Bottom Node
						bool bParent = CheckIfParent(uppernode, bottomnode); 
						if (!bParent)
						{
							//Check Parenting the other way round
							bParent = CheckIfParent(bottomnode, uppernode);
							if (bParent) // SWAPPING
							{
								TreeNode temp = uppernode;
								uppernode = bottomnode;
								bottomnode = temp;
							}
						}
						if (bParent)
						{
							TreeNode n = bottomnode;
							while ( n != uppernode.Parent)
							{
								if ( !selectedNodes.Contains( n ) ) 
									selectedNodes.Add( n );
								n = n.Parent;
							}
						}
							// Parenting Fails ... but check if the NODES are on the same LEVEL.
						else
						{
							if ( (uppernode.Parent==null && bottomnode.Parent==null) || (uppernode.Parent!=null && uppernode.Parent.Nodes.Contains( bottomnode )) ) // are they siblings ?
							{
								int nIndexUpper = uppernode.Index;
								int nIndexBottom = bottomnode.Index;
								//Need to SWAP if the order is reversed...
								if (nIndexBottom < nIndexUpper) 
								{
									TreeNode temp = uppernode;
									uppernode = bottomnode;
									bottomnode = temp;
									nIndexUpper = uppernode.Index;
									nIndexBottom = bottomnode.Index;
								}
								TreeNode n = uppernode;
								selectedNodes.Clear();
								while (nIndexUpper < nIndexBottom)
								{
									//Add all the nodes if nodes not present in the current
									//SelectedNodes list...
									if (!selectedNodes.Contains( n )) 
									{
										selectedNodes.Add(n);
										SelectAllNodesInNode(n.Nodes, n);
									}
									n = n.NextNode;
									nIndexUpper++;
								}
								//Add the Last Node.
								selectedNodes.Add(n);
							}
							else
							{
								if ( !selectedNodes.Contains( uppernode ) ) selectedNodes.Add( uppernode );
								if ( !selectedNodes.Contains( bottomnode ) )selectedNodes.Add( bottomnode );
							}
						}
						ParentNode = e.Node.Parent;
						SelectNodes();
						//Reset the firstNode counter for subsequent "SHIFT" keys.
						firstNode = e.Node; 
					}
				} 
				else
				{
					// If Normal selection then add this to SelectedNodes Collection.
					if (selectedNodes!=null && selectedNodes.Count>0)
					{
						DeselectNodes();
						selectedNodes.Clear();
					}
					selectedNodes.Add( e.Node );
				}
			}
		}
		/// 
		///		Overriden OnLostFocus to mimic TreeView's behavior of de-selecting nodes.
		/// 
		/// 
		protected override void OnLostFocus(EventArgs e)
		{
			base.OnLostFocus (e);
			DeselectNodes();
		}
		/// 
		///		Overriden OnGotFocus to mimic TreeView's behavior of selecting nodes.
		/// 
		/// 
		protected override void OnGotFocus(EventArgs e)
		{
			base.OnGotFocus (e);
			SelectNodes();
		}
		#endregion overrides
		/// 
		///		Private function to check the parenting of the two nodes passed.
		/// 
		/// 
		/// 
		/// 
		private bool CheckIfParent(TreeNode parentNode, TreeNode childNode)
		{
			if (parentNode == childNode)
				return true;
			TreeNode node = childNode;
			bool parentFound = false;
			while (!parentFound && node != null)
			{
				node = node.Parent;
				parentFound = (node == parentNode);
			}
			return parentFound;
		}
		/// 
		///		This function provides the user feedback that the node is selected
		///		Basically the BackColor and the ForeColor is changed for all
		///		the nodes in the selectedNodes collection.
		/// 
		private void SelectNodes()
		{
			foreach ( TreeNode n in selectedNodes )
			{
				n.BackColor = SystemColors.Highlight;
				n.ForeColor = SystemColors.HighlightText;
			}
		}
		/// 
		///		This function provides the user feedback that the node is de-selected
		///		Basically the BackColor and the ForeColor is changed for all
		///		the nodes in the selectedNodes collection.
		/// 
		private void DeselectNodes()
		{
			if (selectedNodes.Count==0) return;
			TreeNode node = (TreeNode) selectedNodes[0];
			if (node.TreeView==null) return;		// on dispose, at end of program
			Color backColor = node.TreeView.BackColor;
			Color foreColor= node.TreeView.ForeColor;
			foreach ( TreeNode n in selectedNodes )
			{
				n.BackColor = backColor;
				n.ForeColor = foreColor;
			}
		}
		/// 
		///		This function selects all the Nodes in the MultiSelectTreeView..
		/// 
		private void SelectAllNodes(TreeNodeCollection nodes)
		{
			foreach (TreeNode n in this.Nodes)
			{
				selectedNodes.Add(n);
				if (n.Nodes.Count > 1)
				{
					SelectAllNodesInNode(n.Nodes, n);
				}
									
			}
			SelectNodes();
		}
		/// 
		///		Recursive function selects all the Nodes in the MultiSelectTreeView's Node
		/// 
		private void SelectAllNodesInNode(TreeNodeCollection nodes, TreeNode node)
		{
			foreach (TreeNode n in node.Nodes)
			{
				selectedNodes.Add(n);
				if (n.Nodes.Count > 1)
				{
					SelectAllNodesInNode(n.Nodes, n);
				}
			}
			SelectNodes();
		}
		public bool MultiSelectActive()
		{
			if (selectedNodes.Count>1)return true;
			return false;
		}
	}
}