diff --git a/OpenNest.WinForms.Tests/Controls/HideNestedDrawingsNoteTests.cs b/OpenNest.WinForms.Tests/Controls/HideNestedDrawingsNoteTests.cs new file mode 100644 index 00000000..2028b5d2 --- /dev/null +++ b/OpenNest.WinForms.Tests/Controls/HideNestedDrawingsNoteTests.cs @@ -0,0 +1,90 @@ +using System.Drawing; +using System.Reflection; +using System.Windows.Forms; +using OpenNest.CNC; +using OpenNest.Controls; +using OpenNest.Forms; + +namespace OpenNest.WinForms.Tests.Controls; + +[Collection("Fill operation lifetime")] +public class HideNestedDrawingsNoteTests +{ + [Fact] + public void HidingEveryDrawingShowsANoteThatTurnsTheFilterOff() => RunSta(() => + { + var nest = new Nest("hide"); + var drawing = new Drawing("a", Square()); + drawing.Quantity.Required = 1; + nest.Drawings.Add(drawing); + var plate = new Plate(100, 100) { Quantity = 1 }; + nest.Plates.Add(plate); + plate.Parts.Add(new Part(drawing)); + Assert.Equal(0, drawing.Quantity.Remaining); + + using var form = new EditNestForm(nest) { Width = 1100, Height = 700 }; + form.PlateView.SetOverlapAutoCheck(null); + form.Show(); + Application.DoEvents(); + + var list = Field(form, "drawingListBox1"); + var hide = Field(form, "hideNestedButton"); + Assert.Null(list.EmptyText); + + hide.Checked = true; + + Assert.Empty(list.Items); + Assert.NotNull(list.EmptyText); + + typeof(DrawingListBox) + .GetMethod("OnMouseUp", BindingFlags.Instance | BindingFlags.NonPublic)! + .Invoke(list, [new MouseEventArgs(MouseButtons.Left, 1, 20, 20, 0)]); + + Assert.False(hide.Checked); + Assert.Single(list.Items); + Assert.Null(list.EmptyText); + }); + + [Fact] + public void ListWithRowsNeverRaisesTheNoteClick() => RunSta(() => + { + using var list = new ClickableDrawingListBox { Size = new Size(260, 400), EmptyText = "note" }; + list.Items.Add(new Drawing("a", Square())); + var clicks = 0; + list.EmptyTextClicked += (_, _) => clicks++; + + list.Release(new MouseEventArgs(MouseButtons.Left, 1, 20, 300, 0)); + Assert.Equal(0, clicks); + + list.Items.Clear(); + list.Release(new MouseEventArgs(MouseButtons.Right, 1, 20, 20, 0)); + Assert.Equal(0, clicks); + + list.Release(new MouseEventArgs(MouseButtons.Left, 1, 20, 20, 0)); + Assert.Equal(1, clicks); + }); + + private static T Field(EditNestForm form, string name) => + (T)typeof(EditNestForm) + .GetField(name, BindingFlags.Instance | BindingFlags.NonPublic)! + .GetValue(form)!; + + private static Program Square() + { + var program = new Program(); + program.MoveTo(0, 0); + program.LineTo(0, 10); + program.LineTo(10, 10); + program.LineTo(10, 0); + program.LineTo(0, 0); + return program; + } + + private static void RunSta(System.Action action) => + StaTestThread.Run(action, TimeSpan.FromMinutes(3), "The STA test did not complete."); + + private sealed class ClickableDrawingListBox : DrawingListBox + { + public void Release(MouseEventArgs e) => OnMouseUp(e); + } +} diff --git a/OpenNest/Controls/DrawingListBox.cs b/OpenNest/Controls/DrawingListBox.cs index 4835921c..a84aa221 100644 --- a/OpenNest/Controls/DrawingListBox.cs +++ b/OpenNest/Controls/DrawingListBox.cs @@ -8,6 +8,7 @@ namespace OpenNest.Controls { public class DrawingListBox : ListBox { + private const int WM_PAINT = 0x000F; private const int WM_ERASEBKGND = 0x0014; private readonly Size imageSize; @@ -115,6 +116,30 @@ namespace OpenNest.Controls /// public Plate Plate { get; set; } + private string emptyText; + + /// + /// A note drawn in the middle of the list while it has no rows; null draws nothing. + /// Clicking the empty list while the note shows raises . + /// + public string EmptyText + { + get => emptyText; + set + { + if (emptyText == value) + return; + + emptyText = value; + Invalidate(); + } + } + + /// Raised when the empty list is clicked while shows. + public event EventHandler EmptyTextClicked; + + private bool IsShowingEmptyText => Items.Count == 0 && !string.IsNullOrEmpty(emptyText); + protected override void OnDrawItem(DrawItemEventArgs e) { if (e.Index >= Items.Count || e.Index <= -1) @@ -227,6 +252,8 @@ namespace OpenNest.Controls { base.OnMouseMove(e); + Cursor = IsShowingEmptyText && EmptyTextClicked != null ? Cursors.Hand : Cursors.Default; + var drawing = pressedDrawing; if (drawing == null || e.Button != MouseButtons.Left) @@ -263,6 +290,36 @@ namespace OpenNest.Controls { pressedDrawing = null; base.OnMouseUp(e); + + if (e.Button == MouseButtons.Left && IsShowingEmptyText) + EmptyTextClicked?.Invoke(this, EventArgs.Empty); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + + // The note wraps to the width, so a resize must repaint all of it. + if (IsShowingEmptyText) + Invalidate(); + } + + private void DrawEmptyText() + { + using var g = CreateGraphics(); + var bounds = Rectangle.Inflate(ClientRectangle, -12, -12); + + TextRenderer.DrawText( + g, + emptyText, + Font, + bounds, + SystemColors.GrayText, + TextFormatFlags.HorizontalCenter + | TextFormatFlags.Top + | TextFormatFlags.WordBreak + | TextFormatFlags.TextBoxControl + ); } /// @@ -310,6 +367,10 @@ namespace OpenNest.Controls return; } base.WndProc(ref m); + + // An owner-drawn list paints nothing when empty, so the note goes on top afterward. + if (m.Msg == WM_PAINT && IsShowingEmptyText) + DrawEmptyText(); } } diff --git a/OpenNest/Forms/EditNestForm.cs b/OpenNest/Forms/EditNestForm.cs index 348c91a7..81abb57d 100644 --- a/OpenNest/Forms/EditNestForm.cs +++ b/OpenNest/Forms/EditNestForm.cs @@ -479,6 +479,7 @@ namespace OpenNest.Forms drawingListBox1.OpenInConverterRequested += drawingListBox1_OpenInConverterRequested; drawingListBox1.DrawingPickedUp += drawingListBox1_DrawingPickedUp; drawingListBox1.DrawingDropped += drawingListBox1_DrawingDropped; + drawingListBox1.EmptyTextClicked += (_, _) => hideNestedButton.Checked = false; Activated += (_, _) => UpdateDrawingLibraryCommand(); UpdateDrawingLibraryCommand(); PlateView.BeginChangeHandler = BeginChange; @@ -584,6 +585,20 @@ namespace OpenNest.Forms drawingListBox1.TopIndex = topIndex; drawingListBox1.EndUpdate(); + UpdateDrawingListEmptyText(); + } + + /// + /// Explains an empty list that only Hide Fully Nested Drawings emptied, so the drawings + /// don't look lost; clicking the note turns the filter off. The filter stays the user's + /// choice and is never turned off on its own. + /// + private void UpdateDrawingListEmptyText() + { + drawingListBox1.EmptyText = + hideNestedButton.Checked && Nest.Drawings.Count > 0 + ? "All drawings are fully nested and hidden.\n\nClick here to show them." + : null; } public void Save() @@ -1827,6 +1842,7 @@ namespace OpenNest.Forms drawingListBox1.EndUpdate(); } + UpdateDrawingListEmptyText(); drawingListBox1.Invalidate(); // Parts were added or removed: rebuild the plate rows so the