using System;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;
using Volian.Controls.Library;
using C1.Win.C1FlexGrid;
namespace Volian.Controls.Library
{
    public partial class VlnFlexGrid : C1.Win.C1FlexGrid.C1FlexGrid
    {
		private TableCellEditor _tableCellEditor;
		private TableClipBoardFuncts _clpbrdCpyPste;
		#region Grid Initialize
		public VlnFlexGrid()
        {
            InitializeComponent();
			SetupGrid(4, 3); // use a default row and column count
		}
		public VlnFlexGrid(int rows, int cols)
		{
			InitializeComponent();
			SetupGrid(rows, cols);
		}
        public VlnFlexGrid(IContainer container)
        {
            container.Add(this);
            InitializeComponent();
			_tableCellEditor = new TableCellEditor(this);
		}
		private void SetupGrid(int numrows, int numcols) //C1FlexGrid NewGrid()
		{
			// setup the default size of each cell in the table/grid
			this.Cols.DefaultSize = 40;
			this.Rows.DefaultSize = 20;
			// setup the number of rows and columns
			this.Rows.Count = numrows;
			this.Cols.Count = numcols;
			// make all the cell editable
			this.Rows.Fixed = 0;
			this.Cols.Fixed = 0;
			this.DrawMode = DrawModeEnum.OwnerDraw;
			this.ScrollBars =  ScrollBars.None;
			// grid styles
			this.Styles.EmptyArea.BackColor = Color.Transparent;
			this.Styles.EmptyArea.Border.Style = BorderStyleEnum.None;
			this.Styles.Normal.Border.Color = Color.Black;
			this.Styles.Normal.TextAlign = C1.Win.C1FlexGrid.TextAlignEnum.LeftCenter;
			SetupCellStyles();
			this.FocusRect = FocusRectEnum.Solid;
			this.Styles.Highlight.BackColor = Color.LightCyan;
			this.Styles.Highlight.ForeColor = Color.Black;
			this.Styles.Focus.BackColor = Color.LightCyan;
			this.HighLight = HighLightEnum.Always;
			this.AllowMerging = C1.Win.C1FlexGrid.AllowMergingEnum.Custom;
			this.AllowResizing = C1.Win.C1FlexGrid.AllowResizingEnum.Both;
			_tableCellEditor = new TableCellEditor(this);
			_clpbrdCpyPste = new TableClipBoardFuncts();
			//this.Enter += new System.EventHandler(this.Grid_Enter);
			this.AfterResizeRow += new C1.Win.C1FlexGrid.RowColEventHandler(this.Grid_AfterResize);
			this.StartEdit += new C1.Win.C1FlexGrid.RowColEventHandler(this._StartEdit);
			this.AfterEdit += new C1.Win.C1FlexGrid.RowColEventHandler(this._AfterEdit);
			this.AfterScroll += new C1.Win.C1FlexGrid.RangeEventHandler(this._AfterScroll);
			this.AfterResizeColumn += new C1.Win.C1FlexGrid.RowColEventHandler(this.Grid_AfterResize);
			this.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this._KeyPress);
			this.OwnerDrawCell += new OwnerDrawCellEventHandler(this.Grid_OwnerDrawCell);
		}
		private void Grid_OwnerDrawCell(object sender, C1.Win.C1FlexGrid.OwnerDrawCellEventArgs e)
		{
			RTF _rtf = new RTF();
			// use nearest solid color
			// (the RTF control doesn't dither, and doesn't support transparent backgrounds)
			Color solid = e.Graphics.GetNearestColor(e.Style.BackColor);
			if (e.Style.BackColor != solid)
				e.Style.BackColor = solid;
			// check whether the cell contains RTF
			string rtfText = this.GetDataDisplay(e.Row, e.Col);
			if (rtfText.StartsWith(@"{\rtf"))
			{
				// it does, so draw background
				e.DrawCell(DrawCellFlags.Background);
				// draw the RTF text
				if (e.Bounds.Width > 0 && e.Bounds.Height > 0)
				{
					_rtf.Rtf = rtfText;
					_rtf.ForeColor = e.Style.ForeColor;
					_rtf.BackColor = e.Style.BackColor;
					_rtf.Render(e.Graphics, e.Bounds);
				}
				// and draw border last
				e.DrawCell(DrawCellFlags.Border);
				// we're done with this cell
				e.Handled = true;
			}
		}
		#endregion //Grid Initialize
		#region Grid and Cell Styles
		public void CellBackgroundYellow()
		{
			CellRange cr = this.Selection;
			cr.Style = this.Styles["Yellow"];
		}
		public void ToggleCellTextAlignment()
		{
			CellRange cr = this.Selection;
			StepRTB srtb = new StepRTB();
			srtb.Rtf = this.GetCellRTFString(cr.r1, cr.c1);
			srtb.SelectAll();
			int align = (int)srtb.SelectionAlignment;
			align = (align + 1) % 3;
			srtb.SelectionAlignment = (HorizontalAlignment)align;
			this.PutCellRTFString(cr.r1, cr.c1, srtb.Rtf);
		}
		public void TableBorderNone()
		{
			this.BorderStyle = C1.Win.C1FlexGrid.Util.BaseControls.BorderStyleEnum.None;
			this.AdjustGridControlSize();
		}
		public void TableBorderFixedSingle()
		{
			this.BorderStyle = C1.Win.C1FlexGrid.Util.BaseControls.BorderStyleEnum.FixedSingle;
			this.AdjustGridControlSize();
		}
		public void TableBorderFixed3d()
		{
			this.BorderStyle = C1.Win.C1FlexGrid.Util.BaseControls.BorderStyleEnum.Fixed3D;
			this.AdjustGridControlSize();
		}
		public void TableBorderLight3D()
		{
			this.BorderStyle = C1.Win.C1FlexGrid.Util.BaseControls.BorderStyleEnum.Light3D;
			this.AdjustGridControlSize();
		}
		public void TableBorderXpThemes()
		{
			this.BorderStyle = C1.Win.C1FlexGrid.Util.BaseControls.BorderStyleEnum.XpThemes;
			this.AdjustGridControlSize();
		}
		private CellRange GetSelectedCellRange()
		{
			return this.GetCellRange(this.Selection.TopRow, this.Selection.LeftCol, this.Selection.BottomRow, this.Selection.RightCol);
		}
		public void SelectedCellsBorderNone()
		{
			CellRange cr = GetSelectedCellRange();
			cr.Style = this.Styles["None"];
		}
		public void SelectedCellsBorderFlat()
		{
			CellRange cr = this.GetCellRange(this.Selection.TopRow, this.Selection.LeftCol, this.Selection.BottomRow, this.Selection.RightCol);
			cr.Style = this.Styles["Flat"];
		}
		public void SelectedCellsBorderDouble()
		{
			CellRange cr = this.GetCellRange(this.Selection.TopRow, this.Selection.LeftCol, this.Selection.BottomRow, this.Selection.RightCol);
			cr.Style = this.Styles["Double"];
		}
		public void SelectedCellsBorderRaised()
		{
			CellRange cr = this.GetCellRange(this.Selection.TopRow, this.Selection.LeftCol, this.Selection.BottomRow, this.Selection.RightCol);
			cr.Style = this.Styles["Raised"];
		}
		public void SelectedCellsBorderInset()
		{
			CellRange cr = this.GetCellRange(this.Selection.TopRow, this.Selection.LeftCol, this.Selection.BottomRow, this.Selection.RightCol);
			cr.Style = this.Styles["Inset"];
		}
		public void SelectedCellsBorderGroove()
		{
			CellRange cr = this.GetCellRange(this.Selection.TopRow, this.Selection.LeftCol, this.Selection.BottomRow, this.Selection.RightCol);
			cr.Style = this.Styles["Groove"];
		}
		public void SelectedCellsBorderFillet()
		{
			CellRange cr = this.GetCellRange(this.Selection.TopRow, this.Selection.LeftCol, this.Selection.BottomRow, this.Selection.RightCol);
			cr.Style = this.Styles["Fillet"];
		}
		public void SelectedCellsBorderDotted()
		{
			CellRange cr = this.GetCellRange(this.Selection.TopRow, this.Selection.LeftCol, this.Selection.BottomRow, this.Selection.RightCol);
			cr.Style = this.Styles["Dotted"];
		}
		public void SetupCellStyles()
		{
			CellStyle cs;
			cs = this.Styles.Add("Dotted");
			cs.Border.Style = BorderStyleEnum.Dotted;
			cs = this.Styles.Add("Double");
			cs.Border.Style = BorderStyleEnum.Double;
			cs = this.Styles.Add("Fillet");
			cs.Border.Style = BorderStyleEnum.Fillet;
			cs = this.Styles.Add("Flat");
			cs.Border.Style = BorderStyleEnum.Flat;
			cs = this.Styles.Add("Groove");
			cs.Border.Style = BorderStyleEnum.Groove;
			cs = this.Styles.Add("Inset");
			cs.Border.Style = BorderStyleEnum.Inset;
			cs = this.Styles.Add("None");
			cs.Border.Style = BorderStyleEnum.None;
			cs = this.Styles.Add("Raised");
			cs.Border.Style = BorderStyleEnum.Raised;
			cs = this.Styles.Add("CenterRight");
			cs.TextAlign = TextAlignEnum.RightCenter; //.LeftCenter; // this is being ignored - probably due to RTF conversion
			cs = this.Styles.Add("Yellow");
			cs.BackColor = Color.Yellow;
			cs = this.Styles.Add("Margins");
			cs.Margins.Bottom = 5;
			cs.Margins.Top = 10;
			cs.Margins.Left = 15;
			cs.Margins.Right = 20;
		}
		#endregion //Grid and Cell Styles
		#region Grid Size Adjustments
		/// 
		/// Adjust the grid control size based on the cell sizes.
		/// 
		public void AdjustGridControlSize()
		{
			int difW = this.Width - this.ClientSize.Width;
			int difH = this.Height - this.ClientSize.Height;
			int wid = 0;
			if (this != null)
			{
				foreach (C1.Win.C1FlexGrid.Column col in this.Cols)
					wid += (col.Width >= 0) ? col.Width : this.Cols.DefaultSize;
				int height = 0;
				foreach (C1.Win.C1FlexGrid.Row row in this.Rows)
					height += (row.Height >= 0) ? row.Height : this.Rows.DefaultSize;
				this.Size = new Size(wid + difW, height + difH);
			}
			this.Refresh();
		}
		public void AdjustGridHeightWidth(int r, int c)
		{
			StepRTB trtb = new StepRTB();
			string tstr = null;
			bool dummyCharWidth = false;
			bool AllowWidthShrink = false;
			trtb.Clear();
			tstr = (string)this[r, c];
			trtb.Font = this.Font;
			if (tstr != null && tstr.Length > 0)
			{
				if (tstr.StartsWith(@"{\rtf"))
					trtb.Rtf = tstr; // already RTF text
				else
					trtb.Text = tstr; // this will convert regular text to RTF text
				// regular text has special characters to toggle Bold, Underline, and Italics
				// we need to subtract the width of these characters (allow column/row to shrink)
				AllowWidthShrink = RemoveBoldUlineItalicChars(trtb.Rtf);
				// this will convert the special characters for Bold, Underline, and Italics
				// into RTF commands
				trtb.Rtf = ConvertTableText(trtb.Rtf);
			}
			else
			{
				trtb.Text = "X"; // this is to trick steprtf in giving a char width to fit one character
								//  note that a space character was too small.
				dummyCharWidth = true;
			}
			// find the needed cell width
			trtb.AdjustWidthForContent();
			if (dummyCharWidth)
			{
				trtb.Text = ""; // clear out the dummy character before saving
				dummyCharWidth = false;
			}
			this[r, c] = trtb.Rtf; // save the cleaned up and processed cell text as RTF
			this.Select(r, c, false);
			// Now see the the selected row,col is in the defined merge ranges
			bool mrgrows = false;
			bool mrgcols = false;
			foreach (CellRange cr in this.MergedRanges)
			{
				if (cr.Contains(r, c))
				{
					if (cr.c1 != cr.c2)
						mrgcols = true; // in a range of merged columns
					if (cr.r1 != cr.r2)
						mrgrows = true; // in a range of merged rows
					continue;
				}
			}
			if (!mrgcols || !mrgrows)
			{
				// IF the row of the selected cell is NOT in merged range
				// then go ahead and adjust the row height (if needed)
				if (!mrgrows)
				{
					// add adjustment for grid and cell borders
					int newheight = trtb.Height + 2;// (int)numGridLineBorderAdj.Value;
					//Console.WriteLine("{0}	{1}	{2}	'{3}'", r, c, newheight,trtb.Text);
					if (newheight > this.Rows[r].Height)
						this.Rows[r].Height = newheight;
				}
				// IF the column of the selected sell is NOT in merged range
				// then go ahead and adjust the column width (if needed)
				if (!mrgcols)
				{
					// add adjustment for grid and cell borders
					int newwidth = trtb.Width + 2;//(int)numGridLineBorderAdj.Value;
					if (newwidth > this.Cols[c].Width || AllowWidthShrink || r == 0)
						this.Cols[c].Width = newwidth;
				}
			}
		}
		private void Grid_AfterResize(object sender, C1.Win.C1FlexGrid.RowColEventArgs e)
		{
			this.AdjustGridControlSize();
		}
		#endregion // Grid Size Adjustments
		#region Cell Text
		public void MakeRTFcells()
		{
			// This will spin through all the cells in the grid:
			// - convert the text to RTF if needed
			// - adjust the grid dimensions based on the cell info.
			for (int r = 0; r < this.Rows.Count; r++)
			{
				this.Rows[r].Height = 20;//10;
				for (int c = 0; c < this.Cols.Count; c++)
					this.AdjustGridHeightWidth(r, c);
			}
			this.AdjustGridControlSize();
		}
		private bool RemoveBoldUlineItalicChars(string str)
		{
			int rtn = 0;
			// Underline next word
			rtn += str.IndexOf(@"\'17");
			// Bold next word
			rtn += str.IndexOf(@"\'13");
			// Italics On
			rtn += str.IndexOf(@"\'1B4");
			// Italics Off
			rtn += str.IndexOf(@"\'1B5");
			// underline On
			rtn += str.IndexOf(@"\'ab");
			// underline Off
			rtn += str.IndexOf(@"\'bb");
			return rtn > 0;
		}
		private string ConvertTableText(string str)
		{
			string rtn = "";
			// Underline next word
			rtn = SomethingNextWord(str, @"\'17", @"\ul ", @"\ulnone ");
			// Bold next word
			rtn = SomethingNextWord(rtn, @"\'13", @"\b ", @"\b0 ");
			// Italics On
			rtn = rtn.Replace(@"\'1B4", @"\i ");
			// Italics Off
			rtn = rtn.Replace(@"\'1B5", @"\i0 ");
			// underline On
			rtn = rtn.Replace(@"\'ab", @"\ul ");
			// underline Off
			rtn = rtn.Replace(@"\'bb", @"\ulnone ");
			return rtn;
		}
		// This converts Underline or bold next word character to the corresponding on/off commands
		private static string SomethingNextWord(string str, string nxtwordcmd, string cmdOn, string cmdOff)
		{
			string rtn = "";
			int bidx = 0;
			int fidx = str.IndexOf(nxtwordcmd, bidx);
			char[] term = " \r\n\x02".ToCharArray();
			while (fidx > 0)
			{
				rtn += str.Substring(bidx, fidx - bidx) + cmdOn;
				bidx = fidx + 4;
				if (bidx < str.Length)
				{
					fidx = str.IndexOfAny(term, bidx);
					if (fidx > 0)
					{
						rtn += str.Substring(bidx, fidx - bidx) + cmdOff + str.Substring(fidx, 1);
						bidx = fidx + 1;
					}
				}
				if (bidx < str.Length)
					fidx = str.IndexOf(nxtwordcmd, bidx);
				else
					fidx = -1;
			}
			if (bidx < str.Length)
				rtn += str.Substring(bidx);
			return rtn;
		}
		private void _StartEdit(object sender, C1.Win.C1FlexGrid.RowColEventArgs e)
		{
			// start editing the cell with the custom editor
			_tableCellEditor.StartEditing(e.Row, e.Col);
			e.Cancel = true;
		}
		// after edit handler (built-in editors)
		private void _AfterEdit(object sender, C1.Win.C1FlexGrid.RowColEventArgs e)
		{
			this.AdjustGridControlSize();
		}
		// if the custom editor is visible, make it follow the cell being edited
		private void _AfterScroll(object sender, C1.Win.C1FlexGrid.RangeEventArgs e)
		{
			this._tableCellEditor.UpdatePosition();
		}
		// save last key pressed for the custom editor
		private void _KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
		{
			this._tableCellEditor.SetPendingKey(e.KeyChar);
		}
		public string GetCellRTFString(int row, int col)
		{
			string rtnstr = "";
			foreach (CellRange r in this.MergedRanges)
			{
				if (r.ContainsRow(row) && r.ContainsCol(col))
				{
					rtnstr = (string)this[r.r1, r.c1];
					return rtnstr;
				}
			}
			rtnstr = (string)this[row, col];
			return rtnstr;
		}
		public void PutCellRTFString(int row, int col, string str)
		{
			foreach (CellRange r in this.MergedRanges)
			{
				if (r.ContainsRow(row) && r.ContainsCol(col))
				{
					this[r.r1, r.c1] = str;
					return;
				}
			}
			this[row, col] = str;
			return;
		}
		#endregion //Cell Text
		#region Merged / Split Range
		public void MergeSelection()
		{
			//string tstr = "";
			//C1.Win.C1FlexGrid.CellRange sel = this.Selection;
			//tstr = sel.Clip; // clip hold the data for the selected cells (used for un-merge)
			this.MergedRanges.Add(this.Selection);
			this.Invalidate();
		}
		public void SplitSelection()
		{
			C1.Win.C1FlexGrid.CellRange sel = this.GetMergedRange(this.Selection.r1, this.Selection.c1);
			if (this.MergedRanges.Contains(sel))
				this.MergedRanges.Remove(sel);
			this.Invalidate();
		}
		private void AdjustMergedRows(int row, bool above, bool removing)
		{
			CellRangeCollection crc = new CellRangeCollection(this);
			if (removing)
			{
				foreach (CellRange r in this.MergedRanges)
				{
					CellRange cr = r;
					if (r.ContainsRow(row))
					{
						if (r.TopRow != r.BottomRow)
						{
							cr.r2--;
							crc.Add(cr);
						}
					}
					else
					{
						if (row <= r.r1)
						{
							if (row < r.r1)
								cr.r1--;
							cr.r2--;
						}
						crc.Add(cr);
					}
				}
			}
			else // adding
				foreach (CellRange r in this.MergedRanges)
				{
					CellRange cr = r;
					int inspos = (above) ? row : row - 1;
					if (r.ContainsRow(inspos))
					{
						if ((above && cr.r1 == inspos) || (!above && cr.r2 == inspos))
						{
							string tstr = "";
							int newrow = 0;
							if (above)
							{
								if (this[cr.r1 + 1, cr.c1] != null)
									tstr = this[cr.r1 + 1, cr.c1].ToString();
								newrow = cr.r1;
							}
							else
							{
								if (this[cr.r2, cr.c1] != null)
									tstr = this[cr.r2, cr.c1].ToString();
								newrow = cr.r2 + 1;
							}
							if (tstr != null && tstr.Length > 0)
								for (int x = cr.c1; x <= cr.c2; x++)
									this[newrow, x] = tstr;
						}
						cr.r2++;
					}
					else
					{
						if (inspos < cr.r1)
						{
							cr.r1++;
							cr.r2++;
						}
					}
					crc.Add(cr);
				}
			this.MergedRanges.Clear();
			foreach (CellRange r in crc)
				this.MergedRanges.Add(r);
		}
		private void AdjustMergedColumns(int col, bool left, bool removing)
		{
			CellRangeCollection crc = new CellRangeCollection(this);
			if (removing)
			{
				foreach (CellRange r in this.MergedRanges)
				{
					CellRange cr = r;
					if (r.ContainsCol(col))
					{
						if (r.LeftCol != r.RightCol)
						{
							cr.c2--;
							crc.Add(cr);
						}
					}
					else
					{
						if (col < cr.c1)
						{
							cr.c1--;
							cr.c2--;
						}
						crc.Add(cr);
					}
				}
			}
			else // adding
				foreach (CellRange r in this.MergedRanges)
				{
					CellRange cr = r;
					int inspos = (left) ? col : col - 1;
					if (r.ContainsCol(inspos))
					{
						string tstr = "";
						int newcol = 0;
						if (left)
						{
							if (inspos == cr.c1)
								tstr = this[cr.r1, cr.c1 + 1].ToString();
							else
								tstr = this[cr.r1, cr.c1].ToString();
							newcol = cr.c1;
						}
						else
						{
							if (this[cr.r1, cr.c2] != null)
								tstr = this[cr.r1, cr.c2].ToString();
							newcol = cr.c2 + 1;
						}
						for (int x = cr.r1; x <= cr.r2; x++)
							this[x, newcol] = tstr;
						cr.c2++;
					}
					else if (col <= r.c1)
					{
						cr.c1++;
						cr.c2++;
					}
					crc.Add(cr);
				}
			this.MergedRanges.Clear();
			foreach (CellRange r in crc)
				this.MergedRanges.Add(r);
		}
		private bool RowIsInMergeRange(int row)
		{
			bool rtn = false;
			if (row >= 0)
			{
				CellRange cr = this.Selection;
				rtn = true;
				int c = cr.c1;
				while (rtn && (c <= cr.c2))
				{
					int idx = this.MergedRanges.IndexOf(row, c);
					rtn = (idx > -1);
					c++;
				}
			}
			return rtn;
		}
		private bool ColIsInMergeRange(int col)
		{
			bool rtn = false;
			if (col >= 0)
			{
				CellRange cr = this.Selection;
				rtn = true;
				int r = cr.r1;
				while (rtn && (r <= cr.r2))
				{
					int idx = this.MergedRanges.IndexOf(col, r);
					rtn = (idx > -1);
					r++;
				}
			}
			return rtn;
		}
		#endregion //Merged / Split Range
		#region Grid Add and Remove Row / Column
		private int GetRowInsertPosition(Boolean before)
		{
			int rtnrow;
			CellRange cr = this.Selection;
			int idx = this.MergedRanges.IndexOf(cr.r1, cr.c2);
			if (idx > -1)
				cr = this.MergedRanges[idx]; // we want first or last row in merge range
			rtnrow = (before) ? cr.r1 : cr.r2;
			return rtnrow;
		}
		private int GetColInsertPosition(Boolean before)
		{
			int rtncol;
			CellRange cr = this.Selection;
			int idx = this.MergedRanges.IndexOf(cr.r1, cr.c2);
			if (idx > -1)
				cr = this.MergedRanges[idx]; // we want the first of last col in merge range
			rtncol = (before) ? cr.c1 : cr.c2;
			return rtncol;
		}
		public void InsertColumnBefore()
		{
			int newcol = this.GetColInsertPosition(true);
			this.Cols.Insert(newcol);
			this.AdjustMergedColumns(newcol, true, false);
			this.AdjustGridControlSize();
		}
		public void InsertColumnAfter()
		{
			int colidx = this.GetColInsertPosition(false);
			if (colidx == this.Cols.Count - 1) // last column
				this.Cols.Add(1);
			else
				this.Cols.Insert(colidx + 1);
			this.AdjustMergedColumns(colidx + 1, false, false);
			this.AdjustGridControlSize();
		}
		public void InsertRowBefore()
		{
			int newrow = this.GetRowInsertPosition(true);
			this.Rows.Insert(newrow);
			this.AdjustMergedRows(newrow, true, false);
			this.AdjustGridControlSize();
		}
		public void InsertRowAfter()
		{
			int rowidx = this.GetRowInsertPosition(false);
			if (rowidx == this.Rows.Count - 1) // last row
				this.Rows.Add(1);
			else
				this.Rows.Insert(rowidx + 1);
			this.AdjustMergedRows(rowidx + 1, false, false);
			this.AdjustGridControlSize();
		}
		public void RemoveSelectedColumn()
		{
			this.SelectionMode = SelectionModeEnum.Column;
			this.Select(this.Selection.r1, this.Selection.c1);
			DialogResult dr = MessageBox.Show("Remove this column?", "Remove Column", MessageBoxButtons.YesNo);
			if (dr == DialogResult.Yes)
				RemoveColumns(this.Selection.r1, this.Selection.c1, 1);
			this.SelectionMode = SelectionModeEnum.Default;
		}
		public void RemoveSelectedRow()
		{
			this.SelectionMode = SelectionModeEnum.Row;
			this.Select(this.Selection.r1, this.Selection.c1);
			DialogResult dr = MessageBox.Show("Remove this Row?", "Remove Row", MessageBoxButtons.YesNo);
			if (dr == DialogResult.Yes)
				this.RemoveRows(this.Selection.r1, this.Selection.c1, 1);
			this.SelectionMode = SelectionModeEnum.Default;
		}
		private void RemoveRows(int strow, int stcol, int cnt)
		{
			for (int i = 0; i < cnt; i++)
			{
				if (this.RowIsInMergeRange(strow))
				{
					for (int cl = 0; cl < this.Cols.Count; cl++)
					{
						int idx = this.MergedRanges.IndexOf(strow, cl);
						if (idx > -1)
						{
							CellRange cr = this.MergedRanges[idx];
							if (cr.r1 < cr.r2)
								this[cr.r1 + 1, cr.c1] = this[cr.r1, cr.c1];
						}
						cl++;
					}
				}
				this.Rows.Remove(strow);
				this.AdjustMergedRows(strow, false, true);
			}
			this.AdjustGridControlSize();
		}
		private void RemoveColumns(int strow, int stcol, int cnt)
		{
			for (int i = 0; i < cnt; i++)
			{
				if (this.ColIsInMergeRange(stcol))
				{
					for (int rw = 0; rw < this.Rows.Count; rw++)
					{
						int idx = this.MergedRanges.IndexOf(rw, stcol);
						if (idx > -1)
						{
							CellRange cr = this.MergedRanges[idx];
							if (cr.c1 < cr.c2)
								this[cr.r1, cr.c1 + 1] = this[cr.r1, cr.c1];
						}
					}
				}
				this.Cols.Remove(stcol);
				this.AdjustMergedColumns(stcol, false, true);
			}
			this.AdjustGridControlSize();
		}
		public void RemoveSelectedCells()
		{
			bool didremove = false;
			int lastRow = this.Rows.Count - 1;
			int lastCol = this.Cols.Count - 1;
			CellRange cr = this.Selection;
			// r2 is last row (or in merge range containing last row)
			//    r1 is first row or r1-m (m is in merge range) is first row
			//    we can remove columns c1 through c2
			int idx = this.MergedRanges.IndexOf(cr.r2, cr.c2);
			CellRange mr = new CellRange();
			if (idx > -1) mr = this.MergedRanges[idx];
			if (cr.r2 == lastRow || idx > -1 && mr.r2 == lastRow)//RowIsInMergeRange(grd,cr.r2))
			{
				if (cr.r1 == 0 || this.RowIsInMergeRange(cr.r1 - 1))
				{
					this.RemoveColumns(cr.r1, cr.c1, cr.RightCol - cr.LeftCol + 1);// remove selected columns
					didremove = true;
				}
			}
			// c2 is last column (or in merge range containing last column)
			//   c1 is first column or c1-m (m is merge range) is first column
			//   we can remove rows r1 through r2
			if (cr.c2 == lastCol || idx > -1 && mr.c2 == lastCol)//ColIsInMergeRange(grd,cr.c1-1))
			{
				if (cr.c1 == 0 || this.ColIsInMergeRange(cr.c1 - 1))
				{
					// remove selected rows
					this.RemoveRows(cr.r1, cr.c1, cr.BottomRow - cr.TopRow + 1);
					didremove = true;
				}
			}
			if (!didremove)
				MessageBox.Show("Cannot Removed Part of a Row or Column.", "Invalid Selection");
			return;
		}
		#endregion //Grid Add and Remove Row / Column
		#region Clipboard
		public void ClipBoardCopyRow()
		{
			this.SelectionMode = SelectionModeEnum.Row;
			this.Select(this.Selection.r1, 0, this.Selection.r2, this.Cols.Count - 1, true);
			DialogResult dr = MessageBox.Show("Copy these Rows?", "Copy Rows", MessageBoxButtons.YesNo);
			if (dr == DialogResult.Yes)
				_clpbrdCpyPste.Put(this.Selection);
			this.SelectionMode = SelectionModeEnum.Default;
		}
		public void ClipBoardCopyColumn()
		{
			this.SelectionMode = SelectionModeEnum.Column;
			this.Select(0, this.Selection.c1, this.Rows.Count - 1, this.Selection.c2, true);
			DialogResult dr = MessageBox.Show("Copy these columns?", "Copy Columns", MessageBoxButtons.YesNo);
			if (dr == DialogResult.Yes)
				_clpbrdCpyPste.Put(this.Selection);
			this.SelectionMode = SelectionModeEnum.Default;
		}
		public void ClipBoardCopySelection()
		{
			this.Select(this.Selection.r1, this.Selection.c1, this.Selection.r2, this.Selection.c2);
			DialogResult dr = MessageBox.Show("Copy Selected Cells?", "Copy Selection", MessageBoxButtons.YesNo);
			if (dr == DialogResult.Yes)
				_clpbrdCpyPste.Put(this.Selection);
		}
		private void CopyTextFromCellRange(ArrayList arylst, int srow, int scol, int erow, int ecol)
		{
			StepRTB trtb = new StepRTB();
			int aryidx = 0;
			for (int r = srow; r <= erow; r++)
			{
				for (int c = scol; c <= ecol; c++)
				{
					if (aryidx >= arylst.Count)
						trtb.Text = "";
					else
					{
						trtb.Rtf = (string)(arylst[aryidx++]);
						trtb.AdjustWidthForContent();
					}
					this[r, c] = trtb.Rtf;
					this.AdjustGridHeightWidth(r, c);
				}
			}
		}
		public enum enmPastePos : int
		{
			Before = 1, Replace = 0, After = -1
		}
		public void ClipBoardPasteRows(enmPastePos pp)
		{
			// Get a list of strings representing the text (rtf text) in each cell
			// that was saved to the clipboard (row/column order)
			ArrayList aryCellList = _clpbrdCpyPste.Get();
			if (aryCellList != null && aryCellList.Count > 0)
			{
				// get row/column starting position in which new rows will be added
				int startrow = this.Selection.r1 + ((pp == enmPastePos.After) ? 1 : 0);
				int startcol = this.Selection.c1;
				// get the number of rows needed based on what was save to the clipboard
				int numrows = Math.Max(1, (aryCellList.Count / this.Cols.Count));
				// insert that number of new rows.
				if (pp != enmPastePos.Replace) // insert new rows before or after
					for (int r = 0; r < numrows; r++)
						if (pp == enmPastePos.After) // create new rows after
							this.InsertRowAfter();
						else
							this.InsertRowBefore();
				// copy each grid cell text (aryCellList) into the newly inserted rows
				CopyTextFromCellRange(aryCellList, startrow, startcol, startrow + numrows - 1, startcol + this.Cols.Count - 1);
				this.AdjustGridControlSize();
			}
		}
		public void ClipBoardPasteColumns(enmPastePos pp)
		{
			// Get a list of strings representing the text (rtf text) in each cell
			// that was saved to the clipboard (row/column order)
			ArrayList aryCellList = _clpbrdCpyPste.Get();
			if (aryCellList != null && aryCellList.Count > 0)
			{
				// get row/column starting position in which new rows will be added
				int startrow = 0;//grd.Selection.r1+((pp == enmPastePos.After) ? 1 : 0);
				int startcol = this.Selection.c1 + ((pp == enmPastePos.After) ? 1 : 0);
				// get the number of rows needed based on what was save to the clipboard
				int numcols = Math.Max(1, (aryCellList.Count / this.Rows.Count));
				// insert that number of new rows.
				if (pp != enmPastePos.Replace) // insert new rows before or after
					for (int c = 0; c < numcols; c++)
						if (pp == enmPastePos.After) // create new rows after
							this.InsertColumnAfter();
						else
							this.InsertColumnBefore();
				// copy each grid cell text (aryCellList) into the newly inserted rows
				this.CopyTextFromCellRange(aryCellList, startrow, startcol, startrow + this.Rows.Count - 1, startcol + numcols - 1);
				this.AdjustGridControlSize();
			}
		}
		public void ClipBoardPasteIntoSelection()
		{
			// Get a list of strings representing the text (rtf text) in each cell
			// that was saved to the clipboard (row/column order)
			ArrayList aryCellList = _clpbrdCpyPste.Get();
			if (aryCellList != null && aryCellList.Count > 0)
			{
				// copy each grid cell text (aryCellList) into the selected cells
				this.CopyTextFromCellRange(aryCellList, this.Selection.r1, this.Selection.c1, this.Selection.r2, this.Selection.c2);
				this.AdjustGridControlSize();
			}
		}
		#endregion //Clipboard
		#region Import / Export Grid
		/// 
		/// Prompts user with Save File dialog.
		/// Grid will be saved to an XML file.
		/// 
		/// 
		public void ExportToXML(string initDir)
		{
			SaveFileDialog sfd = new SaveFileDialog();
			sfd.DefaultExt = ".xml";
			sfd.Filter = "XML files (*.xml)|*.xml|All files (*.*)|*.* ";
			//sfd.InitialDirectory = @"C:\Development\SampleTableData";
			sfd.InitialDirectory = initDir;
			sfd.Title = "Save XML File";
			sfd.ShowDialog();
			this.WriteXml(sfd.FileName);
		}
		/// 
		/// Prompts user with Save File dialog.
		/// Grid will be saved to an Excel file.
		/// 
		/// 
		public void ExportToExcel(string initDir)
		{
			SaveFileDialog sfd = new SaveFileDialog();
			sfd.DefaultExt = ".xls";
			sfd.Filter = "Excel files (*.xls)|*.xls|All files (*.*)|*.* ";
			//sfd.InitialDirectory = @"C:\Development\SampleTableData";
			sfd.InitialDirectory = initDir;
			sfd.Title = "Save Excel File";
			sfd.ShowDialog();
			this.SaveExcel(sfd.FileName);
		}
		/// 
		/// Prompts user with Open File dialog.
		/// XML file will be imported to a table grid.
		/// 
		/// 
		/// 
		public string ImportXML(string initDir)
		{
			string rtn = "";
			OpenFileDialog ofd = new OpenFileDialog();
			ofd.DefaultExt = ".xml";
			ofd.Filter = "XML files (*.xml)|*.xml|All files (*.*)|*.* ";
			//ofd.InitialDirectory = @"C:\Development\SampleTableData";
			ofd.InitialDirectory = initDir;
			ofd.Multiselect = false;
			ofd.Title = "Select XML File";
			ofd.ShowDialog();
			this.Clear();
			this.MergedRanges.Clear();
			this.ReadXml(ofd.FileName);
			this.AdjustGridControlSize();
			rtn = ofd.SafeFileName;
			return rtn;
		}
		/// 
		/// Prompts user with Open File dialog.
		/// Excel file will be imported to a table grid.
		/// 
		/// 
		/// 
		public string ImportExcel(string initDir)
		{
			string rtn = "";
			//VlnFlexGrid grd = rbtDefaultTable.Checked ? vlnFlexGrid2 : vlnFlexGrid3; //GetActiveGrid();
			OpenFileDialog ofd = new OpenFileDialog();
			ofd.DefaultExt = ".xls";
			ofd.Filter = "Excel files (*.xls)|*.xls|All files (*.*)|*.*";
			//ofd.InitialDirectory = @"C:\Development\SampleTableData";
			ofd.InitialDirectory = initDir;
			ofd.Multiselect = false;
			ofd.Title = "Select Excel File";
			ofd.ShowDialog();
			this.Clear();
			this.MergedRanges.Clear();
			this.LoadExcel(ofd.FileName);
			this.AdjustGridControlSize();
			rtn = ofd.SafeFileName;
			return rtn;
		}
		/// 
		/// This will parse a string containing the ascii text of the old style VE-PROMS (16-bit) tables.
		/// It will find the number of rows and columns base on newlines, vertical bars, and dashes.
		/// Then it will parse the the text, place them in celll, and attempt to merge cells were needed.
		/// 
		/// 
		public void ParseTableFromText(string txtbuff)
		{
			int curRow = 0;
			int curCol = 0;
			int maxRow = 0;
			int maxCol = 0;
			// Get Max Rows and Max Cols
			char[] test = "|\n\x02".ToCharArray();
			int idx = 0;
			int idxst = 0;
			int colPos = 0;
			int strow = 0;
			Dictionary dicCols = new Dictionary();
			do
			{
				idx = txtbuff.IndexOfAny(test, idxst);
				if (idx > -1)
				{
					switch (txtbuff[idx])
					{
						case '|': // end of a column
							colPos = idxst - strow;
							if (!dicCols.ContainsKey(colPos))
								dicCols.Add(colPos, curCol);
							else if (curCol > dicCols[colPos])
							{
								dicCols.Remove(colPos);
								dicCols.Add(colPos, curCol);
							}
							curCol++;
							break;
						case '\x02':
						case '\n': // end of a row
							colPos = idxst - strow;
							if (!dicCols.ContainsKey(colPos))
								dicCols.Add(colPos, curCol);
							else if (curCol > dicCols[colPos])
							{
								dicCols.Remove(colPos);
								dicCols.Add(colPos, curCol);
							}
							curRow++;
							strow = idx + 1;
							if (curCol > maxCol)
								maxCol = curCol;
							curCol = 0;
							break;
					}
					idxst = idx + 1;
					if (idxst >= txtbuff.Length)
						idx = -1;
				}
			} while (idx != -1);
			maxRow = curRow + 1;
			curRow = 0;
			curCol = 0;
			// The resulting Table Grid size in rows and columns
			this.Cols.Count = maxCol + 1;
			this.Rows.Count = maxRow + 1;
			// make all rows and columns editable
			this.Rows.Fixed = 0;
			this.Cols.Fixed = 0;
			// TableCellInfo is used to assign merge ranges
			// Make a two dimensional array of TableCellinfo the same size as the table grid
			TableCellInfo[,] tci = new TableCellInfo[maxRow + 1, maxCol + 1];
			for (int r = 0; r <= maxRow; r++)
				for (int c = 0; c <= maxCol; c++)
					tci[r, c] = new TableCellInfo();
			// Read in each cell of the grid
			idx = 0;
			idxst = 0;
			colPos = 0;
			strow = 0;
			int prevCol = 0;
			int tstidx = 0;
			string tstr = "";
			bool incRow = false;
			do
			{
				idx = txtbuff.IndexOfAny(test, idxst);
				if (idx > -1)
				{
					switch (txtbuff[idx])
					{
						case '|': // end of column
							colPos = idxst - strow;
							// based on the position of the | char, find what column we are in.
							// note that this will tell us if any columns to the left were merged
							// the while loop will flag cell that need to be merged
							curCol = dicCols[colPos];
							while (curCol > prevCol + 1)
							{
								tci[curRow, prevCol].MergeColRight = true;
								prevCol++;
							}
							prevCol = curCol;
							// parse out the text to be placed in the table cell
							tstr = txtbuff.Substring(idxst, idx - idxst);
							if (tstr.Length == 0)
								tstr += " "; 
							// test for a string of '-' characters
							for (tstidx = 0; (tstidx < tstr.Length) && (tstr[tstidx] == '-'); tstidx++) ;
							if (tstidx < tstr.Length) // not a full line (row) of '-' chars
							{
								// if this column is in a merged grouping of rows,
								// get the cell text in the first cell of the merged grouping of cells
								// we will append the newly parsed text to this cell's text.
								int rw = curRow;
								while (rw - 1 > 0 && tci[rw - 1, curCol].MergeRowBellow) rw--;
								string jstr = (string)this[rw, curCol];
								if (jstr == null)
									jstr = tstr;
								else
									jstr += "\n" + tstr; // multi line cell
								this[rw, curCol] = jstr;
								// take a peek at the start of the next piece of table text to parse
								// if it starts with a '-' char, then flag to merge the columns up to
								// this point with the same columns in the next row
								if (idx < txtbuff.Length - 1 && txtbuff[idx + 1] == '-')
								{
									for (int c = curCol; c >= -0; c--)
										if (!tci[curRow, c].ColEnd)
											tci[curRow, c].MergeRowBellow = true;
								}
							}
							else // parsed text contains all dashes
							{
								tci[curRow, curCol].ColEnd = true;
								incRow = true;
							}
							break;
						case '\x02': // end of file
						case '\n': // new line of 16-bit table text
							colPos = idxst - strow;
							// see what column we are in - new line might occure before last grid column
							curCol = dicCols[colPos];
							strow = idx + 1;
							// parse out the cell text
							tstr = txtbuff.Substring(idxst, idx - idxst);
							if (tstr.EndsWith("\r")) // strip off carrage return
								tstr = tstr.Substring(0, tstr.Length - 1);
							if (tstr.Length == 0)
								tstr += " ";
							// test for a string of '-' characters
							for (tstidx = 0; (tstidx < tstr.Length) && (tstr[tstidx] == '-'); tstidx++) ;
							if (tstidx < tstr.Length) // not a full line (row) of '-' chars
							{
								// if this column is in a merged grouping of rows,
								// get the cell text in the first cell of the merged grouping of cells
								// we will append the newly parsed text to this cell's text.
								int rw = curRow;
								while (rw - 1 > 0 && tci[rw - 1, curCol].MergeRowBellow) rw--;
								string jstr = (string)this[rw, curCol];
								if (jstr == null)
									jstr = tstr;
								else
									jstr += "\n" + tstr; // multi line cell
								this[rw, curCol] = jstr;
							}
							else if (tstr.Length > 0) // parsed text is all dash characters
							{
								incRow = true;
								if (curRow > 0) // merge the column in the previous row with this one
									tci[curRow - 1, curCol].MergeRowBellow = false;
							}
							// if were are that the end of the 16-bit text line, but not in the last column,
							// merge the remaining columns to the right
							if ((curCol < maxCol) && (tstidx < tstr.Length))
							{
								for (int i = curCol; i < maxCol; i++)
									tci[curRow, i].MergeColRight = true;
							}
							if (incRow)
								curRow++;
							curCol = 0;
							incRow = false;
							break;
					}
					idxst = idx + 1;
					if (idxst >= txtbuff.Length)
						idx = -1;
				}
				else if (idxst < txtbuff.Length - 1) // handle any remaining text not yet parsed
				{
					// find the curent column and merge remaining columns to the right
					colPos = idxst - strow;
					curCol = dicCols[colPos];
					while (curCol > prevCol + 1)
					{
						tci[curRow, prevCol].MergeColRight = true;
						prevCol++;
					}
					// parse out the remaining text
					tstr = txtbuff.Substring(idxst);
					if (tstr.Length == 0)
						tstr += " ";
					// test for a string of '-' characters
					for (tstidx = 0; (tstidx < tstr.Length) && (tstr[tstidx] == '-'); tstidx++) ;
					if (tstidx < tstr.Length)
					{
						// if this column is in a merged grouping of rows,
						// get the cell text in the first cell of the merged grouping of cells
						// we will append the newly parsed text to this cell's text.
						int rw = curRow;
						while (rw - 1 > 0 && tci[rw - 1, curCol].MergeRowBellow) rw--;
						string jstr = (string)this[rw, curCol];
						if (jstr == null)
							jstr = tstr;
						else
							jstr += "\n" + tstr; // multi line cell
						this[rw, curCol] = jstr;
					}
				}
			} while (idx != -1);
			// we are done parsing the 16-bit text.
			// now set the merge ranges in the table grid, based on the
			// information saved in that two dimensional array of TableCellinfo
			this.Rows.Count = curRow + 1;
			this.AllowMerging = C1.Win.C1FlexGrid.AllowMergingEnum.Custom;
			maxRow = curRow;
			int rR = 0;
			int rC = 0;
			for (int r = 0; r <= maxRow; r++)
				for (int c = 0; c <= maxCol; c++)
				{
					if (tci[r, c].MergeColRight)
					{
						rC = c;
						while ((rC < maxCol) && (tci[r, rC].MergeColRight)) rC++;
						if (rC > c)
						{
							this.MergedRanges.Add(this.GetCellRange(r, c, r, rC));
							string cellstr = this[r, c].ToString();
							for (int x = c + 1; x <= rC; x++)
								this[r, x] = cellstr;
							c = rC;
						}
					}
				}
			for (int c = 0; c <= maxCol; c++)
				for (int r = 0; r <= maxRow; r++)
				{
					if (tci[r, c].MergeRowBellow)
					{
						rR = r;
						while ((rR < maxRow) && (tci[rR, c].MergeRowBellow)) rR++;
						if (rR > r)
						{
							this.MergedRanges.Add(this.GetCellRange(r, c, rR, c));
							string cellstr = this[r, c].ToString();
							for (int x = r + 1; x <= rR; x++)
								this[x, c] = cellstr;
							r = rR;
						}
					}
				}
		}
		#endregion //Import / Export Grid
		#region Bug Work Around
		protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
		{
			// Note: this fixes a bug that happens when the grid has more rows than columns
			//  notified ComponentOne of the problem 6/25/09 via Email
			try
			{
				base.OnMouseDown(e);
			}
			catch (Exception ex)
			{
				while (ex != null)
				{
					Console.WriteLine("{0} {1} {2}", ex.GetType().Name, ex.Message, ex.StackTrace);
					ex = ex.InnerException;
				}
			}
		}
		#endregion //Bug Work Around
	}
	#region RTF Class for Cell rendering
	class RTF : StepRTB //RichTextBox
	{
		// messages used by RichEd20.dll
		internal const int
			WM_USER = 0x0400,
			EM_FORMATRANGE = WM_USER + 57;
		// FORMATRANGE is used by RichEd20.dll to render RTF
		internal struct FORMATRANGE
		{
			internal IntPtr hdc, hdcTarget;
			internal Rectangle rc, rcPage;
			internal int cpMin, cpMax;
		}
		// render the control directly into a given Graphics object 
		// (this does not honor clipping set in the target Graphics object; it that is
		// a problem, use the RenderClipped method instead).
		public void Render(Graphics g, Rectangle rc)
		{
			// convert rect from pixels to twips
			rc.X = (int)(rc.X * 1440 / g.DpiX);
			rc.Y = (int)(rc.Y * 1440 / g.DpiY);
			rc.Width = rc.X + (int)((rc.Width) * 1440 / g.DpiX);
			rc.Height = rc.Y + (int)((rc.Height) * 1440 / g.DpiY);
			// get dc
			IntPtr hdc = g.GetHdc();
			// set up FORMATRANGE struct
			FORMATRANGE fmt = new FORMATRANGE();
			fmt.hdc = fmt.hdcTarget = hdc;
			fmt.rc = fmt.rcPage = rc;
			fmt.cpMin = 0;
			fmt.cpMax = -1;
			// render RTF
			int render = 1;
			SendMessageFormatRange(Handle, EM_FORMATRANGE, render, ref fmt);
			// clean up
			SendMessage(Handle, EM_FORMATRANGE, render, 0);
			// done with dc
			g.ReleaseHdc(hdc);
		}
		// render RTF into a Graphics object using a temporary bitmap to ensure
		// clipping works correctly.
		public void RenderClipped(Graphics g, Rectangle rc)
		{
			// create temp bitmap
			Rectangle rcSrc = new Rectangle(0, 0, rc.Width, rc.Height);
			using (Bitmap bmp = new Bitmap(rc.Width, rc.Height))
			{
				// render RTF into bitmap
				using (Graphics gBmp = Graphics.FromImage(bmp))
				{
					Render(gBmp, rcSrc);
					// transfer bitmap to original Graphics object
					// (this honors clipping set in the target Graphics object)
					g.DrawImage(bmp, rc, rcSrc, GraphicsUnit.Pixel);
				}
			}
		}
		// SendMessage
		[DllImport("USER32.DLL", CharSet = CharSet.Auto)]
		static private extern int SendMessage(
			IntPtr hWnd,
			uint wMsg,
			int wParam,
			int lParam);
		[DllImport("USER32.DLL", CharSet = CharSet.Auto, EntryPoint = "SendMessage")]
		static private extern int SendMessageFormatRange(
			IntPtr hWnd,
			uint wMsg,
			int wParam,
			ref FORMATRANGE lParam);
	}
	#endregion //RTF Class for Cell rendering
	# region TableCellInfo for text parse
	class TableCellInfo
	{
		private bool _MergeRowBellow;
		public bool MergeRowBellow
		{
			get { return _MergeRowBellow; }
			set { _MergeRowBellow = value; }
		}
		private bool _MergeColRight;
		public bool MergeColRight
		{
			get { return _MergeColRight; }
			set { _MergeColRight = value; }
		}
		private bool _ColEnd;
		public bool ColEnd
		{
			get { return _ColEnd; }
			set { _ColEnd = value; }
		}
		private string _Text;
		public string Text
		{
			get { return _Text; }
			set { _Text = value; }
		}
	}
	#endregion //TableCellInfo for text parse
	#region TableCellEditor
	/// 
	/// TableCellEditor inherits from a StepRTB and is used as a custom cell editor
	/// for the C1FlexGrid control.
	/// 
	public class TableCellEditor : StepRTB //System.Windows.Forms.RichTextBox //System.Windows.Forms.TextBox // System.Windows.Forms.ComboBox 
	{
		private VlnFlexGrid _owner;
		private int _row, _col;
		private char _pendingKey;
		private bool _cancel;
		// constructor: attach to owner grid
		public TableCellEditor(VlnFlexGrid owner)
		{
			Visible = false;
			AutoSize = false;
			BackColor = Color.Beige;
			BorderStyle = BorderStyle.None;
			_pendingKey = (char)0;
			_cancel = false;
			_owner = owner;
			_owner.Controls.Add(this);
			this.CursorMovement += new StepRTBCursorMovementEvent(TableCellEditor_CursorMovement);
			this.CursorKeyPress += new StepRTBCursorKeysEvent(TableCellEditor_CursorKeyPress);
			this.Leave += new EventHandler(_editor_Leave);
			this.HeightChanged += new StepRTBEvent(_HeightChanged);
		}
		// start editing: move to cell and activate
		public void StartEditing(int row, int col)
		{
			// save coordinates of cell being edited
			_row = row;
			_col = col;
			this.Clear();
			// assume we'll save the edits
			_cancel = false;
			// move editor over the current cell
			Rectangle rc = _owner.GetCellRect(row, col, true);
			rc.Width--;
			rc.Height--;
			Bounds = rc;
			// initialize control content
			Text = "";
			if (_pendingKey > ' ')
				Text = _pendingKey.ToString();
			else if (_owner[row, col] != null)
			{
				string tmp = _owner[row, col].ToString();
				if (tmp.StartsWith(@"{\rtf"))
					Rtf = tmp;
				else
					Text = tmp;
			}
			Select(Text.Length, 0);
			Size sz = new Size(rc.Width, rc.Height);
			this.Size = sz;
			// make editor visible
			Visible = true;
			// and get the focus
			Select();
		}
		void TableCellEditor_CursorKeyPress(object sender, KeyEventArgs args)
		{
			//throw new Exception("The method or operation is not implemented.");
		}
		void TableCellEditor_CursorMovement(object sender, StepRTBCursorMovementEventArgs args)
		{
			int row = _owner.Selection.r1;
			int col = _owner.Selection.c1;
			CellRange cr = _owner.Selection;
			//Console.WriteLine("keystroke {0} selection {1},{2}", args.Key, row, col);
			//vlnStackTrace.ShowStack("keystroke {0} selection {1},{2}", args.Key, row, col);
			_owner.Select();
			// See if we are in a merged range of cells.
			// if so, use the merged range instead of the selected range
			// so that we can jump to the top/bottom/left/right of the range
			// before attempting the move to the next grid cell.
			int idx = _owner.MergedRanges.IndexOf(row, col);
			if (idx > -1) cr = _owner.MergedRanges[idx];
			switch (args.Key)
			{
				case VEPROMS.CSLA.Library.E_ArrowKeys.CtrlDown:
				case VEPROMS.CSLA.Library.E_ArrowKeys.Down:
					row = cr.r2;
					if (row < _owner.Rows.Count - 1)
						_owner.Select(row + 1, col);
					break;
				case VEPROMS.CSLA.Library.E_ArrowKeys.CtrlLeft:
				case VEPROMS.CSLA.Library.E_ArrowKeys.Left:
					col = cr.c1;
					if (col > 0)
						_owner.Select(row, col - 1);
					break;
				case VEPROMS.CSLA.Library.E_ArrowKeys.CtrlRight:
				case VEPROMS.CSLA.Library.E_ArrowKeys.Right:
					col = cr.c2;
					if (col < _owner.Cols.Count - 1)
						_owner.Select(row, col + 1);
					break;
				case VEPROMS.CSLA.Library.E_ArrowKeys.CtrlUp:
				case VEPROMS.CSLA.Library.E_ArrowKeys.Up:
					row = cr.r1;
					if (row > 0)
						_owner.Select(row - 1, col);
					break;
				default:
					break;
			}
			//Console.WriteLine("selection {0}", _owner.Selection);
			_owner.Focus(); // focus was jumping out of the grid this keeps it in.
		}
		// after edit handler (custom editor)
		void _editor_Leave(object sender, EventArgs e)
		{
			_owner.Invalidate();
			_owner.AdjustGridControlSize();
		}
		void _HeightChanged(object sender, EventArgs args)
		{
			_owner.Invalidate();
		}
		// save key that started the editing mode
		public void SetPendingKey(char chr)
		{
			_pendingKey = chr;
		}
		// expose internal variables
		public bool Cancel { get { return _cancel; } }
		public int Row { get { return _row; } }
		public int Col { get { return _col; } }
		// move editor after the grid has scrolled
		public void UpdatePosition()
		{
			// get cell rect now
			Rectangle rcCell = _owner.GetCellRect(_row, _col, false);
			// intersect with scrollable part of the grid
			Rectangle rcScroll = _owner.ClientRectangle;
			rcScroll.X = _owner.Cols[_owner.Cols.Fixed].Left;
			rcScroll.Y = _owner.Rows[_owner.Rows.Fixed].Top;
			rcScroll.Width -= rcScroll.X;
			rcScroll.Height -= rcScroll.Y;
			rcCell = Rectangle.Intersect(rcCell, rcScroll);
			// and move the control
			if (rcCell.Width > 0) rcCell.Width--;
			if (rcCell.Height > 0) rcCell.Height--;
			Bounds = rcCell;
			Size sz = new Size(_owner.GetCellRect(_row, _col).Width, _owner.GetCellRect(_row, _col).Height);
			this.Size = sz;
		}
		// lost focus: hide the editor
		override protected void OnLeave(System.EventArgs e)
		{
			// base processing
			base.OnLeave(e);
			// copy text to owner grid
			if (!_cancel)
			{
				_owner[_row, _col] = Rtf; //Text;
			}
			// no more pending keys
			_pendingKey = (char)0;
			// done for now, hide editor
			Visible = false;
		}
		// we will handle the Tab key ourselves
		override protected bool IsInputKey(Keys keyData)
		{
			if (keyData == Keys.Tab) return true;
			return base.IsInputKey(keyData);
		}
		//// some keys end the editing
		//override protected void OnKeyDown(System.Windows.Forms.KeyEventArgs e)
		//{
		//    switch (e.KeyCode)
		//    {
		//        case Keys.Escape: // cancel edits
		//            _cancel = true;
		//            _owner.Select();
		//            e.Handled = true;
		//            return;
		//        case Keys.Enter: // finish editing
		//        case Keys.Tab:
		//        case Keys.Up:
		//        case Keys.Down:
		//        case Keys.PageUp:
		//        case Keys.PageDown:
		//            _owner.Select();
		//            e.Handled = true;
		//            MoveCursor(e.KeyCode);
		//            return;
		//        default: // default processing
		//            base.OnKeyDown(e);
		//            return;
		//    }
		//}
		//// move cursor after done editing
		//protected void MoveCursor(Keys key)
		//{
		//    int row = _owner.Row;
		//    int col = _owner.Col;
		//    switch (key)
		//    {
		//        case Keys.Tab: col++; break;
		//        case Keys.Up: row--; break;
		//        //case Keys.Return: row++; break;
		//        case Keys.Down: row++; break;
		//        //case Keys.PageUp: row -= 10; break;
		//        //case Keys.PageDown: row += 10; break;
		//    }
		//    // validate new selection
		//    if (row < _owner.Rows.Fixed) row = _owner.Rows.Fixed;
		//    if (col < _owner.Cols.Fixed) col = _owner.Cols.Fixed;
		//    if (row > _owner.Rows.Count - 1) row = _owner.Rows.Count - 1;
		//    if (col > _owner.Cols.Count - 1) col = _owner.Cols.Count - 1;
		//    // apply new selection
		//    _owner.Select(row, col);
		//}
		//// suppress some keys to avoid annoying beep
		//override protected void OnKeyPress(System.Windows.Forms.KeyPressEventArgs e)
		//{
		//    switch (e.KeyChar)
		//    {
		//        case (char)27: // Keys.Escape:
		//        case (char)13: // Keys.Enter:
		//        case (char)9: // Keys.Tab:
		//            e.Handled = true;
		//            return;
		//    }
		//    base.OnKeyPress(e);
		//}
	}
	#endregion // TableCellEditor
	#region TableClipBoardFuncts Class
	class TableClipBoardFuncts
	{
		DataFormats.Format dfmtTableCellRange = DataFormats.GetFormat("TableCellRange");
		public TableClipBoardFuncts()
		{
			//mySeldTableCellsObject = new DataObject(dfmtTableCellRange.Name, seldTableCells);
		}
		public void Put(CellRange cr)
		{
			// Copies myObject into the clipboard.
			// Clip get the value (text) of all the cells in the selected range
			// This is saved as one RTF string.
			Clipboard.SetDataObject(cr.Clip, true); // the "true" make the copy persistent
		}
		public ArrayList Get()
		{
			// Retrieves the data from the clipboard.
			IDataObject myRetrievedObject = Clipboard.GetDataObject();
			string jj = (string)myRetrievedObject.GetData(DataFormats.Text);
			return GetCellStrings(jj);
		}
		private ArrayList GetCellStrings(string instr)
		{
			// The table cells placed on the clipboard is saved as one long RTF string.
			// "\r\n\t" defines a cell border
			// "\r\n\r" defines beginning of next line (row)
			// This function will separate each cell text and place them in an array.
			// This allow the clipboard information to be pasted independently from
			// how it was selected.
			string tstr = ""; // this will contain the parsed out cell text
			ArrayList arylstCellStrings = new ArrayList();
			int sidx = 0; // start index
			int tidx = 0;
			if (instr != null)
			{
				int idx = instr.IndexOf("\r\n\t"); // cell boarder
				if (idx < 0) idx = instr.IndexOf("\r\n\r"); // new line (needed for multiple lines in one cell)
				while (idx > 0)
				{
					tstr = instr.Substring(sidx, idx - sidx);
					tidx = tstr.IndexOf("\r\n\r");
					if (tidx > 0)
					{
						idx = instr.IndexOf("\r\n\r", sidx);
						tstr = instr.Substring(sidx, idx - sidx);
					}
					arylstCellStrings.Add(tstr.Substring(tstr.IndexOf("{\\rtf")));
					sidx = idx + 3;
					if (sidx < instr.Length)
					{
						idx = instr.IndexOf("\r\n\t", sidx);
						if (idx < 0) idx = instr.IndexOf("\r\n\r", sidx);
					}
				}
				if (sidx < instr.Length)
				{
					tstr = instr.Substring(sidx);
					arylstCellStrings.Add(tstr.Substring(tstr.IndexOf("{\\rtf")));
				}
			}
			return arylstCellStrings;
		}
	}
	[Serializable]
	public class SelectedTableCells : Object
	{
		private CellRange _cpbrdCellRange;
		public CellRange CpbrdCellRange
		{
			get { return _cpbrdCellRange; }
			set { _cpbrdCellRange = value; }
		}
		public SelectedTableCells()
		{
		}
	}
	#endregion // TableClipBoardFuncts Class
}