feat(desktop): redesign the pre-post verification form for quick reading

Replace the plain-text report with a colored status banner, per-check
count tiles and a findings list grouped by check type, with the selected
finding shown in full below it. A Copy Report button keeps the full text
report available. Consent, cancellation and failure blocking are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-10-20 23:37:57 -04:00
1 parent 0ca4e64189
commit 11b6032459
3 files changed
+461 -62

No files matched your search

@@ -23,6 +23,9 @@ public class PostVerificationFormTests
var post = Control<Button>(form, "postButton");
Assert.False(consent.Checked);
Assert.False(post.Enabled);
var list = Control<ListView>(form, "findingsList");
Assert.NotEmpty(list.Items);
Assert.All(list.Items.Cast<ListViewItem>(), item => Assert.NotNull(item.Group));
Invoke(form, "PostButton_Click", null, EventArgs.Empty);
Assert.NotEqual(DialogResult.OK, form.DialogResult);
consent.Checked = true;
@@ -54,7 +57,8 @@ public class PostVerificationFormTests
using var form = new PostVerificationForm(nest,
(_, _) => Task.FromResult(PostVerificationAnalyzer.Analyze(nest)));
form.VerifyAsync().GetAwaiter().GetResult();
Assert.NotEmpty(Control<TextBox>(form, "reportBox").Text);
Assert.Equal("No warnings found", Control<Label>(form, "headlineLabel").Text);
Assert.Empty(Control<ListView>(form, "findingsList").Items);
Assert.True(Control<Button>(form, "postButton").Enabled);
Assert.False(Control<CheckBox>(form, "acknowledgeBox").Enabled);
Assert.Equal("Continue Posting", Control<Button>(form, "postButton").Text);
@@ -73,7 +77,7 @@ public class PostVerificationFormTests
form.VerifyAsync().GetAwaiter().GetResult();
Control<CheckBox>(form, "acknowledgeBox").Checked = true;
Assert.False(Control<Button>(form, "postButton").Enabled);
Assert.Contains("test failure", Control<TextBox>(form, "reportBox").Text);
Assert.Contains("test failure", Control<Label>(form, "detailLabel").Text);
Invoke(form, "PostButton_Click", null, EventArgs.Empty);
Assert.NotEqual(DialogResult.OK, form.DialogResult);
});
+208 -46
View File
@@ -6,11 +6,26 @@ namespace OpenNest.Forms;
partial class PostVerificationForm
{
private System.ComponentModel.IContainer components = null;
private TableLayoutPanel banner;
private Label statusIcon;
private Label headlineLabel;
private Label detailLabel;
private ProgressBar progressBar;
private TableLayoutPanel layout;
private Label summaryLabel;
private TextBox reportBox;
private FlowLayoutPanel tilesPanel;
private Panel findingsHost;
private ListView findingsList;
private ColumnHeader plateColumn;
private ColumnHeader partColumn;
private ColumnHeader otherPartColumn;
private ColumnHeader messageColumn;
private Label emptyLabel;
private Label findingDetailLabel;
private Label footnoteLabel;
private Panel acknowledgePanel;
private CheckBox acknowledgeBox;
private FlowLayoutPanel buttons;
private TableLayoutPanel buttonRow;
private Button copyButton;
private Button postButton;
private Button cancelButton;
private bool disposedResources;
@@ -29,96 +44,243 @@ partial class PostVerificationForm
private void InitializeComponent()
{
banner = new TableLayoutPanel();
statusIcon = new Label();
headlineLabel = new Label();
detailLabel = new Label();
progressBar = new ProgressBar();
layout = new TableLayoutPanel();
summaryLabel = new Label();
reportBox = new TextBox();
tilesPanel = new FlowLayoutPanel();
findingsHost = new Panel();
findingsList = new ListView();
plateColumn = new ColumnHeader();
partColumn = new ColumnHeader();
otherPartColumn = new ColumnHeader();
messageColumn = new ColumnHeader();
emptyLabel = new Label();
findingDetailLabel = new Label();
footnoteLabel = new Label();
acknowledgePanel = new Panel();
acknowledgeBox = new CheckBox();
buttons = new FlowLayoutPanel();
buttonRow = new TableLayoutPanel();
copyButton = new Button();
postButton = new Button();
cancelButton = new Button();
banner.SuspendLayout();
layout.SuspendLayout();
buttons.SuspendLayout();
findingsHost.SuspendLayout();
acknowledgePanel.SuspendLayout();
buttonRow.SuspendLayout();
SuspendLayout();
banner.Name = "banner";
banner.Dock = DockStyle.Top;
banner.AutoSize = true;
banner.Padding = new Padding(16, 14, 16, 14);
banner.ColumnCount = 2;
banner.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
banner.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
banner.RowCount = 3;
banner.RowStyles.Add(new RowStyle(SizeType.AutoSize));
banner.RowStyles.Add(new RowStyle(SizeType.AutoSize));
banner.RowStyles.Add(new RowStyle(SizeType.AutoSize));
banner.Controls.Add(statusIcon, 0, 0);
banner.SetRowSpan(statusIcon, 3);
banner.Controls.Add(headlineLabel, 1, 0);
banner.Controls.Add(detailLabel, 1, 1);
banner.Controls.Add(progressBar, 1, 2);
statusIcon.Name = "statusIcon";
statusIcon.AutoSize = true;
statusIcon.Font = new Font("Segoe MDL2 Assets", 22F, FontStyle.Regular, GraphicsUnit.Point);
statusIcon.Margin = new Padding(0, 2, 14, 0);
statusIcon.AccessibleRole = AccessibleRole.Graphic;
headlineLabel.Name = "headlineLabel";
headlineLabel.AutoSize = true;
headlineLabel.Dock = DockStyle.Fill;
headlineLabel.Font = new Font("Segoe UI Semibold", 13F, FontStyle.Regular, GraphicsUnit.Point);
headlineLabel.Margin = new Padding(0);
detailLabel.Name = "detailLabel";
detailLabel.AutoSize = true;
detailLabel.Dock = DockStyle.Fill;
detailLabel.Margin = new Padding(1, 4, 0, 0);
progressBar.Name = "progressBar";
progressBar.Style = ProgressBarStyle.Marquee;
progressBar.MarqueeAnimationSpeed = 30;
progressBar.Height = 6;
progressBar.Dock = DockStyle.Fill;
progressBar.Margin = new Padding(1, 10, 0, 0);
layout.Name = "layout";
layout.ColumnCount = 1;
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
layout.RowCount = 4;
layout.RowCount = 6;
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
layout.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
layout.Dock = DockStyle.Fill;
layout.Padding = new Padding(12);
layout.Controls.Add(summaryLabel, 0, 0);
layout.Controls.Add(reportBox, 0, 1);
layout.Controls.Add(acknowledgeBox, 0, 2);
layout.Controls.Add(buttons, 0, 3);
layout.Padding = new Padding(12, 10, 12, 12);
layout.Controls.Add(tilesPanel, 0, 0);
layout.Controls.Add(findingsHost, 0, 1);
layout.Controls.Add(findingDetailLabel, 0, 2);
layout.Controls.Add(footnoteLabel, 0, 3);
layout.Controls.Add(acknowledgePanel, 0, 4);
layout.Controls.Add(buttonRow, 0, 5);
summaryLabel.Name = "summaryLabel";
summaryLabel.AutoSize = true;
summaryLabel.Dock = DockStyle.Fill;
summaryLabel.Margin = new Padding(3, 3, 3, 10);
summaryLabel.Text = "Checking all plates: overlaps, missing lead-ins, and rapid crossings...";
summaryLabel.TabIndex = 0;
tilesPanel.Name = "tilesPanel";
tilesPanel.AutoSize = true;
tilesPanel.Dock = DockStyle.Fill;
tilesPanel.WrapContents = true;
tilesPanel.Margin = new Padding(0, 0, 0, 6);
reportBox.Name = "reportBox";
reportBox.Dock = DockStyle.Fill;
reportBox.Multiline = true;
reportBox.ReadOnly = true;
reportBox.ScrollBars = ScrollBars.Vertical;
reportBox.WordWrap = true;
reportBox.MaxLength = 0;
reportBox.TabIndex = 1;
reportBox.AccessibleName = "Pre-post verification findings";
reportBox.Text = "Verification is running. No CNC output has been written.";
findingsHost.Name = "findingsHost";
findingsHost.Dock = DockStyle.Fill;
findingsHost.Margin = new Padding(3);
findingsHost.Controls.Add(findingsList);
findingsHost.Controls.Add(emptyLabel);
findingsList.Name = "findingsList";
findingsList.Dock = DockStyle.Fill;
findingsList.View = View.Details;
findingsList.FullRowSelect = true;
findingsList.MultiSelect = false;
findingsList.HideSelection = false;
findingsList.ShowGroups = true;
findingsList.ShowItemToolTips = true;
findingsList.HeaderStyle = ColumnHeaderStyle.Nonclickable;
findingsList.TabIndex = 0;
findingsList.AccessibleName = "Pre-post verification findings";
findingsList.Columns.AddRange(new[] { plateColumn, partColumn, otherPartColumn, messageColumn });
findingsList.SelectedIndexChanged += FindingsList_SelectedIndexChanged;
findingsList.Resize += FindingsList_Resize;
plateColumn.Text = "Plate";
plateColumn.Width = 60;
partColumn.Text = "Part";
partColumn.Width = 55;
otherPartColumn.Text = "Other part";
otherPartColumn.Width = 75;
messageColumn.Text = "Finding";
messageColumn.Width = 600;
emptyLabel.Name = "emptyLabel";
emptyLabel.Dock = DockStyle.Fill;
emptyLabel.TextAlign = ContentAlignment.MiddleCenter;
emptyLabel.ForeColor = Color.FromArgb(110, 110, 110);
emptyLabel.BorderStyle = BorderStyle.FixedSingle;
emptyLabel.BackColor = Color.FromArgb(250, 250, 250);
emptyLabel.Visible = false;
findingDetailLabel.Name = "findingDetailLabel";
findingDetailLabel.AutoSize = true;
findingDetailLabel.Dock = DockStyle.Fill;
findingDetailLabel.BackColor = Color.FromArgb(245, 245, 245);
findingDetailLabel.ForeColor = Color.FromArgb(51, 51, 51);
findingDetailLabel.Padding = new Padding(8, 6, 8, 6);
findingDetailLabel.Margin = new Padding(3, 0, 3, 6);
findingDetailLabel.MinimumSize = new Size(0, 44);
findingDetailLabel.Visible = false;
footnoteLabel.Name = "footnoteLabel";
footnoteLabel.AutoSize = true;
footnoteLabel.Dock = DockStyle.Fill;
footnoteLabel.Font = new Font("Segoe UI", 8.25F, FontStyle.Regular, GraphicsUnit.Point);
footnoteLabel.ForeColor = Color.FromArgb(110, 110, 110);
footnoteLabel.Margin = new Padding(3, 2, 3, 8);
footnoteLabel.Text = "These checks cover direct XY rapids in plate/program order; the post may change order, " +
"routing or retracts. Inspect the posted machine program and machine setup before cutting, " +
"and confirm actual contour gaps give adequate retention.";
acknowledgePanel.Name = "acknowledgePanel";
acknowledgePanel.AutoSize = true;
acknowledgePanel.Dock = DockStyle.Fill;
acknowledgePanel.Padding = new Padding(10, 8, 10, 8);
acknowledgePanel.Margin = new Padding(3, 0, 3, 10);
acknowledgePanel.BackColor = Color.FromArgb(255, 246, 224);
acknowledgePanel.Visible = false;
acknowledgePanel.Controls.Add(acknowledgeBox);
acknowledgeBox.Name = "acknowledgeBox";
acknowledgeBox.AutoSize = true;
acknowledgeBox.Dock = DockStyle.Fill;
acknowledgeBox.Margin = new Padding(3, 12, 3, 12);
acknowledgeBox.Enabled = false;
acknowledgeBox.Checked = false;
acknowledgeBox.ForeColor = Color.FromArgb(110, 60, 0);
acknowledgeBox.Text = "I understand the listed risks, including possible head crashes and machine or material damage.\r\nI choose to bypass these warnings and continue posting this nest.";
acknowledgeBox.TabIndex = 2;
acknowledgeBox.TabIndex = 0;
acknowledgeBox.CheckedChanged += AcknowledgeBox_CheckedChanged;
buttons.Dock = DockStyle.Fill;
buttons.AutoSize = true;
buttons.FlowDirection = FlowDirection.RightToLeft;
buttons.Controls.Add(cancelButton);
buttons.Controls.Add(postButton);
buttons.TabIndex = 3;
buttonRow.Name = "buttonRow";
buttonRow.AutoSize = true;
buttonRow.Dock = DockStyle.Fill;
buttonRow.Margin = new Padding(0);
buttonRow.ColumnCount = 4;
buttonRow.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
buttonRow.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
buttonRow.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
buttonRow.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
buttonRow.RowCount = 1;
buttonRow.RowStyles.Add(new RowStyle(SizeType.AutoSize));
buttonRow.Controls.Add(copyButton, 0, 0);
buttonRow.Controls.Add(postButton, 2, 0);
buttonRow.Controls.Add(cancelButton, 3, 0);
cancelButton.Name = "cancelButton";
cancelButton.Text = "Cancel";
cancelButton.AutoSize = true;
cancelButton.DialogResult = DialogResult.Cancel;
cancelButton.TabIndex = 0;
copyButton.Name = "copyButton";
copyButton.Text = "Copy Report";
copyButton.AutoSize = true;
copyButton.MinimumSize = new Size(100, 30);
copyButton.Enabled = false;
copyButton.TabIndex = 0;
copyButton.Click += CopyButton_Click;
postButton.Name = "postButton";
postButton.Text = "Continue Posting";
postButton.AutoSize = true;
postButton.MinimumSize = new Size(120, 30);
postButton.Enabled = false;
postButton.TabIndex = 1;
postButton.Click += PostButton_Click;
cancelButton.Name = "cancelButton";
cancelButton.Text = "Cancel";
cancelButton.AutoSize = true;
cancelButton.MinimumSize = new Size(100, 30);
cancelButton.DialogResult = DialogResult.Cancel;
cancelButton.TabIndex = 2;
AcceptButton = cancelButton;
CancelButton = cancelButton;
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(850, 560);
MinimumSize = new Size(700, 440);
BackColor = Color.White;
ClientSize = new Size(880, 600);
MinimumSize = new Size(720, 480);
Font = new Font("Segoe UI", 9F, FontStyle.Regular, GraphicsUnit.Point);
// Fill first, then the top banner, so the banner docks ahead of the content.
Controls.Add(layout);
Controls.Add(banner);
MaximizeBox = false;
MinimizeBox = false;
Name = "PostVerificationForm";
ShowInTaskbar = false;
StartPosition = FormStartPosition.CenterParent;
Text = "Verify Nest Before Posting";
banner.ResumeLayout(false);
banner.PerformLayout();
findingsHost.ResumeLayout(false);
acknowledgePanel.ResumeLayout(false);
acknowledgePanel.PerformLayout();
buttonRow.ResumeLayout(false);
buttonRow.PerformLayout();
layout.ResumeLayout(false);
layout.PerformLayout();
buttons.ResumeLayout(false);
buttons.PerformLayout();
ResumeLayout(false);
PerformLayout();
}
}
+247 -14
View File
@@ -1,4 +1,6 @@
using System;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
@@ -12,6 +14,31 @@ namespace OpenNest.Forms;
/// </summary>
public partial class PostVerificationForm : Form
{
private enum Status { Running, Clear, Warning, Failed }
// Most severe first; this order drives both the tiles and the list groups.
private static readonly (PostVerificationKind Kind, string Label)[] Categories =
{
(PostVerificationKind.Incomplete, "Incomplete checks"),
(PostVerificationKind.Overlap, "Overlaps"),
(PostVerificationKind.RapidCrossing, "Rapid crossings"),
(PostVerificationKind.MissingLeadIn, "Missing lead-ins"),
(PostVerificationKind.NestedCutOrder, "Nested part order"),
(PostVerificationKind.NestedLead, "Nested part leads"),
(PostVerificationKind.FixedLead, "Fixed leads"),
(PostVerificationKind.PierceZone, "Pierce zones"),
(PostVerificationKind.PartSpacing, "Part spacing"),
};
// Checks every plate always runs, so their tiles show even at zero.
private static readonly PostVerificationKind[] CoreChecks =
{
PostVerificationKind.Overlap, PostVerificationKind.RapidCrossing, PostVerificationKind.MissingLeadIn,
};
private static readonly Color ClearAccent = Color.FromArgb(22, 128, 61);
private static readonly Color WarningAccent = Color.FromArgb(194, 94, 0);
private readonly Nest nest;
private readonly Func<Nest, CancellationToken, Task<PostVerificationReport>> analyze;
private readonly CancellationTokenSource cancellation = new();
@@ -31,6 +58,10 @@ public partial class PostVerificationForm : Form
this.nest = nest ?? throw new ArgumentNullException(nameof(nest));
this.analyze = analyze ?? throw new ArgumentNullException(nameof(analyze));
InitializeComponent();
SetStatus(Status.Running, "Checking nest…",
"Looking for overlaps, missing lead-ins and rapid crossings on every plate. " +
"No CNC output has been written.");
ShowEmpty("Findings will appear here when the checks finish.");
}
protected override async void OnShown(EventArgs e)
@@ -51,16 +82,7 @@ public partial class PostVerificationForm : Form
if (closeRequested || IsDisposed)
return;
report = result;
reportBox.Text = report.ToDisplayText();
reportBox.SelectionStart = 0;
reportBox.SelectionLength = 0;
summaryLabel.Text = report.HasWarnings
? "Warnings found. Review every finding before deciding whether to post."
: "Verification complete. No warnings found by these checks.";
acknowledgeBox.Visible = report.HasWarnings;
acknowledgeBox.Enabled = report.HasWarnings;
acknowledgeBox.Checked = false;
postButton.Text = report.HasWarnings ? "Post Anyway" : "Continue Posting";
ShowReport();
}
catch (OperationCanceledException) when (cancellation.IsCancellationRequested)
{
@@ -71,9 +93,10 @@ public partial class PostVerificationForm : Form
if (!IsDisposed)
{
report = null;
summaryLabel.Text = "Verification failed. Posting is blocked.";
reportBox.Text = "The verification could not finish. Cancel, correct the problem, and try again."
+ Environment.NewLine + Environment.NewLine + ex.Message;
SetStatus(Status.Failed, "Verification failed — posting is blocked",
"The verification could not finish. Cancel, correct the problem, and try again."
+ Environment.NewLine + ex.Message);
ShowEmpty("No findings are available because the verification did not finish.");
}
}
finally
@@ -91,6 +114,215 @@ public partial class PostVerificationForm : Form
}
}
private void ShowReport()
{
var findings = report.Findings;
if (!report.HasWarnings)
{
SetStatus(Status.Clear, "No warnings found",
"Every plate passed these checks. You can continue posting.");
}
else if (findings.Any(finding => finding.Kind == PostVerificationKind.Incomplete))
{
SetStatus(Status.Warning, "Verification incomplete",
"Some checks could not finish. Review every finding before deciding whether to post.");
}
else
{
var plates = findings.Where(finding => finding.PlateNumber > 0)
.Select(finding => finding.PlateNumber).Distinct().Count();
var headline = Plural(findings.Count, "warning")
+ (plates > 0 ? $" on {Plural(plates, "plate")}" : "");
SetStatus(Status.Warning, headline, "Review every finding before deciding whether to post.");
}
BuildTiles();
BuildList();
acknowledgePanel.Visible = report.HasWarnings;
acknowledgeBox.Enabled = report.HasWarnings;
acknowledgeBox.Checked = false;
postButton.Text = report.HasWarnings ? "Post Anyway" : "Continue Posting";
copyButton.Enabled = true;
}
private void SetStatus(Status status, string headline, string detail)
{
var (back, fore, glyph) = status switch
{
Status.Clear => (Color.FromArgb(225, 244, 230), Color.FromArgb(20, 83, 45), ""),
Status.Warning => (Color.FromArgb(255, 241, 208), Color.FromArgb(124, 63, 0), ""),
Status.Failed => (Color.FromArgb(253, 228, 228), Color.FromArgb(145, 24, 24), ""),
_ => (Color.FromArgb(230, 239, 250), Color.FromArgb(28, 70, 128), ""),
};
banner.BackColor = back;
headlineLabel.ForeColor = fore;
detailLabel.ForeColor = fore;
statusIcon.ForeColor = fore;
statusIcon.Text = glyph;
headlineLabel.Text = headline;
detailLabel.Text = detail;
progressBar.Visible = status == Status.Running;
}
private void BuildTiles()
{
tilesPanel.SuspendLayout();
tilesPanel.Controls.Clear();
foreach (var (kind, label) in Categories)
{
var count = report.Findings.Count(finding => finding.Kind == kind);
if (count == 0 && !CoreChecks.Contains(kind))
continue;
tilesPanel.Controls.Add(CreateTile(kind, label, count));
}
tilesPanel.ResumeLayout();
}
private Control CreateTile(PostVerificationKind kind, string label, int count)
{
var accent = count == 0 ? ClearAccent : WarningAccent;
var tile = new TableLayoutPanel
{
AutoSize = true,
ColumnCount = 1,
RowCount = 2,
MinimumSize = new Size(118, 0),
Margin = new Padding(3, 3, 8, 3),
Padding = new Padding(10, 6, 10, 8),
BackColor = count == 0 ? Color.FromArgb(242, 248, 244) : Color.FromArgb(255, 246, 228),
Cursor = count == 0 ? Cursors.Default : Cursors.Hand,
AccessibleName = $"{label}: {count}",
};
var number = new Label
{
AutoSize = true,
Text = count == 0 ? "✓ 0" : count.ToString(),
Font = new Font("Segoe UI Semibold", 16F, FontStyle.Regular, GraphicsUnit.Point),
ForeColor = accent,
Margin = new Padding(0),
};
var caption = new Label
{
AutoSize = true,
Text = label,
ForeColor = Color.FromArgb(70, 70, 70),
Margin = new Padding(1, 0, 0, 0),
};
tile.Controls.Add(number, 0, 0);
tile.Controls.Add(caption, 0, 1);
if (count != 0)
{
EventHandler jump = (_, _) => SelectFirst(kind);
tile.Click += jump;
number.Click += jump;
caption.Click += jump;
number.Cursor = caption.Cursor = Cursors.Hand;
}
return tile;
}
private void BuildList()
{
findingsList.BeginUpdate();
findingsList.Items.Clear();
findingsList.Groups.Clear();
foreach (var (kind, label) in Categories)
{
var findings = report.Findings.Where(finding => finding.Kind == kind).ToList();
if (findings.Count == 0)
continue;
var group = new ListViewGroup($"{label} ({findings.Count})")
{
Name = kind.ToString(),
CollapsedState = ListViewGroupCollapsedState.Expanded,
};
findingsList.Groups.Add(group);
foreach (var finding in findings)
{
var item = new ListViewItem(new[]
{
finding.PlateNumber == 0 ? "Post" : finding.PlateNumber.ToString(),
finding.PartNumber?.ToString() ?? "",
finding.OtherPartNumber?.ToString() ?? "",
finding.Message,
}, group)
{
Tag = finding,
ToolTipText = finding.Message,
};
findingsList.Items.Add(item);
}
}
findingsList.EndUpdate();
FitMessageColumn();
if (report.HasWarnings)
{
findingsList.Visible = true;
emptyLabel.Visible = false;
findingDetailLabel.Visible = true;
findingDetailLabel.Text = "Select a finding to read it in full.";
}
else
{
ShowEmpty("✓ Nothing to review.");
}
}
private void ShowEmpty(string text)
{
findingsList.Visible = false;
findingDetailLabel.Visible = false;
emptyLabel.Text = text;
emptyLabel.Visible = true;
}
private void SelectFirst(PostVerificationKind kind)
{
var item = findingsList.Items.Cast<ListViewItem>()
.FirstOrDefault(candidate => ((PostVerificationFinding)candidate.Tag).Kind == kind);
if (item == null)
return;
findingsList.SelectedItems.Clear();
item.Selected = true;
item.Focused = true;
item.EnsureVisible();
findingsList.Focus();
}
private void FindingsList_SelectedIndexChanged(object sender, EventArgs e)
{
if (findingsList.SelectedItems.Count == 0 ||
findingsList.SelectedItems[0].Tag is not PostVerificationFinding finding)
{
findingDetailLabel.Text = "Select a finding to read it in full.";
return;
}
var where = finding.PlateNumber == 0 ? "Post processor" : $"Plate {finding.PlateNumber}";
if (finding.PartNumber is { } part)
where += $", part {part}";
if (finding.OtherPartNumber is { } other)
where += $" and part {other}";
findingDetailLabel.Text = $"{where}: {finding.Message}";
}
private void FindingsList_Resize(object sender, EventArgs e) => FitMessageColumn();
private void FitMessageColumn()
{
var used = plateColumn.Width + partColumn.Width + otherPartColumn.Width;
messageColumn.Width = System.Math.Max(200,
findingsList.ClientSize.Width - used - SystemInformation.VerticalScrollBarWidth);
}
private void CopyButton_Click(object sender, EventArgs e)
{
if (report != null)
Clipboard.SetText(report.ToDisplayText());
}
private static string Plural(int count, string noun) => count == 1 ? $"1 {noun}" : $"{count} {noun}s";
private void UpdatePostPermission() =>
postButton.Enabled = !analyzing && !closeRequested
&& report?.CanPost(acknowledgeBox.Checked) == true;
@@ -114,7 +346,8 @@ public partial class PostVerificationForm : Form
cancellation.Cancel();
postButton.Enabled = false;
cancelButton.Enabled = false;
summaryLabel.Text = "Canceling verification...";
SetStatus(Status.Running, "Canceling verification…",
"Waiting for the running checks to stop. No CNC output has been written.");
e.Cancel = true;
}
base.OnFormClosing(e);