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";
}
}