//------------------------------------------------------------------------------
// C# ConsoleView Control
// Copyright (C) 2011 Cores Co., Ltd. Japan
//------------------------------------------------------------------------------
// $Id: ControlScrollBar.cs 88 2011-04-05 11:03:57Z nagasima $
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace ConsoleView
{
	public class ControlScrollBar
	{
		/// <summary>水平スクロールWin32メッセージ</summary>
		public const int WM_HSCROLL = 0x114;
		/// <summary>垂直スクロールWin32メッセージ</summary>
		public const int WM_VSCROLL = 0x115;

		public const int SB_LINELEFT = 0;
		public const int SB_LINERIGHT = 1;
		public const int SB_PAGELEFT = 2;
		public const int SB_PAGERIGHT = 3;
		public const int SB_THUMBPOSITION = 4;
		public const int SB_THUMBTRACK = 5;
		public const int SB_LEFT = 6;
		public const int SB_RIGHT = 7;
		public const int SB_ENDSCROLL = 8;

		public const int SB_LINETOP = 0;
		public const int SB_LINEBOTTOM = 1;
		public const int SB_PAGETOP = 2;
		public const int SB_PAGEBOTTOM = 3;
		public const int SB_TOP = 6;
		public const int SB_BOTTOM = 7;

		public const int SIF_TRACKPOS = 0x10;
		public const int SIF_RANGE = 0x1;
		public const int SIF_POS = 0x4;
		public const int SIF_PAGE = 0x2;
		public const int SIF_ALL = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS;

		[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)]
		public struct ScrollInfoStruct
		{
			public int cbSize;
			public int fMask;
			public int nMin;
			public int nMax;
			public int nPage;
			public int nPos;
			public int nTrackPos;
		}

		[DllImport("user32.dll", SetLastError = true)]
		public static extern int GetScrollInfo(IntPtr hWnd, int n, ref ScrollInfoStruct lpScrollInfo);

		[DllImport("user32.dll", SetLastError = true)]
		public static extern int SetScrollInfo(IntPtr hWnd, int n, ref ScrollInfoStruct lpScrollInfo, bool Redraw);

		private bool m_Enabled = true;
		private int m_LargeChange = 10;
		private int m_SmallChange = 1;
		private Control m_Control;
		private bool m_Visible;
		private ScrollOrientation m_Orientation;
		private ScrollInfoStruct m_ScrollInfo = new ScrollInfoStruct();

		//---------------------------------------------------------------------
		/// <summary>
		/// ConsoleViewScrollBar クラスの新しいインスタンスを初期化します。
		/// </summary>
		/// <param name="container">
		//  このオブジェクトが表すスクロール関連のプロパティを持つ System.Windows.Forms.ScrollableControl。
		/// </param>
		/// <param name="orientation">
		/// スクロールバーの方向
		/// </param>
		//---------------------------------------------------------------------
		public ControlScrollBar(Control control, ScrollOrientation orientation)
		{
			m_Control = control;
			m_Orientation = orientation;
			m_ScrollInfo.cbSize = 28;
			m_ScrollInfo.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
			m_ScrollInfo.nMax = 100;
			m_ScrollInfo.nMin = 0;
			m_ScrollInfo.nPage = 10;
			m_ScrollInfo.nPos = 0;
			SetScrollInfo(m_Control.Handle, (int)m_Orientation, ref m_ScrollInfo, true);
			m_ScrollInfo.fMask = SIF_ALL;
		}

		//---------------------------------------------------------------------
		/// <summary>
		//     コンテナでスクロール バーを使用できるかどうかを取得または設定します。
		//
		// 戻り値:
		//     スクロール バーを使用できる場合は true。それ以外の場合は false。
		//---------------------------------------------------------------------
		[DefaultValue(true)]
		public bool Enabled
		{
			get { return m_Enabled; }
			set { m_Enabled = value; }
		}

		//---------------------------------------------------------------------
		/// <summary>
		/// 移動量の大きなスクロール コマンドが実行された場合に、スクロール バーが移動する距離を取得または設定します。
		/// </summary>
		/// <exception cref="System.ArgumentOutOfRangeException">
		///     System.Windows.Forms.ScrollProperties.LargeChange は 0 未満には設定できません。
		/// </exception>
		//---------------------------------------------------------------------
		[DefaultValue(10)]
		public int LargeChange
		{
			get { return m_LargeChange; }
			set { m_LargeChange = value; }
		}

		//---------------------------------------------------------------------
		/// <summary>
		/// スクロール可能な範囲の上限を取得または設定します。
		/// </summary>>
		//---------------------------------------------------------------------
		[DefaultValue(100)]
		public int Maximum
		{
			get
			{
				if ((m_ScrollInfo.fMask == SIF_ALL) && (m_Control.Handle != IntPtr.Zero))
				{
					GetScrollInfo(m_Control.Handle, (int)m_Orientation, ref m_ScrollInfo);
				}

				return m_ScrollInfo.nMax;
			}
			set
			{
				if ((m_ScrollInfo.fMask == SIF_ALL) && (m_Control.Handle != IntPtr.Zero))
				{
					GetScrollInfo(m_Control.Handle, (int)m_Orientation, ref m_ScrollInfo);
					m_ScrollInfo.fMask = SIF_RANGE;
				}
				else
					m_ScrollInfo.fMask |= SIF_RANGE;

				m_ScrollInfo.nMax = value;

				if (m_Control.Handle != IntPtr.Zero)
				{
					SetScrollInfo(m_Control.Handle, (int)m_Orientation, ref m_ScrollInfo, true);
					m_ScrollInfo.fMask = SIF_ALL;
				}
			}
		}

		//---------------------------------------------------------------------
		/// <summary>
		/// スクロール可能な範囲の下限を取得または設定します。
		/// </summary>
		/// <exception cref="System.ArgumentOutOfRangeException">
		/// System.Windows.Forms.ScrollProperties.LargeChange は 0 未満には設定できません。
		/// </exception>
		//---------------------------------------------------------------------
		[DefaultValue(0)]
		public int Minimum
		{
			get
			{
				if ((m_ScrollInfo.fMask == SIF_ALL) && (m_Control.Handle != IntPtr.Zero))
				{
					GetScrollInfo(m_Control.Handle, (int)m_Orientation, ref m_ScrollInfo);
				}

				return m_ScrollInfo.nMin;
			}
			set
			{
				if ((m_ScrollInfo.fMask == SIF_ALL) && (m_Control.Handle != IntPtr.Zero))
				{
					GetScrollInfo(m_Control.Handle, (int)m_Orientation, ref m_ScrollInfo);
					m_ScrollInfo.fMask = SIF_RANGE;
				}
				else
					m_ScrollInfo.fMask |= SIF_RANGE;

				m_ScrollInfo.nMin = value;

				if (m_Control.Handle != IntPtr.Zero)
				{
					SetScrollInfo(m_Control.Handle, (int)m_Orientation, ref m_ScrollInfo, true);
					m_ScrollInfo.fMask = SIF_ALL;
				}
			}
		}

		//---------------------------------------------------------------------
		/// <summary>
		/// このスクロール情報の適用先のコントロールを取得します。
		/// </summary>
		/// <returns>
		/// System.Windows.Forms.ScrollableControl。
		/// </returns>
		//---------------------------------------------------------------------
		protected Control ParentControl
		{
			get { return m_Control; }
		}

		//---------------------------------------------------------------------
		/// <summary>
		/// 移動量の小さなスクロール コマンドが実行された場合に、スクロール バーが移動する距離を取得または設定します。
		/// </summary>
		/// <returns>
		/// スクロール バーの移動距離をピクセル単位で表す System.Int32。
		/// </returns>
		//---------------------------------------------------------------------
		[DefaultValue(1)]
		public int SmallChange
		{
			get { return m_SmallChange; }
			set { m_SmallChange = value; }
		}

		//---------------------------------------------------------------------
		/// <summary>
		/// スクロール バーの現在位置を表す数値を取得または設定します。
		/// </summary>
		//---------------------------------------------------------------------
		[Bindable(true)]
		[DefaultValue(0)]
		public int Value
		{
			get
			{
				if ((m_ScrollInfo.fMask == SIF_ALL) && (m_Control.Handle != IntPtr.Zero))
				{
					GetScrollInfo(m_Control.Handle, (int)m_Orientation, ref m_ScrollInfo);
				}

				return m_ScrollInfo.nPos;
			}
			set
			{
				int OldValue;

				if ((m_ScrollInfo.fMask == SIF_ALL) && (m_Control.Handle != IntPtr.Zero))
				{
					GetScrollInfo(m_Control.Handle, (int)m_Orientation, ref m_ScrollInfo);
					m_ScrollInfo.fMask = SIF_POS;
				}
				else
					m_ScrollInfo.fMask |= SIF_POS;

				OldValue = m_ScrollInfo.nPos;

				m_ScrollInfo.nPos = value;

				if (m_Control.Handle != IntPtr.Zero)
				{
					SetScrollInfo(m_Control.Handle, (int)m_Orientation, ref m_ScrollInfo, true);
					m_ScrollInfo.fMask = SIF_ALL;
				}

				OnScroll(new ScrollEventArgs(ScrollEventType.ThumbPosition, OldValue, m_ScrollInfo.nPos, m_Orientation));
			}
		}

		//---------------------------------------------------------------------
		/// <summary>
		///     スクロール バーを表示するかどうかを取得または設定します。
		/// </summary>
		//---------------------------------------------------------------------
		[DefaultValue(false)]
		public bool Visible
		{
			get { return m_Visible; }
			set { m_Visible = value; }
		}

		//---------------------------------------------------------------------
		/// スクロールバーのページサイズを取得または設定します。
		//---------------------------------------------------------------------
		[DefaultValue(10)]
		public int PageSize
		{
			get
			{
				if ((m_ScrollInfo.fMask == SIF_ALL) && (m_Control.Handle != IntPtr.Zero))
				{
					GetScrollInfo(m_Control.Handle, (int)m_Orientation, ref m_ScrollInfo);
				}

				return m_ScrollInfo.nPage;
			}
			set
			{
				if ((m_ScrollInfo.fMask == SIF_ALL) && (m_Control.Handle != IntPtr.Zero))
				{
					GetScrollInfo(m_Control.Handle, (int)m_Orientation, ref m_ScrollInfo);
					m_ScrollInfo.fMask = SIF_PAGE;
				}
				else
					m_ScrollInfo.fMask |= SIF_PAGE;

				m_ScrollInfo.nPage = value;

				if (m_Control.Handle != IntPtr.Zero)
				{
					SetScrollInfo(m_Control.Handle, (int)m_Orientation, ref m_ScrollInfo, true);
					m_ScrollInfo.fMask = SIF_ALL;
				}
			}
		}

		//---------------------------------------------------------------------
		/// <summary>
		/// スクロールバーの方向を取得します。
		/// </summary>
		//---------------------------------------------------------------------
		[DefaultValue(ScrollOrientation.HorizontalScroll)]
		public ScrollOrientation Orientation
		{
			get { return m_Orientation; }
		}

		//---------------------------------------------------------------------------
		/// <summary>
		/// WindowメッセージのWM_HSCROLLかWM_VSCROLLの処理
		/// （所有コントロールから呼び出してもらう）
		/// </summary>
		/// <param name="m">Windowメッセージ</param>
		//---------------------------------------------------------------------------
		public void WmScroll(Message m)
		{
			int OldValue;
			ScrollEventType type = (ScrollEventType)(m.WParam.ToInt32() & 0xFFFF);
			uint nPos = (uint)(m.WParam.ToInt32() >> 16);

			if (m_ScrollInfo.fMask == SIF_ALL)
			{
				GetScrollInfo(m_Control.Handle, (int)m_Orientation, ref m_ScrollInfo);
				m_ScrollInfo.fMask = SIF_POS | SIF_TRACKPOS;
			}
			else
				m_ScrollInfo.fMask |= SIF_POS | SIF_TRACKPOS;
			OldValue = m_ScrollInfo.nPos;

			switch (type)
			{
				case ScrollEventType.SmallDecrement:
					m_ScrollInfo.nPos -= m_SmallChange;
					if (m_ScrollInfo.nPos < m_ScrollInfo.nMin)
						m_ScrollInfo.nPos = m_ScrollInfo.nMin;
					break;
				case ScrollEventType.SmallIncrement:
					m_ScrollInfo.nPos += m_SmallChange;
					if (m_ScrollInfo.nPos > (m_ScrollInfo.nMax - m_ScrollInfo.nPage + 1))
						m_ScrollInfo.nPos = m_ScrollInfo.nMax - m_ScrollInfo.nPage + 1;
					break;
				case ScrollEventType.LargeDecrement:
					m_ScrollInfo.nPos -= m_LargeChange;
					if (m_ScrollInfo.nPos < m_ScrollInfo.nMin)
						m_ScrollInfo.nPos = m_ScrollInfo.nMin;
					break;
				case ScrollEventType.LargeIncrement:
					m_ScrollInfo.nPos += m_LargeChange;
					if (m_ScrollInfo.nPos > (m_ScrollInfo.nMax - m_ScrollInfo.nPage + 1))
						m_ScrollInfo.nPos = m_ScrollInfo.nMax - m_ScrollInfo.nPage + 1;
					break;
				case ScrollEventType.ThumbPosition:
					m_ScrollInfo.nPos = m_ScrollInfo.nTrackPos;
					break;
				case ScrollEventType.ThumbTrack:
					m_ScrollInfo.nPos = m_ScrollInfo.nTrackPos;
					break;
				case ScrollEventType.First:
					m_ScrollInfo.nPos = m_ScrollInfo.nMin;
					break;
				case ScrollEventType.Last:
					m_ScrollInfo.nPos = m_ScrollInfo.nMax - m_ScrollInfo.nPage;
					break;
				case ScrollEventType.EndScroll:
					break;
			}

			SetScrollInfo(m_Control.Handle, (int)m_Orientation, ref m_ScrollInfo, true);
			m_ScrollInfo.fMask = SIF_ALL;

			OnScroll(new ScrollEventArgs(type, OldValue, m_ScrollInfo.nPos, m_Orientation));
		}

		/// <summary>スクロールイベント</summary>
		public event ScrollEventHandler Scroll;

		//---------------------------------------------------------------------------
		/// <summary>
		/// 派生先用スクロールイベント（スクロールイベントの呼び出し）
		/// </summary>
		/// <param name="e"></param>
		//---------------------------------------------------------------------------
		protected void OnScroll(ScrollEventArgs e)
		{
			if (Scroll != null)
				Scroll(this, e);
		}
	}
}
