www.pudn.com > sudoku.rar > PuzzleGrid.cs


//-------------------------------------------------------------------------- 
//  
//  Copyright (c) Microsoft Corporation.  All rights reserved.  
//  
//  File: PuzzleGrid.cs 
// 
//  Description: Core control for displaying and interacting with a puzzle. 
//  
//-------------------------------------------------------------------------- 
 
using System; 
using System.Drawing; 
using System.Drawing.Drawing2D; 
using System.Diagnostics; 
using System.Collections; 
using System.Globalization; 
using System.Windows.Forms; 
using System.ComponentModel; 
using System.Drawing.Imaging; 
using System.Runtime.InteropServices; 
using System.Runtime.CompilerServices; 
using Microsoft.Ink; 
using Microsoft.Sudoku.Nullables; 
using Microsoft.Sudoku.Collections; 
using Microsoft.Sudoku.Techniques; 
using Microsoft.Sudoku.Utilities; 
 
namespace Microsoft.Sudoku.Controls 
{ 
	/// Control for displaying and interacting with a PuzzleState. 
	[ToolboxBitmap(typeof(DataGrid))] 
	internal sealed class PuzzleGrid : Control 
	{ 
		#region Component Designer generated code 
		///  
		/// Required method for Designer support - do not modify  
		/// the contents of this method with the code editor. 
		///  
		private void InitializeComponent() 
		{ 
			this.components = new System.ComponentModel.Container(); 
			this._processStrokesTimer = new System.Windows.Forms.Timer(this.components); 
			//  
			// _processStrokesTimer 
			//  
			this._processStrokesTimer.Interval = 500; 
			this._processStrokesTimer.Tick += new System.EventHandler(this.StrokesTimerTick); 
			//  
			// PuzzleGrid 
			//  
			this.BackColor = System.Drawing.Color.Transparent; 
			this.ForeColor = System.Drawing.Color.Black; 
		} 
		#endregion 
 
		#region Member Variables 
		/// Required designer variable. 
		private System.ComponentModel.IContainer components = null; 
		/// Used to time stroke entry so that strokes can be recognized as a group as a number. 
		private System.Windows.Forms.Timer _processStrokesTimer; 
		/// The state currently being displayed in the grid. 
		private PuzzleState _state; 
		/// Original state of the puzzle. 
		private PuzzleState _originalState; 
		/// The original state of a newly generated puzzle, used for comparison. 
		private PuzzleState _solvedOriginalState; 
		/// Whether to highlight cells that can only be a specific number. 
		private bool _showSuggestedCells; 
		/// Whether to show where incorrect numbers have been added to the grid. 
		private bool _showIncorrectNumbers; 
		/// The puzzle difficulty level used for showing hints. 
		private PuzzleDifficulty _difficultyLevel = PuzzleDifficulty.Easy; 
		/// Maintains a history of undo information. 
		private PuzzleStateStack _undoStates = new PuzzleStateStack(); 
		/// Currently selected cell in the grid. 
		private NullablePoint _selectedCell; 
		/// InkOverlay used to receive Tablet ink. 
		private InkOverlay _inkOverlay; 
		/// Tablet recognizer context for parsing user input into numbers. 
		private RecognizerContext _recognizerCtx; 
		/// ID used to retrieve the custom scratchpad strokes collection. 
		private const string ScratchpadStrokesID = "ScratchpadStrokesID"; // does not need to be localized 
		/// ID used to retrieve the custom normal strokes collection. 
		private const string NormalStrokesID = "NormalStrokesID"; // does not need to be localized 
		/// Color used for normal-mode ink. 
		private Color _inkColor; 
		/// Color used for scratchpad-mode ink. 
		private Color _scratchpadInkColor; 
		/// Color used for scratchpad-mode ink when in normal mode. 
		private Color _scratchpadInkInNormalModeColor; 
		#endregion 
		 
		#region Setup and Shutdown 
		/// Initialize the PuzzleGrid. 
		public PuzzleGrid() 
		{ 
			SetStyle( 
				ControlStyles.UserPaint | ControlStyles.DoubleBuffer |  
				ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | 
				ControlStyles.SupportsTransparentBackColor, true); 
 
			InitializeComponent(); 
 
			_userValueBrush = new SolidBrush(Color.Blue); 
			_incorrectValueBrush = new SolidBrush(Color.FromArgb(255,0,0)); 
			_originalValueBrush = new SolidBrush(Color.FromArgb(0,0,0)); 
			_inkColor = Color.Blue; 
			_scratchpadInkColor = Color.Black; 
			_scratchpadInkInNormalModeColor = Color.Gray; 
 
			_centerNumberFormat = new StringFormat(); 
			_centerNumberFormat.Alignment = StringAlignment.Center; 
			_centerNumberFormat.LineAlignment = StringAlignment.Center; 
		} 
 
		/// Clean up any resources being used. 
		/// true if managed resources should be disposed; otherwise, false. 
		protected override void Dispose(bool disposing) 
		{ 
			if (disposing) 
			{ 
				if (components != null) components.Dispose(); 
				DisableTabletSupport(); 
				DisposeDrawingObjects(); 
			} 
			base.Dispose(disposing); 
		} 
 
		/// Disposes all objects involved with drawing the grid. 
		private void DisposeDrawingObjects() 
		{ 
			if (_userValueBrush != null) _userValueBrush.Dispose(); 
			if (_incorrectValueBrush != null) _incorrectValueBrush.Dispose(); 
			if (_originalValueBrush != null) _originalValueBrush.Dispose(); 
			if (_centerNumberFormat != null) _centerNumberFormat.Dispose(); 
 
			_userValueBrush = null; 
			_originalValueBrush = null; 
			_centerNumberFormat = null; 
		} 
 
		/// Refocus when the control is enabled.  Nothing else should ever have focus. 
		/// The event arguments. 
		protected override void OnEnabledChanged(EventArgs e) 
		{ 
			base.OnEnabledChanged(e); 
			if (Enabled) Focus(); 
		} 
		#endregion 
 
		#region Puzzle State 
		/// Gets or sets the current puzzle state. 
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
		[Browsable(false)] 
		public PuzzleState State 
		{ 
			get 
			{ 
				if (_state == null)  
				{ 
					_state = new PuzzleState(); 
					OnStateChanged(); 
				} 
				return _state; 
			} 
			set 
			{ 
				if (value == null) value = new PuzzleState(); 
				if (value != _state) 
				{ 
					PuzzleState oldState = _state; 
					if (oldState != null) 
					{ 
						oldState.StateChanged -= new EventHandler(HandlePuzzleStateChanged); 
						oldState.RaiseStateChangedEvent = false; 
					} 
 
					_state = value; 
					_state.RaiseStateChangedEvent = true; 
					_state.StateChanged += new EventHandler(HandlePuzzleStateChanged); 
					OnStateChanged(); 
					Invalidate(); 
				} 
			} 
		} 
 
		/// Gets or sets the original state of the puzzle. 
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
		[Browsable(false)] 
		internal PuzzleState OriginalState  
		{ 
			get { return _originalState; } 
			set { SetOriginalPuzzleCheckpoint(value); } 
		} 
 
		/// Gets the collection of undo states. 
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
		[Browsable(false)] 
		internal PuzzleStateStack UndoStates 
		{ 
			get { return _undoStates; } 
			set 
			{ 
				if (value == null) throw new ArgumentNullException("value"); 
				_undoStates = value; 
			} 
		} 
 
		/// Event raised when the grid's puzzle state is modified. 
		public event EventHandler StateChanged; 
 
		/// Raises the StateChanged event on the main UI thread. 
		private void OnStateChanged() 
		{ 
			_suggestedCell = null; 
			EventHandler handler = StateChanged; 
			if (handler != null) 
			{ 
				if (InvokeRequired) Invoke(handler, new object[] { this, EventArgs.Empty }); 
				else handler(this, EventArgs.Empty); 
			} 
		} 
 
		/// Raises the StateChanged event in response to the puzzle state's StateChanged event. 
		/// The modified puzzle state. 
		/// The event args. 
		private void HandlePuzzleStateChanged(object sender, EventArgs e) 
		{ 
			OnStateChanged(); 
		} 
		 
		/// Gets whether the specified cell can be modified based on the original state checkpoint. 
		/// The cell to be verified. 
		/// true if the cell can be modified; otherwise, false. 
		private bool CanModifyCell(Point cell) 
		{ 
			return (cell.X >= 0 && cell.X < State.GridSize && cell.Y >= 0 && cell.Y < State.GridSize) &&  
				(_originalState == null || !_originalState[cell].HasValue); 
		}		 
 
		/// Gets whether the specified cell can be cleared. 
		/// The cell to test. 
		/// Whether the cell can be cleared (it can be modified and it has a value). 
		private bool CanClearCell(Point cell) 
		{ 
			return CanModifyCell(cell) && State[cell].HasValue; 
		} 
		 
		/// Gets the grid cell containing the specified point in the control. 
		/// The point, typically a mouse location, for which to find the grid cell. 
		/// The location of the grid cell corresponding to the specified point. 
		public Point GetCellFromLocation(Point point) 
		{ 
			if (point.X < 0 || point.Y < 0) return new Point(-1,-1); 
 
			Rectangle rect = BoardRectangle; 
			if (rect.Width <= 0 || rect.Height <= 0) return new Point(-1, -1); 
 
			float cellWidth = rect.Width / (float)_boardWidth * (_cellImageWidth+1); 
			float gapWidth = rect.Width / (float)_boardWidth * (_gapImageWidth-1); 
 
			int x = 0, y = 0; 
 
			float pos = point.Y - rect.X; 
			while(pos >= cellWidth)  
			{ 
				pos -= cellWidth; 
				++x; 
				if (x % _boxSize == 0) pos -= gapWidth; 
			} 
 
			pos = point.X - rect.Y; 
			while(pos >= cellWidth)  
			{ 
				pos -= cellWidth; 
				++y; 
				if (y % _boxSize == 0) pos -= gapWidth; 
			} 
 
			return new Point(x,y); 
		} 
		#endregion 
 
		#region Hints 
		/// Gets or sets whether to highlight cells that can only be a specific number. 
		public bool ShowSuggestedCells 
		{ 
			get { return _showSuggestedCells; } 
			set 
			{ 
				if (value != _showSuggestedCells) 
				{ 
					_showSuggestedCells = value; 
					Invalidate(); 
				} 
			} 
		} 
 
		/// Gets or sets whether to display the possible numbers in each cell. 
		public bool ShowIncorrectNumbers 
		{ 
			get { return _showIncorrectNumbers; } 
			set 
			{ 
				if (value != _showIncorrectNumbers) 
				{ 
					_showIncorrectNumbers = value; 
					Invalidate(); 
				} 
			} 
		} 
		#endregion 
 
		#region Puzzle Interaction 
		/// Gets whether the puzzle has been modified. 
		public bool PuzzleHasBeenModified 
		{ 
			get 
			{ 
				PuzzleStatus status = State.Status; 
				return status != PuzzleStatus.Solved && 
					(HasScratchpadStrokes || (OriginalState != null && !State.Equals(OriginalState))); 
			} 
		} 
 
		/// Generates a new puzzle and sets it to be the current puzzle in the grid. 
		/// Options to use for the puzzle generation. 
		/// Resets the undo states for the grid. 
		public void GenerateNewPuzzle(GeneratorOptions options) 
		{ 
			LoadNewPuzzle(new Generator(options).Generate()); 
		} 
 
		/// Gets or sets the puzzle difficulty level used for showing hints. 
		public PuzzleDifficulty PossibleNumbersDifficultyLevel 
		{ 
			get { return _difficultyLevel; } 
			set  
			{  
				if (!Enum.IsDefined(typeof(PuzzleDifficulty), value)) throw new ArgumentOutOfRangeException("value"); 
				if (_difficultyLevel != value) 
				{ 
					_difficultyLevel = value;  
					_suggestedCell = null; 
					Invalidate(); 
				} 
			} 
		} 
 
		/// Loads a new puzzle and sets it to be the current puzzle in the grid. 
		/// The puzzle to load. 
		public void LoadNewPuzzle(PuzzleState state) 
		{ 
			ClearUndoCheckpoints(); 
			ClearOriginalPuzzleCheckpoint(); 
			SetOriginalPuzzleCheckpoint(state.Clone()); 
			State = state; 
			ResetTabletSupportForNewPuzzle(); 
			_selectedCell = FirstEmptyCell; 
		} 
 
		/// Restores a puzzle based on the state from a serialized game. 
		/// The current puzzle state. 
		/// The original puzzle state. 
		/// The undo state stack. 
		/// The grid's ink data. 
		public void RestorePuzzle( 
			PuzzleState state, PuzzleState originalState, PuzzleStateStack undoStates, byte [] inkData) 
		{ 
			OriginalState = originalState; 
			UndoStates = undoStates; 
			InkData = inkData; 
			ResizeScratchpadInk(); 
			State = state; 
			_selectedCell = FirstEmptyCell; 
		} 
 
		/// Gets the position of the first empty cell in the puzzle, or the [0,0] cell if none are empty. 
		private Point FirstEmptyCell 
		{ 
			get 
			{ 
				PuzzleState state = State; 
				if (state == null) return new Point(0,0); 
				for(int i=0; iDeletes all scratchpad ink on the grid. 
		private void ResetTabletSupportForNewPuzzle() 
		{ 
			if (_inkOverlay != null) 
			{ 
				using(Strokes scratchpadStrokes = ScratchpadStrokes) 
				{ 
					_inkOverlay.Ink.DeleteStrokes(scratchpadStrokes); 
					scratchpadStrokes.Clear(); 
				} 
			} 
		} 
 
		/// Creates a checkpoint used to determine where cells are invalid in the puzzle. 
		public void SetOriginalPuzzleCheckpoint(PuzzleState original) 
		{ 
			_originalState = original; 
			if (original != null) 
			{ 
				SolverOptions options = new SolverOptions(); 
				options.MaximumSolutionsToFind = 2; 
				SolverResults results = Solver.Solve(original, options); 
				if (results.Status == PuzzleStatus.Solved && results.Puzzles.Count == 1) 
				{ 
					_solvedOriginalState = results.Puzzle;  
				} 
				else _solvedOriginalState = null; 
			} 
		} 
 
		/// Clears the original puzzle checkpoint. 
		public void ClearOriginalPuzzleCheckpoint() 
		{ 
			_originalState = null; 
			_solvedOriginalState = null; 
		} 
 
		/// Clears the undo puzzle checkpoints. 
		public void ClearUndoCheckpoints() 
		{ 
			_undoStates.Clear(); 
		} 
 
		/// Creates an undo checkpoint such that the current grid state can be reached through the undo mechanism. 
		public void SetUndoCheckpoint() 
		{ 
			if (State != null) 
			{ 
				_undoStates.Push(GetClonedStateForUndo()); 
			} 
		} 
 
		/// Deep clones the current puzzle state, also storing the current InkData into its Tag. 
		/// The PuzzleState clone. 
		private PuzzleState GetClonedStateForUndo() 
		{ 
			PuzzleState state = State.Clone(); 
			state.Tag = InkData; 
			return state; 
		} 
 
		/// Gets whether the overlay is currently collecting ink. 
		public bool CollectingInk { get { return _inkOverlay != null && _inkOverlay.CollectingInk; } } 
 
		/// Reverts to a previously saved state. 
		public void Undo() 
		{ 
			if (_undoStates.Count > 0 && !CollectingInk) 
			{ 
				State = _undoStates.Pop(); 
				if (State.Tag is byte[])  
				{ 
					InkData = (byte[])State.Tag; 
					State.Tag = null; 
				} 
				OnStateChanged(); 
				Invalidate(); 
			} 
		} 
 
		/// Attempts to solve the current puzzle, update the state in the grid, and return the results. 
		/// The results from attempting to solve the puzzle. 
		private SolverResults SolvePuzzle() 
		{ 
			// If it's already solved, nothing to do 
			if (State.Status == PuzzleStatus.Solved) return new SolverResults(PuzzleStatus.Solved, State, 0, null); 
	 
			// Otherwise, try to solve it. 
			SolverOptions options = new SolverOptions(); 
			options.MaximumSolutionsToFind = 1u; // this means that if there are multiple solutions, we'll find and use the first 
			options.AllowBruteForce = true; 
			options.EliminationTechniques = new TechniqueCollection(new NakedSingleTechnique()); 
	 
			SolverResults results = Solver.Solve(State, options); 
			if (results.Status == PuzzleStatus.Solved && results.Puzzles.Count == 1) 
			{ 
				SetUndoCheckpoint(); 
				State = results.Puzzle; 
			} 
			return results; 
		} 
		#endregion 
 
		#region Drawing 
		/// The width/height of a box.  This would need to become variable if differently sized puzzles were supported. 
		private const int _boxSize = 3; 
		/// The width of the underlying board image. 
		private const int _boardWidth = 518; 
		/// The height of the underlying board image. 
		private const int _boardHeight = 518; 
		/// The width of a cell in the underlying board image. 
		private const int _cellImageWidth = 54; 
		/// The height of a cell in the underlying board image. 
		private const int _cellImageHeight = 54; 
		/// The width of a the gap between cells in the underlying board image. 
		private const int _cellGapWidth = 1; 
		/// The height of a the gap between cells in the underlying board image. 
		private const int _cellGapHeight = 1; 
		/// The width of a the gap between boxes in the underlying board image. 
		private const int _gapImageWidth = 13; 
		/// The height of a the gap between boxes in the underlying board image. 
		private const int _gapImageHeight = 13; 
 
		/// Gets the bounding rectangle for a specific cell. 
		/// The client rectange. 
		/// The target cell. 
		/// The bounding rectangle. 
		public static RectangleF GetCellRectangle(Rectangle rect, Point cell) 
		{ 
			float width = (_cellImageWidth + _cellGapWidth) / (float)_boardWidth * rect.Width; 
			float height = (_cellImageHeight + _cellGapHeight) / (float)_boardHeight * rect.Height; 
			RectangleF cellRect = new RectangleF( 
				rect.X + (cell.Y*width) + ((cell.Y/_boxSize)*((_gapImageWidth-1)/(float)_boardWidth*rect.Width)),  
				rect.Y + (cell.X*height) + ((cell.X/_boxSize)*((_gapImageHeight-1)/(float)_boardHeight*rect.Height)),  
				_cellImageWidth / (float)_boardWidth * rect.Width, 
				_cellImageHeight / (float)_boardHeight * rect.Height); 
			return cellRect; 
		} 
 
		/// Raises the Paint event. 
		/// A PaintEventArgs that contains the event data. 
		protected override void OnPaint(PaintEventArgs e) 
		{ 
			base.OnPaint(e); 
			e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; 
			DrawToGraphics(e.Graphics, e.ClipRectangle); 
		} 
 
		/// Brush used to draw text and other objects with the font color. 
		private Brush _userValueBrush; 
		/// Brush used to draw incorrect values on the grid. 
		private Brush _incorrectValueBrush; 
		/// Brush used to draw text and other objects with a variation of the font color. 
		private Brush _originalValueBrush; 
		/// StringFormat used to center text within a rectangle. 
		private StringFormat _centerNumberFormat; 
 
		/// The location of the cell to suggest as a "try this one next" hint. 
		private NullablePoint _suggestedCell = null; 
 
		/// Gets the location of the cell to suggest as a "try this one next" hint. 
		private NullablePoint SuggestedCell 
		{ 
			get 
			{ 
				if (_suggestedCell == null) 
				{ 
					// First, see if any cells are the only one left to be filled in their 
					// row, column, or box.  Regardless of techniques in use, only one 
					// cell without a value in a row, column, or box really must be the easiest. 
					NullablePoint foundEmpty = null; 
					for(byte row=0; rowGets the current rectangle for the playing board. 
		public Rectangle BoardRectangle  
		{ 
			get 
			{ 
				Rectangle rect = ClientRectangle; 
				int amount = rect.Width / 51; 
				if (amount < 2) amount = 2; 
				rect.Inflate(-amount, -amount); 
				return rect; 
			} 
		} 
 
		/// The font size, in points, for numbers on the grid. 
		private float _cachedEmSize = -1; 
 
		/// Clears the cached font size when the font is changed. 
		protected override void OnFontChanged(EventArgs e) 
		{ 
			_cachedEmSize = -1; 
			base.OnFontChanged (e); 
		} 
 
		/// Draws the playing grid onto the specified graphics object and within the specified bounding rectangle. 
		/// The graphics object onto 
		public void DrawToGraphics(Graphics graphics, Rectangle clipRectangle) 
		{ 
			if (graphics == null) throw new ArgumentNullException("graphics"); 
			Rectangle rect = BoardRectangle; 
 
			// Draw the underlying board images 
			graphics.DrawImage(ResourceHelper.BoardBackgroundImage, 0, 0, Width, Height); 
			graphics.DrawImage(ResourceHelper.BoardImage, rect);  
 
			// Precompute some important sizes, such as the width and height of 
			// a cell in the grid, positioning information for drawing possible numbers, 
			// and the em size to use for drawing text. 
			RectangleF genericCellRect = GetCellRectangle(rect, new Point(0,0)); 
			float cellWidth = genericCellRect.Width; 
			float cellHeight = genericCellRect.Height; 
 
			// Get the em size for the current font based on the current board size. 
			// This is cached, and the cache is only cleared when the board is resized or when the font is changed. 
			float emSize; 
			if (_cachedEmSize < 0) _cachedEmSize = GraphicsHelpers.GetMaximumEMSize(ResourceHelper.FontSizingString, graphics, this.Font.FontFamily, FontStyle.Bold, cellWidth, cellHeight); 
			emSize = _cachedEmSize; 
 
			bool showSuggestedCells = ShowSuggestedCells; 
			 
			// Draw cell images 
			using (Font setNumberFont = new Font(this.Font.FontFamily, emSize, FontStyle.Bold)) 
			{ 
				for (int i = 0; i < State.GridSize; i++) 
				{ 
					for (int j = 0; j < State.GridSize; j++) 
					{ 
						RectangleF cellRect = GetCellRectangle(rect, new Point(i,j)); 
						if (clipRectangle.IntersectsWith(Rectangle.Ceiling(cellRect))) 
						{ 
							if (State.Status != PuzzleStatus.Solved) 
							{ 
								if (_selectedCell.HasValue && _selectedCell.Value.X == i && _selectedCell.Value.Y == j && 
									_mode != PuzzleGridMode.Scratchpad) 
								{ 
									Image selectedCellImage; 
									if (i == 0 && j == 0) selectedCellImage = ResourceHelper.CellActiveUpperLeft; 
									else if (i == 0 && j == State.GridSize-1) selectedCellImage = ResourceHelper.CellActiveUpperRight; 
									else if (i == State.GridSize-1 && j == 0) selectedCellImage = ResourceHelper.CellActiveLowerLeft; 
									else if (i == State.GridSize-1 && j == State.GridSize-1) selectedCellImage = ResourceHelper.CellActiveLowerRight; 
									else selectedCellImage = ResourceHelper.CellActiveSquare; 
									graphics.DrawImage(selectedCellImage, cellRect.X, cellRect.Y, cellRect.Width, cellRect.Height); 
								} 
								else if (showSuggestedCells && SuggestedCell.HasValue &&  
									SuggestedCell.Value.X == i && SuggestedCell.Value.Y == j) 
								{ 
									Image suggestedCellImage; 
									if (i == 0 && j == 0) suggestedCellImage = ResourceHelper.CellHintUpperLeft; 
									else if (i == 0 && j == State.GridSize-1) suggestedCellImage = ResourceHelper.CellHintUpperRight; 
									else if (i == State.GridSize-1 && j == 0) suggestedCellImage = ResourceHelper.CellHintLowerLeft; 
									else if (i == State.GridSize-1 && j == State.GridSize-1) suggestedCellImage = ResourceHelper.CellHintLowerRight; 
									else suggestedCellImage = ResourceHelper.CellHintSquare; 
									graphics.DrawImage(suggestedCellImage, cellRect.X, cellRect.Y, cellRect.Width, cellRect.Height); 
								} 
							} 
 
							// If a cell has a value, then draw that value 
							if (State[i, j].HasValue) 
							{ 
								Brush b; 
								if (ShowIncorrectNumbers && 
									State[i, j].HasValue && _solvedOriginalState != null &&  
									State[i, j].Value != _solvedOriginalState[i, j].Value) 
								{ 
									b = _incorrectValueBrush; 
								} 
								else if (_originalState != null && _originalState[i,j].HasValue) 
								{ 
									b = _originalValueBrush; 
								}  
								else b = _userValueBrush; 
 
								graphics.DrawString((State[i, j] + 1).ToString(CultureInfo.InvariantCulture), setNumberFont, b, 
									cellRect, _centerNumberFormat); 
							} 
						} 
					} 
				} 
			} 
		 
			// Draw the scratchpad ink 
			try 
			{ 
				RenderInk(graphics); 
			}  
			catch(COMException) {} 
		} 
 
		/// Draws the scratchpad ink. 
		/// The graphics onto which ink is drawn. 
		private void RenderInk(Graphics graphics) 
		{ 
			if (_inkOverlay != null) 
			{ 
				using(Strokes strokes = _inkOverlay.Ink.Strokes) 
				{ 
					if (strokes.Count > 0) 
					{ 
						_inkOverlay.Renderer.Draw(graphics, strokes); 
					} 
				} 
			} 
		} 
		#endregion 
 
		#region Keyboard and Mouse Interaction 
		/// Numeric touch mode value for when we're in touch mode. 
		private byte _touchModeValue; 
		/// The current mode of the grid (pen, eraser, touch, etc.). 
		private PuzzleGridMode _mode; 
 
		/// Gets or sets the numeric touch-mode value for when we're in touch mode. 
		public byte TouchModeValue 
		{ 
			get { return _touchModeValue; } 
			set { _touchModeValue = value; } 
		} 
 
		/// Stats the PuzzleGrid can be in. 
		public enum PuzzleGridMode 
		{ 
			Pen, 
			Eraser, 
			Touch, 
			Scratchpad 
		} 
 
		/// Gets or sets the mode of the PuzzleGrid. 
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
		[Browsable(false)] 
		public PuzzleGridMode Mode 
		{ 
			get { return _mode; }  
			set  
			{ 
				if (_mode != value) 
				{ 
					_mode = value; 
					switch(value) 
					{ 
						case PuzzleGridMode.Pen: 
						case PuzzleGridMode.Eraser: 
							SetTemporaryTabletSupportEnabled(true); 
							SetupScratchpadMode(false); 
							AdaptToCursorAndMode(false); 
							break; 
						case PuzzleGridMode.Scratchpad: 
							SetTemporaryTabletSupportEnabled(true); 
							SetupScratchpadMode(true); 
							AdaptToCursorAndMode(false); 
							break; 
						case PuzzleGridMode.Touch: 
							SetTemporaryTabletSupportEnabled(false); 
							SetupScratchpadMode(false); 
							break; 
						default: 
							throw new InvalidEnumArgumentException("value", (int)value, typeof(PuzzleGridMode)); 
					} 
					Invalidate(); 
				} 
			} 
		} 
 
		/// Determines whether the specified key is a regular input key or a special key that requires processing. 
		/// A Keys value. 
		/// Whether the specified Keys should be treated as an input key. 
		protected override bool IsInputKey(Keys keyData) 
		{ 
			// We want to allow the control to handle up, down, left, right, so that 
			// the user can move around the selected cell marker with the arrow keys 
			switch (keyData) 
			{ 
				case Keys.Up: 
				case Keys.Down: 
				case Keys.Left: 
				case Keys.Right: 
					return true; 
			} 
			return base.IsInputKey(keyData); 
		} 
 
		/// Handles the KeyDown event. 
		/// Event args 
		protected override void OnKeyDown(KeyEventArgs e) 
		{ 
			Keys k = e.KeyCode; 
			bool handled = true; 
			bool invalidate = true; 
 
			if ((_mode != PuzzleGridMode.Scratchpad) && !CollectingInk) 
			{ 
				Point selectedCell = _selectedCell.Value; 
 
				// Alt+Shift+2, in homage to Windows Solitaire :-) 
				if (k == Keys.D2 && e.Shift && e.Alt)  
				{ 
					SolvePuzzle(); 
				} 
					// Digits are treated as numbers and stored into the grid 
				else if ((k >= Keys.D1 && k <= Keys.D9) || (k >= Keys.NumPad1 & k <= Keys.NumPad9)) 
				{ 
					if (State != null && CanModifyCell(selectedCell)) 
					{ 
						SetStateCell(selectedCell, (k >= Keys.NumPad1 && k <= Keys.NumPad9) ? 
							(byte)(k - Keys.NumPad1) : (byte)(k - Keys.D1)); 
					} 
				} 
					// Delete, space, and a variety of other keys are used to clear out the contents of a grid cell 
				else if (k == Keys.Delete || k == Keys.Space || k == Keys.Back) 
				{ 
					if (State != null && CanClearCell(selectedCell))  
					{ 
						ClearStateCellWithInvalidation(selectedCell); 
						invalidate = false; 
					} 
				} 
				else if (k == Keys.Enter && _mode == PuzzleGridMode.Touch && _touchModeValue > 0) 
				{ 
					if (State != null && CanModifyCell(selectedCell))  
					{ 
						SetStateCell(selectedCell, (byte)(_touchModeValue-1)); 
					} 
				} 
					// And the arrow keys are used to move around the grid 
				else if (k == Keys.Down && selectedCell.X + 1 < State.GridSize)  
				{ 
					InvalidateCell(selectedCell); 
					selectedCell.X++; 
					InvalidateCell(selectedCell); 
					invalidate = false; 
				} 
				else if (k == Keys.Up && selectedCell.X - 1 >= 0)  
				{ 
					InvalidateCell(selectedCell); 
					selectedCell.X--; 
					InvalidateCell(selectedCell); 
					invalidate = false; 
				} 
				else if (k == Keys.Right && selectedCell.Y + 1 < State.GridSize)  
				{ 
					InvalidateCell(selectedCell); 
					selectedCell.Y++; 
					InvalidateCell(selectedCell); 
					invalidate = false; 
				} 
				else if (k == Keys.Left && selectedCell.Y - 1 >= 0)  
				{ 
					InvalidateCell(selectedCell); 
					selectedCell.Y--; 
					InvalidateCell(selectedCell); 
					invalidate = false; 
				} 
					// Otherwise, not handled 
				else handled = false; 
 
				// If it was handled, update the necessary state 
				if (handled) 
				{ 
					_selectedCell = selectedCell; 
					e.Handled = true; 
					if (invalidate) Invalidate(); 
				} 
			} 
			 
			if (!e.Handled) base.OnKeyDown(e); 
		} 
 
		/// Update the selected cell when the mouse is clicked down. 
		/// The mouse event args. 
		protected override void OnMouseDown(MouseEventArgs e) 
		{ 
			base.OnMouseDown(e); 
			if (_mode != PuzzleGridMode.Scratchpad) 
			{ 
				Point cell = GetCellFromLocation(new Point(e.X, e.Y)); 
				if (_mode == PuzzleGridMode.Touch && CanModifyCell(cell)) 
				{ 
					if (_touchModeValue > 0)  
					{ 
						SetStateCell(cell, (byte)(_touchModeValue-1)); 
						Invalidate(); 
					} 
				} 
				if (_selectedCell.HasValue) InvalidateCell(_selectedCell.Value); 
				SetSelectedCell(cell); 
				InvalidateCell(cell); 
			} 
		} 
 
		/// Sets the selected cell to the specified cell, if it's valid. 
		/// The cell to be selected. 
		private void SetSelectedCell(Point cell) 
		{ 
			if (cell.X >= 0 && cell.X < State.GridSize && 
				cell.Y >= 0 && cell.Y < State.GridSize && 
				_selectedCell != cell)  
			{ 
				_selectedCell = cell; 
			} 
		} 
		#endregion 
 
		#region Tablet Support 
		/// Enables or disables the ink overlay. 
		/// Whether to enable or disable the overlay. 
		public void SetTemporaryTabletSupportEnabled(bool enabled) 
		{ 
			if (_inkOverlay != null &&  
				_inkOverlay.Enabled != enabled) _inkOverlay.Enabled = enabled; 
		} 
 
		/// Turns on support for the Tablet PC input device. 
		public void EnableTabletSupport() 
		{ 
			if (_inkOverlay == null) 
			{ 
				// Get the recognizer to use and configure it. 
				Recognizer defaultRecognizer = PlatformDetection.GetDefaultRecognizer(); 
				if (defaultRecognizer != null) 
				{ 
					_recognizerCtx = defaultRecognizer.CreateRecognizerContext(); 
					_recognizerCtx.Factoid = Factoid.Digit; 
				} 
 
				// Create the overlay and configure it 
				bool gestureRecognizerInstalled = PlatformDetection.GestureRecognizerInstalled; 
				_inkOverlay = new InkOverlay(this, true); 
				_inkOverlay.Ink.CustomStrokes.Add(ScratchpadStrokesID, _inkOverlay.Ink.CreateStrokes()); 
				_inkOverlay.Ink.CustomStrokes.Add(NormalStrokesID, _inkOverlay.Ink.CreateStrokes()); 
				_inkOverlay.DefaultDrawingAttributes.Color = _inkColor;  
				_inkOverlay.CollectionMode = gestureRecognizerInstalled ? CollectionMode.InkAndGesture : CollectionMode.InkOnly; 
				_inkOverlay.AutoRedraw = false; 
				_inkOverlay.DynamicRendering = true; 
				if (gestureRecognizerInstalled) 
				{ 
					_inkOverlay.SetGestureStatus(ApplicationGesture.AllGestures, false); 
					_inkOverlay.SetGestureStatus(ApplicationGesture.Scratchout, true); 
					_inkOverlay.Gesture += new InkCollectorGestureEventHandler(HandleGesture); 
				} 
				_inkOverlay.Stroke += new InkCollectorStrokeEventHandler(HandleStroke); 
				_inkOverlay.CursorInRange += new InkCollectorCursorInRangeEventHandler(HandleCursorInRange); 
				_inkOverlay.NewPackets += new InkCollectorNewPacketsEventHandler(HandleNewPackets); 
				_inkOverlay.NewInAirPackets += new InkCollectorNewInAirPacketsEventHandler(HandleNewInAirPackets); 
				_inkOverlay.StrokesDeleting += new InkOverlayStrokesDeletingEventHandler(HandleStrokesDeleting); 
				_inkOverlay.Enabled = true; 
 
				Invalidate(); 
			} 
		} 
 
		/// Disables support for the Tablet PC input device. 
		/// True if tablet support could be disabled; otherwise, false. 
		public void DisableTabletSupport() 
		{ 
			if (_inkOverlay != null) 
			{ 
				_inkOverlay.Dispose(); 
				_inkOverlay = null; 
 
				if (_recognizerCtx != null) _recognizerCtx.Dispose(); 
				_recognizerCtx = null; 
				 
				Invalidate(); 
			} 
		} 
		 
		/// Gets the current collection of normal strokes. 
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
		[Browsable(false)] 
		private Strokes NormalStrokes  
		{ 
			get { return _inkOverlay.Ink.CustomStrokes[NormalStrokesID]; } 
		} 
 
		/// Gets the current collection of scratchpad strokes. 
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
		[Browsable(false)] 
		private Strokes ScratchpadStrokes 
		{ 
			get { return _inkOverlay.Ink.CustomStrokes[ScratchpadStrokesID]; } 
		} 
 
		/// Gets whether the overlay currently has any scratchpad strokes. 
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
		[Browsable(false)] 
		private bool HasScratchpadStrokes  
		{  
			get  
			{ 
				using(Strokes strokes = ScratchpadStrokes) 
				{ 
					return strokes.Count > 0;  
				} 
			} 
		} 
 
		/// Sets the editing mode of the overlay based on which side of the pen is being used. 
		/// The overlay. 
		/// The event arguments. 
		void HandleCursorInRange(object sender, InkCollectorCursorInRangeEventArgs e) 
		{ 
			AdaptToCursorAndMode(e.Cursor.Inverted); 
		} 
 
		/// Adapts the overlay to the current cursor state and mode. 
		/// Whether the cursor is known to be currently inverted. 
		internal void AdaptToCursorAndMode(bool cursorKnownToBeInverted) 
		{ 
			if (_inkOverlay == null) return; 
 
			// Is the cursor upside down (eraser) or are we explicitly in eraser mode? 
			bool invertedOrEraserMode = cursorKnownToBeInverted || _mode == PuzzleGridMode.Eraser; 
 
			switch(_inkOverlay.EditingMode) 
			{ 
					// If we're currently in ink mode but the cursor has become 
					// inverted or eraser mode has been set explicitly,  
					// swap into delete mode 
				case InkOverlayEditingMode.Ink: 
					if (invertedOrEraserMode) 
					{ 
						_processStrokesTimer.Stop(); 
						using(Strokes normalStrokes = NormalStrokes) 
						{ 
							_inkOverlay.Ink.DeleteStrokes(normalStrokes); 
							normalStrokes.Clear(); 
						} 
						_inkOverlay.Enabled = false; 
						_inkOverlay.EditingMode = InkOverlayEditingMode.Delete; 
						_inkOverlay.Enabled = true; 
					} 
					break; 
 
					// If we're currently in delete mode but the cursor 
					// is not inverted and we're not in eraser mode, 
					// swap back into ink mode 
				case InkOverlayEditingMode.Delete: 
					if(!invertedOrEraserMode) 
					{ 
						_processStrokesTimer.Stop(); 
						using(Strokes normalStrokes = NormalStrokes) 
						{ 
							_inkOverlay.Ink.DeleteStrokes(normalStrokes); 
							normalStrokes.Clear(); 
						} 
						_inkOverlay.Enabled = false; 
						_inkOverlay.EditingMode = InkOverlayEditingMode.Ink; 
						_inkOverlay.Enabled = true; 
					} 
					break; 
			} 
		} 
 
		/// Determines in which cell a stroke was made. 
		/// The stroke to analyze. 
		/// The location of the cell under the stroke. 
		private Point GetCellFromStroke(Stroke s) 
		{ 
			Rectangle rect = s.GetBoundingBox(); 
			InkToPixelSpace(ref rect); 
			Point cell = GetCellFromLocation(new Point((rect.Left + rect.Right) / 2, (rect.Top + rect.Bottom) / 2)); 
			return cell; 
		} 
 
		/// Responds to a gesture made on the overlay. 
		/// The overlay. 
		/// The event arguments. 
		void HandleGesture(object sender, InkCollectorGestureEventArgs e) 
		{ 
			if (InvokeRequired) 
			{ 
				Invoke(new InkCollectorGestureEventHandler(HandleGesture), new object[]{sender, e}); 
				return; 
			} 
 
			if (_mode == PuzzleGridMode.Scratchpad)  
			{ 
				HandleGestureInScratchpadMode(sender, e); 
			} 
			else  
			{ 
				HandleGestureInNormalMode(sender, e); 
			} 
		} 
 
		/// Responds to a gesture made on the overlay while in normal mode. 
		/// The overlay. 
		/// The event arguments. 
		void HandleGestureInNormalMode(object sender, InkCollectorGestureEventArgs e) 
		{ 
			try 
			{ 
				switch (e.Gestures[0].Id) 
				{ 
						// If the scratchout gesture was made over a cell 
						// that can be modified, delete its contents 
					case ApplicationGesture.Scratchout: 
						Point cell = GetCellFromStroke(e.Strokes[0]); 
						_inkOverlay.Ink.DeleteStrokes(e.Strokes); 
						RecognizePreviousNormalStrokes(); 
						if (CanClearCell(cell)) ClearStateCell(cell); 
						break; 
					default: 
						e.Cancel = true; 
						break; 
				} 
			} 
			catch (COMException) { } 
			Invalidate(); 
		} 
 
		/// Responds to a gesture made on the overlay while in scratchpad mode. 
		/// The overlay. 
		/// The event arguments. 
		void HandleGestureInScratchpadMode(object sender, InkCollectorGestureEventArgs e) 
		{ 
			if (e.Strokes.Count > 0) 
			{ 
				switch (e.Gestures[0].Id) 
				{ 
						// Erase any strokes in the bounding box of the gesture 
					case ApplicationGesture.Scratchout: 
						Rectangle intersectRect = e.Strokes[0].GetBoundingBox(); 
						EraseScratchpadStrokes(intersectRect); 
						break; 
					default: 
						e.Cancel = true; 
						break; 
				} 
			} 
			Invalidate(); 
		} 
 
		/// Erases any scratchpad strokes that intersect with the specified rectangle. 
		/// The rectangle to test for intersection, in ink space coordinates. 
		private void EraseScratchpadStrokes(Rectangle intersectRect) 
		{ 
			using(Strokes strokes = ScratchpadStrokes) 
			{ 
				for(int i=strokes.Count-1; i>=0; --i) 
				{ 
					Stroke s = strokes[i]; 
					if (s.GetBoundingBox().IntersectsWith(intersectRect)) 
					{ 
						strokes.RemoveAt(i); 
						_inkOverlay.Ink.DeleteStroke(s); 
					} 
				} 
			} 
		} 
 
		/// Gets or sets the current ink data for the overlay. 
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
		[Browsable(false)] 
		public byte [] InkData 
		{ 
			get  
			{  
				return _inkOverlay != null ? 
					_inkOverlay.Ink.Save(PersistenceFormat.InkSerializedFormat) : 
					new byte[0];  
			} 
			set  
			{  
				if (_inkOverlay != null) 
				{ 
					if (value != null && value.Length > 0) 
					{ 
						InkOverlayEditingMode mode = _inkOverlay.EditingMode; 
						bool overlayWasEnabled = _inkOverlay.Enabled; 
						_inkOverlay.Enabled = false; 
					 
						// Load the ink data into a new Ink and setup the overlay 
						// to use that new Ink 
						Microsoft.Ink.Ink ink = new Microsoft.Ink.Ink(); 
						ink.Load(value); 
						Microsoft.Ink.Ink oldInk = _inkOverlay.Ink; 
						_inkOverlay.Ink = ink; 
						if (oldInk != null) oldInk.Dispose(); 
 
						// Normal strokes are sometimes captured into undo data, and before they're  
						// added to the normal collection. So when restoring ink, remove any strokes  
						// that aren't in the scratchpad collection. 
						using(Strokes goodStrokes = ScratchpadStrokes) 
						using(Strokes strokes = _inkOverlay.Ink.Strokes) 
						{ 
							Hashtable scratchpadStrokesIndex = new Hashtable(goodStrokes.Count); 
							foreach(Stroke s in goodStrokes) 
							{ 
								scratchpadStrokesIndex.Add(s.Id, s); 
							} 
							if (strokes.Count > 0) 
							{ 
								foreach(Stroke s in strokes) 
								{ 
									if (!scratchpadStrokesIndex.ContainsKey(s.Id)) 
									{ 
										_inkOverlay.Ink.DeleteStroke(s); 
									} 
								} 
							} 
						} 
						using(Strokes normalStrokes = NormalStrokes) normalStrokes.Clear(); 
 
						_inkOverlay.EditingMode = mode; 
						if (overlayWasEnabled) 
						{ 
							_inkOverlay.Enabled = true; 
						} 
 
						// Update the scratchpad ink we just restored to fit the current grid size 
						ResizeScratchpadInk(); 
					} 
					SetupScratchpadMode(_mode == PuzzleGridMode.Scratchpad); 
				} 
			} 
		} 
 
		/// Sets up for scratchpad mode or for leaving scratchpad mode. 
		/// true if entering scratchpad mode; otherwise, false. 
		private void SetupScratchpadMode(bool enterMode) 
		{ 
			if (_inkOverlay != null) 
			{ 
				_processStrokesTimer.Stop(); 
 
				using(Strokes normalStrokes = NormalStrokes)  
				{ 
					_inkOverlay.Ink.DeleteStrokes(normalStrokes); 
					normalStrokes.Clear(); 
				} 
					 
				if (enterMode) 
				{ 
					using(Strokes strokes = ScratchpadStrokes) 
					{ 
						foreach(Stroke s in strokes) 
						{ 
							s.DrawingAttributes.Color = _scratchpadInkColor; 
						} 
					} 
				} 
				else 
				{ 
					using(Strokes strokes = ScratchpadStrokes) 
					{ 
						foreach(Stroke s in strokes) 
						{ 
							s.DrawingAttributes.Color = _scratchpadInkInNormalModeColor; 
						} 
					} 
				} 
 
				_inkOverlay.DefaultDrawingAttributes.Color = enterMode ? _scratchpadInkColor : _inkColor; 
					 
				Invalidate(); 
			} 
		} 
 
		/// Handles a stroke made on the overlay. 
		/// The overlay. 
		/// The event arguments. 
		void HandleStroke(object sender, InkCollectorStrokeEventArgs e) 
		{ 
			if (InvokeRequired) 
			{ 
				Invoke(new InkCollectorStrokeEventHandler(HandleStroke), new object[]{sender, e}); 
				return; 
			} 
 
			if (_mode == PuzzleGridMode.Scratchpad)  
			{ 
				HandleStrokeInScratchpadMode(sender, e); 
			} 
			else  
			{ 
				HandleStrokeInNormalMode(sender, e); 
			} 
		} 
 
		/// Handles a stroke being deleted from the overlay. 
		/// The overlay. 
		/// The event arguments. 
		private void HandleStrokesDeleting(object sender, InkOverlayStrokesDeletingEventArgs e) 
		{ 
			SetUndoCheckpoint(); 
			using(Strokes normalStrokes = NormalStrokes)  
			{ 
				normalStrokes.Remove(e.StrokesToDelete); 
			} 
			using(Strokes scratchpadStrokes = ScratchpadStrokes) 
			{ 
				scratchpadStrokes.Remove(e.StrokesToDelete); 
			} 
		} 
 
		/// GUID used to track the original X coordinate of a scratchpad stroke. 
		private static readonly Guid OriginalStrokeBoundRectXGuid = new Guid("45ea29c2-8afa-4d81-8196-62ba29f33075"); 
		/// GUID used to track the original Y coordinate of a scratchpad stroke. 
		private static readonly Guid OriginalStrokeBoundRectYGuid = new Guid("45ea29c2-8afa-4d81-8196-62ba29f33076"); 
		/// GUID used to track the original width of a scratchpad stroke. 
		private static readonly Guid OriginalStrokeBoundRectWidthGuid = new Guid("45ea29c2-8afa-4d81-8196-62ba29f33077"); 
		/// GUID used to track the original height of a scratchpad stroke. 
		private static readonly Guid OriginalStrokeBoundRectHeightGuid = new Guid("45ea29c2-8afa-4d81-8196-62ba29f33078"); 
		/// GUID used to track the width of the puzzle grid when a scratchpad stroke was made. 
		private static readonly Guid OriginalClientRectWidthGuid = new Guid("45ea29c2-8afa-4d81-8196-62ba29f33079"); 
		/// GUID used to track the height of the puzzle grid when a scratchpad stroke was made. 
		private static readonly Guid OriginalClientRectHeightGuid = new Guid("45ea29c2-8afa-4d81-8196-62ba29f3307A"); 
 
		/// Handles a stroke made on the overlay. 
		/// The overlay. 
		/// The event arguments. 
		void HandleStrokeInScratchpadMode(object sender, InkCollectorStrokeEventArgs e) 
		{ 
			try 
			{ 
				Stroke s = e.Stroke; 
				Rectangle boundingBox = s.GetBoundingBox(); 
				switch (_inkOverlay.EditingMode) 
				{ 
					case InkOverlayEditingMode.Ink: 
						 
						// Since there's no "pre-stroke" event, workaround to get undo checkpoints working correctly 
						TabletPropertyDescriptionCollection tpdc = new TabletPropertyDescriptionCollection(); 
						foreach (Guid g in s.PacketDescription) 
						{ 
							TabletPropertyDescription tpd = new TabletPropertyDescription(g, e.Cursor.Tablet.GetPropertyMetrics(g)); 
							tpdc.Add(tpd); 
						} 
						int[] packetData = s.GetPacketData(); 
						_inkOverlay.Ink.DeleteStroke(s); 
						SetUndoCheckpoint(); 
						s = _inkOverlay.Ink.CreateStroke(packetData, tpdc); 
 
						s.DrawingAttributes.Color = _scratchpadInkColor; 
						using(Graphics graphics = CreateGraphics()) 
						{ 
							// Every scratchpad stroke has associated with it at creation time the current size of the 
							// stroke and the current size of the grid.  Every time the grid is resized, this information 
							// allows the stroke to be resized accordingly. 
							InkSpaceToPixelSpace(graphics, ref boundingBox); 
							s.ExtendedProperties.Add(OriginalStrokeBoundRectXGuid, boundingBox.X); 
							s.ExtendedProperties.Add(OriginalStrokeBoundRectYGuid, boundingBox.Y); 
							s.ExtendedProperties.Add(OriginalStrokeBoundRectWidthGuid, boundingBox.Width); 
							s.ExtendedProperties.Add(OriginalStrokeBoundRectHeightGuid, boundingBox.Height); 
							s.ExtendedProperties.Add(OriginalClientRectWidthGuid, ClientRectangle.Width); 
							s.ExtendedProperties.Add(OriginalClientRectHeightGuid, ClientRectangle.Height); 
						} 
						using(Strokes scratchpadStrokes = ScratchpadStrokes)	 
						{ 
							scratchpadStrokes.Add(s); 
						} 
						OnStateChanged(); 
						break; 
 
					case InkOverlayEditingMode.Delete: 
						break; 
				} 
			} 
			catch (COMException) { } 
		} 
 
		/// Resizes the control. 
		/// The event arguments. 
		protected override void OnResize(EventArgs e) 
		{ 
			_cachedEmSize = -1; 
			ResizeScratchpadInk(); 
			base.OnResize(e); 
		} 
 
		/// Resizes all ink currently on the scratchpad to match the current size of the puzzle grid. 
		internal void ResizeScratchpadInk() 
		{ 
			if (_inkOverlay != null) 
			{ 
				Rectangle currentClientRect = ClientRectangle; 
				using(Strokes scratchpadStrokes = ScratchpadStrokes)	 
				{ 
					if (scratchpadStrokes != null) 
					{ 
						// Every scratchpad stroke has associated with it at creation time the current size of the 
						// stroke and the current size of the grid.  Every time the grid is resized, this information 
						// allows the stroke to be resized accordingly. 
						foreach(Stroke s in scratchpadStrokes) 
						{ 
							int originalBoundsX = (int)s.ExtendedProperties[OriginalStrokeBoundRectXGuid].Data; 
							int originalBoundsY = (int)s.ExtendedProperties[OriginalStrokeBoundRectYGuid].Data; 
							int originalBoundsWidth = (int)s.ExtendedProperties[OriginalStrokeBoundRectWidthGuid].Data; 
							int originalBoundsHeight = (int)s.ExtendedProperties[OriginalStrokeBoundRectHeightGuid].Data; 
							int originalClientRectWidth = (int)s.ExtendedProperties[OriginalClientRectWidthGuid].Data; 
							int originalClientRectHeight = (int)s.ExtendedProperties[OriginalClientRectHeightGuid].Data; 
							double scaleX = currentClientRect.Width / (double)originalClientRectWidth; 
							double scaleY = currentClientRect.Height / (double)originalClientRectHeight; 
 
							Rectangle newBounds = new Rectangle( 
								(int)(originalBoundsX*scaleX), (int)(originalBoundsY*scaleY),  
								(int)(originalBoundsWidth*scaleX), (int)(originalBoundsHeight*scaleY)); 
 
							using(Graphics graphics = CreateGraphics()) 
							{								 
								// Rescale the stroke 
								PixelSpaceToInkSpace(graphics, ref newBounds); 
								s.ScaleToRectangle(newBounds); 
							} 
						} 
					} 
				} 
			} 
		} 
 
		/// Converts a rectangle from ink space to control space. 
		/// The rectangle to convert. 
		private void InkToPixelSpace(ref Rectangle rect) 
		{ 
			using(Graphics g = CreateGraphics()) InkSpaceToPixelSpace(g, ref rect); 
		} 
 
		/// Converts a rectangle from ink space to control space. 
		/// The graphics object to use for the conversion. 
		/// The rectangle to convert. 
		private void InkSpaceToPixelSpace(Graphics graphics, ref Rectangle rect) 
		{ 
			Point topLeft = new Point(rect.Left, rect.Top); 
			Point widthHeight = new Point(rect.Width, rect.Height); 
			_inkOverlay.Renderer.InkSpaceToPixel(graphics, ref topLeft); 
			_inkOverlay.Renderer.InkSpaceToPixel(graphics, ref widthHeight); 
			rect = new Rectangle(topLeft.X, topLeft.Y, widthHeight.X, widthHeight.Y); 
		} 
 
		/// Converts a rectangle from control space to ink space. 
		/// The graphics object to use for the conversion. 
		/// The rectangle to convert. 
		private void PixelSpaceToInkSpace(Graphics graphics, ref Rectangle rect) 
		{ 
			Point topLeft = new Point(rect.Left, rect.Top); 
			Point widthHeight = new Point(rect.Width, rect.Height); 
			_inkOverlay.Renderer.PixelToInkSpace(graphics, ref topLeft); 
			_inkOverlay.Renderer.PixelToInkSpace(graphics, ref widthHeight); 
			rect = new Rectangle(topLeft.X, topLeft.Y, widthHeight.X, widthHeight.Y); 
		} 
 
		/// Handles a stroke made on the overlay. 
		/// The overlay. 
		/// The event arguments. 
		void HandleStrokeInNormalMode(object sender, InkCollectorStrokeEventArgs e) 
		{ 
			e.Stroke.DrawingAttributes.Color = _inkColor; 
			try 
			{ 
				// Get the cell under the stroke 
				Point currentStrokeCell = GetCellFromStroke(e.Stroke); 
 
				// If there were previously unprocessed strokes made in 
				// a different cell, process those separately before 
				// processing this one. 
				RecognizePreviousNormalStrokesInDifferentCell(currentStrokeCell); 
 
				switch (_inkOverlay.EditingMode) 
				{ 
					case InkOverlayEditingMode.Ink: 
						// Only care about "significant" strokes in order to better avoid user error 
						const float MINIMINUM_BOUNDING_RATIO_TO_RECOGNIZE = .15f; 
						Rectangle boundingBox = e.Stroke.GetBoundingBox(); 
						InkToPixelSpace(ref boundingBox); 
						RectangleF cellRect = GetCellRectangle(ClientRectangle, Point.Empty); 
						if (boundingBox.Width < cellRect.Width * MINIMINUM_BOUNDING_RATIO_TO_RECOGNIZE && 
							boundingBox.Height < cellRect.Height * MINIMINUM_BOUNDING_RATIO_TO_RECOGNIZE) 
						{ 
							e.Cancel = true; 
							using(Strokes normalStrokes = NormalStrokes)  
							{ 
								if (normalStrokes.Count > 0) _processStrokesTimer.Start(); 
							} 
						} 
						else 
						{ 
							// Store the stroke 
							using(Strokes normalStrokes = NormalStrokes)  
							{ 
								normalStrokes.Add(e.Stroke); 
							} 
							 
							// Restart the timer after each stroke is made 
							_processStrokesTimer.Stop(); 
							_processStrokesTimer.Start(); 
						} 
						break; 
 
					case InkOverlayEditingMode.Delete: 
						// Delete the current cell's contents 
						if (CanClearCell(currentStrokeCell)) ClearStateCell(currentStrokeCell); 
						using(Strokes normalStrokes = NormalStrokes)  
						{ 
							_inkOverlay.Ink.DeleteStrokes(normalStrokes); 
							normalStrokes.Clear(); 
						} 
						e.Cancel = true; 
						Invalidate(); 
						break; 
				} 
			} 
			catch (COMException) 
			{ 
				using(Strokes normalStrokes = NormalStrokes)  
				{ 
					_inkOverlay.Ink.DeleteStrokes(normalStrokes); 
					normalStrokes.Clear(); 
				} 
			} 
		} 
 
		/// Handles the arrival of new in-air packets. 
		private void HandleNewInAirPackets(object sender, InkCollectorNewInAirPacketsEventArgs e) 
		{ 
			if (e.PacketData.Length >= 2) 
			{ 
				RecognizePreviousCellFromPacket(new Point(e.PacketData[0], e.PacketData[1]));	 
			} 
		} 
 
		/// Recognizes the previous normal strokes that aren't in the cell specified by the ink-space point. 
		/// The ink-space point. 
		private void RecognizePreviousCellFromPacket(Point tabletPoint) 
		{ 
			Point cell = TabletToCell(tabletPoint); 
			using(Strokes normalStrokes = NormalStrokes)  
			{ 
				if (normalStrokes.Count > 0) 
				{ 
					Point otherCell = GetCellFromStroke(normalStrokes[0]); 
					if (cell != otherCell)  
					{ 
						RecognizePreviousNormalStrokes(); 
					} 
				} 
			} 
		} 
 
		/// Gets the cell containing an ink-space point. 
		/// The point. 
		/// The cell. 
		private Point TabletToCell(Point p) 
		{ 
			using (Graphics g = CreateGraphics()) 
			{ 
				_inkOverlay.Renderer.InkSpaceToPixel(g, ref p); 
				return GetCellFromLocation(p); 
			} 
		} 
 
		/// Handles the arrival of new data packets. 
		/// The overlay. 
		/// The event arguments 
		void HandleNewPackets(object sender, InkCollectorNewPacketsEventArgs e) 
		{ 
			_processStrokesTimer.Stop(); 
			if (_mode == PuzzleGridMode.Eraser || e.Cursor.Inverted) 
			{ 
				if (e.PacketData.Length >= 2) 
				{ 
					Point cell = TabletToCell(new Point(e.PacketData[0], e.PacketData[1])); 
					if (CanClearCell(cell) && State[cell].HasValue)  
					{ 
						ClearStateCellWithInvalidation(cell); 
					} 
					if (_selectedCell.HasValue)  
					{ 
						InvalidateCell(_selectedCell.Value); 
					} 
					SetSelectedCell(cell); 
					InvalidateCell(_selectedCell.Value); 
				} 
			} 
			else if (_mode == PuzzleGridMode.Pen) 
			{ 
				if (e.PacketData.Length >= 2) 
				{ 
					RecognizePreviousCellFromPacket(new Point(e.PacketData[0], e.PacketData[1])); 
				} 
			} 
		} 
 
		/// Recognize the current strokes after the last stroke is made. 
		/// The timer. 
		/// The event arguments. 
		private void StrokesTimerTick(object sender, EventArgs e) 
		{ 
			_processStrokesTimer.Stop(); 
 
			if (_inkOverlay != null) 
			{ 
				// Recognize the strokes 
				using(Strokes strokes = NormalStrokes) 
				{ 
					if (strokes.Count > 0) 
					{ 
						byte number = 0; 
						bool recognized = false; 
 
						Point cell = GetCellFromStroke(strokes[0]); 
						if (CanModifyCell(cell))  
						{ 
							recognized = RecognizeStrokes(strokes, out number); 
						} 
 
						// Delete the strokes 
						_inkOverlay.Ink.DeleteStrokes(strokes); 
						strokes.Clear(); 
 
						// Set the recognized number if available 
						if (recognized)  
						{ 
							SetStateCell(cell, (byte)(number-1)); 
						} 
					} 
				} 
 
				// Refresh 
				Invalidate(); 
			} 
		} 
 
		/// Recognize all but the last stroke if they were made in different cells. 
		/// The current stroke's cell. 
		private void RecognizePreviousNormalStrokesInDifferentCell(Point currentStrokeCell) 
		{ 
			// Get the current set of strokes 
			using(Strokes strokes = NormalStrokes) 
			{ 
				// If there are multiple strokes, including this one, then make sure 
				// they're all in the same cell.  If they're not, recognize all 
				// but the latest stroke, and then proceed as normal.  This lets people 
				// quickly write multiple numbers into multiple boxes, while still 
				// allowing for multiple stroke recognition of digits. 
				if (strokes.Count > 0) 
				{ 
					Point firstStrokeCell = GetCellFromStroke(strokes[0]); 
					if (firstStrokeCell != currentStrokeCell) 
					{ 
						RecognizePreviousNormalStrokes(); 
					} 
				} 
			} 
		} 
 
		/// Recognize all but the last stroke. 
		private void RecognizePreviousNormalStrokes() 
		{ 
			// Get the current set of strokes 
			using(Strokes strokes = NormalStrokes) 
			{ 
				if (strokes.Count > 0) 
				{ 
					// Recognize the remaining strokes 
					byte number = 0; 
					bool recognized = false; 
					Point cell = GetCellFromStroke(strokes[0]); 
					if (CanModifyCell(cell)) 
					{ 
						recognized = RecognizeStrokes(strokes, out number); 
					} 
 
					// Delete them 
					_inkOverlay.Ink.DeleteStrokes(strokes); 
					strokes.Clear(); 
 
					// If we were able to recognize them, set the number 
					if (recognized)  
					{ 
						SetStateCell(cell, (byte)(number-1)); 
						Invalidate(); 
					} 
				} 
			} 
		} 
 
		/// Recognize the current set of strokes as a digit, if possible. 
		/// The strokes to recognize. 
		/// The recognized number, if one is recognized. 
		private bool RecognizeStrokes(Strokes strokes, out byte number) 
		{ 
			number = 0; 
			// Make sure we have something to recognize and something to recognize it with 
			if (_recognizerCtx != null && strokes.Count > 0) 
			{ 
				try 
				{ 
					// Store the strokes into the recognizer 
					_recognizerCtx.Strokes = strokes; 
					_recognizerCtx.EndInkInput(); 
 
					// Recognize the strokes 
					RecognitionStatus rs; 
					RecognitionResult rr = _recognizerCtx.Recognize(out rs); 
					if (rr != null && rs == RecognitionStatus.NoError) 
					{ 
						// Was it a digit from 1 through 9? 
						string inputNumberText = rr.TopString; 
						if (inputNumberText != null && inputNumberText.Length > 0) 
						{ 
							try 
							{ 
								number = byte.Parse(inputNumberText, CultureInfo.InvariantCulture); 
							}  
							catch(OverflowException){} 
							catch(FormatException){} 
							if (number >= 1 && number <= 9) return true; 
						} 
					} 
				}  
				catch (COMException) { } 
			} 
			return false; 
		} 
 
		/// Sets the contents of a cell in the current puzzle. 
		/// The cell to be set. 
		/// The value to which the cell should be set. 
		/// Sets an undo checkpoint and deletes any scratchpad strokes for the specified cell. 
		private void SetStateCell(Point cell, byte number) 
		{ 
			if (State[cell] != number) 
			{ 
				SetUndoCheckpoint(); 
				ClearTabletStateCell(cell); 
				State[cell] = number; 
			} 
		} 
 
		/// Clears a cell of all tablet strokes centered within that cell. 
		/// The cell to be cleared. 
		private void ClearTabletStateCell(Point cell) 
		{ 
			if (_inkOverlay != null) 
			{ 
				// Delete any scratchpad strokes in that cell 
				using(Strokes strokes = ScratchpadStrokes) 
				{ 
					for(int i=strokes.Count-1; i>=0; --i) 
					{ 
						Stroke s = strokes[i]; 
						if (GetCellFromStroke(s) == cell) 
						{ 
							strokes.RemoveAt(i); 
							_inkOverlay.Ink.DeleteStroke(s); 
						} 
					} 
				} 
			} 
		} 
 
		/// Clears the contents of a cell in the current puzzle. 
		/// The cell to be cleared. 
		/// Sets an undo checkpoint. 
		private void ClearStateCell(Point cell) 
		{ 
			if (State[cell].HasValue) 
			{ 
				SetUndoCheckpoint(); 
				State[cell] = null; 
			} 
		} 
 
		/// Invalidates the specified cell. 
		/// The cell to be repainted. 
		private void InvalidateCell(Point cell) 
		{ 
			if (cell.X >= 0 && cell.X < State.GridSize && 
				cell.Y >= 0 && cell.Y < State.GridSize) 
			{ 
				const int BUFFER_DIST = 2; 
				RectangleF rect = GetCellRectangle(BoardRectangle, cell); 
				rect.Inflate(BUFFER_DIST,BUFFER_DIST); 
				Invalidate(Rectangle.Ceiling(rect)); 
			} 
		} 
 
		/// Clears a cell and invalidates it. 
		/// The cell to be cleared and repainted. 
		private void ClearStateCellWithInvalidation(Point cell) 
		{ 
			bool showSuggestedCells = ShowSuggestedCells; 
			if (showSuggestedCells) InvalidateCell(SuggestedCell); 
			ClearStateCell(cell); 
			InvalidateCell(cell); 
			if (showSuggestedCells) InvalidateCell(SuggestedCell); 
		} 
		#endregion 
	} 
}