//-----------------------------------------------------------------------
//
// Created Sept. 2009 by Nicholas Armstrong. Available online at http://nicholasarmstrong.com
//
//
// Loops sample application content. Created for the Fall 2009 offering of ECE 150.
//
// This application uses the AvalonEdit component from SharpDevelop, licensed under LGPL.
// The compiled version of this library is located in the Libraries/ folder.
//
//-----------------------------------------------------------------------
namespace NicholasArmstrong.Samples.ECE150.Loops
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Highlighting;
///
/// ECE 150 Demo Main Window.
///
public partial class DemoContentControl : UserControl
{
#region Fields
///
/// DependencyProperty as the backing store for Samples.
///
public static readonly DependencyProperty SamplesProperty =
DependencyProperty.Register("Samples", typeof(Dictionary), typeof(DemoContentControl), new UIPropertyMetadata(new Dictionary()));
///
/// DependencyProperty backing store for SelectedSample.
///
public static readonly DependencyProperty SelectedSampleProperty =
DependencyProperty.Register("SelectedSample", typeof(string), typeof(DemoContentControl), new UIPropertyMetadata(String.Empty, new PropertyChangedCallback(OnSelectedSampleChanged)));
///
/// Command to start or stop program execution.
///
public static RoutedCommand ToggleExecutionCommand = new RoutedCommand("ToggleExecution", typeof(DemoContentControl));
///
/// Regex for extracting the name of a sample.
///
private static readonly Regex sampleName = new Regex("[/][/][ ]Sample:[ ]?([^\r\n]*)[\r]");
///
/// Value indicating whether the application is ready to execute a program.
///
private bool readyToExecute = true;
///
/// Value indicating whether the application should attempt to stop the running program.
///
private bool stopExecuting;
///
/// Value indicating whether the application is currently executing a program.
///
private bool executing;
///
/// Dynamic evaluation class for compiling and executing typed functions.
///
private DynamicEval eval;
///
/// The text editor control.
///
private TextEditor editor;
///
/// The grid of boxes, loading of which is delayed until the window's Loaded event.
///
private BoxGrid boxesGrid;
#endregion
#region Constructor
///
/// Initializes a new instance of the DemoContentControl class.
///
public DemoContentControl()
{
InitializeComponent();
this.CommandBindings.Add(new CommandBinding(ToggleExecutionCommand, new ExecutedRoutedEventHandler(OnToggleExecutionCommand)));
this.InputBindings.Add(new InputBinding(ToggleExecutionCommand, new KeyGesture(Key.F5)));
this.Loaded += new RoutedEventHandler(DemoContentControl_Loaded);
this.PreEditorTextBlock.Inlines.Add(new Run("public") { Foreground = Utilities.BrushFromString("Blue"), FontWeight = FontWeights.Bold });
this.PreEditorTextBlock.Inlines.Add(new Run(" void ") { Foreground = Utilities.BrushFromString("Red") });
this.PreEditorTextBlock.Inlines.Add(new Run("HighlightBoxes") { Foreground = Utilities.BrushFromString("MidnightBlue"), FontWeight = FontWeights.Bold });
this.PreEditorTextBlock.Inlines.Add(new Run("()\n{"));
this.PostEditorTextBlock.Text = "}";
this.editor = new TextEditor();
this.editor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(".cs");
this.editor.Style = (Style)this.FindResource("EditorStyle");
this.TextEditorArea.Content = this.editor;
this.ReadyToExecute();
}
#endregion
#region Properties
///
/// Gets the dictionary containing all of the available samples.
///
public Dictionary Samples
{
get { return (Dictionary)GetValue(SamplesProperty); }
private set { SetValue(SamplesProperty, value); }
}
///
/// Gets or sets the currently selected sample.
///
public string SelectedSample
{
get { return (string)GetValue(SelectedSampleProperty); }
set { SetValue(SelectedSampleProperty, value); }
}
#endregion
#region Methods
#region User-Accessible Methods
///
/// Highlights a box; this function is passed to the user's code as a delegate.
///
/// The number of the box to highlight.
/// The colour to use when highlighting a box.
public void Highlight(int position, string color)
{
// Make sure this is executed on the UI thread
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(delegate
{
if (position < 0 || position >= this.boxesGrid.Children.Count)
{
return;
}
try
{
Box box = (Box)this.boxesGrid.Children[position];
box.Highlighted = true;
if (!String.IsNullOrEmpty(color.Trim()))
{
Brush highlightColor = Utilities.BrushFromString(color);
box.HighlightColor = highlightColor;
}
}
catch (Exception e)
{
this.Exception(e.ToString());
return;
}
}));
if (this.stopExecuting)
{
Thread.CurrentThread.Abort();
}
}
///
/// Highlights a box; this function is passed to the user's code as a delegate.
///
/// The box to highlight.
/// The colour to use when highlighting the box.
public void HighlightBox(Box box, string color)
{
// Make sure this is executed on the UI thread
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(delegate
{
if (box == null)
{
return;
}
try
{
box.Highlighted = true;
if (!String.IsNullOrEmpty(color.Trim()))
{
Brush highlightColor = Utilities.BrushFromString(color);
box.HighlightColor = highlightColor;
}
}
catch (Exception e)
{
this.Exception(e.ToString());
return;
}
}));
if (this.stopExecuting)
{
Thread.CurrentThread.Abort();
}
}
///
/// Pauses for the specified duration; this function is passed to the user's code as a delegate.
///
/// The number of milliseconds to pause for.
public void Wait(int millisecondsTimeout)
{
int microSleepDuration = 25; // Minimum resolution of the Wait function
int currentDuration = 0;
if (millisecondsTimeout <= 0)
{
return;
}
// Sleep in small spurts so we can interrupt the thread in the middle of a long sleep
while (currentDuration <= millisecondsTimeout)
{
if (this.stopExecuting)
{
Thread.CurrentThread.Abort();
}
currentDuration += microSleepDuration;
Thread.Sleep(microSleepDuration);
}
}
#endregion
#region Event Methods
///
/// Switches sample text when the selected sample changes.
///
/// The source of the event.
/// Arguments describing the event.
private static void OnSelectedSampleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
DemoContentControl control = sender as DemoContentControl;
if (control != null)
{
control.editor.Text = control.Samples[control.SelectedSample];
}
}
///
/// Toggles the execution state when the execute command is activated.
///
/// The source of the event.
/// Arguments describing the event.
private static void OnToggleExecutionCommand(object sender, ExecutedRoutedEventArgs e)
{
DemoContentControl control = sender as DemoContentControl;
if (control != null)
{
control.ToggleExecution();
}
}
///
/// Loads the samples and the compiler once the window itself has loaded.
///
/// The source of the event.
/// Arguments describing the event.
private static void DemoContentControl_Loaded(object sender, RoutedEventArgs e)
{
DemoContentControl control = sender as DemoContentControl;
if (control != null)
{
control.boxesGrid = new BoxGrid();
control.BoxesGridArea.Content = control.boxesGrid;
control.LoadSamples();
control.editor.TextChanged += new EventHandler(control.Editor_TextChanged);
control.eval = new DynamicEval(new HighlightDelegate(control.Highlight), new HighlightBoxDelegate(control.HighlightBox), new WaitDelegate(control.Wait));
control.editor.Focus();
}
}
///
/// Stops program execution when the user starts typing or switches samples.
///
/// The source of the event.
/// Arguments describing the event.
private void Editor_TextChanged(object sender, EventArgs e)
{
if (this.executing)
{
this.stopExecuting = true;
}
else if (!this.readyToExecute)
{
this.ReadyToExecute();
this.readyToExecute = true;
}
}
///
/// Toggles the execution state when the execute button is clicked.
///
/// The source of the event.
/// Arguments describing the event.
private void ExecuteButton_Click(object sender, RoutedEventArgs e)
{
this.ToggleExecution();
}
#endregion
///
/// Loads the sample programs into the program.
///
private void LoadSamples()
{
var samples = Assembly.GetExecutingAssembly().GetManifestResourceNames().Where(s => s.StartsWith("NicholasArmstrong.Samples.ECE150.Loops.Samples", StringComparison.Ordinal)).OrderBy(s => s);
foreach (var sample in samples)
{
string content = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream(sample)).ReadToEnd();
string name = sampleName.Match(content).Groups[1].Value.Trim();
this.Samples.Add(name, content);
}
this.SelectedSample = "How To";
}
///
/// Toggles the program execution state.
///
private void ToggleExecution()
{
if (this.executing)
{
this.StopExecution();
this.executing = false;
}
else
{
this.StartExecution();
}
}
///
/// Compiles and starts execution of the user's program.
///
private void StartExecution()
{
this.executing = true;
this.stopExecuting = false;
this.Compiling();
this.ResetBoxes();
this.readyToExecute = false;
string programText = this.editor.Text;
int capacity = this.boxesGrid.Capacity;
int rows = this.boxesGrid.Rows;
int columns = this.boxesGrid.Columns;
var boxes = this.boxesGrid.Children.Cast().ToList();
// Run on a background thread so we don't block the UI.
ThreadPool.QueueUserWorkItem(delegate
{
try
{
string errors;
if (!this.eval.CompileSnippet(programText, out errors))
{
this.Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(delegate { this.Error(errors); }));
return;
}
this.Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(delegate { this.Executing(); }));
this.eval.ExecuteSnippet(capacity, rows, columns, boxes);
this.Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(delegate { this.Complete(); }));
}
catch (ThreadAbortException)
{
this.Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(delegate { this.Complete(); }));
}
catch (TargetInvocationException e)
{
this.Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(delegate { this.Exception(e.InnerException.ToString()); this.Complete(); }));
}
});
this.editor.Focus();
}
///
/// Halts execution of a user program.
///
private void StopExecution()
{
this.stopExecuting = true;
this.editor.Focus();
}
///
/// Resets the boxes to their default un-highlighted state.
///
private void ResetBoxes()
{
foreach (var box in this.boxesGrid.Children.Cast())
{
if (box.Highlighted)
{
box.Highlighted = false;
if (box.HighlightColor != Box.DefaultHighlight)
{
box.HighlightColor = Box.DefaultHighlight;
}
}
}
}
///
/// Updates the application status to indicate that the user's program can be executed.
///
private void ReadyToExecute()
{
this.StatusText.Inlines.Clear();
this.StatusText.Inlines.Add(new Run("Ready to Execute") { Foreground = Utilities.BrushFromString("DarkGreen") });
this.StatusText.ToolTip = null;
this.ExecuteButton.Content = "Execute";
this.ExecuteButton.IsEnabled = true;
}
///
/// Updates the application status to indicate that the user's program is currently being compiled.
///
private void Compiling()
{
this.StatusText.Inlines.Clear();
this.StatusText.Inlines.Add(new Run("Compiling...") { Foreground = Box.DefaultHighlight });
this.StatusText.ToolTip = null;
this.ExecuteButton.IsEnabled = false;
}
///
/// Updates the application status to indicate that errors were found during compilation.
///
/// Errors found during compilation.
private void Error(string errors)
{
this.StatusText.Inlines.Clear();
this.StatusText.Inlines.Add(new Run("Error: " + errors) { Foreground = Utilities.BrushFromString("Red") });
this.StatusText.ToolTip = errors;
this.ExecuteButton.Content = "Execute";
this.ExecuteButton.IsEnabled = true;
this.executing = false;
}
///
/// Updates the application status to indicate an exception was encountered during execution.
///
/// The exception that was encountered.
private void Exception(string message)
{
this.StatusText.Inlines.Clear();
this.StatusText.Inlines.Add(new Run("Exception: " + message) { Foreground = Utilities.BrushFromString("Red") });
this.StatusText.ToolTip = message;
this.stopExecuting = true;
}
///
/// Updates the application status to indicate that the user program is executing.
///
private void Executing()
{
this.StatusText.Inlines.Clear();
this.StatusText.Inlines.Add(new Run("Executing...") { Foreground = Box.DefaultHighlight });
this.StatusText.ToolTip = null;
this.ExecuteButton.Content = "Stop";
this.ExecuteButton.IsEnabled = true;
}
///
/// Updates the application status to indicate that the user program has finished executing.
///
private void Complete()
{
if (!this.StatusText.Text.StartsWith("Exception: ", StringComparison.Ordinal))
{
this.StatusText.Inlines.Clear();
this.StatusText.Inlines.Add(new Run("Execution complete") { Foreground = Box.DefaultHighlight });
this.StatusText.ToolTip = null;
}
this.ExecuteButton.Content = "Execute";
this.ExecuteButton.IsEnabled = true;
this.executing = false;
}
#endregion
}
}