AvalonLife Listing 4 – ALMainWin.xaml.cs

using System;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Documents;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Shapes;

namespace AvalonLife
{
	/// <summary>
	/// UIStateChanges
	/// 
	/// This enum provides indicators for the user interface states
	/// that the program can be in. Used to call UIStateChange().
	/// </summary>
	enum UIStateChanges
	{
		ModelCreated,
		ModelSaved,
		ModelSavedAs,
		ModelLoadedFromFile,
		ModelLoadedFromNet,
		ModelCellEdited,
		ModelRun,
		ModelPaused,
		ModelHalted,
		ModelReset,
		ModelPropertiesEdited    
	}
    
	/// <summary>
	/// Used to indicate the current type of cell brush used to pain
	/// the grid
	/// </summary>
	enum CellBrushType
	{
		Radial,
		Linear,
		Solid
	}
    
	enum ALFileType
	{
		None,
		AVL,
		Cells
	}
    
	public partial class ALMainWin : System.Windows.Window
	{

		#region ReticleAdorner class
        
		/// <summary>
		/// ReticleAdorner
		/// 
		/// This class is derived from Adorner, and renders the reticle (crosshairs)
		/// over the main game grid.
		/// </summary>
		public class ReticleAdorner : Adorner
		{
			public ReticleAdorner( UIElement target )
			: base(target)
			{
				_reticleColor = ALSettings.Default.ReticleColor;
				_reticlePen = new Pen(new SolidColorBrush(_reticleColor), 1);
			}
            
			protected override void OnRender( DrawingContext dctxt )
			{
				double height = ((Grid)(this.AdornedElement)).ActualHeight;
				double width = ((Grid)(this.AdornedElement)).ActualWidth;

				Point start = new Point( 0, height / 2 );
				Point end = new Point( width, height / 2 );
				dctxt.DrawLine( _reticlePen, start, end );

				start = new Point( width / 2, 0 );
				end = new Point( width / 2, height );
				dctxt.DrawLine( _reticlePen, start, end );
			}           
        
			/// <summary>
			/// Set this property to change the color of the reticle
			/// </summary>
			private Color _reticleColor;
			public Color ReticleColor
			{
				get
				{
					return _reticleColor;
				}
				set
				{
					_reticleColor = value;
					ALSettings.Default.ReticleColor = _reticleColor;
					_reticlePen = new Pen(new SolidColorBrush(_reticleColor), 1);
				}
			}

			private Pen _reticlePen;
        }
        
        #endregion
        
		public ALMainWin()
		{
			InitializeComponent();
		}

		/// <summary>
		/// PrepMessage( string, int )
		/// 
		/// A simple function to insert line breaks into a string destined for a messagebox.
		/// MessageBox.Show() seems to work differently on XP and Vista. On Vista a long
		/// message is sensibly wrapped at word boundaries. On XP it is not. If you insert
		/// line breaks to look right in XP, they don't look right in Vista. So this is an
		/// attempt to get some consistency.
		/// </summary>
		/// <param name="msg"></param>
		/// <param name="lineLen"></param>
		/// <returns></returns>
		public string PrepMessage(string msg, int lineLen)
		{
			string strout = "";
			int j = 0;
			for (int i = 0; i < msg.Length; i++)
			{
				if ((j >= lineLen && msg[i] == ' ') || i == msg.Length - 1)
				{
					strout += msg.Substring(i - j, j);
					strout += "\n";
					j = 0;
				}
				else j++;
			}
			return strout;
		}

		#region menu bar event handlers

		/// <summary>
		/// Menu_OnGameNew(Object, RoutedEventArgs)
		/// 
		/// Handles the click event for the game menu, new command. Prompts the user and
		/// on yes rebuilds the simulation and life models and resets the grid.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnGameNew(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;
			LifeGrid.Cursor = Cursors.Wait;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			if ( ExecNew(false) )
				oldCursor = LifeGrid.Cursor;
			else
				_ls.IsPaused = paused;

			LifeGrid.Cursor = oldCursor;
		}
        
		/// <summary>
		/// Menu_OnGameReset(Object, RoutedEventArgs)
		/// 
		/// Handles a click event on the Game menu, Reset item. Prompts the user, and on 'Yes' 
		/// rebuilds the model and simulation controller, and resets various parameters.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnGameReset(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;
			LifeGrid.Cursor = Cursors.Wait;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			if (MessageBox.Show(this, Properties.Resources.UI_MB_PromptText2, Properties.Resources.UI_MB_CaptionText2,
			MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
			{
				_ls.ResetSim();
				UIStateChange(UIStateChanges.ModelReset);
				oldCursor = LifeGrid.Cursor;
			}
			else
				_ls.IsPaused = paused;

			LifeGrid.Cursor = oldCursor;
		}
        
		/// <summary>
		/// Menu_OnGameSave(Object, RoutedEventArgs)
		/// 
		/// Handles the click event for the game menu, save command. Pauses the sim if
		/// it is not already, and then calls ExecSave()
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnGameSave(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;
			LifeGrid.Cursor = Cursors.Wait;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			ExecSave(false);

			_ls.IsPaused = paused;

			LifeGrid.Cursor = oldCursor;
		}

		/// <summary>
		/// Menu_OnGameSaveAs(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the "save as" command in the Game menu. The only difference
		/// from the function above is that this one calls ExecSave with true, causing
		/// a new filename to be chosen.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnGameSaveAs(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;
			LifeGrid.Cursor = Cursors.Wait;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			ExecSave(true);

			_ls.IsPaused = paused;

			LifeGrid.Cursor = oldCursor;
		}

		/// <summary>
		/// Menu_OnGameLoad(Object, RoutedEventArgs)
		/// 
		/// Handles the click event for the game menu, load command. Pauses the sim and
		/// then checks to see if the current game is dirty. If so it pops a save dialog 
		/// and calls ExecSave() if the user answers affirm. It then calls ExecLoad(). If
		/// load fails (ExecLoad returns false) the function unpauses the game (if it was
		/// paused on entry). 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnGameLoad(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;
			LifeGrid.Cursor = Cursors.Wait;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;
			bool final = false;

			System.Windows.Forms.OpenFileDialog opendlg =
			new System.Windows.Forms.OpenFileDialog();

			opendlg.DefaultExt = ".avl";
			opendlg.Filter = "AvalonLife Saved Games (.avl)|*.avl|Life Lexicon Cells (.cells)|*.cells";
			opendlg.Title = "Load Saved Model";
			if (opendlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
			{
				if ( ExecLoadFile(opendlg.FileName) )
				{
					final = true;
				}
			}
			if ( !final )
				_ls.IsPaused = paused;

			LifeGrid.Cursor = oldCursor;
		}

		/// <summary>
		/// Menu_OnGameExit(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the game menu, exit command, and shuts down the application
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnGameExit(Object sender, RoutedEventArgs e)
		{
			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			if ( CheckSave() == true )
				Application.Current.Shutdown();

			_ls.IsPaused = paused;
		}

		/// <summary>
		/// Menu_OnSettingsGridLines(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the settings menu, show grid command, and turns the
		/// grid lines on/off accordingly.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsGridLines(Object sender, RoutedEventArgs e)
		{
			if (MenuSettingsGridLines.IsChecked == false)
			{
				ALSettings.Default.GridOn = true;
				LifeGrid.ShowGridLines = true;
				MenuSettingsGridLines.IsChecked = true;
			}
			else
			{
				ALSettings.Default.GridOn = false;
				LifeGrid.ShowGridLines = false;
				MenuSettingsGridLines.IsChecked = false;
			}
		}

		/// <summary>
		/// Menu_OnSettingsReticle(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the settings menu, show reticle command, and turns the
		/// reticle (crosshairs) on/off accordingly.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsReticle(Object sender, RoutedEventArgs e)
		{
			if ( MenuSettingsReticle.IsChecked == false )
			{
				ALSettings.Default.ReticleOn = true;
				_gridAdornerLayer.GetAdorners(LifeGrid)[0].Visibility = Visibility.Visible;
				MenuSettingsReticle.IsChecked = true;
			}
			else
			{
				ALSettings.Default.ReticleOn = false;
				_gridAdornerLayer.GetAdorners(LifeGrid)[0].Visibility = Visibility.Hidden;
				MenuSettingsReticle.IsChecked = false;
			}
		}

		/// <summary>
		/// Menu_OnSettingsHaltStable(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the Settings | Halt Stable Model command. Flips the
		/// state of this property in ALSettings. Controls whether the simulation will
		/// halt when a model ceases evolving.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsHaltStable(Object sender, RoutedEventArgs e)
		{
			if (MenuSettingsHaltStable.IsChecked == false)
			{    
				MenuSettingsHaltStable.IsChecked = true;
				ALSettings.Default.HaltOnStability = true;
			}
			else
			{
				MenuSettingsHaltStable.IsChecked = false;
				ALSettings.Default.HaltOnStability = false;
			}
		}

		/// <summary>
		/// Menu_OnSettingsGridBackground(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the settings menu, grid background command. Opens a color picker
		/// dialog to let the user choose the grid background color. I'm sure there must be an easier
		/// way to deal with the collision between System.Windows.Media.xxx, which is used by
		/// the WPF shapes and brushes, and System.Drawing.xxx which is used by the standard color
		/// dialog in System.Windows.Forms, but I haven't taken the time to research it yet. So
		/// for the moment this function is full of ugly conversions. Works, though.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsGridBackground(Object sender, RoutedEventArgs e)
		{
			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			System.Windows.Forms.ColorDialog clrDlg = new System.Windows.Forms.ColorDialog();

			clrDlg.AllowFullOpen = true;
			clrDlg.SolidColorOnly = true;
			System.Drawing.Color origColor = System.Drawing.Color.FromArgb(
			((SolidColorBrush)LifeGrid.Background).Color.A,
			((SolidColorBrush)LifeGrid.Background).Color.R,
			((SolidColorBrush)LifeGrid.Background).Color.G,
			((SolidColorBrush)LifeGrid.Background).Color.B );

			clrDlg.Color = origColor;
			if ( clrDlg.ShowDialog() == System.Windows.Forms.DialogResult.OK )
			{
				Color newColor = Color.FromArgb(clrDlg.Color.A, clrDlg.Color.R, clrDlg.Color.G, clrDlg.Color.B);
				ALSettings.Default.GridBackground = newColor;
				SolidColorBrush gridBkgBrush = new SolidColorBrush( newColor );
				LifeGrid.Background = gridBkgBrush; 
			}
			_ls.IsPaused = paused;
		}
        
		/// <summary>
		/// Menu_OnSettingsReticleColor(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the settings menu, reticle color command. Opens a color
		/// picker and allows the user to choose a new color for drawing the reticle.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsReticleColor(Object sender, RoutedEventArgs e)
		{
			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;
			ReticleAdorner ad = _gridAdornerLayer.GetAdorners(LifeGrid)[0] as ReticleAdorner;

			System.Windows.Forms.ColorDialog clrDlg = new System.Windows.Forms.ColorDialog();

			clrDlg.AllowFullOpen = true;
			clrDlg.SolidColorOnly = true;

			if ( clrDlg.ShowDialog() == System.Windows.Forms.DialogResult.OK )
			{
				Color newColor = Color.FromArgb(clrDlg.Color.A, clrDlg.Color.R, clrDlg.Color.G, clrDlg.Color.B);
				ad.ReticleColor = newColor;
				ALSettings.Default.ReticleColor = newColor;
				ad.InvalidateVisual();
			}
			_ls.IsPaused = paused;
		}

		/// <summary>
		/// Menu_OnSettingsCellBrush(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the Settings | Cell Brush command, and pops the brush definer
		/// dialog. Calls ApplyCellBrush on return if the user accepted the dialog.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsCellBrush(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			ALBrushDlg dlg = new ALBrushDlg();

			dlg.Owner = this;
			if ( dlg.ShowDialog() == true )
			{
				LifeGrid.Cursor = Cursors.Wait;
				ApplyRectStyle();
				LifeGrid.Cursor = oldCursor;
			}    
			_ls.IsPaused = paused;
		}
        
		/// <summary>
		/// Menu_OnSettingsGridType(Object, RoutedEventArgs)
		/// 
		/// Handles a click on one of the grid type checkable menuitems in Settings | Grid Type.
		/// The Tag property of these controls has been set to the appropriate value from
		/// the GridType enumeration, so the tag can just be cast and passed through to
		/// SetGridType.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsGridType(Object sender, RoutedEventArgs e)
		{
			GridType gridType = (GridType)((MenuItem)sender).Tag;
			if (_ls.Model.LifeGridType != gridType)
			{
				_ls.Model.LifeGridType = gridType;
				SetGridType(gridType);
				UIStateChange(UIStateChanges.ModelPropertiesEdited);
			}
		}

		/// <summary>
		/// Menu_OnSettingsGridSize(Object, RoutedEventArgs)
		/// 
		/// Handles a click on one of the pre-defined grid sizes in the Settings | Grid Size
		/// | [predefined grid size] menu. Since the predefined model sizes are square we can
		/// just store one dimensionin the object tag at startup, grab it here, and use it to
		/// figure out what size was requested.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsGridSize(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;
			LifeGrid.Cursor = Cursors.Wait;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			int size = (int)(((MenuItem)sender).Tag);
			if ( _ls.Model.ResizeModel(size, size) )
			{
				InitUIState();
				UIStateChange(UIStateChanges.ModelPropertiesEdited);
			}
			else
				MessageBox.Show(this, Properties.Resources.UI_MB_ResizeFailedMsg, Properties.Resources.UI_MB_ResizeFailedCaption,
					MessageBoxButton.OK, MessageBoxImage.Error);

			_ls.IsPaused = paused;
			LifeGrid.Cursor = oldCursor;    
		}

		/// <summary>
		/// Menu_OnSettingsGridSizeCustom(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the Settings | Grid Size | Custom menu. The dialog enforces type
		/// constraints on the edit, so we know we're getting ints, but we need to check how
		/// large a model the user has asked for. You can ask the sim to make a 1000 x 1000 grid.
		/// I did, and by the time I killed the process five minutes later it had a 1.7 gig
		/// working set. A grid that size requires the system to create about 3 million objects,
		/// plus or minus a couple hundred thousand. So we check to see if the aggregate grid
		/// size is greater than 10k cells, and pop a warning dialog. If the user wants to go
		/// ahead we let them. I've done a 200 x 200 grid and it loads relatively quickly and will
		/// actually run, but not smoothly.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsGridSizeCustom(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;
			LifeGrid.Cursor = Cursors.Wait;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			ALModelSizeDlg dlg = new ALModelSizeDlg();
			dlg.Owner = this;
			dlg.Tag = _ls.Model;
			dlg.Resize = true;

			if ( dlg.ShowDialog() == true )
			{
				int rows = dlg.Rows;
				int cols = dlg.Columns;
				if ( rows * cols > 10000 )
				{
					string msg = PrepMessage(Properties.Resources.UI_MB_LargeModelMsg, 80);
					msg += "\n\nRequested model size will cause the program to create approximately " 
					+ Convert.ToString((rows * cols) * 3) + " objects.";
					if ( MessageBox.Show(this, msg, Properties.Resources.UI_MB_LargeModelCaption, 
						MessageBoxButton.OKCancel, MessageBoxImage.Question) == MessageBoxResult.Cancel )
					{    
						LifeGrid.Cursor = oldCursor;
						return;
					}
				}

				if (_ls.Model.ResizeModel(rows, cols))
				{
					InitUIState();
					UIStateChange(UIStateChanges.ModelPropertiesEdited);
				}
				else
					MessageBox.Show(this, Properties.Resources.UI_MB_ResizeFailedMsg, Properties.Resources.UI_MB_ResizeFailedCaption,
						MessageBoxButton.OK, MessageBoxImage.Error);
			}
			_ls.IsPaused = paused;
			LifeGrid.Cursor = oldCursor;
		}

		/// <summary>
		/// Menu_OnSettingsGridSizeShrink(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the Settings | Grid Size | Shrink to Model menu item. Calls
		/// the model to resize the grid to the model size. See LifeModel.ResizeModel() for
		/// failure conditions.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsGridSizeShrink(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;
			LifeGrid.Cursor = Cursors.Wait;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			if (_ls.Model.ResizeModel(0, 0))
			{
				InitUIState();
				UIStateChange(UIStateChanges.ModelPropertiesEdited);
			}
			else
				MessageBox.Show(this, Properties.Resources.UI_MB_ResizeFailedMsg, Properties.Resources.UI_MB_ResizeFailedCaption,
					MessageBoxButton.OK, MessageBoxImage.Error);

			_ls.IsPaused = paused;
			LifeGrid.Cursor = oldCursor;
		}

		/// <summary>
		/// Menu_OnSettingsGridSettings(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the grid settings menuitem in Settings | Grid Settings. Pops
		/// the grid settings dialog window.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsGridSettings(Object sender, RoutedEventArgs e)
		{
			ALGridDlg dlg = new ALGridDlg();
			dlg.Owner = this;
			dlg.ShowDialog();
		}

		/// <summary>
		/// Menu_OnSettingsModelName(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the model name menuitem in Settings. Pops the model
		/// name dialog window. Tracks changes to the name of the model so it can
		/// update the window title bar after the dialog closes.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsModelName(Object sender, RoutedEventArgs e)
		{
			ALModelNameDlg dlg = new ALModelNameDlg();
			dlg.Owner = this;
			dlg.Tag = _ls;
			if ( dlg.ShowDialog() == true )
				UIStateChange(UIStateChanges.ModelPropertiesEdited);
		}

		/// <summary>
		/// Menu_OnHelpHowTo(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the help menu, How to Play command. 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnHelpHowTo(Object sender, RoutedEventArgs e)
		{
			System.Windows.Forms.HelpNavigator nav =
			System.Windows.Forms.HelpNavigator.TopicId;

			System.Windows.Forms.Help.ShowHelp(null, @"avalonlife.chm", nav, "1020");
		}

		/// <summary>
		/// Menu_OnHelpAbout(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the help menu, about AvalonLife command.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnHelpAbout(Object sender, RoutedEventArgs e)
		{
			System.Windows.Forms.HelpNavigator nav =
			System.Windows.Forms.HelpNavigator.TopicId;

			System.Windows.Forms.Help.ShowHelp(null, @"avalonlife.chm", nav, "1000");
		}

		/// <summary>
		/// Menu_OnHelpAboutLife(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the help menu, about the Game of Life command.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnHelpAboutLife(Object sender, RoutedEventArgs e)
		{
			System.Windows.Forms.HelpNavigator nav =
			System.Windows.Forms.HelpNavigator.TopicId;

			System.Windows.Forms.Help.ShowHelp(null, @"avalonlife.chm", nav, "1010");
		}
        
		#endregion

		#region other ui event handlers

		/// <summary>
		/// ALMainWin_OnLoaded()
		/// 
		/// Handles the loaded event for the main window. Initializes the simulation
		/// model, controller, and display grid.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void ALMainWin_OnLoaded(Object sender, RoutedEventArgs e)
		{
			ExecNew(true);
			RunSpeedSlider.ToolTip = Properties.Resources.UI_RunSpeedSlider_ToolTip;
			StatusGenCount.ToolTip = Properties.Resources.UI_StatusGenCount_ToolTip;
			CellBirthCount.ToolTip = Properties.Resources.UI_CellBirthCount_ToolTip;
			CellDeathCount.ToolTip = Properties.Resources.UI_CellDeathCount_ToolTip;
			PopulationCount.ToolTip = Properties.Resources.UI_PopulationCount_ToolTip;
			PeakPopulationCount.ToolTip = Properties.Resources.UI_PeakPopulationCount_ToolTip;
			MenuGridSizeText.ToolTip = Properties.Resources.UI_GridSize_ToolTip;

			_gridAdornerLayer = AdornerLayer.GetAdornerLayer(LifeGrid);
			ReticleAdorner ad = new ReticleAdorner(LifeGrid);
			_gridAdornerLayer.Add(ad);

			MenuSettingsReticle.IsChecked = ALSettings.Default.ReticleOn;
			if ( MenuSettingsReticle.IsChecked )
				ad.Visibility = Visibility.Visible;
			else    
				ad.Visibility = Visibility.Hidden;


			LifeGrid.Drop += new DragEventHandler( Grid_OnDrop );
			LifeGrid.MouseUp += new MouseButtonEventHandler( Grid_OnMouseUp );
			this.MouseLeave += new MouseEventHandler( Window_OnMouseLeave ); 
			this.Closing += new CancelEventHandler( ALMainWin_OnClosing );

			Application.Current.SessionEnding += new SessionEndingCancelEventHandler( ALMainWin_OnClosing );

			LifeGrid.Background = new SolidColorBrush(ALSettings.Default.GridBackground);
			LifeGrid.ShowGridLines = ALSettings.Default.GridOn;
			MenuSettingsGridLines.IsChecked = LifeGrid.ShowGridLines;

			MenuSettingsGridTorus.Tag = GridType.Torus;
			MenuSettingsGridXCyl.Tag = GridType.XCylinder;
			MenuSettingsGridYCyl.Tag = GridType.YCylinder;
			MenuSettingsGridFinite.Tag = GridType.Finite;

			MenuSettingsGrid40x40.Tag = 40;
			MenuSettingsGrid50x50.Tag = 50;
			MenuSettingsGrid60x60.Tag = 60;
			MenuSettingsGrid70x70.Tag = 70;

			if ( ALSettings.Default.HaltOnStability )
				MenuSettingsHaltStable.IsChecked = true;
			else
				MenuSettingsHaltStable.IsChecked = false;
		}
        
		/// <summary>
		/// ALMainWin_OnClosing(Object, CancelEventArgs)
		/// 
		/// This handles the closing event for the main window. If the game is not
		/// dirty or the user saves it using 'Ok' in CheckSave, then this handler
		/// will commit any settings changes and allow the close to continue. Other
		/// wise if the user clicks Cancel in CheckSave it will cancel.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void ALMainWin_OnClosing(Object sender, CancelEventArgs e )
		{
			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;
			if ( CheckSave() == true )
			{
				if ( ALSettings.Default.Changed )
					ALSettings.Default.Save();
			}
			else
			{
				e.Cancel = true;
			}
			_ls.IsPaused = paused;
		}

		/// <summary>
		/// RunButton_OnClick()
		/// 
		/// Handles the click event for the run button on the main window. The button
		/// changes state when it is clicked. The behavior of the sim drives off of
		/// the IsPaused property of _ls (LifeSim). When the sim is paused the UI allows
		/// editing of the grid cells.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void RunButton_OnClick(Object sender, RoutedEventArgs e)
		{
			if ( _ls.IsPaused )
			{
				_ls.IsPaused = false;
				UIStateChange(UIStateChanges.ModelRun);
			}
			else
			{
				_ls.IsPaused = true;
				UIStateChange(UIStateChanges.ModelPaused);
			}
		}

		/// <summary>
		/// Rect_OnMouseDown(Object, MouseButtonEventArgs)
		/// 
		/// Handles the mouse down event on a Rectangle in the grid, and if the game is
		/// in the paused state (editable) flips the cell state. Since we allow dragging
		/// _lastMouseCell is used to avoid the effects of repeated MouseEnter events
		/// getting fired.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Rect_OnMouseDown(Object sender, MouseButtonEventArgs e)
		{
			if ( _ls.IsPaused && (_ls.Generation == 0) )
			{
				LifeCell lc = ((Rectangle)sender).DataContext as LifeCell;
				if ( lc != null )
				{
					_startEdit = true;
					_lastMouseCell = lc;
					lc.IsAlive = !lc.IsAlive;
					UIStateChange(UIStateChanges.ModelCellEdited);
				}
				else throw (new System.InvalidOperationException("Rect_OnMouseDown"));
			}
		}
        
		/// <summary>
		/// Rect_OnMouseEnter(Object, MouseEventArgs)
		/// 
		/// Handles the mouseover event for an Ellipse. If the game is paused and the mouse
		/// is entering a new cell, flip that cell's state.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Rect_OnMouseEnter(Object sender, MouseEventArgs e)
		{
			if ( _startEdit )
			{
				LifeCell lc = ((Rectangle)sender).DataContext as LifeCell;
				if ( lc != null && lc != _lastMouseCell )
				{
					_lastMouseCell = lc;
					lc.IsAlive = !lc.IsAlive;    
				}
				else if ( lc == null )
					throw (new System.InvalidOperationException("Rect_OnMouseEnter"));
			}
		}
        
		/// <summary>
		/// Grid_OnMouseUp(Object, MouseEventArgs)
		/// 
		/// Handles the mouse left button up event for the grid. We use it to check
		/// the grid state and enable/disable menu items accordingly.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Grid_OnMouseUp(Object sender, MouseButtonEventArgs e)
		{
			if ( _startEdit )
			{
				_startEdit = false;
				if ( _ls.Model.IsEmpty() )
				{
					UIStateChange(UIStateChanges.ModelCreated);
				}
			}
		}

		/// <summary>
		/// Window_OnMouseLeave(Object, MouseEventArgs)
		/// 
		/// This function makes sure we handle things correctly if the user drags
		/// the mouse out of the window while drawing cells. Ordinarily you would
		/// capture the mouse and not release it until mouse up, but that's
		/// cumbersome here, because we're getting events at the rectangle level
		/// but can't capture the mouse there. If we capture it at the grid the
		/// rectangles stop getting events. So this is the next best alternative.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Window_OnMouseLeave(Object sender, MouseEventArgs e)
		{
			if ( _startEdit )
			{
				_startEdit = false;
				if (_ls.Model.IsEmpty())
				{
					UIStateChange(UIStateChanges.ModelCreated);
				}
			}    
		}
        
		/// <summary>
		/// Grid_OnDrop(Object, DragEventArgs)
		/// 
		/// This function gets called when an object is dropped on the grid. It handles drops
		/// of .cells data from the Life Lexicon website, as well as .avl and .cells saved files.
		/// The first half of the function detects a drop of a link from the Lexicon, and calls
		/// ExecLoadWeb to get the data from the net. The second half detects file drops, and hands
		/// off to ExecFileLoad. 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Grid_OnDrop(Object sender, DragEventArgs e)
		{
			Cursor old = this.Cursor;
			bool force = this.ForceCursor;
			this.ForceCursor = true;
			this.Cursor = Cursors.Wait;

			if (e.Data.GetDataPresent(DataFormats.Text))
			{
				if ( (e.AllowedEffects & DragDropEffects.Link) == DragDropEffects.Link )
				{    
					string url = e.Data.GetData(DataFormats.Text) as string;
					if ( Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute) )
					{
						ExecLoadWeb( url );
					}
				}
			}
			else if (e.Data.GetDataPresent(DataFormats.FileDrop) )
			{
				string[] files = e.Data.GetData(DataFormats.FileDrop) as string[];
				ExecLoadFile(files[files.Length - 1]);
			}
			this.Cursor = old;
			this.ForceCursor = force;
		}

		/// <summary>
		/// SimStatusCallback()
		/// 
		/// This function handles a callback from the simulation controller when it detects
		/// that model evolution has halted. Will only be called if ALSettings.HaltOnStability
		/// is true.
		/// </summary>
		/// <returns></returns>
		public void SimStatusCallback()
		{
			string msg = null;

			if ( _ls.Model.EvolutionHalted )
			{
				msg = Properties.Resources.UI_SimStatus_HaltMsg;
				msg += " " + _ls.Generation.ToString();
				msg += "\n\nCell births: " + _ls.Model.CellBirths.ToString();
				msg += "\nCell deaths: " + _ls.Model.CellDeaths.ToString();
				msg += "\nPopulation: " + _ls.Model.Population.ToString();
				msg += "\nPeak population: " + _ls.Model.PeakPopulation.ToString();
				msg += "\n\nThe simulation has been halted.";

				MessageBox.Show(this, msg, "Simulation Status", MessageBoxButton.OK, MessageBoxImage.Information);

				UIStateChange(UIStateChanges.ModelHalted);
			}
		}

		#endregion

		#region ALMainWin private methods

		/// <summary>
		/// UIStateChange(UIStateChanges)
		/// 
		/// This function wraps up all the UI state changes that the program goes through.
		/// The possible states are defined in the AvalonLIfe.UIStateChanges enum. Each
		/// case of the switch statement handles setting the UI controls and some variables
		/// to appropriate settings for a given state.
		/// </summary>
		/// <param name="uis"></param>
		private void UIStateChange(UIStateChanges uis)
		{
			switch (uis)
			{
				case UIStateChanges.ModelCreated:
					RunButton.Content = Properties.Resources.UI_RunButton_Content1;
					RunButton.ToolTip = Properties.Resources.UI_RunButton_ToolTip1;
					LifeGrid.ToolTip = Properties.Resources.UI_LifeGrid_ToolTip;
					LifeGrid.Cursor = Cursors.Hand;
					RunButton.IsEnabled = false;
					MenuGameSave.IsEnabled = false;
					MenuGameSaveAs.IsEnabled = false;
					MenuGameReset.IsEnabled = false;
					SetGameDirty(false);
					_currentModelFileBase = null;
					break;

				case UIStateChanges.ModelSaved:
				case UIStateChanges.ModelSavedAs:
					MenuGameSave.IsEnabled = false;
					SetGameDirty(false);
					break;

				case UIStateChanges.ModelLoadedFromFile:
					RunButton.Content = Properties.Resources.UI_RunButton_Content1;
					RunButton.ToolTip = Properties.Resources.UI_RunButton_ToolTip1;
					RunButton.IsEnabled = true;
					LifeGrid.ToolTip = Properties.Resources.UI_LifeGrid_ToolTip;
					LifeGrid.Cursor = Cursors.Hand;
					MenuGameSave.IsEnabled = false;
					MenuGameSaveAs.IsEnabled = true;
					SetGameDirty(false);
					break;

				case UIStateChanges.ModelLoadedFromNet:
					RunButton.Content = Properties.Resources.UI_RunButton_Content1;
					RunButton.ToolTip = Properties.Resources.UI_RunButton_ToolTip1;
					RunButton.IsEnabled = true;
					LifeGrid.ToolTip = Properties.Resources.UI_LifeGrid_ToolTip;
					LifeGrid.Cursor = Cursors.Hand;
					MenuGameSave.IsEnabled = true;
					MenuGameSaveAs.IsEnabled = true;
					SetGameDirty(true);
					break;

				case UIStateChanges.ModelCellEdited:
					RunButton.IsEnabled = true;
					MenuGameSave.IsEnabled = true;
					MenuGameSaveAs.IsEnabled = true;
					SetGameDirty(true);
					break;

				case UIStateChanges.ModelRun:
					RunButton.Content = Properties.Resources.UI_RunButton_Content2;
					RunButton.ToolTip = Properties.Resources.UI_RunButton_ToolTip2;
					LifeGrid.Cursor = Cursors.Arrow;
					LifeGrid.ToolTip = null;
					MenuGameReset.IsEnabled = true;
					break;

				case UIStateChanges.ModelPaused:
					RunButton.Content = Properties.Resources.UI_RunButton_Content1;
					RunButton.ToolTip = Properties.Resources.UI_RunButton_ToolTip1;
					break;

				case UIStateChanges.ModelHalted:
					RunButton.Content = Properties.Resources.UI_RunButton_Content1;
					RunButton.ToolTip = Properties.Resources.UI_RunButton_ToolTip1;
					RunButton.IsEnabled = false;
					break;

				case UIStateChanges.ModelReset:
					RunButton.Content = Properties.Resources.UI_RunButton_Content1;
					RunButton.ToolTip = Properties.Resources.UI_RunButton_ToolTip1;
					RunButton.IsEnabled = true;
					LifeGrid.ToolTip = Properties.Resources.UI_LifeGrid_ToolTip;
					LifeGrid.Cursor = Cursors.Hand;
					MenuGameReset.IsEnabled = false;
					break;

				case UIStateChanges.ModelPropertiesEdited:
					MenuGameSave.IsEnabled = true;
					SetGameDirty(true);
					break;
			}
		}

		/// <summary>
		/// UpdateWrapInidicators(bool, bool, bool, bool)
		/// 
		/// This method is called from SetGridType() to enable/disable the wrap indicator
		/// bars that border the grid.
		/// </summary>
		/// <param name="left">True if the left bar is on, else false</param>
		/// <param name="top">True if the top bar is on, else false</param>
		/// <param name="right">True if the right bar is on, else false</param>
		/// <param name="bottom">True if the bottom bar is on, else false</param>
		private void UpdateWrapIndicators( bool left, bool top, bool right, bool bottom )
		{
			if ( left )
				LeftWrapIndicator.Width = 4;
			else
				LeftWrapIndicator.Width = 0;

			if (top)
				TopWrapIndicator.Height = 5;
			else
				TopWrapIndicator.Height = 0;

			if (right)
				RightWrapIndicator.Width = 4;
			else
				RightWrapIndicator.Width = 0;

			if (bottom)
				BottomWrapIndicator.Height = 5;
			else
				BottomWrapIndicator.Height = 0;
		}
        
		/// <summary>
		/// SetGridType( GridType )
		/// 
		/// This method is called from ExecLoad or Menu_OnGridXXX to update the menu and UI to
		/// correspond with the grid type of the loaded model. 
		/// </summary>
		/// <param name="gridType"></param>
		private void SetGridType( GridType gridType )
		{
			MenuSettingsGridTorus.IsChecked = false;
			MenuSettingsGridXCyl.IsChecked = false;
			MenuSettingsGridYCyl.IsChecked = false;
			MenuSettingsGridFinite.IsChecked = false;

			switch ( gridType )
			{
				case GridType.Torus:
					MenuSettingsGridTorus.IsChecked = true;
					UpdateWrapIndicators(false, false, false, false);
					break;

				case GridType.XCylinder:
					MenuSettingsGridXCyl.IsChecked = true;
					UpdateWrapIndicators(false, true, false, true);
					break;

				case GridType.YCylinder:
					MenuSettingsGridYCyl.IsChecked = true;
					UpdateWrapIndicators(true, false, true, false);
					break;

				case GridType.Finite:
					MenuSettingsGridFinite.IsChecked = true;
					UpdateWrapIndicators(true, true, true, true);
					break;
			}
		}
        
		/// <summary>
		/// SetWindowTitle()
		/// 
		/// Called from one or two spots to set the proper window title.
		/// </summary>
		private void SetWindowTitle()
		{
			this.Title = _winTitleBase;
			if (_ls.Model.ModelName != null && _ls.Model.ModelName.Length > 0)
				this.Title += " [" + _ls.Model.ModelName;
			else
				this.Title += " [Untitled";

			if (_gameIsDirty)
				this.Title += "*";

			this.Title += "]";
		}

		/// <summary>
		/// SetGameDirty(bool)
		/// 
		/// Called from various places where the current game becomes "dirty", or in need
		/// of saving to disk. Called with true if the game has become dirty. Updates
		/// the _gameIsDirty flag and window title on a state change.
		/// </summary>
		/// <param name="dirty"></param>
		private void SetGameDirty( bool dirty )
		{
			_gameIsDirty = dirty;
			SetWindowTitle();
		}
        
		/// <summary>
		/// SetGridSizeMenu()
		/// 
		/// Called from InitUIState to update the state of the grid size menu
		/// to reflect the size of a model.
		/// </summary>
		private void SetGridSizeMenu()
		{
			MenuSettingsGrid40x40.IsChecked = false;
			MenuSettingsGrid50x50.IsChecked = false;
			MenuSettingsGrid60x60.IsChecked = false;
			MenuSettingsGrid70x70.IsChecked = false;
			MenuSettingsGridCustom.IsChecked = false;

			if ((_ls.Model.Columns != _ls.Model.Rows) ||
			(_ls.Model.Columns != 40 && _ls.Model.Columns != 50 && 
			_ls.Model.Columns != 60 && _ls.Model.Columns != 70))
			{
				MenuSettingsGridCustom.IsChecked = true;
			}
			else
			{
				switch (_ls.Model.Rows)
				{
					case 40:
						MenuSettingsGrid40x40.IsChecked = true;
						break;
					case 50:
						MenuSettingsGrid50x50.IsChecked = true;
						break;
					case 60:
						MenuSettingsGrid60x60.IsChecked = true;
						break;
					case 70:
						MenuSettingsGrid70x70.IsChecked = true;
						break;
				}
			}
			MenuGridSizeText.Text = "r:" + _ls.Model.Rows.ToString() + " c:" + _ls.Model.Columns.ToString();
		}
        
		/// <summary>
		/// InitUIState()
		/// 
		/// This function peforms common setup work when a game is created or loaded.
		/// Initializes the grid and populates it, sets up some data contexts, and sets
		/// the window title.
		/// </summary>
		private void InitUIState()
		{
			InitGrid();
			PopulateGrid();
			ApplyRectStyle();
			SetGridSizeMenu();
			SetWindowTitle();
			SetGridType(_ls.Model.LifeGridType);

			StatusGenCount.DataContext = _ls;
			RunSpeedSlider.DataContext = _ls;
			CellBirthCount.DataContext = _ls.Model;
			CellDeathCount.DataContext = _ls.Model;
			PopulationCount.DataContext = _ls.Model;
			PeakPopulationCount.DataContext = _ls.Model;
		}

		/// <summary>
		/// ExecNew()
		/// 
		/// Does the grunt work of initializing a new game with an empty grid. Initializes
		/// the model and controller, populates the grid, and wires up some UI fields by setting
		/// data contexts for items with property bindings.
		/// </summary>
		private bool ExecNew(bool defaults)
		{
			bool result = false;

			if ( CheckSave() == true )
			{
				if ( !defaults)
				{
					ALModelSizeDlg dlg = new ALModelSizeDlg();
					dlg.Owner = this;
					dlg.Tag = _ls.Model;
					if ( dlg.ShowDialog() == true )
					{
						int rows = dlg.Rows;
						int cols = dlg.Columns;
						if ( rows * cols > 10000 )
						{
							string msg = PrepMessage(Properties.Resources.UI_MB_LargeModelMsg, 80);
							msg += "\n\nRequested model size will cause the program to create approximately " 
							+ Convert.ToString((rows * cols) * 3) + " objects.";
							if ( MessageBox.Show(this, msg, Properties.Resources.UI_MB_LargeModelCaption, 
								MessageBoxButton.OKCancel, MessageBoxImage.Question) == MessageBoxResult.Cancel )
							{    
								return result;
							}
						}
						if (_ls == null)
						{
							_ls = new LifeSim(rows, cols);
							_ls.UICallback = SimStatusCallback;
						}
						else
							_ls.NewModel(rows, cols);
					}
					else return result;
				}
				else
				{
					if (_ls == null)
					{
						_ls = new LifeSim();
						_ls.UICallback = SimStatusCallback;
					}
					else
						_ls.NewModel();
				}
				InitUIState();
				UIStateChange(UIStateChanges.ModelCreated);
				result = true;
			}
			return result;
		}
        
		/// <summary>
		/// ExecLoadWeb(string)
		/// 
		/// This function handles loading a stream of cells from the Life Lexicon website.
		/// Most of the work is actually done in LifeModel.LifeModel(Stream), which is called
		/// from LifeSim.NewModel(Stream).
		/// </summary>
		/// <param name="url"></param>
		/// <returns></returns>
		private bool ExecLoadWeb( string url )
		{
			bool result = false;
			if (CheckSave() == true)
			{
				Uri uri = new Uri(url);

				char[] sep = { '.' };
				string[] splitstr = uri.LocalPath.Split(sep);
				if (string.Compare("cells", splitstr[splitstr.Length - 1], true) == 0)
				{
					HttpWebRequest cellReq = (HttpWebRequest)HttpWebRequest.Create(uri);
					cellReq.Timeout = 10000;
					cellReq.UserAgent = "AvalonLife_1_0";
					try
					{
						HttpWebResponse cellRes = (HttpWebResponse)cellReq.GetResponse();
						if ( _ls.NewModel(cellRes.GetResponseStream()) )
						{
							InitUIState();
							UIStateChange(UIStateChanges.ModelLoadedFromNet);
							_currentModelFileType = ALFileType.None;
							result = true;
						}
						else
							MessageBox.Show(this, "Failed to load model data.", "Load Error",
								MessageBoxButton.OK, MessageBoxImage.Error);

						cellRes.Close();
					}
					catch (WebException wex)
					{
						string msg = "Failed to retrieve cell data. Response: ";
						msg += wex.Response;
						MessageBox.Show(this, msg, "Network Error",
							MessageBoxButton.OK, MessageBoxImage.Error);
					}
				}
				else
				{
					string msg = "Invalid data type: " + url;
					MessageBox.Show(this, msg, "Invalid Data",
						MessageBoxButton.OK, MessageBoxImage.Error);
				}
			}
			return result;
		}
       
		/// <summary>
		/// ExecLoadFile(string)
		/// 
		/// Does the work of loading a game from a file. Handles saved .avl files as
		/// well as .cells files saved from Life Lexicon data.
		/// </summary>
		/// <returns></returns>
		private bool ExecLoadFile(string fileName)
		{
			bool result = false;

			if (CheckSave() == true)
			{
				ALFileType ft = GetFileType(fileName);
				if ( ft == ALFileType.AVL )
				{
					BinaryFormatter formatter = new BinaryFormatter();
					Stream str = File.OpenRead(fileName);
					try
					{
						_ls = formatter.Deserialize(str) as LifeSim;
						_ls.UICallback = SimStatusCallback;
						SetFileType(fileName);
						InitUIState();
						UIStateChange(UIStateChanges.ModelLoadedFromFile);
						result = true;
					}
					catch (System.Runtime.Serialization.SerializationException ex)
					{
						MessageBox.Show(this, Properties.Resources.UI_MB_LoadFailedMsg + " " + ex.Message,
							Properties.Resources.UI_MB_LoadFailedCaption,
							MessageBoxButton.OK, MessageBoxImage.Error);
					}
					finally
					{
						str.Close();
					}
					SetGameDirty(false);
				}
				else if ( ft == ALFileType.Cells )
				{
					Stream str = File.OpenRead(fileName);
					if ( _ls.NewModel(str) )
					{
						SetFileType(fileName);
						InitUIState();
						UIStateChange(UIStateChanges.ModelLoadedFromFile);
						result = true;
					}
					else
						MessageBox.Show(this, "Failed to load model data.", "Load Error",
							MessageBoxButton.OK, MessageBoxImage.Error);
					str.Close();
				}
				else
				{
					string msg = "Invalid file type: " + fileName;
					MessageBox.Show(this, msg, "Invalid File",
					MessageBoxButton.OK, MessageBoxImage.Error);
				}
			}                      
			return result;
		}
        
		/// <summary>
		/// ExecSave()
		/// 
		/// Called from the menu savegame handler, or from the load game handler
		/// if necessary. Saves the current model to disk.
		/// </summary>
		private void ExecSave( bool saveAs )
		{
			System.Windows.Forms.SaveFileDialog savedlg =
				new System.Windows.Forms.SaveFileDialog();

			savedlg.AddExtension = true;
			savedlg.Filter = "AvalonLife Saved Games (.avl)|*.avl|Life Lexicon Cells (.cells)|*.cells";
			bool haveFile = false;

			if (_currentModelFileBase != null && _currentModelFileType != ALFileType.None)
			{
				haveFile = true;
				savedlg.FileName = _currentModelFileBase;
				if ( _currentModelFileType == ALFileType.Cells )
				{
					savedlg.DefaultExt = ".cells";
					savedlg.FilterIndex = 2;
				}
				else if ( _currentModelFileType == ALFileType.AVL )
				{    
					savedlg.DefaultExt = ".avl";
					savedlg.FilterIndex = 1;
				}
			}
			else
			{
				savedlg.FileName = "AvalonLife Saved Game";
				savedlg.DefaultExt = ".avl";
				savedlg.FilterIndex = 1;
			}

			if ( saveAs == true )
				savedlg.Title = "Save Model As";
			else
				savedlg.Title = "Save Model";

			if (savedlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
			{

				ALFileType ft = GetFileType(savedlg.FileName);

				if ( ft == ALFileType.AVL )
				{
					BinaryFormatter formatter = new BinaryFormatter();
					Stream str = File.OpenWrite(savedlg.FileName);
					try
					{
						formatter.Serialize(str, _ls);
						if ( !saveAs || !haveFile )
							SetFileType(savedlg.FileName);
						UIStateChange(UIStateChanges.ModelSaved);
					}
					catch (System.Exception ex)
					{
						MessageBox.Show(this, Properties.Resources.UI_MB_SaveFailedMsg + " " + ex.InnerException.Message,
						Properties.Resources.UI_MB_SaveFailedCaption,
						MessageBoxButton.OK, MessageBoxImage.Error);
					}
					finally
					{
						str.Close();
					}
				}
				else if ( ft == ALFileType.Cells )
				{
					Stream str = File.OpenWrite(savedlg.FileName);
					try
					{
						_ls.Model.StreamCells(str);
						if (!saveAs || !haveFile)
							SetFileType(savedlg.FileName);
						UIStateChange(UIStateChanges.ModelSaved);
					}
					catch (System.Exception)
					{
					}
					finally
					{
						str.Close();
					}
				}
				else
				{
					string msg = "Invalid file type: " + savedlg.FileName;
					MessageBox.Show(this, msg, "Invalid File",
						MessageBoxButton.OK, MessageBoxImage.Error);
				}
			}
		}
        
		/// <summary>
		/// CheckSave()
		/// 
		/// This utility function checks to see if the current game is dirty. If it is
		/// it asks the user whether they want to save the game before continuing with
		/// whatever operation called this function. Returns true if the operation is
		/// good to proceed, false if the user clicks cancel.
		/// </summary>
		/// <returns></returns>
		private bool CheckSave()
		{
			bool result = true;

			if (_gameIsDirty)
			{
				MessageBoxResult mbres = MessageBox.Show( this, Properties.Resources.UI_MB_PromptText3, Properties.Resources.UI_MB_CaptionText3,
				MessageBoxButton.YesNoCancel, MessageBoxImage.Question );
				if ( mbres == MessageBoxResult.Yes )
					ExecSave(false);
				else if ( mbres == MessageBoxResult.No )
					SetGameDirty(false);
				else if ( mbres == MessageBoxResult.Cancel )
					result = false;
			}
			return result;
		}
        
		/// <summary>
		/// InitGrid()
		/// 
		/// Called from the OnLoaded event handler for the main window to initialize the
		/// UI display grid with the appropriate number of rows and columns based on the
		/// _lm.Rows and _lm.Columns properties. It then adds an ellipse to each cell and
		/// sets its style.
		/// </summary>
		private void InitGrid()
		{
			LifeGrid.Children.Clear();
			LifeGrid.RowDefinitions.Clear();
			LifeGrid.ColumnDefinitions.Clear();

			for (int i = 0; i < _ls.Model.Rows; i++)
			{
				LifeGrid.RowDefinitions.Add(new RowDefinition());
			}
			for (int i = 0; i < _ls.Model.Columns; i++)
			{
				LifeGrid.ColumnDefinitions.Add(new ColumnDefinition());
			}
		}
        
/// <summary>
/// GetCellBrush()
/// 
/// Constructs a brush from the settings in the config file.
/// </summary>
/// <returns></returns>
private Brush GetCellBrush()
{
	CellBrushType brushType = ALSettings.Default.LifeCellBrushType;

	if ( brushType == CellBrushType.Radial )
	{
		RadialGradientBrush brush = new RadialGradientBrush();
		brush.GradientOrigin = new Point(0.5, 0.5);
		brush.RadiusX = 0.5; 
		brush.RadiusY = 0.5;

		brush.GradientStops.Add( new GradientStop(ALSettings.Default.CellBrushC1, ALSettings.Default.CellBrushC1Off) );
		brush.GradientStops.Add(new GradientStop(ALSettings.Default.CellBrushC2, ALSettings.Default.CellBrushC2Off));
		brush.GradientStops.Add(new GradientStop(ALSettings.Default.CellBrushC3, ALSettings.Default.CellBrushC3Off));

		brush.Freeze();

		return brush;
	}
	else if ( brushType == CellBrushType.Linear )
	{
		LinearGradientBrush brush = new LinearGradientBrush();
		brush.StartPoint = new Point(0, 0);
		brush.EndPoint = new Point(1, 1);

		brush.GradientStops.Add(new GradientStop(ALSettings.Default.CellBrushC1, ALSettings.Default.CellBrushC1Off));
		brush.GradientStops.Add(new GradientStop(ALSettings.Default.CellBrushC2, ALSettings.Default.CellBrushC2Off));
		brush.GradientStops.Add(new GradientStop(ALSettings.Default.CellBrushC3, ALSettings.Default.CellBrushC3Off));

		brush.Freeze();

		return brush;
	}
	else if ( brushType == CellBrushType.Solid )
	{
		SolidColorBrush brush = new SolidColorBrush(ALSettings.Default.CellBrushC1);
		brush.Freeze();

		return brush;
	}
	else
	{    
		SolidColorBrush brush = new SolidColorBrush(Colors.Red);
		brush.Freeze();

		return brush;
	}
}
        
		/// <summary>
		/// ApplyRectStyle()
		/// 
		/// If you look at the source you'll see that this function is only called when a new
		/// grid is being initialized, and when the cell brush has been changed. It builds a new
		/// style that sets the Fill property of a rectangle to the new brush, and then goes
		/// through the children of the grid setting this style. The style is based on an
		/// existing style that binds the opacity property to govern visibility. So why go to
		/// all this trouble to change fill brushes? Why not just set the fill property on the
		/// rects and be done with it? Here's the issue: again, opacity is controlled by a
		/// binding in a style. Element properties override style settings, and they happen at
		/// different times too. If in the process of creating a new grid I set the rectangle
		/// DataContext to point to a LifeCell, which will drive the opacity binding, and then
		/// set the Fill property directly, sometimes, depending on timing, I get a repaint
		/// before the opacity property is correctly set, and the grid renders all the cells
		/// visible. It looks messy, and I don't want the grid repainted until the state of all
		/// the cells is correct. I'm sure there must be other ways to handle suppressing the
		/// repaint, but the issue there is that the flash happens after I return control to
		/// the message pump. If I somehow surpress the repaint when will I unsurpress it? The
		/// best way around this that I have found so far is to do as I have below: change
		/// brushes by building a new style and then applying that style.
		/// </summary>
		private void ApplyRectStyle()
		{
			Brush cellBrush = GetCellBrush();

			Style style = new Style(typeof(Rectangle), (Style)LifeGrid.FindResource(typeof(Rectangle)));
			Setter setter = new Setter();
			setter.Property = Rectangle.FillProperty;
			setter.Value = cellBrush;
			style.Setters.Add(setter);
			LifeGrid.Resources.Remove("RectStyle");
			LifeGrid.Resources.Add("RectStyle", style);

			UIElementCollection rects = LifeGrid.Children;

			foreach (UIElement uie in rects)
				((Rectangle)uie).Style = (Style)(LifeGrid.Resources["RectStyle"]);
		}
        
		/// <summary>
		/// PopulateGrid()
		/// 
		/// Does the work of setting up the rectangles in the cells of the life grid. Creates
		/// the rectangles and assigns them to the grid, adds them to the child collection,
		/// sets up rectangle data contexts for the rect->cell link, sets the rectangle style,
		/// and wires up the rectangle mouse events
		/// </summary>
		private void PopulateGrid()
		{
			for (int row = 0; row < _ls.Model.Rows; row++)
			{
				for (int col = 0; col < _ls.Model.Columns; col++)
				{
					Rectangle rect = new Rectangle();
					Grid.SetRow(rect, row);
					Grid.SetColumn(rect, col);
					LifeGrid.Children.Add(rect);
					rect.DataContext = _ls.Model.CellGrid[row, col];
					rect.MouseDown += new MouseButtonEventHandler(Rect_OnMouseDown);
					rect.MouseMove += new MouseEventHandler(Rect_OnMouseEnter);
				}
			}
		}

		/// <summary>
		/// GetFileType(string)
		/// 
		/// Called from ExecSave to determine the type of file that the user selected.
		/// </summary>
		/// <param name="fileName"></param>
		/// <returns></returns>
		private ALFileType GetFileType( string fileName )
		{
			string ext = System.IO.Path.GetExtension(fileName);
			ALFileType ft = ALFileType.None;

			if (string.Compare(".avl", ext, true) == 0)
				ft = ALFileType.AVL;
			else if (string.Compare(".cells", ext, true) == 0)
				ft = ALFileType.Cells;

			return ft;
		}
        
		/// <summary>
		/// SetFileType( string )
		/// 
		/// Called from the ExecSave and ExecLoad functions. Retrieves and stores
		/// the base filename and sets the file format type.
		/// </summary>
		/// <param name="fileName"></param>
		private ALFileType SetFileType( string fileName )
		{
			_currentModelFileBase = System.IO.Path.GetFileNameWithoutExtension(fileName);
			string ext = System.IO.Path.GetExtension(fileName);

			if ( string.Compare(".avl", ext, true) == 0 )
				_currentModelFileType = ALFileType.AVL;
			else if ( string.Compare(".cells", ext, true) == 0 )
				_currentModelFileType = ALFileType.Cells;
			else 
				_currentModelFileType = ALFileType.None;

			return _currentModelFileType;
		}

		#endregion
        
		/// <summary>
		/// Holds the instance of the simulation controller
		/// </summary>
		private LifeSim _ls = null;

		/// <summary>
		/// True if the game has been modified since last save
		/// </summary>
		private bool _gameIsDirty = false;

		/// <summary>
		/// Tracks the last cell that the mouse was in
		/// </summary>
		private LifeCell _lastMouseCell = null;

		/// <summary>
		/// Container for the ReticleAdorner
		/// </summary>
		private AdornerLayer _gridAdornerLayer = null;

		/// <summary>
		/// True if we are dragging across cells in edit mode
		/// </summary>
		private bool _startEdit = false;

		/// <summary>
		/// If not null contains the last file name used to load or
		/// save the current model.
		/// </summary>
		private string _currentModelFileBase = null;

		private ALFileType _currentModelFileType = ALFileType.AVL;

		/// <summary>
		/// Self-explanatory
		/// </summary>
		private string _winTitleBase = "AvalonLife 1.0";
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *