// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Runtime.InteropServices.ComTypes; using System.Text; using System.Windows.Forms; using Common; using Common.Utilities; using Microsoft.PowerToys.PreviewHandler.Gcode.Telemetry.Events; using Microsoft.PowerToys.Telemetry; namespace Microsoft.PowerToys.PreviewHandler.Gcode { /// /// Implementation of Control for Gcode Preview Handler. /// public class GcodePreviewHandlerControl : FormHandlerControl { /// /// Picture box control to display the G-code thumbnail. /// private PictureBox _pictureBox; /// /// Text box to display the information about blocked elements from Svg. /// private RichTextBox _textBox; /// /// Represent if an text box info bar is added for showing message. /// private bool _infoBarAdded; /// /// Start the preview on the Control. /// /// Stream reference to access source file. public override void DoPreview(T dataSource) { InvokeOnControlThread(() => { try { Bitmap thumbnail = null; using (var stream = new ReadonlyStream(dataSource as IStream)) { using (var reader = new StreamReader(stream)) { #pragma warning disable CA2000 // Do not dispose here thumbnail = GetThumbnail(reader); #pragma warning restore CA2000 } } _infoBarAdded = false; if (thumbnail == null) { _infoBarAdded = true; AddTextBoxControl(Resource.GcodeWithoutEmbeddedThumbnails); } else { AddPictureBoxControl(thumbnail); } Resize += FormResized; base.DoPreview(dataSource); PowerToysTelemetry.Log.WriteEvent(new GcodeFilePreviewed()); } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) #pragma warning restore CA1031 // Do not catch general exception types { PreviewError(ex, dataSource); } }); } /// /// Reads the G-code content searching for thumbnails and returns the largest. /// /// The TextReader instance for the G-code content. /// A thumbnail extracted from the G-code content. public static Bitmap GetThumbnail(TextReader reader) { if (reader == null) { return null; } Bitmap thumbnail = null; var bitmapBase64 = GetBase64Thumbnails(reader) .OrderByDescending(x => x.Length) .FirstOrDefault(); if (!string.IsNullOrEmpty(bitmapBase64)) { var bitmapBytes = Convert.FromBase64String(bitmapBase64); using (var bitmapStream = new MemoryStream(bitmapBytes)) { thumbnail = new Bitmap(bitmapStream); } } return thumbnail; } /// /// Gets all thumbnails in base64 format found on the G-code data. /// /// The TextReader instance for the G-code content. /// An enumeration of thumbnails in base64 format found on the G-code. private static IEnumerable GetBase64Thumbnails(TextReader reader) { string line; StringBuilder capturedText = null; while ((line = reader.ReadLine()) != null) { if (line.StartsWith("; thumbnail begin", StringComparison.InvariantCulture)) { capturedText = new StringBuilder(); } else if (line == "; thumbnail end") { if (capturedText != null) { yield return capturedText.ToString(); capturedText = null; } } else if (capturedText != null) { capturedText.Append(line[2..]); } } } /// /// Occurs when RichtextBox is resized. /// /// Reference to resized control. /// Provides data for the ContentsResized event. private void RTBContentsResized(object sender, ContentsResizedEventArgs e) { var richTextBox = sender as RichTextBox; richTextBox.Height = e.NewRectangle.Height + 5; } /// /// Occurs when form is resized. /// /// Reference to resized control. /// Provides data for the resize event. private void FormResized(object sender, EventArgs e) { if (_infoBarAdded) { _textBox.Width = Width; } } /// /// Adds a PictureBox Control to Control Collection. /// /// Image to display on PictureBox Control. private void AddPictureBoxControl(Image image) { _pictureBox = new PictureBox(); _pictureBox.BackgroundImage = image; _pictureBox.BackgroundImageLayout = ImageLayout.Center; _pictureBox.Dock = DockStyle.Fill; Controls.Add(_pictureBox); } /// /// Adds a Text Box in Controls for showing information about blocked elements. /// /// Message to be displayed in textbox. private void AddTextBoxControl(string message) { _textBox = new RichTextBox(); _textBox.Text = message; _textBox.BackColor = Color.LightYellow; _textBox.Multiline = true; _textBox.Dock = DockStyle.Top; _textBox.ReadOnly = true; _textBox.ContentsResized += RTBContentsResized; _textBox.ScrollBars = RichTextBoxScrollBars.None; _textBox.BorderStyle = BorderStyle.None; Controls.Add(_textBox); } /// /// Called when an error occurs during preview. /// /// The exception which occurred. /// Stream reference to access source file. private void PreviewError(Exception exception, T dataSource) { PowerToysTelemetry.Log.WriteEvent(new GcodeFilePreviewError { Message = exception.Message }); Controls.Clear(); _infoBarAdded = true; AddTextBoxControl(Resource.GcodeNotPreviewedError); base.DoPreview(dataSource); } } }