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