// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Copyright (c) 2004 Novell, Inc.
//
// Authors:
//	Peter Bartok	pbartok@novell.com
//

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Runtime.InteropServices;

namespace System.Windows.Forms {
	[Designer ("System.Windows.Forms.Design.ScrollableControlDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
	[ClassInterface (ClassInterfaceType.AutoDispatch)]
	[ComVisible (true)]
	public class ScrollableControl : Control {
		#region Local Variables
		private bool			force_hscroll_visible;
		private bool			force_vscroll_visible;
		private bool			auto_scroll;
		private Size			auto_scroll_margin;
		private Size			auto_scroll_min_size;
		private Point			scroll_position;
		private DockPaddingEdges	dock_padding;
		private SizeGrip		sizegrip;
		internal ImplicitHScrollBar	hscrollbar;
		internal ImplicitVScrollBar	vscrollbar;
		internal Size			canvas_size;
		private Rectangle		display_rectangle;
		private Control			old_parent;
		private HScrollProperties	horizontalScroll;
		private VScrollProperties	verticalScroll;
		#endregion	// Local Variables

		[TypeConverter(typeof(ScrollableControl.DockPaddingEdgesConverter))]
		#region Subclass DockPaddingEdges
		public class DockPaddingEdges : ICloneable
		{
			private Control	owner;
			
			internal DockPaddingEdges (Control owner)
			{
				this.owner = owner;
			}

			#region DockPaddingEdges Public Instance Properties
			[RefreshProperties (RefreshProperties.All)]
			public int All {
				get { return owner.Padding.All; }
				set { owner.Padding = new Padding (value); }
			}

			[RefreshProperties (RefreshProperties.All)]
			public int Bottom {
				get { return owner.Padding.Bottom; }
				set { owner.Padding = new Padding (Left, Top, Right, value); }
			}

			[RefreshProperties (RefreshProperties.All)]
			public int Left {
				get { return owner.Padding.Left; }
				set { owner.Padding = new Padding (value, Top, Right, Bottom); }
			}

			[RefreshProperties (RefreshProperties.All)]
			public int Right {
				get { return owner.Padding.Right; }
				set { owner.Padding = new Padding (Left, Top, value, Bottom); }
			}

			[RefreshProperties (RefreshProperties.All)]
			public int Top {
				get { return owner.Padding.Top; }
				set { owner.Padding = new Padding (Left, value, Right, Bottom); }
			}
			#endregion	// DockPaddingEdges Public Instance Properties

			// Public Instance Methods
			public override bool Equals (object other)
			{
				if (!(other is DockPaddingEdges)) {
					return false;
				}

				if ((this.All == ((DockPaddingEdges)other).All) && (this.Left == ((DockPaddingEdges)other).Left) &&
					(this.Right == ((DockPaddingEdges)other).Right) && (this.Top == ((DockPaddingEdges)other).Top) &&
					(this.Bottom == ((DockPaddingEdges)other).Bottom)) {
					return true;
				}

				return false;
			}

			public override int GetHashCode ()
			{
				return All * Top * Bottom * Right * Left;
			}

			public override string ToString ()
			{
				return "All = " + All.ToString () + " Top = " + Top.ToString () + " Left = " + Left.ToString () + " Bottom = " + Bottom.ToString () + " Right = " + Right.ToString ();
			}

			internal void Scale (float dx, float dy)
			{
				Left = (int)(Left * dx);
				Right = (int)(Right * dx);
				Top = (int)(Top * dy);
				Bottom = (int)(Bottom * dy);
			}

			object ICloneable.Clone ()
			{
				return new DockPaddingEdges (owner);
			}
		}
		#endregion	// Subclass DockPaddingEdges

		#region Subclass DockPaddingEdgesConverter
		public class DockPaddingEdgesConverter : System.ComponentModel.TypeConverter {
			// Public Constructors
			public DockPaddingEdgesConverter() {
			}

			// Public Instance Methods
			public override PropertyDescriptorCollection GetProperties(System.ComponentModel.ITypeDescriptorContext context, object value, Attribute[] attributes) {
				return TypeDescriptor.GetProperties(typeof(DockPaddingEdges), attributes);
			}

			public override bool GetPropertiesSupported(System.ComponentModel.ITypeDescriptorContext context) {
				return true;
			}
		}
		#endregion	// Subclass DockPaddingEdgesConverter

		#region Public Constructors
		public ScrollableControl() {
			SetStyle(ControlStyles.ContainerControl, true);
			SetStyle(ControlStyles.AllPaintingInWmPaint, false);

			auto_scroll = false;
			force_hscroll_visible = false;
			force_vscroll_visible = false;
			auto_scroll_margin = new Size(0, 0);
			auto_scroll_min_size = new Size(0, 0);
			scroll_position = new Point(0, 0);
			SizeChanged +=new EventHandler(Recalculate);
			VisibleChanged += new EventHandler (VisibleChangedHandler);
			LocationChanged += new EventHandler (LocationChangedHandler);
			ParentChanged += new EventHandler (ParentChangedHandler);
			HandleCreated += new EventHandler (AddScrollbars);

			CreateScrollbars ();
			
			horizontalScroll = new HScrollProperties (this);
			verticalScroll = new VScrollProperties (this);
		}

		void VisibleChangedHandler (object sender, EventArgs e)
		{
			Recalculate (false);
		}

		void LocationChangedHandler (object sender, EventArgs e)
		{
			UpdateSizeGripVisible ();
		}

		void ParentChangedHandler (object sender, EventArgs e)
		{
			
			if (old_parent == Parent)
				return;
				
			if (old_parent != null) {
				old_parent.SizeChanged -= new EventHandler (Parent_SizeChanged);
				old_parent.PaddingChanged -= new EventHandler (Parent_PaddingChanged);
			}
			
			if (Parent != null) {
				Parent.SizeChanged += new EventHandler (Parent_SizeChanged);
				Parent.PaddingChanged += new EventHandler (Parent_PaddingChanged);
			}
			
			old_parent = Parent;
		}

		void Parent_PaddingChanged (object sender, EventArgs e)
		{
			UpdateSizeGripVisible ();
		}

		void Parent_SizeChanged (object sender, EventArgs e)
		{
			UpdateSizeGripVisible ();
		}
		#endregion	// Public Constructors

		#region Protected Static Fields
		protected const int ScrollStateAutoScrolling	= 1;
		protected const int ScrollStateFullDrag		= 16;
		protected const int ScrollStateHScrollVisible	= 2;
		protected const int ScrollStateUserHasScrolled	= 8;
		protected const int ScrollStateVScrollVisible	= 4;
		#endregion	// Protected Static Fields

		#region Public Instance Properties
		[DefaultValue(false)]
		[Localizable(true)]
		[MWFCategory("Layout")]
		public virtual bool AutoScroll {
			get {
				return	auto_scroll;
			}

			set {
				if (auto_scroll != value) {
					auto_scroll = value;
					PerformLayout (this, "AutoScroll");
				}
			}
		}

		[Localizable(true)]
		[MWFCategory("Layout")]
		public Size AutoScrollMargin {
			get {
				return auto_scroll_margin;
			}

			set {
				if (value.Width < 0) {
					throw new ArgumentException("Width is assigned less than 0", "value.Width");
				}

				if (value.Height < 0) {
					throw new ArgumentException("Height is assigned less than 0", "value.Height");
				}

				auto_scroll_margin = value;
			}
		}

		internal bool ShouldSerializeAutoScrollMargin ()
		{
			return this.AutoScrollMargin != new Size (0, 0);
		}

		[Localizable(true)]
		[MWFCategory("Layout")]
		public Size AutoScrollMinSize {
			get {
				return auto_scroll_min_size;
			}

			set {
				if (value != auto_scroll_min_size) {
					auto_scroll_min_size = value;
					AutoScroll = true;
					PerformLayout (this, "AutoScrollMinSize");
				}
			}
		}

		internal bool ShouldSerializeAutoScrollMinSize ()
		{
			return this.AutoScrollMinSize != new Size (0, 0);
		}

		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Point AutoScrollPosition {
			get {
				return DisplayRectangle.Location;
			}

			set {
				if (value != AutoScrollPosition) {
					int	shift_x;
					int	shift_y;

					shift_x = 0;
					shift_y = 0;
					if (hscrollbar.VisibleInternal) {
						int max = hscrollbar.Maximum - hscrollbar.LargeChange + 1;
						value.X = value.X < hscrollbar.Minimum ? hscrollbar.Minimum : value.X;
						value.X = value.X > max ? max : value.X;
						shift_x = value.X - scroll_position.X;
					}

					if (vscrollbar.VisibleInternal) {
						int max = vscrollbar.Maximum - vscrollbar.LargeChange + 1;
						value.Y = value.Y < vscrollbar.Minimum ? vscrollbar.Minimum : value.Y;
						value.Y = value.Y > max ? max : value.Y;
						shift_y = value.Y - scroll_position.Y;
					}

					ScrollWindow(shift_x, shift_y);

					if (hscrollbar.VisibleInternal) {
						if (scroll_position.X >= hscrollbar.Minimum && scroll_position.X <= hscrollbar.Maximum)
							hscrollbar.Value = scroll_position.X;
					}

					if (vscrollbar.VisibleInternal) {
						if (scroll_position.Y >= vscrollbar.Minimum && scroll_position.Y <= vscrollbar.Maximum)
							vscrollbar.Value = scroll_position.Y;
					}

				}
			}
		}

		public override Rectangle DisplayRectangle {
			get {
				if (auto_scroll) {
					int		width;
					int		height;

					if (canvas_size.Width <= base.DisplayRectangle.Width) {
						width = base.DisplayRectangle.Width;
						if (vscrollbar.VisibleInternal) {
							width -= vscrollbar.Width;
						}
					} else {
						width = canvas_size.Width;
					}

					if (canvas_size.Height <= base.DisplayRectangle.Height) {
						height = base.DisplayRectangle.Height;
						if (hscrollbar.VisibleInternal) {
							height -= hscrollbar.Height;
						}
					} else {
						height = canvas_size.Height;
					}

					display_rectangle.X = -scroll_position.X;
					display_rectangle.Y = -scroll_position.Y;
					display_rectangle.Width = Math.Max(auto_scroll_min_size.Width, width);
					display_rectangle.Height = Math.Max(auto_scroll_min_size.Height, height);
				}
				else {
					display_rectangle = base.DisplayRectangle;
				}

				// DockPadding is the same as Padding (according to documentation) but is
				// calculated lazily, so we use Padding here instead.
				if (Padding != Padding.Empty) {
					display_rectangle.X += Padding.Left;
					display_rectangle.Y += Padding.Top;
					display_rectangle.Width -= Padding.Horizontal;
					display_rectangle.Height -= Padding.Vertical;
				}

				return display_rectangle;
			}
		}

		[MWFCategory("Layout")]
		[Browsable (false)]
		[EditorBrowsable (EditorBrowsableState.Never)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public DockPaddingEdges DockPadding {
			get {
				if (dock_padding == null)
					CreateDockPadding ();

				return dock_padding;
			}
		}

		[Browsable (false)]
		[EditorBrowsable (EditorBrowsableState.Always)]
		public HScrollProperties HorizontalScroll {
			get { return horizontalScroll; }
		}

		[Browsable (false)]
		[EditorBrowsable (EditorBrowsableState.Always)]
		public VScrollProperties VerticalScroll {
			get { return verticalScroll; }
		}
		#endregion	// Public Instance Properties

		#region Protected Instance Methods
		protected override CreateParams CreateParams {
			get {
				return base.CreateParams;
			}
		}

		protected bool HScroll {
			get {
				return hscrollbar.VisibleInternal;
			}

			set {
				if (!AutoScroll && hscrollbar.VisibleInternal != value) {
					force_hscroll_visible = value;
					Recalculate (false);
				}
			}
		}

		protected bool VScroll {
			get {
				return vscrollbar.VisibleInternal;
			}

			set {
				if (!AutoScroll && vscrollbar.VisibleInternal != value) {
					force_vscroll_visible = value;
					Recalculate (false);
				}
			}
		}
		#endregion	// Protected Instance Methods

		#region Public Instance Methods
		public void ScrollControlIntoView(Control activeControl) {
			int	corner_x;
			int	corner_y;

			Rectangle within = new Rectangle ();
			within.Size = ClientSize;
			
			if (!AutoScroll || (!hscrollbar.VisibleInternal && !vscrollbar.VisibleInternal)) {
				return;
			}

			if (!Contains(activeControl)) {
				return;
			}

			if (vscrollbar.Visible) {
				within.Width -= vscrollbar.Width;
			}
			if (hscrollbar.Visible) {
				within.Height -= hscrollbar.Height;
			}

			// Don't scroll if already visible
			if (within.Contains (activeControl.Location) && within.Contains (activeControl.Right, activeControl.Bottom)) {
				return;
			}

			// If the control is above the top or the left, move it down and right until it aligns 
			// with the top/left.
			// If the control is below the bottom or to the right, move it up/left until it aligns
			// with the bottom/right, but do never move it further than the top/left side.
			int x_diff = 0, y_diff = 0;
			if (activeControl.Top <= 0 || activeControl.Height >= within.Height) {
				y_diff = -activeControl.Top;
			} else if (activeControl.Bottom > within.Height) {
				y_diff = within.Height - activeControl.Bottom;
			}
			if (activeControl.Left <= 0 || activeControl.Width >= within.Width) {
				x_diff = -activeControl.Left;
			} else if (activeControl.Right > within.Width) {
				x_diff = within.Width - activeControl.Right;
			}
			corner_x = hscrollbar.Value - x_diff;
			corner_y = vscrollbar.Value - y_diff;

			if (hscrollbar.VisibleInternal) {
				if (corner_x > hscrollbar.Maximum) {
					corner_x = hscrollbar.Maximum;
				} else if (corner_x < hscrollbar.Minimum) {
					corner_x = hscrollbar.Minimum;
				}
				if (corner_x != hscrollbar.Value) {
					hscrollbar.Value = corner_x;
				}
			}

			if (vscrollbar.VisibleInternal) {
				if (corner_y > vscrollbar.Maximum) {
					corner_y = vscrollbar.Maximum;
				} else if (corner_y < vscrollbar.Minimum) {
					corner_y = vscrollbar.Minimum;
				}
				if (corner_y != vscrollbar.Value) {
					vscrollbar.Value = corner_y;
				}
			}
		}

		public void SetAutoScrollMargin(int x, int y) {
			if (x < 0) {
				x = 0;
			}

			if (y < 0) {
				y = 0;
			}

			auto_scroll_margin = new Size(x, y);
			Recalculate (false);
		}
		#endregion	// Public Instance Methods

		#region Protected Instance Methods
		[EditorBrowsable(EditorBrowsableState.Advanced)]
		protected virtual void AdjustFormScrollbars(bool displayScrollbars) {
			Recalculate (false);
		}

		[EditorBrowsable(EditorBrowsableState.Advanced)]
		protected bool GetScrollState(int bit) {
			// Internal MS
			return false;
		}

		[EditorBrowsable(EditorBrowsableState.Advanced)]
		protected override void OnLayout(LayoutEventArgs levent) {
			CalculateCanvasSize (true);

			AdjustFormScrollbars(AutoScroll);	// Dunno what the logic is. Passing AutoScroll seems to match MS behaviour
			base.OnLayout(levent);

			// The first time through, we just set the canvas to clientsize
			// so we could re-layout everything according to the flow.
			// This time we want to actually calculate the canvas.
			if (this is FlowLayoutPanel) {
				CalculateCanvasSize (false);
				AdjustFormScrollbars (AutoScroll);
			}
		}

		[EditorBrowsable(EditorBrowsableState.Advanced)]
		protected override void OnMouseWheel(MouseEventArgs e) {
			if (vscrollbar.VisibleInternal) {
				if (e.Delta > 0) {
					if (vscrollbar.Minimum < (vscrollbar.Value - vscrollbar.LargeChange)) {
						vscrollbar.Value -= vscrollbar.LargeChange;
					} else {
						vscrollbar.Value = vscrollbar.Minimum;
					}
				} else {
					int maximum_scrollbar_value = vscrollbar.Maximum - vscrollbar.LargeChange + 1;
					if (maximum_scrollbar_value > (vscrollbar.Value + vscrollbar.LargeChange)) {
						vscrollbar.Value += vscrollbar.LargeChange;
					} else {
						vscrollbar.Value = maximum_scrollbar_value;
					}
				}
			}
			base.OnMouseWheel(e);
		}

		[EditorBrowsable(EditorBrowsableState.Advanced)]
		protected override void OnVisibleChanged(EventArgs e) {
			if (Visible) {
				UpdateChildrenZOrder ();
				PerformLayout(this, "Visible");
			}
			base.OnVisibleChanged(e);
		}

		[EditorBrowsable (EditorBrowsableState.Never)]
		protected override void ScaleCore(float dx, float dy) {
			if (dock_padding != null)
				dock_padding.Scale(dx, dy);

			base.ScaleCore(dx, dy);
		}

		protected override void ScaleControl (SizeF factor, BoundsSpecified specified)
		{
			base.ScaleControl (factor, specified);
		}
		
		protected virtual Point ScrollToControl (Control activeControl)
		{
			int corner_x;
			int corner_y;

			Rectangle within = new Rectangle ();
			within.Size = ClientSize;

			if (vscrollbar.Visible)
				within.Width -= vscrollbar.Width;

			if (hscrollbar.Visible)
				within.Height -= hscrollbar.Height;

			// If the control is above the top or the left, move it down and right until it aligns 
			// with the top/left.
			// If the control is below the bottom or to the right, move it up/left until it aligns
			// with the bottom/right, but do never move it further than the top/left side.
			int x_diff = 0, y_diff = 0;
			
			if (activeControl.Top <= 0 || activeControl.Height >= within.Height)
				y_diff = -activeControl.Top;
			else if (activeControl.Bottom > within.Height)
				y_diff = within.Height - activeControl.Bottom;

			if (activeControl.Left <= 0 || activeControl.Width >= within.Width)
				x_diff = -activeControl.Left;
			else if (activeControl.Right > within.Width)
				x_diff = within.Width - activeControl.Right;

			corner_x = AutoScrollPosition.X + x_diff;
			corner_y = AutoScrollPosition.Y + y_diff;
			
			return new Point (corner_x, corner_y);
		}

		protected void SetDisplayRectLocation(int x, int y) {
			// This method is weird. MS documents that the scrollbars are not
			// updated. We need to move stuff, but leave the scrollbars as is

			if (x > 0) {
				x = 0;
			}

			if (y > 0) {
				y = 0;
			}

			ScrollWindow(scroll_position.X - x , scroll_position.Y - y);
		}

		protected void SetScrollState(int bit, bool value) {
			//throw new NotImplementedException();
		}

		[EditorBrowsable(EditorBrowsableState.Advanced)]
		protected override void WndProc(ref Message m) {
			base.WndProc(ref m);
		}
		#endregion	// Protected Instance Methods

		#region Internal & Private Methods
		internal override IntPtr AfterTopMostControl ()
		{
			// order of scrollbars:
			// top = vertical
			//       sizegrid
			// bottom = horizontal
			if (hscrollbar != null && hscrollbar.Visible)
				return hscrollbar.Handle;
			// no need to check for sizegrip since it will only
			// be visible if hbar is visible.
			if (vscrollbar != null && vscrollbar.Visible)
				return hscrollbar.Handle;

			return base.AfterTopMostControl ();
		}

		internal virtual void CalculateCanvasSize (bool canOverride) {
			Control		child;
			int		num_of_children;
			int		width;
			int		height;
			int		extra_width;
			int		extra_height;

			num_of_children = Controls.Count;
			width = 0;
			height = 0;
			extra_width = hscrollbar.Value;
			extra_height = vscrollbar.Value;
			if (dock_padding != null) {
				extra_width += dock_padding.Right;
				extra_height += dock_padding.Bottom;
			}

			for (int i = 0; i < num_of_children; i++) {
				child = Controls[i];
				if (child.Dock == DockStyle.Right) {
					extra_width += child.Width;
				} else if (child.Dock == DockStyle.Bottom) {
					extra_height += child.Height;
				}
			}

			if (!auto_scroll_min_size.IsEmpty) {
				width = auto_scroll_min_size.Width;
				height = auto_scroll_min_size.Height;
			}

			for (int i = 0; i < num_of_children; i++) {
				child = Controls[i];

				switch(child.Dock) {
					case DockStyle.Left: {
						if ((child.Right + extra_width) > width) {
							width = child.Right + extra_width;
						}
						continue;
					}

					case DockStyle.Top: {
						if ((child.Bottom + extra_height) > height) {
							height = child.Bottom + extra_height;
						}
						continue;
					}

					case DockStyle.Fill:
					case DockStyle.Right:
					case DockStyle.Bottom: {
						continue;
					}

					default: {
						AnchorStyles	anchor;

						anchor = child.Anchor;

						if (((anchor & AnchorStyles.Left) != 0) && ((anchor & AnchorStyles.Right) == 0)) {
							if ((child.Right + extra_width) > width) {
								width = child.Right + extra_width;
							}
						}

						if (((anchor & AnchorStyles.Top) != 0) || ((anchor & AnchorStyles.Bottom) == 0)) {
							if ((child.Bottom + extra_height) > height) {
								height = child.Bottom + extra_height;
							}
						}
						continue;
					}
				}
			}

			canvas_size.Width = width;
			canvas_size.Height = height;
		}

		// Normally DockPadding is created lazyly, as observed in the test cases, but some children
		// may need to have it always.
		internal void CreateDockPadding ()
		{
			if (dock_padding == null)
				dock_padding = new DockPaddingEdges (this);
		}

		private void Recalculate (object sender, EventArgs e) {
			Recalculate (true);
		}
				
		private void Recalculate (bool doLayout) {
			if (!IsHandleCreated) {
				return;
			}

			Size canvas = canvas_size;
			Size client = ClientSize;

			canvas.Width += auto_scroll_margin.Width;
			canvas.Height += auto_scroll_margin.Height;

			int right_edge = client.Width;
			int bottom_edge = client.Height;
			int prev_right_edge;
			int prev_bottom_edge;

			bool hscroll_visible;
			bool vscroll_visible;

			do {
				prev_right_edge = right_edge;
				prev_bottom_edge = bottom_edge;

				if ((force_hscroll_visible || (canvas.Width > right_edge && auto_scroll)) && client.Width > 0) {
					hscroll_visible = true;
					bottom_edge = client.Height - SystemInformation.HorizontalScrollBarHeight;
				} else {
					hscroll_visible = false;
					bottom_edge = client.Height;
				}

				if ((force_vscroll_visible || (canvas.Height > bottom_edge && auto_scroll)) && client.Height > 0) {
					vscroll_visible = true;
					right_edge = client.Width - SystemInformation.VerticalScrollBarWidth;
				} else {
					vscroll_visible = false;
					right_edge = client.Width;
				}

			} while (right_edge != prev_right_edge || bottom_edge != prev_bottom_edge);

			if (right_edge < 0) right_edge = 0;
			if (bottom_edge < 0) bottom_edge = 0;

			Rectangle hscroll_bounds;
			Rectangle vscroll_bounds;

			hscroll_bounds = new Rectangle (0, client.Height - SystemInformation.HorizontalScrollBarHeight,
							ClientRectangle.Width, SystemInformation.HorizontalScrollBarHeight);
			vscroll_bounds = new Rectangle (client.Width - SystemInformation.VerticalScrollBarWidth, 0,
							SystemInformation.VerticalScrollBarWidth, ClientRectangle.Height);

			/* the ScrollWindow calls here are needed
			 * because (this explanation sucks):
			 * 
			 * when we transition from having a scrollbar to
			 * not having one, we won't receive a scrollbar
			 * moved (value changed) event, so we need to
			 * manually scroll the canvas.
			 * 
			 * if you can fix this without requiring the
			 * ScrollWindow calls, pdb and toshok will each
			 * pay you $5.
			*/

			if (!vscrollbar.Visible) {
				vscrollbar.Value = 0;
			}
			if (!hscrollbar.Visible) {
				hscrollbar.Value = 0;
			}

			/* Manually setting the size of the thumb should be done before
			 * the other assignments */
			if (hscroll_visible) {
				hscrollbar.manual_thumb_size = right_edge;
				hscrollbar.LargeChange = right_edge;
				hscrollbar.SmallChange = 5;
				hscrollbar.Maximum = canvas.Width - 1;
			} else {
				if (hscrollbar != null && hscrollbar.VisibleInternal) {
					ScrollWindow (- scroll_position.X, 0);
				}
				scroll_position.X = 0;
			}

			if (vscroll_visible) {
				vscrollbar.manual_thumb_size = bottom_edge;
				vscrollbar.LargeChange = bottom_edge;
				vscrollbar.SmallChange = 5;
				vscrollbar.Maximum = canvas.Height - 1;
			} else {
				if (vscrollbar != null && vscrollbar.VisibleInternal) {
					ScrollWindow (0, - scroll_position.Y);
				}
				scroll_position.Y = 0;
			}

			if (hscroll_visible && vscroll_visible) {
				hscroll_bounds.Width -= SystemInformation.VerticalScrollBarWidth;
				vscroll_bounds.Height -= SystemInformation.HorizontalScrollBarHeight;

				sizegrip.Bounds = new Rectangle (hscroll_bounds.Right,
								 vscroll_bounds.Bottom,
								 SystemInformation.VerticalScrollBarWidth,
								 SystemInformation.HorizontalScrollBarHeight);
			}
			
			SuspendLayout ();

			hscrollbar.SetBoundsInternal (hscroll_bounds.X, hscroll_bounds.Y, hscroll_bounds.Width, hscroll_bounds.Height, BoundsSpecified.None);
			hscrollbar.Visible = hscroll_visible;
			if (hscrollbar.Visible)
				XplatUI.SetZOrder (hscrollbar.Handle, IntPtr.Zero, true, false);

			vscrollbar.SetBoundsInternal (vscroll_bounds.X, vscroll_bounds.Y, vscroll_bounds.Width, vscroll_bounds.Height, BoundsSpecified.None);
			vscrollbar.Visible = vscroll_visible;
			if (vscrollbar.Visible)
				XplatUI.SetZOrder (vscrollbar.Handle, IntPtr.Zero, true, false);

			UpdateSizeGripVisible ();

			ResumeLayout (doLayout);
			
			// We should now scroll the active control into view, 
			// the funny part is that ScrollableControl does not have 
			// the concept of active control.
			ContainerControl container = this as ContainerControl;
			if (container != null && container.ActiveControl != null) {
				ScrollControlIntoView (container.ActiveControl);
			}
		}

		internal void UpdateSizeGripVisible ()
		{
			if (!IsHandleCreated) {
				return;
			}

			sizegrip.CapturedControl = Parent;
			// This is really wierd, the size grip is only showing up 
			// if the bottom right corner of the scrollable control is within
			// two pixels from the bottom right corner of its parent.
			bool show_sizegrip = hscrollbar.VisibleInternal && vscrollbar.VisibleInternal;
			bool enable_sizegrip = false;
			if (show_sizegrip && Parent != null) {
				Point diff = new Point (Parent.ClientRectangle.Bottom - Bottom, Parent.ClientRectangle.Right - Right);
				enable_sizegrip = diff.X <= 2 && diff.X >= 0 && diff.Y <= 2 && diff.Y >= 0;
			}
			sizegrip.Visible = show_sizegrip;
			sizegrip.Enabled = enable_sizegrip || sizegrip.Capture;
			if (sizegrip.Visible)
				XplatUI.SetZOrder (sizegrip.Handle, vscrollbar.Handle, false, false);
		}

		private void HandleScrollBar(object sender, EventArgs e) {
			if (sender == vscrollbar) {
				if (!vscrollbar.Visible)
					return;
				ScrollWindow(0, vscrollbar.Value- scroll_position.Y);
			} else {
				if (!hscrollbar.Visible)
					return;
				ScrollWindow(hscrollbar.Value - scroll_position.X, 0);
			}
		}

		private void HandleScrollEvent (object sender, ScrollEventArgs args)
		{
			OnScroll (args);
		}

		private void AddScrollbars (object o, EventArgs e)
		{
			Controls.AddRangeImplicit (new Control[] {hscrollbar, vscrollbar, sizegrip});
			HandleCreated -= new EventHandler (AddScrollbars);
		}

		private void CreateScrollbars ()
		{
			hscrollbar = new ImplicitHScrollBar ();
			hscrollbar.Visible = false;
			hscrollbar.ValueChanged += new EventHandler (HandleScrollBar);
			hscrollbar.Height = SystemInformation.HorizontalScrollBarHeight;
			hscrollbar.use_manual_thumb_size = true;
			hscrollbar.Scroll += new ScrollEventHandler (HandleScrollEvent);

			vscrollbar = new ImplicitVScrollBar ();
			vscrollbar.Visible = false;
			vscrollbar.ValueChanged += new EventHandler (HandleScrollBar);
			vscrollbar.Width = SystemInformation.VerticalScrollBarWidth;
			vscrollbar.use_manual_thumb_size = true;
			vscrollbar.Scroll += new ScrollEventHandler (HandleScrollEvent);

			sizegrip = new SizeGrip (this);
			sizegrip.Visible = false;
		}

		private void ScrollWindow(int XOffset, int YOffset) {
			int	num_of_children;

			if (XOffset == 0 && YOffset == 0) {
				return;
			}

			SuspendLayout();

			num_of_children = Controls.Count;

			for (int i = 0; i < num_of_children; i++) {
				Controls[i].Location = new Point (Controls[i].Left - XOffset, Controls[i].Top - YOffset);
				//Controls[i].Left -= XOffset;
				//Controls[i].Top -= YOffset;
				// Is this faster? Controls[i].Location -= new Size(XOffset, YOffset);
			}

			scroll_position.X += XOffset;
			scroll_position.Y += YOffset;

			XplatUI.ScrollWindow (Handle, ClientRectangle, -XOffset, -YOffset, false);
			ResumeLayout(false);
		}
		#endregion	// Internal & Private Methods

		static object OnScrollEvent = new object ();
		
		protected virtual void OnScroll (ScrollEventArgs se)
		{
			ScrollEventHandler eh = (ScrollEventHandler) (Events [OnScrollEvent]);
			if (eh != null)
				eh (this, se);
		}

		protected override void OnPaddingChanged (EventArgs e)
		{
			base.OnPaddingChanged (e);
		}
		
		protected override void OnPaintBackground (PaintEventArgs e)
		{
			base.OnPaintBackground (e);
		}

		[EditorBrowsable (EditorBrowsableState.Advanced)]
		protected override void OnRightToLeftChanged (EventArgs e)
		{
			base.OnRightToLeftChanged (e);
		}

		public event ScrollEventHandler Scroll {
			add { Events.AddHandler (OnScrollEvent, value); }
			remove { Events.RemoveHandler (OnScrollEvent, value); }
		}
	}
}
