1
0
mirror of https://github.com/chylex/Brotli-Builder.git synced 2024-12-22 07:42:47 +01:00
Brotli-Builder/BrotliBuilder/FormMain.cs

383 lines
14 KiB
C#

using System;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using BrotliBuilder.Blocks;
using BrotliBuilder.Components;
using BrotliBuilder.State;
using BrotliBuilder.Utils;
using BrotliBuilder.Utils.Compat;
using BrotliLib.Brotli;
namespace BrotliBuilder{
partial class FormMain : Form{
private string lastFileName = "compressed";
private bool isDirty = false;
private byte[]? lastOriginalFileBytes;
private BrotliFileStructure? lastGeneratedFile;
private bool skipNextBlockRegeneration = false;
private bool skipNextOriginalToGeneratedFeed = false;
private readonly BrotliFileController fileGenerated;
private readonly BrotliFileController fileOriginal;
private readonly StatusBarBase statusBar;
private readonly StatusBarBase.Panel statusBarPanelTimeStructure;
private readonly StatusBarBase.Panel statusBarPanelTimeBits;
private readonly StatusBarBase.Panel statusBarPanelTimeOutput;
public FormMain(){
InitializeComponent();
SuspendLayout();
var mainMenu = DeprecatedControls.CreateMainMenu(this, components);
var menuItemFile = mainMenu.AddItem("&File");
var menuItemView = mainMenu.AddItem("&View");
var menuItemTools = mainMenu.AddItem("&Tools");
var menuItemEncode = mainMenu.AddItem("&Encode");
var menuItemTransform = mainMenu.AddItem("Transfor&m");
InitializeMenuFile(menuItemFile);
InitializeMenuView(menuItemView);
InitializeMenuTools(menuItemTools);
InitializeMenuEncoders(menuItemEncode);
InitializeMenuTransformers(menuItemTransform);
statusBar = DeprecatedControls.CreateStatusBar(this);
statusBar.AddPadding(12);
statusBarPanelTimeStructure = statusBar.AddPanel(200);
statusBar.AddPadding(9);
statusBarPanelTimeBits = statusBar.AddPanel(200);
statusBar.AddPadding(9);
statusBarPanelTimeOutput = statusBar.AddPanel(200);
ResumeLayout(true);
this.fileGenerated = new BrotliFileController(brotliFilePanelGenerated.Title);
this.fileGenerated.StateChanged += FileGenerated_StateChanged;
this.fileOriginal = new BrotliFileController(brotliFilePanelOriginal.Title);
this.fileOriginal.StateChanged += FileOriginal_StateChanged;
fileGenerated.ResetToEmpty();
fileOriginal.ResetToNothing();
}
// Status bars
private void ResetStatusBars(string text){
statusBarPanelTimeStructure.Text = text;
statusBarPanelTimeBits.Text = text;
statusBarPanelTimeOutput.Text = text;
}
private void UpdateStatusBar(StatusBarBase.Panel panel, string type, Stopwatch? sw){
if (sw != null){
panel.Text = "Generated " + type + " in " + sw.ElapsedMilliseconds + " ms.";
}
else{
panel.Text = "Loaded " + type + ".";
}
}
// File handling
private void HandleFileError(BrotliFileState.Error error, BrotliFilePanel panel){
Exception ex = error.Exception;
string message = ex.Message;
switch(error.Type){
case ErrorType.ReadingFile:
MessageBox.Show(message, "File Open Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
break;
case ErrorType.EncodingBytes:
MessageBox.Show(message, "Encoder Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
break;
case ErrorType.TransformingStructure:
MessageBox.Show(message, "Transformer Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
break;
case ErrorType.DeserializingFile:
case ErrorType.SerializingStructure:
panel.UpdateBits(ex);
break;
case ErrorType.DecompressingStructure:
panel.UpdateOutput(ex);
break;
}
}
private void CheckOutputMatches(BrotliFileState.HasOutput hasOutput){
var prev = hasOutput.PreviousBytes;
var gen = hasOutput.OutputBytes;
if (prev == null){
return;
}
bool sameLength = prev.Length == gen.Length;
if (sameLength && prev.SequenceEqual(gen)){
return;
}
static string BytesText(int n) => n + (n == 1 ? " byte" : " bytes");
string sizeMessage = sameLength ? "same length" : "previously " + BytesText(prev.Length) + ", now " + BytesText(gen.Length);
if (MessageBox.Show("Found mismatched output (" + sizeMessage + "). Would you like to compare?", "Output Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes){
WinMerge.CompareText(Encoding.UTF8.GetString(prev), Encoding.UTF8.GetString(gen));
}
}
private void FileGenerated_StateChanged(object? sender, StateChangedEventArgs e){
BrotliFilePanel filePanel = brotliFilePanelGenerated;
switch(e.To){
case BrotliFileState.NoFile _:
menuItemCompareMarkersWithBitCounts!.Enabled = false;
menuItemCompareMarkersNoBitCounts!.Enabled = false;
menuItemCloneGeneratedToOriginal!.Enabled = false;
break;
case BrotliFileState.Starting _:
if (!skipNextBlockRegeneration){
flowPanelBlocks.Controls.Clear();
}
filePanel.InvalidatePanel();
ResetStatusBars("Generating...");
break;
case BrotliFileState.Waiting _:
filePanel.InvalidatePanel();
break;
case BrotliFileState.HasStructure hasStructure:
RegenerateBuildingBlocks(hasStructure.File);
UpdateStatusBar(statusBarPanelTimeStructure, "structure", hasStructure.Stopwatch);
break;
case BrotliFileState.HasBits hasBits:
filePanel.UpdateBits(hasBits);
UpdateStatusBar(statusBarPanelTimeBits, "bits", hasBits.Stopwatch);
break;
case BrotliFileState.HasMarkers hasMarkers:
filePanel.UpdateMarkers(hasMarkers);
break;
case BrotliFileState.HasOutput hasOutput:
filePanel.UpdateOutput(hasOutput);
UpdateStatusBar(statusBarPanelTimeOutput, "output", hasOutput.Stopwatch);
CheckOutputMatches(hasOutput);
break;
case BrotliFileState.Loaded loaded:
filePanel.FinalizeOutput(loaded);
lastGeneratedFile = loaded.File;
menuItemCloneGeneratedToOriginal!.Enabled = true;
if (brotliFilePanelOriginal.MarkerRoot != null && brotliFilePanelGenerated.MarkerRoot != null){
menuItemCompareMarkersWithBitCounts!.Enabled = true;
menuItemCompareMarkersNoBitCounts!.Enabled = true;
}
break;
case BrotliFileState.Error error:
HandleFileError(error, filePanel);
break;
}
}
private void FileOriginal_StateChanged(object? sender, StateChangedEventArgs e){
BrotliFilePanel filePanel = brotliFilePanelOriginal;
switch(e.To){
case BrotliFileState.NoFile _:
filePanel.ResetPanel();
splitContainerRightBottom.Panel2Collapsed = true;
menuItemCloseOriginal!.Enabled = false;
menuItemCompareMarkersWithBitCounts!.Enabled = false;
menuItemCompareMarkersNoBitCounts!.Enabled = false;
menuItemCloneOriginalToGenerated!.Enabled = false;
lastOriginalFileBytes = null;
return;
case BrotliFileState.Starting _:
if (!skipNextOriginalToGeneratedFeed){
flowPanelBlocks.Controls.Clear();
fileGenerated.ResetToWaiting();
}
filePanel.InvalidatePanel();
ResetStatusBars("Loading...");
isDirty = false;
skipNextBlockRegeneration = false;
break;
case BrotliFileState.Waiting _:
filePanel.InvalidatePanel();
break;
case BrotliFileState.HasBits hasBits:
filePanel.UpdateBits(hasBits);
break;
case BrotliFileState.HasMarkers hasMarkers:
filePanel.UpdateMarkers(hasMarkers);
break;
case BrotliFileState.HasOutput hasOutput:
filePanel.UpdateOutput(hasOutput);
CheckOutputMatches(hasOutput);
lastOriginalFileBytes = hasOutput.OutputBytes;
break;
case BrotliFileState.Loaded loaded:
filePanel.FinalizeOutput(loaded);
if (skipNextOriginalToGeneratedFeed){
skipNextOriginalToGeneratedFeed = false;
}
else{
fileGenerated.LoadStructure(loaded.File, lastOriginalFileBytes);
}
menuItemCloseOriginal!.Enabled = true;
menuItemCloneOriginalToGenerated!.Enabled = true;
break;
case BrotliFileState.Error error:
HandleFileError(error, filePanel);
break;
}
splitContainerRightBottom.Panel2Collapsed = false; // skipped for State.NoFile
}
private bool PromptUnsavedChanges(string message){
if (!isDirty){
return false;
}
DialogResult result = MessageBox.Show(message, "Unsaved Changes", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
if (result == DialogResult.Cancel){
return true;
}
else if (result == DialogResult.Yes){
SaveBrotli(); // sets isDirty to false on success
if (isDirty){
return true;
}
}
return false;
}
// Main events
private void FormMain_FormClosing(object? sender, FormClosingEventArgs e){
e.Cancel = PromptUnsavedChanges("Would you like to save changes before exiting?");
}
private void FormMain_Resize(object? sender, EventArgs e){
int statusBarPanelWidth = Width < 645 ? 200 - Math.Min(100, (645 - Width) / 3) : 200;
statusBarPanelTimeStructure.Width = statusBarPanelWidth;
statusBarPanelTimeBits.Width = statusBarPanelWidth;
statusBarPanelTimeOutput.Width = statusBarPanelWidth;
}
private void flowPanelBlocks_ControlAdded(object? sender, ControlEventArgs e){
e.Control.Height = flowPanelBlocks.ClientSize.Height - 8;
}
private void flowPanelBlocks_SizeChanged(object? sender, EventArgs e){
flowPanelBlocks.SetChildHeight(flowPanelBlocks.ClientSize.Height - 8);
}
private void brotliFilePanel_MarkersUpdated(object? sender, MarkedTextBox.MarkerUpdateEventArgs e){
brotliMarkerInfoPanel.UpdateMarkers(e.Title, e.MarkerRoot, e.MarkerSequence, e.HighlightedNodes, e.CaretNode);
}
// Building blocks
private void RegenerateBuildingBlocks(BrotliFileStructure file){
if (skipNextBlockRegeneration){
skipNextBlockRegeneration = false;
return;
}
flowPanelBlocks.Controls.Clear();
flowPanelBlocks.Controls.Add(new BuildFileStructure(new BuildingBlockContext(this, flowPanelBlocks), file));
}
private void RegenerateBrotliStream(){
isDirty = true;
skipNextBlockRegeneration = true;
timerRegenerationDelay.Stop();
timerRegenerationDelay.Start();
}
private void timerRegenerationDelay_Tick(object? sender, EventArgs e){
timerRegenerationDelay.Stop();
if (lastGeneratedFile != null){
fileGenerated.LoadStructure(lastGeneratedFile);
}
}
private class BuildingBlockContext : IBuildingBlockContext{
public event EventHandler<EventArgs>? Notified;
private readonly FormMain owner;
private readonly Panel container;
private readonly IBuildingBlockContext? parent;
private readonly int depth;
public BuildingBlockContext(FormMain owner, Panel container, IBuildingBlockContext? parent = null, int depth = 0){
this.owner = owner;
this.container = container;
this.parent = parent;
this.depth = depth;
}
public void SetChildBlock(Func<IBuildingBlockContext, UserControl>? blockFactory){
int childDepth = depth + 1;
var controls = container.Controls;
while(childDepth < controls.Count){
controls.RemoveAt(childDepth);
}
if (blockFactory != null){
controls.Add(blockFactory(new BuildingBlockContext(owner, container, this, childDepth)));
}
}
public void NotifyParent(EventArgs args){
Notified?.Invoke(container.Controls[depth], args);
if (parent == null){
owner.RegenerateBrotliStream();
}
else{
parent.NotifyParent(args);
}
}
}
}
}