//----------------------------------------------------------------------- // // 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 } }