using System; using System.IO; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media; using MPF.Core.Data; using MPF.UI.Core.UserControls; namespace MPF.UI.Core.ViewModels { public class LogViewModel { #region Fields /// /// Parent OptionsWindow object /// public LogOutput Parent { get; private set; } #endregion #region Private State Variables /// /// Paragraph backing the log /// private readonly Paragraph _paragraph; /// /// Cached value of the last line written /// private Run lastLine = null; /// /// Queue of items that need to be logged /// private readonly ProcessingQueue logQueue; #endregion /// /// Constructor /// public LogViewModel(LogOutput parent) { Parent = parent; // Add handlers Parent.OutputViewer.SizeChanged += OutputViewerSizeChanged; Parent.Output.TextChanged += OnTextChanged; Parent.ClearButton.Click += OnClearButton; Parent.SaveButton.Click += OnSaveButton; // Update the internal state var document = new FlowDocument() { Background = new SolidColorBrush(Color.FromArgb(0xFF, 0x20, 0x20, 0x20)) }; _paragraph = new Paragraph(); document.Blocks.Add(_paragraph); Parent.Output.Document = document; logQueue = new ProcessingQueue(ProcessLogLine); } #region Logging /// /// Log level for output /// public enum LogLevel { USER, VERBOSE, ERROR, SECRET, } /// /// Log line wrapper /// private struct LogLine { public readonly string Text; public readonly LogLevel LogLevel; public LogLine(string text, LogLevel logLevel) { this.Text = text; this.LogLevel = logLevel; } /// /// Get the foreground Brush for the current LogLevel /// /// Brush representing the color public Brush GetForegroundColor() { switch (this.LogLevel) { case LogLevel.SECRET: return Brushes.Blue; case LogLevel.ERROR: return Brushes.Red; case LogLevel.VERBOSE: return Brushes.Yellow; case LogLevel.USER: default: return Brushes.White; } } /// /// Generate a Run object from the current LogLine /// /// Run object based on internal values public Run GenerateRun() { return new Run { Text = this.Text, Foreground = GetForegroundColor() }; } } /// /// Enqueue text to the log /// /// Text to write to the log public void Log(string text) => LogInternal(text, LogLevel.USER); /// /// Enqueue text with a newline to the log /// /// Text to write to the log public void LogLn(string text) => Log(text + "\n"); /// /// Enqueue error text to the log /// /// Text to write to the log public void ErrorLog(string text) => LogInternal(text, LogLevel.ERROR); /// /// Enqueue error text with a newline to the log /// /// Text to write to the log public void ErrorLogLn(string text) => ErrorLog(text + "\n"); /// /// Enqueue secret text to the log /// /// Text to write to the log public void SecretLog(string text) => LogInternal(text, LogLevel.SECRET); /// /// Enqueue secret text with a newline to the log /// /// Text to write to the log public void SecretLogLn(string text) => SecretLog(text + "\n"); /// /// Enqueue verbose text to the log /// /// Text to write to the log public void VerboseLog(string text) => LogInternal(text, LogLevel.VERBOSE); /// /// Enqueue verbose text with a newline to the log /// /// Text to write to the log public void VerboseLogLn(string text) => VerboseLog(text + "\n"); /// /// Reset the progress bar state /// public void ResetProgressBar() { Parent.Dispatcher.Invoke(() => { Parent.ProgressBar.Value = 0; Parent.ProgressLabel.Text = string.Empty; }); } /// /// Enqueue text to the log with formatting /// /// Text to write to the log /// LogLevel for the log private void LogInternal(string text, LogLevel logLevel) { // Null text gets ignored if (text == null) return; // Enqueue the text logQueue.Enqueue(new LogLine(text, logLevel)); } /// /// Process the log lines in the queue /// /// LogLine item to process private void ProcessLogLine(LogLine nextLogLine) { // Null text gets ignored string nextText = Parent.Dispatcher.Invoke(() => nextLogLine.Text); if (nextText == null) return; try { if (nextText.StartsWith("\r")) ReplaceLastLine(nextLogLine); else AppendToTextBox(nextLogLine); } catch (Exception ex) { // In the event that something fails horribly, we want to log AppendToTextBox(new LogLine(ex.ToString(), LogLevel.ERROR)); } } /// /// Append log line to the log text box /// /// LogLine value to append private void AppendToTextBox(LogLine logLine) { Parent.Dispatcher.Invoke(() => { var run = logLine.GenerateRun(); _paragraph.Inlines.Add(run); lastLine = run; }); } /// /// Replace the last line written to the log text box /// /// LogLine value to append private void ReplaceLastLine(LogLine logLine) { Parent.Dispatcher.Invoke(() => { lastLine.Text = logLine.Text; lastLine.Foreground = logLine.GetForegroundColor(); }); } #endregion #region Helpers /// /// Scroll the current view to the bottom /// public void ScrollToBottom() { Parent.OutputViewer.ScrollToBottom(); } #endregion #region EventHandlers private void OnClearButton(object sender, EventArgs e) { _paragraph.Inlines.Clear(); ResetProgressBar(); } private void OnSaveButton(object sender, EventArgs e) { using (StreamWriter tw = new StreamWriter(File.OpenWrite("console.log"))) { foreach (var inline in _paragraph.Inlines) { if (inline is Run run) tw.Write(run.Text); } } } private void OnTextChanged(object sender, TextChangedEventArgs e) => ScrollToBottom(); private void OutputViewerSizeChanged(object sender, SizeChangedEventArgs e) => ScrollToBottom(); #endregion } }