feat(desktop): one Drawing Sets window with Sets and Releases tabs

The drawing sets window now has two tabs. Releases hosts the release list
(paging, nests, Undo Release, New Release) as a SetReleasesView control that
replaces SetReleasesForm. File > Releases stays as a shortcut that opens the
window on that tab. A set's Releases (Sets tab) and a library drawing's
Releases (Drawing Library) open the tab filtered to it, with the filter shown
above the list and Clear Filter to list every release. Releases re-reads its
page whenever the tab is shown again, so a release made on Sets appears.

The set editor gets History... for a saved set; the Sets tab keeps its
History button until the browser's buttons are rearranged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-10-24 15:33:13 -04:00
1 parent 49bb7389f7
commit b376d217dd
13 files changed
+515 -204

No files matched your search

+3
View File
@@ -56,6 +56,9 @@ public sealed class DrawingSetEditSession
public Guid Id { get; }
/// <summary>The store the set is read from and saved to; also reads its history.</summary>
public IDrawingSetStore Store => _sets;
public string SavedBy { get; }
/// <summary>The set as last read or saved; null until a new set is first saved.</summary>
@@ -66,6 +66,43 @@ public class DrawingSetFormsTests
"The new set test did not complete.");
}
[Fact]
public void Editor_History_ShowsTheSavedSetsRevisions_AndIsHiddenForANewSet()
{
StaTestThread.Run(
() =>
{
var library = new FakeLibrary();
var a = library.Add("SYN-A", "Synthetic Steel", 0.25);
var sets = new InMemoryDrawingSets();
var created = Run(sets.CreateAsync(new DrawingSetRecord
{
Id = Guid.NewGuid(),
Name = "Synthetic kit",
Members = new[] { new DrawingSetMember { DrawingId = a.Id, Quantity = 2, Drawing = a } },
}));
var shown = new List<DrawingSetRecord>();
using (var editor = new EditDrawingSetForm(new DrawingSetEditSession(sets, created, "Synthetic operator"), library, Server))
{
editor.ShowHistory = shown.Add;
editor.Show();
var history = Field<Button>(editor, "historyButton");
Assert.True(history.Visible);
history.PerformClick();
Assert.Equal(created.Id, Assert.Single(shown).Id);
}
using var fresh = new EditDrawingSetForm(new DrawingSetEditSession(sets, null, "Synthetic operator"), library, Server);
fresh.Show();
Assert.False(Field<Button>(fresh, "historyButton").Visible);
},
TestTimeout,
"The editor history test did not complete.");
}
[Fact]
public void EditorLibraryPane_ListsEveryDrawing_AddsTheSelectionInListOrder_AndGreysMembers()
{
@@ -293,12 +293,18 @@ public class SetReleaseFormsTests
Nests = new[] { Nest("N1", NestStatus.HasBeenCut), Nest("N2", NestStatus.ToBeCut), Nest("N3", NestStatus.Draft, deleted: true) },
});
using var form = new SetReleasesForm(sets, set);
using var form = new DrawingSetsForm(sets, new PackageLibrary(), Server);
Run(form.ShowReleasesAsync(set));
form.Show();
var releases = Field<DataGridView>(form, "releaseGrid");
var nests = Field<DataGridView>(form, "nestGrid");
var view = form.Releases;
var releases = Field<DataGridView>(view, "releaseGrid");
var nests = Field<DataGridView>(view, "nestGrid");
PumpUntil(() => releases.Rows.Count == 1 && nests.Rows.Count == 3, "the releases");
Assert.True(form.ShowsReleases);
Assert.True(Field<TableLayoutPanel>(view, "filterTable").Visible);
Assert.Equal("Releases of set 'Synthetic kit'", Field<Label>(view, "filterLabel").Text);
Assert.True(releases.Columns["revisionColumn"]!.Visible);
Assert.Equal("1 of 2 cut, 1 deleted", releases.Rows[0].Cells["progressColumn"].Value);
Assert.Equal("×2", releases.Rows[0].Cells["multiplierColumn"].Value);
Assert.Equal(new object[] { "Has Been Cut", "To Be Cut", "Deleted" },
@@ -505,21 +511,23 @@ public class SetReleaseFormsTests
};
var questions = new List<string>();
using var form = new SetReleasesForm(sets, set);
form.ShowMessage = (text, buttons, _) =>
using var form = new DrawingSetsForm(sets, new PackageLibrary(), Server);
var view = form.Releases;
view.ShowMessage = (text, buttons, _) =>
{
questions.Add(text);
return buttons == MessageBoxButtons.YesNo ? DialogResult.Yes : DialogResult.OK;
};
Run(form.ShowReleasesAsync(set));
form.Show();
var releases = Field<DataGridView>(form, "releaseGrid");
var nests = Field<DataGridView>(form, "nestGrid");
var releases = Field<DataGridView>(view, "releaseGrid");
var nests = Field<DataGridView>(view, "nestGrid");
PumpUntil(() => releases.Rows.Count == 1 && nests.Rows.Count == 2, "the releases");
Assert.Equal("×2 (+1 other set)", releases.Rows[0].Cells["multiplierColumn"].Value);
Assert.Equal(new object[] { "Created", "Merged (revision 3 → 4)" }, nests.Rows.Cast<DataGridViewRow>().Select(r => r.Cells["changeColumn"].Value));
Assert.True(Field<Button>(form, "undoButton").Enabled);
Assert.True(Field<Button>(view, "undoButton").Enabled);
Run(form.UndoSelectedAsync());
Run(view.UndoSelectedAsync());
Assert.Contains("1 nest it created is deleted", questions[0]);
Assert.Contains("1 draft it merged into goes back", questions[0]);
@@ -529,7 +537,8 @@ public class SetReleaseFormsTests
PumpUntil(() => (string)releases.Rows[0].Cells["progressColumn"].Value == "Undone", "the release to read as undone");
Assert.Equal(new object[] { "Created; undo deleted it", "Merged (revision 3 → 4); undo left it as it was" },
nests.Rows.Cast<DataGridViewRow>().Select(r => r.Cells["changeColumn"].Value));
Assert.False(Field<Button>(form, "undoButton").Enabled);
Assert.False(Field<Button>(view, "undoButton").Enabled);
Assert.Same(set, view.FilterSet);
},
TestTimeout,
"The release undo test did not complete.");
@@ -679,17 +688,20 @@ public class SetReleaseFormsTests
sets.UndoHandler = (id, request) => new SetReleaseRecord { Id = id, UndoneAt = DateTime.UtcNow };
var questions = new List<string>();
using var form = new SetReleasesForm(sets, a);
form.ShowMessage = (text, _, _) => { questions.Add(text); return DialogResult.OK; };
using var form = new DrawingSetsForm(sets, library, Server);
var view = form.Releases;
view.ShowMessage = (text, _, _) => { questions.Add(text); return DialogResult.OK; };
Run(form.ShowReleasesAsync(drawing: a));
form.Show();
var releases = Field<DataGridView>(form, "releaseGrid");
var releases = Field<DataGridView>(view, "releaseGrid");
PumpUntil(() => releases.Rows.Count == 1, "the releases");
Assert.Equal("Releases of drawing 'SYN-A'", form.Text);
Assert.Equal("Releases of drawing 'SYN-A' on its own", Field<Label>(view, "filterLabel").Text);
Assert.Equal("Quantity", releases.Columns["multiplierColumn"]!.HeaderText);
Assert.Equal("×4 (+1 set)", releases.Rows[0].Cells["multiplierColumn"].Value);
Assert.False(releases.Columns["revisionColumn"]!.Visible);
Run(form.UndoSelectedAsync());
Run(view.UndoSelectedAsync());
Assert.Equal(release.Id, Assert.Single(sets.UndoRequests).Id);
Assert.Contains("deleted (they can be recovered)", Assert.Single(questions));
@@ -729,9 +741,10 @@ public class SetReleaseFormsTests
Array.Empty<SetReleaseLine>(),
new[] { new SetReleaseDrawing { DrawingId = a.Id, DrawingName = a.Name, Quantity = 1 } }));
using var form = new SetReleasesForm(sets);
using var form = new DrawingSetsForm(sets, library, Server);
var view = form.Releases;
var opened = 0;
form.ShowNewRelease = () =>
view.ShowNewRelease = () =>
{
opened++;
// The dialog records a release before it closes; a cancelled dialog records none.
@@ -740,25 +753,26 @@ public class SetReleaseFormsTests
Array.Empty<SetReleaseDrawing>()));
return opened == 2;
};
Run(form.ShowReleasesAsync());
form.Show();
var releases = Field<DataGridView>(form, "releaseGrid");
var releases = Field<DataGridView>(view, "releaseGrid");
PumpUntil(() => releases.Rows.Count == 2, "the releases");
Assert.Equal("Releases", form.Text);
Assert.False(Field<TableLayoutPanel>(view, "filterTable").Visible);
Assert.False(releases.Columns["revisionColumn"]!.Visible);
Assert.Equal("Released", releases.Columns["multiplierColumn"]!.HeaderText);
Assert.Equal(new object[] { "SYN-A ×1", "Synthetic kit ×2, Synthetic frame; SYN-A ×4" },
releases.Rows.Cast<DataGridViewRow>().Select(r => r.Cells["multiplierColumn"].Value));
Assert.Equal("Releases 1-2 of 2", Field<Label>(form, "summaryLabel").Text);
Assert.False(Field<Button>(form, "newReleaseButton").Visible);
Assert.Equal("Releases 1-2 of 2", Field<Label>(view, "summaryLabel").Text);
Assert.False(Field<Button>(view, "newReleaseButton").Visible);
Run(form.NewReleaseAsync());
Run(view.NewReleaseAsync());
Assert.Equal(2, releases.Rows.Count);
Run(form.NewReleaseAsync());
Run(view.NewReleaseAsync());
PumpUntil(() => releases.Rows.Count == 3, "the re-read releases");
Assert.Equal("Synthetic kit", releases.Rows[0].Cells["multiplierColumn"].Value);
Assert.Equal("Releases 1-3 of 3", Field<Label>(form, "summaryLabel").Text);
Assert.Equal("Releases 1-3 of 3", Field<Label>(view, "summaryLabel").Text);
},
TestTimeout,
"The all-releases view test did not complete.");
@@ -828,6 +842,70 @@ public class SetReleaseFormsTests
"The empty release test did not complete.");
}
[Fact]
public void DrawingSetsWindow_ReadsEachTabWhenShown_AndASetsReleasesFilterTheReleasesTab_UntilCleared()
{
StaTestThread.Run(
() =>
{
var library = new PackageLibrary();
var a = library.Add("SYN-A", "Synthetic Steel");
var sets = new InMemoryDrawingSets();
var kit = CreateSet(sets, "Synthetic kit", (a, 1));
var frame = CreateSet(sets, "Synthetic frame", (a, 2));
SetReleaseRecord Release(DrawingSetRecord set) => new()
{
Id = Guid.NewGuid(),
Sets = new[] { new SetReleaseLine { SetId = set.Id, SetRevision = 1, SetName = set.Name, Quantity = 1 } },
ReleasedBy = "Synthetic operator",
Nests = new[] { new SetReleaseNest { NestId = Guid.NewGuid(), Nest = new NestRecord { Name = "N1", Status = NestStatus.Draft } } },
};
sets.Releases.Add(Release(kit));
sets.Releases.Add(Release(frame));
using var form = new DrawingSetsForm(sets, library, Server);
var browser = form.Browser;
var view = form.Releases;
form.Show();
var setGrid = Field<DataGridView>(browser, "setGrid");
var releases = Field<DataGridView>(view, "releaseGrid");
var filter = Field<TableLayoutPanel>(view, "filterTable");
PumpUntil(() => setGrid.Rows.Count == 2, "the set list");
Assert.False(form.ShowsReleases);
Assert.False(view.HasLoaded);
setGrid.CurrentCell = setGrid.Rows.Cast<DataGridViewRow>()
.Single(r => (string)r.Cells["nameColumn"].Value == "Synthetic kit").Cells[0];
Field<Button>(browser, "releasesButton").PerformClick();
PumpUntil(() => releases.Rows.Count == 1, "the set's releases");
Assert.True(form.ShowsReleases);
Assert.Equal(kit.Id, view.FilterSet.Id);
Assert.True(filter.Visible);
Assert.Equal("Releases of set 'Synthetic kit'", Field<Label>(view, "filterLabel").Text);
Assert.Equal("Quantity", releases.Columns["multiplierColumn"]!.HeaderText);
Field<Button>(view, "clearFilterButton").PerformClick();
PumpUntil(() => releases.Rows.Count == 2, "every release");
Assert.Null(view.FilterSet);
Assert.False(filter.Visible);
Assert.Equal("Released", releases.Columns["multiplierColumn"]!.HeaderText);
Assert.False(releases.Columns["revisionColumn"]!.Visible);
// A release made meanwhile (as Make Nests on the Sets tab does) shows once the tab is shown again.
form.ShowSets();
Assert.False(form.ShowsReleases);
sets.Releases.Add(Release(frame));
Field<TabControl>(form, "tabs").SelectedIndex = 1;
PumpUntil(() => releases.Rows.Count == 3, "the re-read releases");
Assert.Equal("Releases 1-3 of 3", Field<Label>(view, "summaryLabel").Text);
},
TestTimeout,
"The drawing sets window tabs test did not complete.");
}
private static DrawingSetRecord CreateSet(InMemoryDrawingSets sets, params (DrawingLibraryRecord Drawing, int Quantity)[] members) =>
CreateSet(sets, "Synthetic kit", members);
+5 -9
View File
@@ -12,7 +12,8 @@ namespace OpenNest.Controls
/// <summary>
/// Browses the server's drawing sets one bounded page at a time through
/// <see cref="DrawingSetBrowseSession"/>, and creates, edits, archives or restores them and
/// shows their history. <see cref="DrawingSetsForm"/> hosts it to manage and release sets. In pick mode
/// shows their history. <see cref="DrawingSetsForm"/> hosts it on its Sets tab to manage and
/// release sets, and shows a set's Releases on its Releases tab. In pick mode
/// (<see cref="ChooseDrawingSetForm"/>) only active sets are listed, and choosing a set or
/// New Set raises <see cref="SetActivated"/> or <see cref="NewSetChosen"/> instead.
/// </summary>
@@ -38,7 +39,7 @@ namespace OpenNest.Controls
/// <summary>Shows a set's revision history; replaced by tests.</summary>
internal Action<DrawingSetRecord> ShowHistory { get; set; }
/// <summary>Shows a set's releases; replaced by tests.</summary>
/// <summary>Shows a set's releases; set by the host (Releases is disabled without it) and replaced by tests.</summary>
internal Action<DrawingSetRecord> ShowReleases { get; set; }
/// <summary>Shows the release dialog for a set read with its members; replaced by tests.</summary>
@@ -101,11 +102,6 @@ namespace OpenNest.Controls
using var form = new DrawingSetHistoryForm(sets, set);
form.ShowDialog(FindForm());
};
ShowReleases = set =>
{
using var form = new SetReleasesForm(sets, set);
form.ShowDialog(FindForm());
};
ShowRelease = set =>
{
using var form = new ReleaseSetForm(sets, library, nests, this.serverUrl, set, openNest) { IsNestOpen = isNestOpen };
@@ -228,7 +224,7 @@ namespace OpenNest.Controls
private void ReleasesButton_Click(object sender, EventArgs e)
{
if (SelectedSet is { } set)
ShowReleases(set);
ShowReleases?.Invoke(set);
}
private async void ReleaseButton_Click(object sender, EventArgs e) => await ReleaseSelectedAsync();
@@ -440,7 +436,7 @@ namespace OpenNest.Controls
archiveButton.Enabled = ready && selected != null;
archiveButton.Text = selected is { Archived: true } ? "Restore" : "Archive";
historyButton.Enabled = ready && selected != null;
releasesButton.Enabled = ready && selected != null;
releasesButton.Enabled = ready && selected != null && ShowReleases != null;
releaseButton.Enabled = ready && nests != null && selected is { Archived: false, MemberCount: > 0 };
}
}
@@ -1,6 +1,6 @@
namespace OpenNest.Forms
namespace OpenNest.Controls
{
partial class SetReleasesForm
partial class SetReleasesView
{
private System.ComponentModel.IContainer components = null;
@@ -19,6 +19,9 @@ namespace OpenNest.Forms
{
System.Windows.Forms.DataGridViewCellStyle rightAligned = new System.Windows.Forms.DataGridViewCellStyle();
layoutTable = new System.Windows.Forms.TableLayoutPanel();
filterTable = new System.Windows.Forms.TableLayoutPanel();
filterLabel = new System.Windows.Forms.Label();
clearFilterButton = new System.Windows.Forms.Button();
releaseGrid = new System.Windows.Forms.DataGridView();
revisionColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
releasedColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
@@ -41,8 +44,8 @@ namespace OpenNest.Forms
summaryLabel = new System.Windows.Forms.Label();
newReleaseButton = new System.Windows.Forms.Button();
undoButton = new System.Windows.Forms.Button();
closeButton = new System.Windows.Forms.Button();
layoutTable.SuspendLayout();
filterTable.SuspendLayout();
((System.ComponentModel.ISupportInitialize)releaseGrid).BeginInit();
nestsGroup.SuspendLayout();
((System.ComponentModel.ISupportInitialize)nestGrid).BeginInit();
@@ -53,20 +56,59 @@ namespace OpenNest.Forms
//
layoutTable.ColumnCount = 1;
layoutTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
layoutTable.Controls.Add(releaseGrid, 0, 0);
layoutTable.Controls.Add(nestsGroup, 0, 1);
layoutTable.Controls.Add(bottomTable, 0, 2);
layoutTable.Controls.Add(filterTable, 0, 0);
layoutTable.Controls.Add(releaseGrid, 0, 1);
layoutTable.Controls.Add(nestsGroup, 0, 2);
layoutTable.Controls.Add(bottomTable, 0, 3);
layoutTable.Dock = System.Windows.Forms.DockStyle.Fill;
layoutTable.Location = new System.Drawing.Point(0, 0);
layoutTable.Name = "layoutTable";
layoutTable.Padding = new System.Windows.Forms.Padding(8);
layoutTable.RowCount = 3;
layoutTable.Padding = new System.Windows.Forms.Padding(0);
layoutTable.RowCount = 4;
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
layoutTable.Size = new System.Drawing.Size(824, 541);
layoutTable.Size = new System.Drawing.Size(984, 541);
layoutTable.TabIndex = 0;
//
// filterTable
//
filterTable.AutoSize = true;
filterTable.ColumnCount = 3;
filterTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
filterTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
filterTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
filterTable.Controls.Add(filterLabel, 0, 0);
filterTable.Controls.Add(clearFilterButton, 1, 0);
filterTable.Dock = System.Windows.Forms.DockStyle.Fill;
filterTable.Margin = new System.Windows.Forms.Padding(0, 0, 0, 6);
filterTable.Name = "filterTable";
filterTable.RowCount = 1;
filterTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
filterTable.TabIndex = 0;
//
// filterLabel
//
filterLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
filterLabel.AutoSize = true;
filterLabel.BackColor = System.Drawing.SystemColors.Info;
filterLabel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
filterLabel.ForeColor = System.Drawing.SystemColors.InfoText;
filterLabel.Name = "filterLabel";
filterLabel.Padding = new System.Windows.Forms.Padding(4, 2, 4, 2);
filterLabel.TabIndex = 0;
//
// clearFilterButton
//
clearFilterButton.AutoSize = true;
clearFilterButton.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
clearFilterButton.Name = "clearFilterButton";
clearFilterButton.TabIndex = 1;
clearFilterButton.Text = "Clear Filter";
clearFilterButton.UseVisualStyleBackColor = true;
clearFilterButton.Click += ClearFilterButton_Click;
//
// releaseGrid
//
releaseGrid.AllowUserToAddRows = false;
@@ -79,7 +121,7 @@ namespace OpenNest.Forms
releaseGrid.RowHeadersVisible = false;
releaseGrid.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
releaseGrid.StandardTab = true;
releaseGrid.TabIndex = 0;
releaseGrid.TabIndex = 1;
releaseGrid.CurrentCellChanged += ReleaseGrid_CurrentCellChanged;
//
// revisionColumn
@@ -142,7 +184,7 @@ namespace OpenNest.Forms
nestsGroup.Dock = System.Windows.Forms.DockStyle.Fill;
nestsGroup.Margin = new System.Windows.Forms.Padding(0, 6, 0, 0);
nestsGroup.Name = "nestsGroup";
nestsGroup.TabIndex = 1;
nestsGroup.TabIndex = 2;
nestsGroup.TabStop = false;
nestsGroup.Text = "Nests of the selected release";
//
@@ -206,25 +248,23 @@ namespace OpenNest.Forms
// bottomTable
//
bottomTable.AutoSize = true;
bottomTable.ColumnCount = 6;
bottomTable.ColumnCount = 5;
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
bottomTable.Controls.Add(previousButton, 0, 0);
bottomTable.Controls.Add(nextButton, 1, 0);
bottomTable.Controls.Add(summaryLabel, 2, 0);
bottomTable.Controls.Add(newReleaseButton, 3, 0);
bottomTable.Controls.Add(undoButton, 4, 0);
bottomTable.Controls.Add(closeButton, 5, 0);
bottomTable.Dock = System.Windows.Forms.DockStyle.Fill;
bottomTable.Margin = new System.Windows.Forms.Padding(0, 6, 0, 0);
bottomTable.Name = "bottomTable";
bottomTable.RowCount = 1;
bottomTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
bottomTable.TabIndex = 2;
bottomTable.TabIndex = 3;
//
// previousButton
//
@@ -273,32 +313,17 @@ namespace OpenNest.Forms
undoButton.UseVisualStyleBackColor = true;
undoButton.Click += UndoButton_Click;
//
// closeButton
//
closeButton.AutoSize = true;
closeButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
closeButton.Name = "closeButton";
closeButton.TabIndex = 5;
closeButton.Text = "Close";
closeButton.UseVisualStyleBackColor = true;
//
// SetReleasesForm
// SetReleasesView
//
AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
CancelButton = closeButton;
ClientSize = new System.Drawing.Size(1004, 541);
Controls.Add(layoutTable);
Font = new System.Drawing.Font("Segoe UI", 9.75F);
MinimizeBox = false;
MinimumSize = new System.Drawing.Size(600, 400);
Name = "SetReleasesForm";
ShowIcon = false;
ShowInTaskbar = false;
StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
Text = "Set Releases";
Shown += SetReleasesForm_Shown;
Name = "SetReleasesView";
Size = new System.Drawing.Size(984, 541);
layoutTable.ResumeLayout(false);
layoutTable.PerformLayout();
filterTable.ResumeLayout(false);
filterTable.PerformLayout();
((System.ComponentModel.ISupportInitialize)releaseGrid).EndInit();
nestsGroup.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)nestGrid).EndInit();
@@ -310,6 +335,9 @@ namespace OpenNest.Forms
#endregion
private System.Windows.Forms.TableLayoutPanel layoutTable;
private System.Windows.Forms.TableLayoutPanel filterTable;
private System.Windows.Forms.Label filterLabel;
private System.Windows.Forms.Button clearFilterButton;
private System.Windows.Forms.DataGridView releaseGrid;
private System.Windows.Forms.DataGridViewTextBoxColumn revisionColumn;
private System.Windows.Forms.DataGridViewTextBoxColumn releasedColumn;
@@ -332,6 +360,5 @@ namespace OpenNest.Forms
private System.Windows.Forms.Label summaryLabel;
private System.Windows.Forms.Button newReleaseButton;
private System.Windows.Forms.Button undoButton;
private System.Windows.Forms.Button closeButton;
}
}
@@ -5,29 +5,31 @@ using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Forms;
using OpenNest.Controls;
using OpenNest.Data;
using OpenNest.Forms;
namespace OpenNest.Forms
namespace OpenNest.Controls
{
/// <summary>
/// File &gt; Releases: every release on the nest server, newest first, a bounded page at a
/// time, each with what it released ("Kit A ×2; Bracket ×4") and its progress ("2 of 3
/// cut"), and the nests of the selected release with what the release did to each and
/// their current status, or Deleted. Drawing Sets &gt; Releases and Drawings tab &gt;
/// Releases show the same view for the releases that include one set, or one library
/// drawing on its own. The server joins every nest's current metadata on each read. New
/// Release opens the release dialog empty; the list re-reads once a release is recorded.
/// Undo Release deletes the nests a release created and restores the drafts it merged
/// into, all or nothing; nests changed since can be left as they are.
/// The Releases tab of <see cref="DrawingSetsForm"/>: every release on the nest server,
/// newest first, a bounded page at a time, each with what it released ("Kit A ×2; Bracket
/// ×4") and its progress ("2 of 3 cut"), and the nests of the selected release with what
/// the release did to each and their current status, or Deleted. A filter narrows the list
/// to the releases that include one set, or one library drawing on its own; the filter
/// shows above the list until Clear Filter. The server joins every nest's current metadata
/// on each read. New Release opens the release dialog empty; the list re-reads once a
/// release is recorded. Undo Release deletes the nests a release created and restores the
/// drafts it merged into, all or nothing; nests changed since can be left as they are.
/// </summary>
public partial class SetReleasesForm : Form
public partial class SetReleasesView : UserControl
{
private const int PageSize = 100;
private readonly IDrawingSetStore sets;
private readonly DrawingSetRecord set;
private readonly DrawingLibraryRecord drawing;
private readonly int releasedWidth;
private readonly DataGridViewCellStyle releasedStyle;
private IDrawingSetStore sets;
private DrawingSetRecord set;
private DrawingLibraryRecord drawing;
private SetReleasePage page;
private int offset;
private int pageRequest;
@@ -42,43 +44,31 @@ namespace OpenNest.Forms
/// </summary>
internal Func<bool> ShowNewRelease { get; set; }
/// <summary>Every release on the server, newest first.</summary>
public SetReleasesForm(IDrawingSetStore sets)
: this(sets, all: true)
public SetReleasesView()
{
Text = "Releases";
revisionColumn.Visible = false;
multiplierColumn.HeaderText = "Released";
multiplierColumn.Width = 320;
multiplierColumn.DefaultCellStyle = null;
}
public SetReleasesForm(IDrawingSetStore sets, DrawingSetRecord set)
: this(sets, all: false)
{
this.set = set ?? throw new ArgumentNullException(nameof(set));
Text = $"Releases of '{set.Name}'";
}
/// <summary>The releases that had <paramref name="drawing"/> on its own, outside any set.</summary>
public SetReleasesForm(IDrawingSetStore sets, DrawingLibraryRecord drawing)
: this(sets, all: false)
{
this.drawing = drawing ?? throw new ArgumentNullException(nameof(drawing));
Text = $"Releases of drawing '{drawing.Name}'";
revisionColumn.Visible = false;
multiplierColumn.HeaderText = "Quantity";
}
// The flag only tells the public constructors apart; the public ones set what differs.
private SetReleasesForm(IDrawingSetStore sets, bool all)
{
_ = all;
InitializeComponent();
this.sets = sets ?? throw new ArgumentNullException(nameof(sets));
GridStyle.Apply(releaseGrid);
GridStyle.Apply(nestGrid);
ShowMessage = (text, buttons, icon) => MessageBox.Show(this, text, Text, buttons, icon);
releasedWidth = multiplierColumn.Width;
releasedStyle = multiplierColumn.DefaultCellStyle;
ApplyFilter();
}
/// <summary>True after a page of releases has been requested at least once.</summary>
public bool HasLoaded { get; private set; }
/// <summary>The set the list is narrowed to, or null.</summary>
public DrawingSetRecord FilterSet => set;
/// <summary>The drawing the list is narrowed to, or null.</summary>
public DrawingLibraryRecord FilterDrawing => drawing;
public void Initialize(IDrawingSetStore sets)
{
if (this.sets != null)
throw new InvalidOperationException("The releases view is already initialized.");
this.sets = sets ?? throw new ArgumentNullException(nameof(sets));
ShowMessage = (text, buttons, icon) => MessageBox.Show(FindForm(), text, FindForm()?.Text ?? "Releases", buttons, icon);
}
/// <summary>
@@ -92,18 +82,60 @@ namespace OpenNest.Forms
ArgumentNullException.ThrowIfNull(library);
ArgumentNullException.ThrowIfNull(nests);
ArgumentNullException.ThrowIfNull(openNest);
if (sets == null)
throw new InvalidOperationException("New Release needs an initialized releases view.");
ShowNewRelease = () =>
{
using var form = new ReleaseSetForm(sets, library, nests, serverUrl, Array.Empty<DrawingSetRecord>(),
Array.Empty<SetReleaseDrawingLine>(), openNest)
{ IsNestOpen = isNestOpen ?? (_ => false) };
form.ShowDialog(this);
form.ShowDialog(FindForm());
return form.Result?.Release != null;
};
newReleaseButton.Visible = true;
}
private async void SetReleasesForm_Shown(object sender, EventArgs e) => await LoadPageAsync(0);
/// <summary>
/// Narrows the list to the releases that include <paramref name="set"/>, or that had
/// <paramref name="drawing"/> on its own outside any set; both null lists every release.
/// The next read starts at the newest page.
/// </summary>
public void SetFilter(DrawingSetRecord set, DrawingLibraryRecord drawing)
{
if (set != null && drawing != null)
throw new ArgumentException("Filter by a set or a drawing, not both.");
this.set = set;
this.drawing = drawing;
offset = 0;
ApplyFilter();
}
/// <summary>Re-reads the current page (the first call reads the newest).</summary>
public Task RefreshAsync() => LoadPageAsync(offset);
/// <summary>Lists every release again, from the newest.</summary>
internal Task ClearFilterAsync()
{
SetFilter(null, null);
return LoadPageAsync(0);
}
private async void ClearFilterButton_Click(object sender, EventArgs e) => await ClearFilterAsync();
// Per set the release's revision of it and its quantity; per drawing its quantity; across
// all releases, everything each one had.
private void ApplyFilter()
{
var all = set == null && drawing == null;
revisionColumn.Visible = set != null;
multiplierColumn.HeaderText = all ? "Released" : "Quantity";
multiplierColumn.Width = all ? 320 : releasedWidth;
multiplierColumn.DefaultCellStyle = all ? null : releasedStyle;
filterTable.Visible = !all;
filterLabel.Text = set != null ? $"Releases of set '{set.Name}'"
: drawing != null ? $"Releases of drawing '{drawing.Name}' on its own"
: "";
}
private async void NewReleaseButton_Click(object sender, EventArgs e) => await NewReleaseAsync();
@@ -192,12 +224,16 @@ namespace OpenNest.Forms
/// <summary>Reads one page of releases; a later request discards an earlier one's result.</summary>
internal async Task LoadPageAsync(int requestedOffset)
{
if (sets == null)
return;
HasLoaded = true;
var request = ++pageRequest;
var (filterSet, filterDrawing) = (set, drawing);
previousButton.Enabled = nextButton.Enabled = false;
summaryLabel.Text = "Loading...";
try
{
var loaded = await sets.QueryReleasesAsync(setId: set?.Id, drawingId: drawing?.Id, offset: requestedOffset, limit: PageSize);
var loaded = await sets.QueryReleasesAsync(setId: filterSet?.Id, drawingId: filterDrawing?.Id, offset: requestedOffset, limit: PageSize);
if (IsDisposed || request != pageRequest)
return;
@@ -212,29 +248,29 @@ namespace OpenNest.Forms
// counted; the all-releases view names everything the release had.
var revision = 0;
string released;
if (set == null && drawing == null)
if (filterSet == null && filterDrawing == null)
{
released = Contents(release);
}
else
{
int quantity, otherSets, otherDrawings;
if (set != null)
if (filterSet != null)
{
var line = release.Sets.First(s => s.SetId == set.Id);
var line = release.Sets.First(s => s.SetId == filterSet.Id);
(revision, quantity) = (line.SetRevision, line.Quantity);
(otherSets, otherDrawings) = (release.Sets.Count - 1, release.Drawings.Count);
}
else
{
var line = release.Drawings.First(d => d.DrawingId == drawing.Id);
var line = release.Drawings.First(d => d.DrawingId == filterDrawing.Id);
quantity = line.Quantity;
(otherSets, otherDrawings) = (release.Sets.Count, release.Drawings.Count - 1);
}
var others = new[]
{
otherSets == 0 ? null : $"{otherSets} {(set != null ? "other " : "")}{(otherSets == 1 ? "set" : "sets")}",
otherDrawings == 0 ? null : $"{otherDrawings} {(drawing != null ? "other " : "")}{(otherDrawings == 1 ? "drawing" : "drawings")}",
otherSets == 0 ? null : $"{otherSets} {(filterSet != null ? "other " : "")}{(otherSets == 1 ? "set" : "sets")}",
otherDrawings == 0 ? null : $"{otherDrawings} {(filterDrawing != null ? "other " : "")}{(otherDrawings == 1 ? "drawing" : "drawings")}",
}.Where(o => o != null).ToList();
released = others.Count == 0 ? $"×{quantity}" : $"×{quantity} (+{string.Join(", ", others)})";
}
@@ -255,8 +291,8 @@ namespace OpenNest.Forms
releaseGrid.CurrentCell = releaseGrid.Rows[0].Cells[0];
summaryLabel.Text = loaded.Total == 0
? set != null ? "This set has not been released yet."
: drawing != null ? "This drawing has not been released on its own yet."
? filterSet != null ? "This set has not been released yet."
: filterDrawing != null ? "This drawing has not been released on its own yet."
: "Nothing has been released yet."
: $"Releases {offset + 1:N0}-{offset + loaded.Items.Count:N0} of {loaded.Total:N0}";
}
+8 -3
View File
@@ -24,8 +24,9 @@ namespace OpenNest.Forms
/// selected drawing's details, notes and nesting settings (Cut first, priority, rotation,
/// color) on the server as a new revision with the same package. With a set
/// store, the drawing grid's menu lists the sets a drawing is in, adds it to a set, lists
/// the releases that had it on its own and, once release is enabled, releases it; sets
/// themselves are browsed and released in <see cref="DrawingSetsForm"/>.
/// the releases that had it on its own (the Releases tab of <see cref="DrawingSetsForm"/>,
/// filtered to it) and, once release is enabled, releases it; sets themselves are browsed
/// and released in <see cref="DrawingSetsForm"/>.
/// The details pane previews the selected revision's geometry, downloaded shortly after
/// the selection settles and cached per revision for the dialog's lifetime. The In This
/// Nest tab lists the nest's drawings and edits their required quantities in place.
@@ -48,6 +49,7 @@ namespace OpenNest.Forms
private Task<Drawing> previewLoad;
private bool populatingNest;
private bool adding;
private Action<DrawingSetsForm> enableSetsRelease = _ => { };
/// <summary>
/// Reads the drawing a record's package holds for the preview, or null when the
@@ -131,7 +133,9 @@ namespace OpenNest.Forms
};
ShowReleases = record =>
{
using var form = new SetReleasesForm(sets, record);
using var form = new DrawingSetsForm(sets, library, this.serverUrl);
enableSetsRelease(form);
_ = form.ShowReleasesAsync(drawing: record);
form.ShowDialog(this);
};
LoadPreview = async (record, token) =>
@@ -394,6 +398,7 @@ namespace OpenNest.Forms
{ IsNestOpen = isNestOpen ?? (_ => false) };
form.ShowDialog(this);
};
enableSetsRelease = form => form.EnableRelease(nests, openNest, isNestOpen);
releaseSeparator.Visible = true;
releaseMenuItem.Visible = true;
}
+53 -3
View File
@@ -18,10 +18,17 @@ namespace OpenNest.Forms
private void InitializeComponent()
{
layoutTable = new System.Windows.Forms.TableLayoutPanel();
tabs = new System.Windows.Forms.TabControl();
setsTab = new System.Windows.Forms.TabPage();
browser = new OpenNest.Controls.DrawingSetBrowser();
releasesTab = new System.Windows.Forms.TabPage();
releasesView = new OpenNest.Controls.SetReleasesView();
buttonPanel = new System.Windows.Forms.FlowLayoutPanel();
closeButton = new System.Windows.Forms.Button();
layoutTable.SuspendLayout();
tabs.SuspendLayout();
setsTab.SuspendLayout();
releasesTab.SuspendLayout();
buttonPanel.SuspendLayout();
SuspendLayout();
//
@@ -29,7 +36,7 @@ namespace OpenNest.Forms
//
layoutTable.ColumnCount = 1;
layoutTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
layoutTable.Controls.Add(browser, 0, 0);
layoutTable.Controls.Add(tabs, 0, 0);
layoutTable.Controls.Add(buttonPanel, 0, 1);
layoutTable.Dock = System.Windows.Forms.DockStyle.Fill;
layoutTable.Location = new System.Drawing.Point(0, 0);
@@ -41,6 +48,26 @@ namespace OpenNest.Forms
layoutTable.Size = new System.Drawing.Size(984, 601);
layoutTable.TabIndex = 0;
//
// tabs
//
tabs.Controls.Add(setsTab);
tabs.Controls.Add(releasesTab);
tabs.Dock = System.Windows.Forms.DockStyle.Fill;
tabs.Margin = new System.Windows.Forms.Padding(0);
tabs.Name = "tabs";
tabs.SelectedIndex = 0;
tabs.TabIndex = 0;
tabs.SelectedIndexChanged += Tabs_SelectedIndexChanged;
//
// setsTab
//
setsTab.Controls.Add(browser);
setsTab.Name = "setsTab";
setsTab.Padding = new System.Windows.Forms.Padding(6);
setsTab.TabIndex = 0;
setsTab.Text = "Sets";
setsTab.UseVisualStyleBackColor = true;
//
// browser
//
browser.Dock = System.Windows.Forms.DockStyle.Fill;
@@ -48,6 +75,22 @@ namespace OpenNest.Forms
browser.Name = "browser";
browser.TabIndex = 0;
//
// releasesTab
//
releasesTab.Controls.Add(releasesView);
releasesTab.Name = "releasesTab";
releasesTab.Padding = new System.Windows.Forms.Padding(6);
releasesTab.TabIndex = 1;
releasesTab.Text = "Releases";
releasesTab.UseVisualStyleBackColor = true;
//
// releasesView
//
releasesView.Dock = System.Windows.Forms.DockStyle.Fill;
releasesView.Margin = new System.Windows.Forms.Padding(0);
releasesView.Name = "releasesView";
releasesView.TabIndex = 0;
//
// buttonPanel
//
buttonPanel.AutoSize = true;
@@ -71,11 +114,11 @@ namespace OpenNest.Forms
//
AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
CancelButton = closeButton;
ClientSize = new System.Drawing.Size(984, 601);
ClientSize = new System.Drawing.Size(1024, 641);
Controls.Add(layoutTable);
Font = new System.Drawing.Font("Segoe UI", 9.75F);
MinimizeBox = false;
MinimumSize = new System.Drawing.Size(640, 380);
MinimumSize = new System.Drawing.Size(720, 440);
Name = "DrawingSetsForm";
ShowIcon = false;
ShowInTaskbar = false;
@@ -84,6 +127,9 @@ namespace OpenNest.Forms
Shown += DrawingSetsForm_Shown;
layoutTable.ResumeLayout(false);
layoutTable.PerformLayout();
tabs.ResumeLayout(false);
setsTab.ResumeLayout(false);
releasesTab.ResumeLayout(false);
buttonPanel.ResumeLayout(false);
buttonPanel.PerformLayout();
ResumeLayout(false);
@@ -92,7 +138,11 @@ namespace OpenNest.Forms
#endregion
private System.Windows.Forms.TableLayoutPanel layoutTable;
private System.Windows.Forms.TabControl tabs;
private System.Windows.Forms.TabPage setsTab;
private OpenNest.Controls.DrawingSetBrowser browser;
private System.Windows.Forms.TabPage releasesTab;
private OpenNest.Controls.SetReleasesView releasesView;
private System.Windows.Forms.FlowLayoutPanel buttonPanel;
private System.Windows.Forms.Button closeButton;
}
+66 -9
View File
@@ -1,34 +1,91 @@
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using OpenNest.Controls;
using OpenNest.Data;
namespace OpenNest.Forms
{
/// <summary>
/// File &gt; Drawing Sets (Database mode): browses, edits and releases the server's drawing
/// sets with <see cref="Controls.DrawingSetBrowser"/>. It needs no open nest, since a
/// release creates new nests or merges into draft ones and opens each in its own tab.
/// File &gt; Drawing Sets and File &gt; Releases (Database mode), one window with two tabs.
/// Sets browses, edits and releases the server's drawing sets with
/// <see cref="DrawingSetBrowser"/>; Releases lists the releases with
/// <see cref="SetReleasesView"/>, and a set's Releases (or a library drawing's) opens that
/// tab filtered to it. It needs no open nest, since a release creates new nests or merges
/// into draft ones and opens each in its own tab. Each tab reads its list when first shown;
/// Releases re-reads its page whenever it is shown again, so a release made on Sets appears.
/// </summary>
public partial class DrawingSetsForm : Form
{
internal OpenNest.Controls.DrawingSetBrowser Browser => browser;
private readonly IDrawingLibraryStore library;
private readonly string serverUrl;
private bool shown;
internal DrawingSetBrowser Browser => browser;
internal SetReleasesView Releases => releasesView;
/// <summary>True while the Releases tab is showing.</summary>
public bool ShowsReleases => tabs.SelectedTab == releasesTab;
/// <param name="library">Used by the set editor to pick drawings.</param>
public DrawingSetsForm(IDrawingSetStore sets, IDrawingLibraryStore library, string serverUrl)
{
InitializeComponent();
this.library = library ?? throw new ArgumentNullException(nameof(library));
this.serverUrl = serverUrl ?? "";
browser.Initialize(sets, library, serverUrl);
browser.ShowReleases = set => _ = ShowReleasesAsync(set);
releasesView.Initialize(sets);
Text = $"Drawing Sets — {serverUrl}";
}
/// <summary>
/// Shows Release: nests are saved through <paramref name="nests"/> and each is opened
/// with <paramref name="openNest"/>; a nest <paramref name="isNestOpen"/> reports as open
/// in a tab is never merged into.
/// Shows Release, and New Release on the Releases tab: nests are saved through
/// <paramref name="nests"/> and each is opened with <paramref name="openNest"/>; a nest
/// <paramref name="isNestOpen"/> reports as open in a tab is never merged into.
/// </summary>
public void EnableRelease(INestRepository nests, Action<Nest, NestRecord> openNest, Func<Guid, bool> isNestOpen = null) =>
public void EnableRelease(INestRepository nests, Action<Nest, NestRecord> openNest, Func<Guid, bool> isNestOpen = null)
{
browser.EnableRelease(nests, openNest, isNestOpen);
releasesView.EnableRelease(library, nests, serverUrl, openNest, isNestOpen);
}
private async void DrawingSetsForm_Shown(object sender, EventArgs e) => await browser.RefreshAsync();
/// <summary>
/// Switches to the Releases tab, listing the releases that include <paramref name="set"/>,
/// that had <paramref name="drawing"/> on its own, or (both null) every release. Callable
/// before the window is shown, to open it on that tab; the page is read once it shows.
/// </summary>
public Task ShowReleasesAsync(DrawingSetRecord set = null, DrawingLibraryRecord drawing = null)
{
releasesView.SetFilter(set, drawing);
if (ShowsReleases)
return shown ? releasesView.RefreshAsync() : Task.CompletedTask;
// Selecting the tab reads the page once the window is shown.
tabs.SelectedTab = releasesTab;
return Task.CompletedTask;
}
/// <summary>Switches to the Sets tab.</summary>
public void ShowSets() => tabs.SelectedTab = setsTab;
private async void DrawingSetsForm_Shown(object sender, EventArgs e)
{
shown = true;
await RefreshSelectedTabAsync();
}
private async void Tabs_SelectedIndexChanged(object sender, EventArgs e)
{
if (shown)
await RefreshSelectedTabAsync();
}
private Task RefreshSelectedTabAsync()
{
if (ShowsReleases)
return releasesView.RefreshAsync();
return browser.HasLoaded ? Task.CompletedTask : browser.RefreshAsync();
}
}
}
+20 -5
View File
@@ -65,6 +65,7 @@ namespace OpenNest.Forms
downButton = new System.Windows.Forms.Button();
bottomTable = new System.Windows.Forms.TableLayoutPanel();
summaryLabel = new System.Windows.Forms.Label();
historyButton = new System.Windows.Forms.Button();
saveButton = new System.Windows.Forms.Button();
cancelButton = new System.Windows.Forms.Button();
statusLabel = new System.Windows.Forms.Label();
@@ -572,13 +573,15 @@ namespace OpenNest.Forms
// bottomTable
//
bottomTable.AutoSize = true;
bottomTable.ColumnCount = 3;
bottomTable.ColumnCount = 4;
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
bottomTable.Controls.Add(summaryLabel, 0, 0);
bottomTable.Controls.Add(saveButton, 1, 0);
bottomTable.Controls.Add(cancelButton, 2, 0);
bottomTable.Controls.Add(historyButton, 1, 0);
bottomTable.Controls.Add(saveButton, 2, 0);
bottomTable.Controls.Add(cancelButton, 3, 0);
bottomTable.Dock = System.Windows.Forms.DockStyle.Fill;
bottomTable.Margin = new System.Windows.Forms.Padding(0, 6, 0, 0);
bottomTable.Name = "bottomTable";
@@ -593,12 +596,23 @@ namespace OpenNest.Forms
summaryLabel.Name = "summaryLabel";
summaryLabel.TabIndex = 0;
//
// historyButton
//
historyButton.AutoSize = true;
historyButton.Margin = new System.Windows.Forms.Padding(3, 3, 12, 3);
historyButton.Name = "historyButton";
historyButton.TabIndex = 1;
historyButton.Text = "History...";
historyButton.UseVisualStyleBackColor = true;
historyButton.Visible = false;
historyButton.Click += HistoryButton_Click;
//
// saveButton
//
saveButton.AutoSize = true;
saveButton.Enabled = false;
saveButton.Name = "saveButton";
saveButton.TabIndex = 1;
saveButton.TabIndex = 2;
saveButton.Text = "Save";
saveButton.UseVisualStyleBackColor = true;
saveButton.Click += SaveButton_Click;
@@ -608,7 +622,7 @@ namespace OpenNest.Forms
cancelButton.AutoSize = true;
cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
cancelButton.Name = "cancelButton";
cancelButton.TabIndex = 2;
cancelButton.TabIndex = 3;
cancelButton.Text = "Cancel";
cancelButton.UseVisualStyleBackColor = true;
//
@@ -731,6 +745,7 @@ namespace OpenNest.Forms
private System.Windows.Forms.Button downButton;
private System.Windows.Forms.TableLayoutPanel bottomTable;
private System.Windows.Forms.Label summaryLabel;
private System.Windows.Forms.Button historyButton;
private System.Windows.Forms.Button saveButton;
private System.Windows.Forms.Button cancelButton;
private System.Windows.Forms.Label statusLabel;
+19 -2
View File
@@ -20,8 +20,9 @@ namespace OpenNest.Forms
/// selected drawings at the entered quantity, and a drawing already in the set is greyed
/// and not added twice. Edit Drawing (a double-click on a member, its right-click menu or
/// the button) reads the member's current library record and edits its details there, so
/// a drawing without a material or thickness can be fixed in place. Saving adds a revision
/// on the server, guarded by the revision the set was read at.
/// a drawing without a material or thickness can be fixed in place. History lists a saved
/// set's revisions. Saving adds a revision on the server, guarded by the revision the set
/// was read at.
/// </summary>
public partial class EditDrawingSetForm : Form
{
@@ -39,6 +40,9 @@ namespace OpenNest.Forms
/// <summary>Shows Edit Drawing and returns the saved record, or null; replaced by tests.</summary>
internal Func<DrawingLibraryEditSession, DrawingLibraryRecord> EditDrawing { get; set; }
/// <summary>Shows a set's revision history; replaced by tests.</summary>
internal Action<DrawingSetRecord> ShowHistory { get; set; }
/// <summary>The saved revision once the dialog closes with OK; otherwise null.</summary>
public DrawingSetRecord Saved { get; private set; }
@@ -62,6 +66,11 @@ namespace OpenNest.Forms
using var form = new EditLibraryDrawingForm(edit);
return form.ShowDialog(this) == DialogResult.OK ? form.Saved : null;
};
ShowHistory = set =>
{
using var form = new DrawingSetHistoryForm(session.Store, set);
form.ShowDialog(this);
};
GridStyle.Apply(memberGrid);
GridStyle.Apply(libraryGrid);
@@ -80,6 +89,8 @@ namespace OpenNest.Forms
Text = session.IsNew
? "New Drawing Set"
: $"Edit Set '{session.Saved.Name}' — revision {session.Saved.Revision}";
// A new set has no history until it is saved, and the dialog closes on save.
historyButton.Visible = !session.IsNew;
Populate(session.Members.Count > 0 ? 0 : -1);
UpdateCommands();
}
@@ -96,6 +107,12 @@ namespace OpenNest.Forms
private void MemberGrid_CurrentCellChanged(object sender, EventArgs e) => UpdateCommands();
private void HistoryButton_Click(object sender, EventArgs e)
{
if (session.Saved is { } saved)
ShowHistory(saved);
}
private void MemberGrid_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
{
if (e.ColumnIndex != quantityColumn.Index)
+8 -30
View File
@@ -142,7 +142,12 @@ namespace OpenNest.Forms
/// Browses, edits and releases drawing sets; needs no open nest, since a release
/// creates or merges into nests and opens each in its own tab.
/// </summary>
private void DrawingSets_Click(object sender, EventArgs e)
private void DrawingSets_Click(object sender, EventArgs e) => ShowDrawingSets(releases: false);
/// <summary>A shortcut to the Drawing Sets window's Releases tab, listing every release.</summary>
private void Releases_Click(object sender, EventArgs e) => ShowDrawingSets(releases: true);
private void ShowDrawingSets(bool releases)
{
if (!NestStorage.Settings.IsDatabaseMode)
return;
@@ -164,35 +169,8 @@ namespace OpenNest.Forms
using var form = new DrawingSetsForm(sets, library, NestStorage.Settings.ServerUrl);
form.EnableRelease(nests, OpenServerNest, IsServerNestOpen);
form.ShowDialog(this);
}
/// <summary>
/// Lists every release on the nest server and starts new ones; needs no open nest, since
/// a release creates or merges into nests and opens each in its own tab.
/// </summary>
private void Releases_Click(object sender, EventArgs e)
{
if (!NestStorage.Settings.IsDatabaseMode)
return;
IDrawingLibraryStore library;
IDrawingSetStore sets;
INestRepository nests;
try
{
library = NestStorage.CreateDrawingLibrary();
sets = NestStorage.CreateDrawingSetStore();
nests = NestStorage.CreateRepository();
}
catch (ArgumentException ex)
{
MessageBox.Show(this, ex.Message, "Releases", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
using var form = new SetReleasesForm(sets);
form.EnableRelease(library, nests, NestStorage.Settings.ServerUrl, OpenServerNest, IsServerNestOpen);
if (releases)
_ = form.ShowReleasesAsync();
form.ShowDialog(this);
}
+36 -24
View File
@@ -404,7 +404,10 @@ revision; save the nest to keep it.
**File > Drawing Sets...** (Database mode only) opens the drawing sets window;
it needs no open nest, since releasing a set makes new nests or merges into
draft ones and opens each in its own tab. It lists sets a page at a time
draft ones and opens each in its own tab. The window has two tabs, **Sets** and
**Releases** ([below](#releases-tab)); **File > Releases...** is a shortcut that
opens the same window on the Releases tab. Each tab reads its list when first
shown, and Releases re-reads its page each time it is shown again. Sets lists sets a page at a time
(`OpenNest.Data.DrawingSetBrowseSession`): search by name, customer or notes,
sort by name, customer, revision or saved time, and **Show archived**. Selecting
a set shows its notes.
@@ -428,16 +431,19 @@ a set shows its notes.
before its first save, and a save that fails without a definite answer stays
pending: the next Save reads that id and adopts the stored set (saving any
later edit as its next revision) instead of creating it twice. Closing with
unsaved edits asks first.
unsaved edits asks first. Editing a saved set, the editor's **History...**
lists the set's revisions (below).
- **Archive** (after confirming) and **Restore** add a revision; nothing is
deleted. **History...** lists revisions newest first with the members of the
selected one, shown with their drawings' current library details.
deleted. **History...** (in the set editor, and on the Sets tab for the
selected set) lists revisions newest first with the members of the selected
one, shown with their drawings' current library details.
In the Drawing Library dialog, right-click a drawing for **Used in Sets...** (the sets
whose current revision contains it) or **Add to Set...**: choose an active set
or **New Set**, and the set editor opens with the drawing added at the entered
quantity for review and Save. **Releases...** lists the releases that had the
drawing on its own (see below), with Undo Release. When the dialog was opened
quantity for review and Save. **Releases...** opens the drawing sets window on
its Releases tab, filtered to the releases that had the drawing on its own (see
below), with Undo Release. When the dialog was opened
from a nest, **Make Nests...** reads the drawing's current record and opens the
release dialog with it on its own at the entered quantity; more drawings and
sets are added there.
@@ -456,7 +462,7 @@ library's latest revision. Anything kept, skipped, archived in the library or
newer there than in the nest is listed first, then the set editor opens for
review and Save; nothing is stored before then.
**Make Nests...** (in the drawing sets window) reads the
**Make Nests...** (on the drawing sets window's Sets tab) reads the
selected active set with its members and opens the release dialog with it listed.
More sets are added from **Add a set**: the list opens with the first active sets
by name and narrows as you type; Enter or **Add** takes the picked set, or the one
@@ -531,24 +537,30 @@ the button becomes **Try Again** and posts the same release again without making
the nests again. A dialog
runs one release.
**Releases...** lists the releases that include the selected set, newest first:
when, by whom, the set's **Revision**, the **Quantity** of this set (and how many other sets
and drawings the release had), the number of nests, progress such as "2 of 3 cut" (deleted
nests are not counted) or "Undone", and the notes. The selected release's nests
show their **Material**, the **Action** ("Created", "Merged (revision
3 → 4)", and what undo did) and current status, or Deleted. **Undo Release...**
deletes the nests the release created (they can be recovered) and restores the
drafts it merged into. When some nests were changed after the release, it lists
them and offers to undo the others, leaving those as they are.
<a id="releases-tab"></a>
The **Releases** tab lists every release on the server, newest first, 100 at a
time (**< Newer** / **Older >**): when, by whom, a **Released** column naming what
each one had ("Frame A ×2, Frame B; Bracket ×4": sets with their quantity past
one, then drawings on their own with their quantities), the number of nests,
progress such as "2 of 3 cut" (deleted nests are not counted) or "Undone", and the
notes. The selected release's nests show their **Material**, the **Action**
("Created", "Merged (revision 3 → 4)", and what undo did) and current status, or
Deleted. **Undo Release...** deletes the nests the release created (they can be
recovered) and restores the drafts it merged into. When some nests were changed
after the release, it lists them and offers to undo the others, leaving those as
they are. **New Release...** opens the release dialog empty; sets are added by
name and drawings with Add Drawings, and once the dialog records a release the
list re-reads from the newest.
**File > Releases...** lists every release on the server the same way, newest
first, with a **Released** column naming what each one had ("Frame A ×2, Frame B;
Bracket ×4": sets with their quantity past one, then drawings on their own with
their quantities) in place of the revision and quantity, and Undo Release.
**New Release...** opens the release dialog empty; sets are added by name and
drawings with Add Drawings, and once the dialog records a release the list
re-reads from the newest. Like Drawing Sets, it is enabled in database mode
only.
A filter narrows the tab to one set or one drawing, shown above the list
("Releases of set 'Frame A'") until **Clear Filter** lists every release again:
- **Releases...** on the Sets tab filters to the releases that include the
selected set, with the set's **Revision** and the **Quantity** of this set (and
how many other sets and drawings the release had) in place of Released.
- **Releases...** in the Drawing Library's right-click menu opens the window
filtered to the releases that had that drawing on its own, with its
**Quantity** (and how many sets and other drawings the release had).
**Nest > Edit** (Nest Info) shows, beside its buttons, the newest release that
made or merged into a server nest ("Released from set 'Frame A' revision 3 ×2 on