// 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) 2005-2008 Novell, Inc. (http://www.novell.com)
//
// Authors:
//      Jonathan Chambers	(jonathan.chambers@ansys.com)
//      Ivan N. Zlatev		(contact@i-nz.net)
//
//

// NOT COMPLETE

using System;
using System.Collections;
using System.ComponentModel.Design;
using System.Drawing;
using System.Drawing.Design;
using System.ComponentModel;
using System.Threading;
using ShiftUI.Design;
using System.Collections.Generic;

namespace ShiftUI.PropertyGridInternal {
	internal class PropertyGridView : ScrollableWidget, IWindowsFormsEditorService {

		#region Private Members
		private const char PASSWORD_PAINT_CHAR = '\u25cf'; // the dot char
		private const char PASSWORD_TEXT_CHAR = '*';
		private const int V_INDENT = 16;
		private const int ENTRY_SPACING = 2;
		private const int RESIZE_WIDTH = 3;
		private const int BUTTON_WIDTH = 25;
		private const int VALUE_PAINT_WIDTH = 19;
		private const int VALUE_PAINT_INDENT = 27;
		private double splitter_percent = .5;
		private int row_height;
		private int font_height_padding = 3;
		private PropertyGridTextBox grid_textbox;
		private PropertyGrid property_grid;
		private bool resizing_grid;
		private PropertyGridDropDown dropdown_form;
		private Form dialog_form;
		private ImplicitVScrollBar vbar;
		private StringFormat string_format;
		private Font bold_font;
		private Brush inactive_text_brush;
		private ListBox dropdown_list;
		private Point last_click;
		private Padding dropdown_form_padding;
		#endregion

		#region Contructors
		public PropertyGridView (PropertyGrid propertyGrid) {
			property_grid = propertyGrid;

			string_format = new StringFormat ();
			string_format.FormatFlags = StringFormatFlags.NoWrap;
			string_format.Trimming = StringTrimming.None;

			grid_textbox = new PropertyGridTextBox ();
			grid_textbox.DropDownButtonClicked +=new EventHandler (DropDownButtonClicked);
			grid_textbox.DialogButtonClicked +=new EventHandler (DialogButtonClicked);

			dropdown_form = new PropertyGridDropDown ();
			dropdown_form.FormBorderStyle = FormBorderStyle.None;
			dropdown_form.StartPosition = FormStartPosition.Manual;
			dropdown_form.ShowInTaskbar = false;

			dialog_form = new Form ();
			dialog_form.StartPosition = FormStartPosition.Manual;
			dialog_form.FormBorderStyle = FormBorderStyle.None;
			dialog_form.ShowInTaskbar = false;

			dropdown_form_padding = new Padding (0, 0, 2, 2);

			row_height = Font.Height + font_height_padding;

			grid_textbox.Visible = false;
			grid_textbox.Font = this.Font;
			grid_textbox.BackColor = SystemColors.Window;
			grid_textbox.Validate += new CancelEventHandler (grid_textbox_Validate);
			grid_textbox.ToggleValue+=new EventHandler (grid_textbox_ToggleValue);
			grid_textbox.KeyDown+=new KeyEventHandler (grid_textbox_KeyDown);
			this.Widgets.Add (grid_textbox);

			vbar = new ImplicitVScrollBar ();
			vbar.Visible = false;
			vbar.Value = 0;
			vbar.ValueChanged+=new EventHandler (VScrollBar_HandleValueChanged);
			vbar.Dock = DockStyle.Right;
			this.Widgets.AddImplicit (vbar);

			resizing_grid = false;

			bold_font = new Font (this.Font, FontStyle.Bold);
			inactive_text_brush = new SolidBrush (ThemeEngine.Current.ColorGrayText);

			ForeColorChanged+=new EventHandler (RedrawEvent);
			BackColorChanged+=new System.EventHandler (RedrawEvent);
			FontChanged+=new EventHandler (RedrawEvent);
			
			SetStyle (Widgetstyles.Selectable, true);
			SetStyle (Widgetstyles.DoubleBuffer, true);
			SetStyle (Widgetstyles.UserPaint, true);
			SetStyle (Widgetstyles.AllPaintingInWmPaint, true);
			SetStyle (Widgetstyles.ResizeRedraw, true);
		}

		#endregion

		private GridEntry RootGridItem {
			get { return (GridEntry)property_grid.RootGridItem; }
		}

		private GridEntry SelectedGridItem {
			get { return (GridEntry)property_grid.SelectedGridItem; }
			set { property_grid.SelectedGridItem = value; }
		}

		#region Protected Instance Methods

		protected override void OnFontChanged (EventArgs e) {
			base.OnFontChanged (e);

			bold_font = new Font (this.Font, FontStyle.Bold);
			row_height = Font.Height + font_height_padding;
		}

		private void InvalidateItemLabel (GridEntry item)
		{
			Invalidate (new Rectangle (0, ((GridEntry)item).Top, SplitterLocation, row_height));
		}

		private void InvalidateItem (GridEntry item)
		{
			if (item == null)
				return;

			Rectangle rect = new Rectangle (0, item.Top, Width, row_height);
			Invalidate (rect);

			if (item.Expanded) {
				rect = new Rectangle (0, item.Top + row_height, Width,
						      Height - (item.Top + row_height));
				Invalidate (rect);
			}
		}

		// [+] expanding is handled in OnMouseDown, so in order to prevent 
		// duplicate expanding ignore it here.
		// 
		protected override void OnDoubleClick (EventArgs e) 
		{
			if (this.SelectedGridItem != null && this.SelectedGridItem.Expandable && 
			    !this.SelectedGridItem.PlusMinusBounds.Contains (last_click))
				this.SelectedGridItem.Expanded = !this.SelectedGridItem.Expanded;
			else
				ToggleValue (this.SelectedGridItem);
		}

		protected override void OnPaint (PaintEventArgs e) 
		{
			// Background
			e.Graphics.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (BackColor), ClientRectangle);
			
			int yLoc = -vbar.Value*row_height;
			if (this.RootGridItem != null)
				DrawGridItems (this.RootGridItem.GridItems, e, 1, ref yLoc);

			UpdateScrollBar ();
		}

		protected override void OnMouseWheel (MouseEventArgs e) 
		{
			if (vbar == null || !vbar.Visible)
				return;
			if (e.Delta < 0)
				vbar.Value = Math.Min (vbar.Maximum - GetVisibleRowsCount () + 1, vbar.Value + SystemInformation.MouseWheelScrollLines);
			else
				vbar.Value = Math.Max (0, vbar.Value - SystemInformation.MouseWheelScrollLines);
			base.OnMouseWheel (e);
		}


		protected override void OnMouseMove (MouseEventArgs e) {
			if (this.RootGridItem == null)
				return;

			if (resizing_grid) {
				int loc = Math.Max (e.X,2*V_INDENT);
				SplitterPercent = 1.0*loc/Width;
			}
			if (e.X > SplitterLocation - RESIZE_WIDTH && e.X < SplitterLocation + RESIZE_WIDTH) 
				this.Cursor = Cursors.SizeWE;
			else
				this.Cursor = Cursors.Default;
			base.OnMouseMove (e);
		}

		protected override void OnMouseDown (MouseEventArgs e) 
		{
			base.OnMouseDown (e);
			last_click = e.Location;
			if (this.RootGridItem == null)
				return;

			if (e.X > SplitterLocation - RESIZE_WIDTH && e.X < SplitterLocation + RESIZE_WIDTH) {
				resizing_grid = true;
			}
			else {
				int offset = -vbar.Value*row_height;
				GridItem foundItem = GetSelectedGridItem (this.RootGridItem.GridItems, e.Y, ref offset);

				if (foundItem != null) {
					if (foundItem.Expandable && ((GridEntry)foundItem).PlusMinusBounds.Contains (e.X, e.Y))
						foundItem.Expanded = !foundItem.Expanded;
					
					this.SelectedGridItem = (GridEntry)foundItem;
					if (!GridLabelHitTest (e.X)) {
						// send mouse down so we get the carret under cursor
						grid_textbox.SendMouseDown (PointToScreen (e.Location));
					}
				}
			}
		}

		protected override void OnMouseUp (MouseEventArgs e) {
			resizing_grid = false;
			base.OnMouseUp (e);
		}

		protected override void OnResize (EventArgs e) {
			base.OnResize (e);
			if (this.SelectedGridItem != null) // initialized already
				UpdateView ();
		}

		private void UnfocusSelection ()
		{
			Select (this);
		}

		private void FocusSelection ()
		{
			Select (grid_textbox);
		}

		protected override bool ProcessDialogKey (Keys keyData) {
			GridEntry selectedItem = this.SelectedGridItem;
			if (selectedItem != null
			    && grid_textbox.Visible) {
				switch (keyData) {
				case Keys.Enter:
					if (TrySetEntry (selectedItem, grid_textbox.Text))
						UnfocusSelection ();
					return true;
				case Keys.Escape:
					if (selectedItem.IsEditable)
						UpdateItem (selectedItem); // reset value
					UnfocusSelection ();
					return true;
				case Keys.Tab:
					FocusSelection ();
					return true;
				default:
					return false;
				}
			}
			return base.ProcessDialogKey (keyData);
		}

		private bool TrySetEntry (GridEntry entry, object value)
		{
			if (entry == null || grid_textbox.Text.Equals (entry.ValueText))
				return true;

			if (entry.IsEditable || !entry.IsEditable && (entry.HasCustomEditor || entry.AcceptedValues != null) ||
			    !entry.IsMerged || entry.HasMergedValue || 
			    (!entry.HasMergedValue && grid_textbox.Text != String.Empty)) {
				string error = null;
				bool changed = entry.SetValue (value, out error);
				if (!changed && error != null) {
					if (property_grid.ShowError (error, MessageBoxButtons.OKCancel) == DialogResult.Cancel) {
						UpdateItem (entry); // restore value, repaint, etc
						UnfocusSelection ();
					}
					return false;
				}
			}
			UpdateItem (entry); // restore value, repaint, etc
			return true;
		}

		protected override bool IsInputKey (Keys keyData) {
			switch (keyData) {
			case Keys.Left:
			case Keys.Right:
			case Keys.Enter:
			case Keys.Escape:
			case Keys.Up:
			case Keys.Down:
			case Keys.PageDown:
			case Keys.PageUp:
			case Keys.Home:
			case Keys.End:
				return true;
			default:
				return false;
			}
		}

		private GridEntry MoveUpFromItem (GridEntry item, int up_count)
		{
			GridItemCollection items;
			int index;

			/* move back up the visible rows (and up the hierarchy as necessary) until
			   up_count == 0, or we reach the top of the display */
			while (up_count > 0) {
				items = item.Parent != null ? item.Parent.GridItems : this.RootGridItem.GridItems;
				index = items.IndexOf (item);

				if (index == 0) {
					if (item.Parent.GridItemType == GridItemType.Root) // we're at the top row
						return item;
					item = (GridEntry)item.Parent;
					up_count --;
				}
				else {
					GridEntry prev_item = (GridEntry)items[index-1];
					if (prev_item.Expandable && prev_item.Expanded) {
						item = (GridEntry)prev_item.GridItems[prev_item.GridItems.Count - 1];
					}
					else {
						item = prev_item;
					}
					up_count --;
				}
			}
			return item;
		}

		private GridEntry MoveDownFromItem (GridEntry item, int down_count)
		{
			while (down_count > 0) {
				/* if we're a parent node and we're expanded, move to our first child */
				if (item.Expandable && item.Expanded) {
					item = (GridEntry)item.GridItems[0];
					down_count--;
				}
				else {
					GridItem searchItem = item;
					GridItemCollection searchItems = searchItem.Parent.GridItems;
					int searchIndex = searchItems.IndexOf (searchItem);

					while (searchIndex == searchItems.Count - 1) {
						searchItem = searchItem.Parent;

						if (searchItem == null || searchItem.Parent == null)
							break;

						searchItems = searchItem.Parent.GridItems;
						searchIndex = searchItems.IndexOf (searchItem);
					}

					if (searchIndex == searchItems.Count - 1) {
						/* if we got all the way back to the root with no nodes after
						   us, the original item was the last one */
						return item;
					}
					else {
						item = (GridEntry)searchItems[searchIndex+1];
						down_count--;
					}
				}
			}

			return item;
		}

		protected override void OnKeyDown (KeyEventArgs e) 
		{
			GridEntry selectedItem = this.SelectedGridItem;

			if (selectedItem == null) {
				/* XXX not sure what MS does, but at least we shouldn't crash */
				base.OnKeyDown (e);
				return;
			}

			switch (e.KeyData & Keys.KeyCode) {
			case Keys.Left:
				if (e.Widget) {
					if (SplitterLocation > 2 * V_INDENT)
						SplitterPercent -= 0.01;

					e.Handled = true;
					break;
				}
				else {
					/* if the node is expandable and is expanded, collapse it.
					   otherwise, act just like the user pressed up */
					if (selectedItem.Expandable && selectedItem.Expanded) {
						selectedItem.Expanded = false;
						e.Handled = true;
						break;
					}
					else
						goto case Keys.Up;
				}
			case Keys.Right:
				if (e.Widget) {
					if (SplitterLocation < Width)
						SplitterPercent += 0.01;

					e.Handled = true;
					break;
				}
				else {
					/* if the node is expandable and not expanded, expand it.
					   otherwise, act just like the user pressed down */
					if (selectedItem.Expandable && !selectedItem.Expanded) {
						selectedItem.Expanded = true;
						e.Handled = true;
						break;
					}
					else
						goto case Keys.Down;
				}
			case Keys.Enter:
				/* toggle the expanded state of the selected item */
				if (selectedItem.Expandable) {
					selectedItem.Expanded = !selectedItem.Expanded;
				}
				e.Handled = true;
				break;
			case Keys.Up:
				this.SelectedGridItem = MoveUpFromItem (selectedItem, 1);
				e.Handled = true;
				break;
			case Keys.Down:
				this.SelectedGridItem = MoveDownFromItem (selectedItem, 1);
				e.Handled = true;
				break;
			case Keys.PageUp:
				this.SelectedGridItem = MoveUpFromItem (selectedItem, vbar.LargeChange);
				e.Handled = true;
				break;
			case Keys.PageDown:
				this.SelectedGridItem = MoveDownFromItem (selectedItem, vbar.LargeChange);
				e.Handled = true;
				break;
			case Keys.End:
				/* find the last, most deeply nested visible item */
				GridEntry item = (GridEntry)this.RootGridItem.GridItems[this.RootGridItem.GridItems.Count - 1];
				while (item.Expandable && item.Expanded)
					item = (GridEntry)item.GridItems[item.GridItems.Count - 1];
				this.SelectedGridItem = item;
				e.Handled = true;
				break;
			case Keys.Home:
				this.SelectedGridItem = (GridEntry)this.RootGridItem.GridItems[0];
				e.Handled = true;
				break;
			}

			base.OnKeyDown (e);
		}

		#endregion

		#region Private Helper Methods

		private int SplitterLocation {
			get {
				return (int)(splitter_percent*Width);
			}
		}

		private double SplitterPercent {
			set {
				int old_splitter_location = SplitterLocation;
				
				splitter_percent = Math.Max (Math.Min (value, .9),.1);

				if (old_splitter_location != SplitterLocation) {
					int x = old_splitter_location > SplitterLocation ? SplitterLocation : old_splitter_location;
					Invalidate (new Rectangle (x, 0, Width - x - (vbar.Visible ? vbar.Width : 0), Height));
					UpdateItem (this.SelectedGridItem);
				}
			}
			get {
				return splitter_percent;
			}
		}

		private bool GridLabelHitTest (int x)
		{
			if (0 <= x && x <= splitter_percent * this.Width)
				return true;
			return false;
		}

		private GridItem GetSelectedGridItem (GridItemCollection grid_items, int y, ref int current) {
			foreach (GridItem child_grid_item in grid_items) {
				if (y > current && y < current + row_height) {
					return child_grid_item;
				}
				current += row_height;
				if (child_grid_item.Expanded) {
					GridItem foundItem = GetSelectedGridItem (child_grid_item.GridItems, y, ref current);
					if (foundItem != null)
						return foundItem;
				}
			}
			return null;
		}

		private int GetVisibleItemsCount (GridEntry entry)
		{
			if (entry == null)
				return 0;

			int count = 0;
			foreach (GridEntry e in entry.GridItems) {
				count += 1;
				if (e.Expandable && e.Expanded)
					count += GetVisibleItemsCount (e);
			}
			return count;
		}

		private int GetVisibleRowsCount ()
		{
			return this.Height / row_height;
		}

		private void UpdateScrollBar ()
		{
			if (this.RootGridItem == null)
				return;

			int visibleRows = GetVisibleRowsCount ();
			int openedItems = GetVisibleItemsCount (this.RootGridItem);
			if (openedItems > visibleRows) {
				vbar.Visible = true;
				vbar.SmallChange = 1;
				vbar.LargeChange = visibleRows;
				vbar.Maximum = Math.Max (0, openedItems - 1);
			} else {
				vbar.Value = 0;
				vbar.Visible = false;
			}
			UpdateGridTextBoxBounds (this.SelectedGridItem);
		}

		// private bool GetScrollBarVisible ()
		// {
		// 	if (this.RootGridItem == null)
		// 		return false;
                // 
		// 	int visibleRows = GetVisibleRowsCount ();
		// 	int openedItems = GetVisibleItemsCount (this.RootGridItem);
		// 	if (openedItems > visibleRows)
		// 		return true;
		// 	return false;
		// }
		#region Drawing Code

		private void DrawGridItems (GridItemCollection grid_items, PaintEventArgs pevent, int depth, ref int yLoc) {
			foreach (GridItem grid_item in grid_items) {
				DrawGridItem ((GridEntry)grid_item, pevent, depth, ref yLoc);
				if (grid_item.Expanded)
					DrawGridItems (grid_item.GridItems, pevent, (grid_item.GridItemType == GridItemType.Category) ? depth : depth+1, ref yLoc);
			}
		}

		private void DrawGridItemLabel (GridEntry grid_item, PaintEventArgs pevent, int depth, Rectangle rect) {
			Font font = this.Font;
			Brush brush;

			if (grid_item.GridItemType == GridItemType.Category) {
				font = bold_font;
				brush = SystemBrushes.ControlText;

				pevent.Graphics.DrawString (grid_item.Label, font, brush, rect.X + 1, rect.Y + ENTRY_SPACING);
				if (grid_item == this.SelectedGridItem) {
					SizeF size = pevent.Graphics.MeasureString (grid_item.Label, font);
					WidgetPaint.DrawFocusRectangle (pevent.Graphics, new Rectangle (rect.X + 1, rect.Y+ENTRY_SPACING, (int)size.Width, (int)size.Height));
				}
			}
			else {
				if (grid_item == this.SelectedGridItem) {
					Rectangle highlight = rect;
					if (depth > 1) {
						highlight.X -= V_INDENT;
						highlight.Width += V_INDENT;
					}
					pevent.Graphics.FillRectangle (SystemBrushes.Highlight, highlight);
					// Label
					brush = SystemBrushes.HighlightText;
				}
				else {
					brush = grid_item.IsReadOnly ? inactive_text_brush : SystemBrushes.ControlText;
				}
			}
			pevent.Graphics.DrawString (grid_item.Label, font, brush,
						    new Rectangle (rect.X + 1, rect.Y + ENTRY_SPACING, rect.Width - ENTRY_SPACING, rect.Height - ENTRY_SPACING),
						    string_format);
		}

		private void DrawGridItemValue (GridEntry grid_item, PaintEventArgs pevent, int depth, Rectangle rect) 
		{
			if (grid_item.PropertyDescriptor == null)
				return; 

			int xLoc = SplitterLocation+ENTRY_SPACING;
			if (grid_item.PaintValueSupported) {
				pevent.Graphics.DrawRectangle (Pens.Black, SplitterLocation + ENTRY_SPACING, 
							       rect.Y + 2, VALUE_PAINT_WIDTH + 1, row_height - ENTRY_SPACING*2);
				grid_item.PaintValue (pevent.Graphics, 
						      new Rectangle (SplitterLocation + ENTRY_SPACING + 1, 
								     rect.Y + ENTRY_SPACING + 1,
								     VALUE_PAINT_WIDTH, row_height - (ENTRY_SPACING*2 +1)));
				xLoc += VALUE_PAINT_INDENT;
			}

			Font font = this.Font;
			if (grid_item.IsResetable || !grid_item.HasDefaultValue)
				font = bold_font;
			Brush brush = grid_item.IsReadOnly ? inactive_text_brush : SystemBrushes.ControlText;
			string valueText = String.Empty;
			if (!grid_item.IsMerged || grid_item.IsMerged && grid_item.HasMergedValue) {
				if (grid_item.IsPassword)
					valueText = new String (PASSWORD_PAINT_CHAR, grid_item.ValueText.Length);
				else
					valueText = grid_item.ValueText;
			}
			pevent.Graphics.DrawString (valueText, font,
						    brush,
						    new RectangleF (xLoc + ENTRY_SPACING, rect.Y + ENTRY_SPACING,
								    ClientRectangle.Width-(xLoc), row_height - ENTRY_SPACING*2), 
						    string_format);
		}

		private void DrawGridItem (GridEntry grid_item, PaintEventArgs pevent, int depth, ref int yLoc) {
			if (yLoc > -row_height && yLoc < ClientRectangle.Height) {
				// Left column
				pevent.Graphics.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (property_grid.LineColor),
							       0, yLoc, V_INDENT, row_height);
			
				if (grid_item.GridItemType == GridItemType.Category) {
					pevent.Graphics.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (property_grid.CategoryForeColor), depth*V_INDENT,yLoc,ClientRectangle.Width-(depth*V_INDENT), row_height);
				}

				DrawGridItemLabel (grid_item, pevent,
						   depth,
						  new Rectangle (depth * V_INDENT, yLoc, SplitterLocation - depth * V_INDENT, row_height));
				DrawGridItemValue (grid_item, pevent,
						  depth,
						  new Rectangle (SplitterLocation + ENTRY_SPACING , yLoc, 
								 ClientRectangle.Width - SplitterLocation - ENTRY_SPACING - (vbar.Visible ? vbar.Width : 0), 
								 row_height));

				if (grid_item.GridItemType != GridItemType.Category) {
					Pen pen = ThemeEngine.Current.ResPool.GetPen (property_grid.LineColor);
					// vertical divider line
					pevent.Graphics.DrawLine (pen, SplitterLocation, yLoc, SplitterLocation, yLoc + row_height);
			
					// draw the horizontal line
					pevent.Graphics.DrawLine (pen, 0, yLoc + row_height, ClientRectangle.Width, yLoc + row_height);
				}				
				
				if (grid_item.Expandable) {
					int y = yLoc + row_height / 2 - ENTRY_SPACING + 1;
					grid_item.PlusMinusBounds = DrawPlusMinus (pevent.Graphics, (depth - 1) * V_INDENT + ENTRY_SPACING + 1, 
										   y, grid_item.Expanded, grid_item.GridItemType == GridItemType.Category);
				}

			}
			grid_item.Top = yLoc;
			yLoc += row_height;
		}

		private Rectangle DrawPlusMinus (Graphics g, int x, int y, bool expanded, bool category) {
			Rectangle bounds = new Rectangle (x, y, 8, 8);
			if (!category) g.FillRectangle (Brushes.White, bounds);
			Pen pen = ThemeEngine.Current.ResPool.GetPen (property_grid.ViewForeColor);
			g.DrawRectangle (pen, bounds);
			g.DrawLine (pen, x+2, y+4, x + 6, y+4);
			if (!expanded)
				g.DrawLine (pen, x+4, y+2, x+4, y+6);

			return bounds;
		}

		#endregion

		#region Event Handling
		private void RedrawEvent (object sender, System.EventArgs e) 
		{
			Refresh ();
		}

		#endregion

		private void listBox_MouseUp (object sender, MouseEventArgs e) {
			AcceptListBoxSelection (sender);
		}

		private void listBox_KeyDown (object sender, KeyEventArgs e)
		{
			switch (e.KeyData & Keys.KeyCode) {
			case Keys.Enter:
				AcceptListBoxSelection (sender);
				return;
			case Keys.Escape:
				CloseDropDown ();
				return;
			}
		}

		void AcceptListBoxSelection (object sender) 
		{
			GridEntry entry = this.SelectedGridItem as GridEntry;
			if (entry != null) {
				grid_textbox.Text = (string) ((ListBox) sender).SelectedItem;
				CloseDropDown ();
				if (TrySetEntry (entry, grid_textbox.Text))
					UnfocusSelection ();
			}
		}

		private void DropDownButtonClicked (object sender, EventArgs e) 
		{
			DropDownEdit ();
		}

		private void DropDownEdit ()
		{
			GridEntry entry = SelectedGridItem as GridEntry;
			if (entry == null)
				return;

			if (entry.HasCustomEditor) {
				entry.EditValue ((IWindowsFormsEditorService) this);
			} else {
				if (dropdown_form.Visible) {
					CloseDropDown ();
				}
				else {
					ICollection std_values = entry.AcceptedValues;
					if (std_values != null) {
						if (dropdown_list == null) {
							dropdown_list = new ListBox ();
							dropdown_list.KeyDown += new KeyEventHandler (listBox_KeyDown);
							dropdown_list.MouseUp += new MouseEventHandler (listBox_MouseUp);
						}
						dropdown_list.Items.Clear ();
						dropdown_list.BorderStyle = BorderStyle.FixedSingle;
						int selected_index = 0;
						int i = 0;
						string valueText = entry.ValueText;
						foreach (object obj in std_values) {
							dropdown_list.Items.Add (obj);
							if (valueText != null && valueText.Equals (obj))
								selected_index = i;
							i++;
						}
						dropdown_list.Height = row_height * Math.Min (dropdown_list.Items.Count, 15);
						dropdown_list.Width = ClientRectangle.Width - SplitterLocation - (vbar.Visible ? vbar.Width : 0);
						if (std_values.Count > 0)
							dropdown_list.SelectedIndex = selected_index;
						DropDownWidget (dropdown_list);
					}
				}
			}
		}

		private void DialogButtonClicked (object sender, EventArgs e) 
		{
			GridEntry entry = this.SelectedGridItem as GridEntry;
            if (entry != null && entry.HasCustomEditor)
            {
                entry.Value = GetResultFromEditor(entry.Value);
            }
		}

        public object GetResultFromEditor(object val)
        {
            var EditorDialogs = new Dictionary<Type, Dialogs.IEditorDialog>();
            EditorDialogs.Add(typeof(string[]), new Dialogs.StringArrayDialog());
            EditorDialogs.Add(typeof(ComboBox.ObjectCollection), new Dialogs.ComboBoxEditorDialog((val as ComboBox.ObjectCollection).Owner));
            object retval = val;
            try
            {
                var dialog = EditorDialogs[retval.GetType()];
                dialog.ShowEditor();
                retval = dialog.Value;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Object {retval} is not valid for the given property. {ex.Message}", "Property Grid");
                retval = val;
            }
            MessageBox.Show($"val: {val.GetType().Name}, retval: {retval.GetType().Name}", "Property Grid");
            return retval;
        }

		private void VScrollBar_HandleValueChanged (object sender, EventArgs e) 
		{
			UpdateView ();
		}

		private void grid_textbox_ToggleValue (object sender, EventArgs args) 
		{
			ToggleValue (this.SelectedGridItem);
		}

		private void grid_textbox_KeyDown (object sender, KeyEventArgs e) 
		{
			switch (e.KeyData & Keys.KeyCode) {
			case Keys.Down:
				if (e.Alt) {
					DropDownEdit ();
					e.Handled = true;
				}
				break;
			}
		}

		private void grid_textbox_Validate (object sender, CancelEventArgs args)
		{
			if (!TrySetEntry (this.SelectedGridItem, grid_textbox.Text))
				args.Cancel = true;
		}

		private void ToggleValue (GridEntry entry)
		{
			if (entry != null && !entry.IsReadOnly && entry.GridItemType == GridItemType.Property)
				entry.ToggleValue ();
		}

		internal void UpdateItem (GridEntry entry)
		{
			if (entry == null || entry.GridItemType == GridItemType.Category || 
			    entry.GridItemType == GridItemType.Root) {
				grid_textbox.Visible = false;
				InvalidateItem (entry);
				return;
			}

			if (this.SelectedGridItem == entry) {
				SuspendLayout ();
				grid_textbox.Visible = false;
				if (entry.IsResetable || !entry.HasDefaultValue)
					grid_textbox.Font = bold_font;
				else
					grid_textbox.Font = this.Font;

				if (entry.IsReadOnly) {
					grid_textbox.DropDownButtonVisible = false;
					grid_textbox.DialogButtonVisible = false;
					grid_textbox.ReadOnly = true;
					grid_textbox.ForeColor = SystemColors.GrayText;
				} else {
					grid_textbox.DropDownButtonVisible = entry.AcceptedValues != null || 
						entry.EditorStyle == UITypeEditorEditStyle.DropDown;
					grid_textbox.DialogButtonVisible = entry.EditorStyle == UITypeEditorEditStyle.Modal;
					grid_textbox.ForeColor = SystemColors.ControlText;
					grid_textbox.ReadOnly = !entry.IsEditable;
				}
				UpdateGridTextBoxBounds (entry);
				grid_textbox.PasswordChar = entry.IsPassword ? PASSWORD_TEXT_CHAR : '\0';
				grid_textbox.Text = entry.IsMerged && !entry.HasMergedValue ? String.Empty : entry.ValueText;
				grid_textbox.Visible = true;
				InvalidateItem (entry);
				ResumeLayout (false);
			} else {
				grid_textbox.Visible = false;
			}
		}

		private void UpdateGridTextBoxBounds (GridEntry entry)
		{
			if (entry == null || this.RootGridItem == null)
				return;

			int y = -vbar.Value*row_height;
			CalculateItemY (entry, this.RootGridItem.GridItems, ref y);
			int x = SplitterLocation + ENTRY_SPACING + (entry.PaintValueSupported ? VALUE_PAINT_INDENT : 0);
			grid_textbox.SetBounds (x + ENTRY_SPACING, y + ENTRY_SPACING,
						ClientRectangle.Width - ENTRY_SPACING - x - (vbar.Visible ? vbar.Width : 0),
						row_height - ENTRY_SPACING);
		}

		// Calculates the sum of the heights of all items before the one
		//
		private bool CalculateItemY (GridEntry entry, GridItemCollection items, ref int y)
		{
			foreach (GridItem item in items) {
				if (item == entry)
					return true;
				y += row_height;
				if (item.Expandable && item.Expanded)
					if (CalculateItemY (entry, item.GridItems, ref y))
						return true;
			}
			return false;
		}

		private void ScrollToItem (GridEntry item)
		{
			if (item == null || this.RootGridItem == null)
				return;

			int itemY = -vbar.Value*row_height;
			int value = vbar.Value;;
			CalculateItemY (item, this.RootGridItem.GridItems, ref itemY);
			if (itemY < 0) // the new item is above the viewable area
				value += itemY / row_height;
			else if (itemY + row_height > Height) // the new item is below the viewable area
				value += ((itemY + row_height) - Height) / row_height + 1;
			if (value >= vbar.Minimum && value <= vbar.Maximum)
				vbar.Value = value;
		}

		internal void SelectItem (GridEntry oldItem, GridEntry newItem) 
		{
			if (oldItem != null)
				InvalidateItemLabel (oldItem);
			if (newItem != null) {
				UpdateItem (newItem);
				ScrollToItem (newItem);
			} else {
				grid_textbox.Visible = false;
				vbar.Visible = false;
			}
		}

		internal void UpdateView ()
		{
			UpdateScrollBar ();
			Invalidate ();
			Update ();
			UpdateItem (this.SelectedGridItem);
		}

		internal void ExpandItem (GridEntry item)
		{
			UpdateItem (this.SelectedGridItem);
			Invalidate (new Rectangle (0, item.Top, Width, Height - item.Top));
		}

		internal void CollapseItem (GridEntry item)
		{
			UpdateItem (this.SelectedGridItem);
			Invalidate (new Rectangle (0, item.Top, Width, Height - item.Top));
		}

		private void ShowDropDownControl (Widget control, bool resizeable) 
		{
			dropdown_form.Size = control.Size;
			control.Dock = DockStyle.Fill;

			if (resizeable) {
				dropdown_form.Padding = dropdown_form_padding;
				dropdown_form.Width += dropdown_form_padding.Right;
				dropdown_form.Height += dropdown_form_padding.Bottom;
				dropdown_form.FormBorderStyle = FormBorderStyle.Sizable;
				dropdown_form.SizeGripStyle = SizeGripStyle.Show;
			} else {
				dropdown_form.FormBorderStyle = FormBorderStyle.None;
				dropdown_form.SizeGripStyle = SizeGripStyle.Hide;
				dropdown_form.Padding = Padding.Empty;
			}

			dropdown_form.Widgets.Add (control);
			dropdown_form.Width = Math.Max (ClientRectangle.Width - SplitterLocation - (vbar.Visible ? vbar.Width : 0), 
							control.Width);
			dropdown_form.Location = PointToScreen (new Point (grid_textbox.Right - dropdown_form.Width, grid_textbox.Location.Y + row_height));
			RepositionInScreenWorkingArea (dropdown_form);
			Point location = dropdown_form.Location;

			Form owner = FindForm ();
			owner.AddOwnedForm (dropdown_form);
			dropdown_form.Show ();
			if (dropdown_form.Location != location)
				dropdown_form.Location = location;
            control.Show();
			ShiftUI.MSG msg = new MSG ();
			object queue_id = XplatUI.StartLoop (Thread.CurrentThread);
			control.Focus ();
			while (dropdown_form.Visible && XplatUI.GetMessage (queue_id, ref msg, IntPtr.Zero, 0, 0)) {
				switch (msg.message) {
					case Msg.WM_NCLBUTTONDOWN:
				    case Msg.WM_NCMBUTTONDOWN:
				    case Msg.WM_NCRBUTTONDOWN:
				    case Msg.WM_LBUTTONDOWN:
				    case Msg.WM_MBUTTONDOWN:
				    case Msg.WM_RBUTTONDOWN:
				    	if (!HwndInControl (dropdown_form, msg.hwnd))
							CloseDropDown ();
					break;
					case Msg.WM_ACTIVATE:
					case Msg.WM_NCPAINT:
				 		if (owner.window.Handle == msg.hwnd)
							CloseDropDown ();
					break;						
				}
				XplatUI.TranslateMessage (ref msg);
				XplatUI.DispatchMessage (ref msg);
			}
			XplatUI.EndLoop (Thread.CurrentThread);			
		}

		private void RepositionInScreenWorkingArea (Form form)
		{
			Rectangle workingArea = Screen.FromControl (form).WorkingArea;
			if (!workingArea.Contains (form.Bounds)) {
				int x, y;
				x = form.Location.X;
				y = form.Location.Y;

				if (form.Location.X < workingArea.X)
					x = workingArea.X;

				if (form.Location.Y + form.Size.Height > workingArea.Height) {
					Point aboveTextBox = PointToScreen (new Point (grid_textbox.Right - form.Width, grid_textbox.Location.Y));
					y = aboveTextBox.Y - form.Size.Height;
				}

				form.Location = new Point (x, y);
			}
		}

		private bool HwndInControl (Widget c, IntPtr hwnd)
		{
			if (hwnd == c.window.Handle)
				return true;
			foreach (Widget cc in c.Widgets.GetAllWidgets ()) {
				if (HwndInControl (cc, hwnd))
					return true;
			}
			return false;
		}
		#endregion

		#region IWindowsFormsEditorService Members


		public void CloseDropDown () 
		{
			dropdown_form.Hide ();
			dropdown_form.Widgets.Clear ();
		}

		public void DropDownWidget (Widget control) 
		{
			bool resizeable = this.SelectedGridItem != null ? SelectedGridItem.EditorResizeable : false;
			ShowDropDownControl (control, resizeable);
		}

		public ShiftUI.DialogResult ShowDialog (Form dialog) {
			return dialog.ShowDialog (this);
		}

        #endregion

        internal class PropertyGridDropDown : Form 
		{
			protected override CreateParams CreateParams {
				get {
					CreateParams cp = base.CreateParams;
					cp.Style = unchecked ((int)(WindowStyles.WS_POPUP | WindowStyles.WS_CLIPSIBLINGS | WindowStyles.WS_CLIPCHILDREN));
					cp.ExStyle |= (int)(WindowExStyles.WS_EX_TOPMOST);				
					return cp;
				}
			}

		}
	}
}