using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace AT.STO.UI.Win
{
	/// 
	/// A class to assist in creating popup windows like Combo Box drop-downs and Menus.
	/// This class includes functionality to keep the title bar of the popup owner form
	/// active whilst the popup is displayed, and to automatically cancel the popup
	/// whenever the user clicks outside the popup window or shifts focus to another 
	/// application.
	/// 
	/// Thousand thanks to Steve McMahon:
	/// http://www.vbaccelerator.com/home/NET/Code/Controls/Popup_Windows/Popup_Windows/Popup_Form_Demonstration.asp
	/// 
	internal class DropDownWindowHelper : NativeWindow
	{
	#region Private Variable Declarations
		private EventHandler DropDownClosedHandler = null;
		
		private Form					_dropDown			= null;
		private bool					_dropDownShowing	= false;
		private DropDownMessageFilter	_filter				= null;
		private Form					_owner				= null;
		private bool					_skipClose			= false;
	#endregion
	#region Event Declarations
		public event DropDownCancelEventHandler DropDownCancel;
		public event DropDownClosedEventHandler DropDownClosed;
	#endregion
	#region Constructor / Destructor
		/// 
		/// Default constructor.
		/// 
		/// Use the 
		/// method to attach this class to the form you want to show popups from.
		public DropDownWindowHelper()
		{
			_filter = new DropDownMessageFilter(this);
			_filter.DropDownCancel -= new DropDownCancelEventHandler(Popup_Cancel);
			_filter.DropDownCancel += new DropDownCancelEventHandler(Popup_Cancel);
		}
	#endregion
	#region Event Handler
		private void Popup_Cancel(object sender, DropDownCancelEventArgs e)
		{
			OnDropDownCancel(e);
		}
		/// 
		/// Responds to the 
		/// event from the popup form.
		/// 
		/// Popup form that has been closed.
		/// Not used.
		private void Popup_Closed(object sender, EventArgs e)
		{
			CloseDropDown();
		}
	
		/// 
		/// Subclasses the owning form's existing Window Procedure to enables the 
		/// title bar to remain active when a popup is show, and to detect if
		/// the user clicks onto another application whilst the popup is visible.
		/// 
		/// Window Procedure Message
		protected override void WndProc(ref Message m)
		{
			base.WndProc(ref m);
			if (DropDownShowing)
			{
				if (m.Msg == UIApiCalls.WM_NCACTIVATE)
				{
					if (((int)m.WParam) == 0)																// Check if the title bar will made inactive:
					{	// Note it's no good to try and consume this message; if you try to do that you'll end up with windows
						UIApiCalls.SendMessage(this.Handle, UIApiCalls.WM_NCACTIVATE, 1, IntPtr.Zero);		// If so reactivate it.
					}
				}
				else if (m.Msg == UIApiCalls.WM_ACTIVATEAPP)
				{
					if ((int)m.WParam == 0)																	// Check if the application is being deactivated.
					{
						CloseDropDown();																		// It is so cancel the popup:
						UIApiCalls.PostMessage(this.Handle, UIApiCalls.WM_NCACTIVATE, 0, IntPtr.Zero);		// And put the title bar into the inactive state:
					}
				}
			}
		}
	#endregion
	#region Public Methods
		/// 
		/// Shows the specified Form as a popup window, keeping the
		/// Owner's title bar active and preparing to cancel the popup
		/// should the user click anywhere outside the popup window.
		/// Typical code to use this message is as follows:
		/// 
		///    frmPopup popup = new frmPopup();
		///    Point location = this.PointToScreen(new Point(button1.Left, button1.Bottom));
		///    popupHelper.ShowPopup(this, popup, location);
		/// 
		/// Put as much initialisation code as possible
		/// into the popup form's constructor, rather than the 
		/// event as this will improve visual appearance.
		/// 
		/// Main form which owns the popup
		/// Window to show as a popup
		/// Location relative to the screen to show the popup at.
		public void ShowDropDown(Form Owner, Form DropDown, Point Location)
		{
			_owner = Owner;
			_dropDown = DropDown;
			Application.AddMessageFilter(_filter);					// Start checking for the popup being cancelled
			DropDown.StartPosition = FormStartPosition.Manual;		// Set the location of the popup form:
			DropDown.Location = Location;
			Owner.AddOwnedForm(DropDown);							// Make it owned by the window that's displaying it:		
			DropDownClosedHandler = new EventHandler(Popup_Closed);	// Respond to the Closed event in case the popup is closed by its own internal means
			DropDown.Closed += DropDownClosedHandler;
			_dropDownShowing = true;								// Show the popup:
			DropDown.Show();
			DropDown.Activate();
			
			// A little bit of fun.  We've shown the popup, but because we've kept the main window's
			// title bar in focus the tab sequence isn't quite right.  This can be fixed by sending a tab,
			// but that on its own would shift focus to the second control in the form.  So send a tab,
			// followed by a reverse-tab.
			UIApiCalls.keybd_event((byte) Keys.Tab, 0, 0, 0);
				UIApiCalls.keybd_event((byte) Keys.Tab, 0, UIApiCalls.KEYEVENTF_KEYUP, 0);
			UIApiCalls.keybd_event((byte) Keys.ShiftKey, 0, 0, 0);
			UIApiCalls.keybd_event((byte) Keys.Tab, 0, 0, 0);
		UIApiCalls.keybd_event((byte) Keys.Tab, 0, UIApiCalls.KEYEVENTF_KEYUP, 0);
			UIApiCalls.keybd_event((byte) Keys.ShiftKey, 0, UIApiCalls.KEYEVENTF_KEYUP, 0);
			_filter.DropDown = DropDown;							// Start filtering for mouse clicks outside the popup	
		}
		/// 
		/// Called when the popup is being hidden.
		/// 
		public void CloseDropDown()
		{
			if (DropDownShowing)
			{
				if (!_skipClose)
				{
					OnPDropDownClosed(new DropDownClosedEventArgs(_dropDown));						
				}
				
				_skipClose = false;
				_owner.RemoveOwnedForm(_dropDown);					// Make sure the popup is closed and we've cleaned up:
				_dropDownShowing = false;
				_dropDown.Closed -= DropDownClosedHandler;
				DropDownClosedHandler = null;
				_dropDown.Close();
				Application.RemoveMessageFilter(_filter);			// No longer need to filter for clicks outside the popup.
				// If we did something from the popup which shifted focus to a new form, like showing another popup
				// or dialog, then Windows won't know how to bring the original owner back to the foreground, so
				// force it here:
				_owner.Activate();
				_dropDown = null;									// Null out references for GC
				_owner = null;				
			}
		}
	#endregion
	#region Public Properties
		/// 
		/// Indicator weither the DropDown is showing.
		/// 
		public bool DropDownShowing
		{
			get { return _dropDownShowing; }
		}
	#endregion
	#region Event Implementation
		protected virtual void OnDropDownCancel(DropDownCancelEventArgs e)
		{
			if (this.DropDownCancel != null)
			{
				this.DropDownCancel(this, e);
				if (!e.Cancel)
				{
					_skipClose = true;
				}
			}
		}
		
		protected virtual void OnPDropDownClosed(DropDownClosedEventArgs e)
		{
		    if (this.DropDownClosed != null)
		    {
		        this.DropDownClosed(this, e);
		    }
		}
	#endregion
	}
}