From be8d1443e311506ff6987b95ce3c6fdea4a2729a Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Mon, 19 Oct 2026 11:47:42 -0400 Subject: [PATCH] feat(ui): replace status bar plate labels with nest material MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The status bar's "Plate N of M" counted the trailing empty plate, and its plate number and size repeated what the plate view header already shows. Drop Plate, Size and Qty from the status bar. Show the plate quantity in the header as "×N" when it is above one, and keep Util in the status bar so it stays visible while the Drawings tab is open. Add a Material label (name and thickness) that opens Nest Info when clicked. Nest Info remains the only place to edit material, and the label refreshes after the dialog closes. Co-Authored-By: Claude Opus 5.5 --- OpenNest.Core/PlateDisplayNumbering.cs | 17 +++++-- OpenNest.Tests/PlateDisplayNumberingTests.cs | 12 +++++ .../Forms/MainStatusBarTests.cs | 26 +++++++++++ OpenNest/Forms/EditNestForm.cs | 2 +- OpenNest/Forms/MainForm.Designer.cs | 44 ++++++------------ OpenNest/Forms/MainForm.Nest.cs | 6 ++- OpenNest/Forms/MainForm.Status.cs | 3 ++ OpenNest/Forms/MainForm.cs | 2 +- OpenNest/Forms/MainStatusBar.cs | 46 +++++++++---------- 9 files changed, 94 insertions(+), 64 deletions(-) create mode 100644 OpenNest.WinForms.Tests/Forms/MainStatusBarTests.cs diff --git a/OpenNest.Core/PlateDisplayNumbering.cs b/OpenNest.Core/PlateDisplayNumbering.cs index e6b761b9..396ba3ca 100644 --- a/OpenNest.Core/PlateDisplayNumbering.cs +++ b/OpenNest.Core/PlateDisplayNumbering.cs @@ -48,17 +48,24 @@ namespace OpenNest return plate != null && plate.Parts.Count == 0; } - /// Header text for the plate at . - public static string FormatHeader(IList plates, int storageIndex, string plateSizeText) + /// + /// Header text for the plate at . A quantity above one is + /// appended as "×N"; a single sheet shows no quantity. + /// + public static string FormatHeader(IList plates, int storageIndex, string plateSizeText, int quantity = 1) { var displayed = DisplayedPlateNumber(plates, storageIndex); if (displayed == null) return IsTrailingSentinel(plates, storageIndex) ? "New plate (empty)" : "No plates"; - return string.IsNullOrEmpty(plateSizeText) - ? string.Format("Plate {0} of {1}", displayed.Value, DisplayedPlateCount(plates)) - : string.Format("Plate {0} of {1} | {2}", displayed.Value, DisplayedPlateCount(plates), plateSizeText); + + var text = string.Format("Plate {0} of {1}", displayed.Value, DisplayedPlateCount(plates)); + if (!string.IsNullOrEmpty(plateSizeText)) + text += " | " + plateSizeText; + if (quantity > 1) + text += " | ×" + quantity; + return text; } } } diff --git a/OpenNest.Tests/PlateDisplayNumberingTests.cs b/OpenNest.Tests/PlateDisplayNumberingTests.cs index 105735a6..31540823 100644 --- a/OpenNest.Tests/PlateDisplayNumberingTests.cs +++ b/OpenNest.Tests/PlateDisplayNumberingTests.cs @@ -44,6 +44,18 @@ public class PlateDisplayNumberingTests Assert.Equal("New plate (empty)", PlateDisplayNumbering.FormatHeader(nest.Plates, sentinel, null)); } + [Fact] + public void Header_ShowsSizeAndQuantityAboveOne() + { + var nest = CreateNest(); + nest.Plates.Add(WithPart()); + + Assert.Equal("Plate 1 of 1 | 60 x 120", PlateDisplayNumbering.FormatHeader(nest.Plates, 0, "60 x 120")); + Assert.Equal("Plate 1 of 1 | 60 x 120", PlateDisplayNumbering.FormatHeader(nest.Plates, 0, "60 x 120", 1)); + Assert.Equal("Plate 1 of 1 | 60 x 120 | ×4", PlateDisplayNumbering.FormatHeader(nest.Plates, 0, "60 x 120", 4)); + Assert.Equal("Plate 1 of 1 | ×2", PlateDisplayNumbering.FormatHeader(nest.Plates, 0, null, 2)); + } + [Fact] public void TwoRealPlatesThenSentinel_TotalIsTwo() { diff --git a/OpenNest.WinForms.Tests/Forms/MainStatusBarTests.cs b/OpenNest.WinForms.Tests/Forms/MainStatusBarTests.cs new file mode 100644 index 00000000..035788b4 --- /dev/null +++ b/OpenNest.WinForms.Tests/Forms/MainStatusBarTests.cs @@ -0,0 +1,26 @@ +using System.Globalization; +using OpenNest.Forms; + +namespace OpenNest.WinForms.Tests.Forms; + +public class MainStatusBarTests +{ + [Fact] + public void FormatMaterial_ShowsNameAndThicknessWithUnits() + { + var previous = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; + try + { + Assert.Equal("Material: A36 · 0.25 in", MainStatusBar.FormatMaterial("A36", 0.25, Units.Inches)); + Assert.Equal("Material: 304 SS · 3 mm", MainStatusBar.FormatMaterial(" 304 SS ", 3, Units.Millimeters)); + Assert.Equal("Material: A36", MainStatusBar.FormatMaterial("A36", 0, Units.Inches)); + Assert.Equal("Material: 0.1875 in", MainStatusBar.FormatMaterial(null, 0.1875, Units.Inches)); + Assert.Equal("Material: (none)", MainStatusBar.FormatMaterial("", 0, Units.Inches)); + } + finally + { + CultureInfo.CurrentCulture = previous; + } + } +} diff --git a/OpenNest/Forms/EditNestForm.cs b/OpenNest/Forms/EditNestForm.cs index c0e7e6f5..13de566b 100644 --- a/OpenNest/Forms/EditNestForm.cs +++ b/OpenNest/Forms/EditNestForm.cs @@ -995,7 +995,7 @@ namespace OpenNest.Forms // Display-only numbering: the trailing empty new-plate sentinel is excluded from the // total (and labeled as such); navigation and storage indexes are untouched. plateInfoLabel.Text = PlateDisplayNumbering.FormatHeader( - Nest.Plates, PlateManager.CurrentIndex, plate.Size.ToString()); + Nest.Plates, PlateManager.CurrentIndex, plate.Size.ToString(), plate.Quantity); btnFirstPlate.Enabled = !PlateManager.IsFirst; btnPreviousPlate.Enabled = !PlateManager.IsFirst; diff --git a/OpenNest/Forms/MainForm.Designer.cs b/OpenNest/Forms/MainForm.Designer.cs index df0af5b8..b770d868 100644 --- a/OpenNest/Forms/MainForm.Designer.cs +++ b/OpenNest/Forms/MainForm.Designer.cs @@ -167,9 +167,7 @@ namespace OpenNest.Forms statusLabel1 = new System.Windows.Forms.ToolStripStatusLabel(); locationStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); spacerLabel = new System.Windows.Forms.ToolStripStatusLabel(); - plateIndexStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); - plateSizeStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); - plateQtyStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); + materialStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); plateUtilStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); selectionStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); toolStrip1 = new System.Windows.Forms.ToolStrip(); @@ -1094,7 +1092,7 @@ namespace OpenNest.Forms // // statusStrip1 // - statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { statusLabel1, locationStatusLabel, selectionStatusLabel, spacerLabel, plateIndexStatusLabel, plateSizeStatusLabel, plateQtyStatusLabel, plateUtilStatusLabel }); + statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { statusLabel1, locationStatusLabel, selectionStatusLabel, spacerLabel, materialStatusLabel, plateUtilStatusLabel }); statusStrip1.Location = new System.Drawing.Point(0, 630); statusStrip1.Name = "statusStrip1"; statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0); @@ -1130,30 +1128,16 @@ namespace OpenNest.Forms spacerLabel.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); spacerLabel.Size = new System.Drawing.Size(931, 19); spacerLabel.Spring = true; - // - // plateIndexStatusLabel - // - plateIndexStatusLabel.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Left; - plateIndexStatusLabel.Name = "plateIndexStatusLabel"; - plateIndexStatusLabel.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); - plateIndexStatusLabel.Size = new System.Drawing.Size(85, 19); - plateIndexStatusLabel.Text = "Plate : 0 of 0"; - // - // plateSizeStatusLabel - // - plateSizeStatusLabel.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Left; - plateSizeStatusLabel.Name = "plateSizeStatusLabel"; - plateSizeStatusLabel.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); - plateSizeStatusLabel.Size = new System.Drawing.Size(67, 19); - plateSizeStatusLabel.Text = "Size : 0x0"; - // - // plateQtyStatusLabel - // - plateQtyStatusLabel.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Left; - plateQtyStatusLabel.Name = "plateQtyStatusLabel"; - plateQtyStatusLabel.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); - plateQtyStatusLabel.Size = new System.Drawing.Size(55, 19); - plateQtyStatusLabel.Text = "Qty : 0"; + // + // materialStatusLabel + // + materialStatusLabel.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Left; + materialStatusLabel.Name = "materialStatusLabel"; + materialStatusLabel.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + materialStatusLabel.Size = new System.Drawing.Size(85, 19); + materialStatusLabel.Text = "Material: (none)"; + materialStatusLabel.ToolTipText = "Click to edit nest info"; + materialStatusLabel.Click += MaterialStatusLabel_Click; // // plateUtilStatusLabel // @@ -1374,11 +1358,10 @@ namespace OpenNest.Forms private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; private System.Windows.Forms.ToolStripMenuItem mnuPlateRotate180; private System.Windows.Forms.StatusStrip statusStrip1; - private System.Windows.Forms.ToolStripStatusLabel plateSizeStatusLabel; private System.Windows.Forms.ToolStripStatusLabel locationStatusLabel; private System.Windows.Forms.ToolStrip toolStrip1; private System.Windows.Forms.ToolStripButton btnOpen; - private System.Windows.Forms.ToolStripStatusLabel plateIndexStatusLabel; + private System.Windows.Forms.ToolStripStatusLabel materialStatusLabel; private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; private System.Windows.Forms.ToolStripMenuItem mnuWindow; private System.Windows.Forms.ToolStripMenuItem mnuWindowCascade; @@ -1386,7 +1369,6 @@ namespace OpenNest.Forms private System.Windows.Forms.ToolStripMenuItem mnuWindowTileHorizontal; private System.Windows.Forms.ToolStripSeparator toolStripMenuItem10; private System.Windows.Forms.ToolStripMenuItem mnuCloseAll; - private System.Windows.Forms.ToolStripStatusLabel plateQtyStatusLabel; private System.Windows.Forms.ToolStripStatusLabel plateUtilStatusLabel; private System.Windows.Forms.ToolStripMenuItem mnuFileExportAll; private System.Windows.Forms.ToolStripMenuItem mnuFileExportNestReport; diff --git a/OpenNest/Forms/MainForm.Nest.cs b/OpenNest/Forms/MainForm.Nest.cs index fc7ff18d..c8ba479e 100644 --- a/OpenNest/Forms/MainForm.Nest.cs +++ b/OpenNest/Forms/MainForm.Nest.cs @@ -47,8 +47,10 @@ namespace OpenNest.Forms private async void EditNest_Click(object sender, EventArgs e) { - if (activeForm != null) - await activeForm.ShowNestInfoEditorAsync(); + if (activeForm == null) + return; + await activeForm.ShowNestInfoEditorAsync(); + statusBar.UpdatePlate(); // Material and thickness may have changed. } private void Clamps_Click(object sender, EventArgs e) diff --git a/OpenNest/Forms/MainForm.Status.cs b/OpenNest/Forms/MainForm.Status.cs index 3d5c704e..c781a220 100644 --- a/OpenNest/Forms/MainForm.Status.cs +++ b/OpenNest/Forms/MainForm.Status.cs @@ -65,6 +65,9 @@ namespace OpenNest.Forms private void LocationStatusLabel_Click(object sender, EventArgs e) => statusBar.ToggleLocationMode(); + private void MaterialStatusLabel_Click(object sender, EventArgs e) => + EditNest_Click(sender, e); + #endregion Statusbar Events } } diff --git a/OpenNest/Forms/MainForm.cs b/OpenNest/Forms/MainForm.cs index 76c56364..3b1a40b1 100644 --- a/OpenNest/Forms/MainForm.cs +++ b/OpenNest/Forms/MainForm.cs @@ -106,7 +106,7 @@ namespace OpenNest.Forms storage.StateChanged += Storage_StateChanged; InitializeComponent(); statusBar = new MainStatusBar(statusLabel1, locationStatusLabel, selectionStatusLabel, - plateIndexStatusLabel, plateSizeStatusLabel, plateQtyStatusLabel, plateUtilStatusLabel); + materialStatusLabel, plateUtilStatusLabel); toolStrip1.Items.Add(CreateActionButton("btnCutPlanner", "Plan Cutting", Resources.cut_planner, PlatePlanCutting_Click)); toolStrip1.Items.Add(CreateActionButton("btnSimulateCutting", "Simulate Cutting", diff --git a/OpenNest/Forms/MainStatusBar.cs b/OpenNest/Forms/MainStatusBar.cs index a0d95a87..cb56577a 100644 --- a/OpenNest/Forms/MainStatusBar.cs +++ b/OpenNest/Forms/MainStatusBar.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Drawing; +using System.Globalization; using System.Linq; using System.Windows.Forms; using OpenNest.Collections; @@ -10,7 +12,8 @@ namespace OpenNest.Forms { /// /// The main window's status labels for the active nest document: status text, cursor - /// location, selection bounds and plate index/size/quantity/utilization. The location + /// location, selection bounds, nest material and plate utilization. Plate number, size and + /// quantity live in the plate view header instead. The location /// follows the mouse (the default, throttled), or only left clicks while click mode is on; /// the mode persists as a user setting. /// @@ -22,9 +25,7 @@ namespace OpenNest.Forms private readonly ToolStripStatusLabel status; private readonly ToolStripStatusLabel location; private readonly ToolStripStatusLabel selection; - private readonly ToolStripStatusLabel plateIndex; - private readonly ToolStripStatusLabel plateSize; - private readonly ToolStripStatusLabel plateQty; + private readonly ToolStripStatusLabel material; private readonly ToolStripStatusLabel plateUtil; private readonly Timer locationTimer; @@ -33,15 +34,12 @@ namespace OpenNest.Forms private bool locationPending; public MainStatusBar(ToolStripStatusLabel status, ToolStripStatusLabel location, - ToolStripStatusLabel selection, ToolStripStatusLabel plateIndex, ToolStripStatusLabel plateSize, - ToolStripStatusLabel plateQty, ToolStripStatusLabel plateUtil) + ToolStripStatusLabel selection, ToolStripStatusLabel material, ToolStripStatusLabel plateUtil) { this.status = status; this.location = location; this.selection = selection; - this.plateIndex = plateIndex; - this.plateSize = plateSize; - this.plateQty = plateQty; + this.material = material; this.plateUtil = plateUtil; locationTimer = new Timer { Interval = LocationRefreshIntervalMs }; @@ -126,25 +124,13 @@ namespace OpenNest.Forms { if (form == null) { - plateIndex.Text = string.Empty; - plateSize.Text = string.Empty; - plateQty.Text = string.Empty; + material.Text = string.Empty; plateUtil.Text = string.Empty; return; } - plateIndex.Text = string.Format( - "Plate: {0} of {1}", - form.PlateManager.CurrentIndex + 1, - form.PlateManager.Count - ); - - plateSize.Text = string.Format("Size: {0}", form.PlateView.Plate.Size); - - plateQty.Text = string.Format( - "Qty: {0}", - form.PlateView.Plate.Quantity - ); + var nest = form.Nest; + material.Text = FormatMaterial(nest.Material?.Name, nest.Thickness, nest.Units); plateUtil.Text = string.Format( "Util: {0:P1}", @@ -152,6 +138,18 @@ namespace OpenNest.Forms ); } + /// Status text for the nest's material and thickness, e.g. "Material: A36 · 0.25 in". + internal static string FormatMaterial(string name, double thickness, Units units) + { + var parts = new List(); + if (!string.IsNullOrWhiteSpace(name)) + parts.Add(name.Trim()); + if (thickness > 0) + parts.Add(thickness.ToString("0.####", CultureInfo.CurrentCulture) + " " + UnitsHelper.GetShortString(units)); + + return "Material: " + (parts.Count == 0 ? "(none)" : string.Join(" · ", parts)); + } + public void UpdateSelection() { if (form == null || form.PlateView.SelectedParts.Count == 0)