diff --git a/OpenNest.Data/DrawingSetEditSession.cs b/OpenNest.Data/DrawingSetEditSession.cs
index 620e1b07..e3324ba0 100644
--- a/OpenNest.Data/DrawingSetEditSession.cs
+++ b/OpenNest.Data/DrawingSetEditSession.cs
@@ -56,6 +56,9 @@ public sealed class DrawingSetEditSession
public Guid Id { get; }
+ /// The store the set is read from and saved to; also reads its history.
+ public IDrawingSetStore Store => _sets;
+
public string SavedBy { get; }
/// The set as last read or saved; null until a new set is first saved.
diff --git a/OpenNest.WinForms.Tests/Forms/DrawingSetFormsTests.cs b/OpenNest.WinForms.Tests/Forms/DrawingSetFormsTests.cs
index 1bc95d8b..5984d4b9 100644
--- a/OpenNest.WinForms.Tests/Forms/DrawingSetFormsTests.cs
+++ b/OpenNest.WinForms.Tests/Forms/DrawingSetFormsTests.cs
@@ -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();
+
+ using (var editor = new EditDrawingSetForm(new DrawingSetEditSession(sets, created, "Synthetic operator"), library, Server))
+ {
+ editor.ShowHistory = shown.Add;
+ editor.Show();
+ var history = Field(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(fresh, "historyButton").Visible);
+ },
+ TestTimeout,
+ "The editor history test did not complete.");
+ }
+
[Fact]
public void EditorLibraryPane_ListsEveryDrawing_AddsTheSelectionInListOrder_AndGreysMembers()
{
diff --git a/OpenNest.WinForms.Tests/Forms/SetReleaseFormsTests.cs b/OpenNest.WinForms.Tests/Forms/SetReleaseFormsTests.cs
index 4043de77..611bfef8 100644
--- a/OpenNest.WinForms.Tests/Forms/SetReleaseFormsTests.cs
+++ b/OpenNest.WinForms.Tests/Forms/SetReleaseFormsTests.cs
@@ -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(form, "releaseGrid");
- var nests = Field(form, "nestGrid");
+ var view = form.Releases;
+ var releases = Field(view, "releaseGrid");
+ var nests = Field(view, "nestGrid");
PumpUntil(() => releases.Rows.Count == 1 && nests.Rows.Count == 3, "the releases");
+ Assert.True(form.ShowsReleases);
+ Assert.True(Field(view, "filterTable").Visible);
+ Assert.Equal("Releases of set 'Synthetic kit'", Field(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();
- 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(form, "releaseGrid");
- var nests = Field(form, "nestGrid");
+ var releases = Field(view, "releaseGrid");
+ var nests = Field(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().Select(r => r.Cells["changeColumn"].Value));
- Assert.True(Field(form, "undoButton").Enabled);
+ Assert.True(Field(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().Select(r => r.Cells["changeColumn"].Value));
- Assert.False(Field(form, "undoButton").Enabled);
+ Assert.False(Field(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();
- 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(form, "releaseGrid");
+ var releases = Field(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(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(),
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()));
return opened == 2;
};
+ Run(form.ShowReleasesAsync());
form.Show();
- var releases = Field(form, "releaseGrid");
+ var releases = Field(view, "releaseGrid");
PumpUntil(() => releases.Rows.Count == 2, "the releases");
- Assert.Equal("Releases", form.Text);
+ Assert.False(Field(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().Select(r => r.Cells["multiplierColumn"].Value));
- Assert.Equal("Releases 1-2 of 2", Field(form, "summaryLabel").Text);
- Assert.False(Field(form, "newReleaseButton").Visible);
+ Assert.Equal("Releases 1-2 of 2", Field(view, "summaryLabel").Text);
+ Assert.False(Field(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(form, "summaryLabel").Text);
+ Assert.Equal("Releases 1-3 of 3", Field(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(browser, "setGrid");
+ var releases = Field(view, "releaseGrid");
+ var filter = Field(view, "filterTable");
+ PumpUntil(() => setGrid.Rows.Count == 2, "the set list");
+
+ Assert.False(form.ShowsReleases);
+ Assert.False(view.HasLoaded);
+
+ setGrid.CurrentCell = setGrid.Rows.Cast()
+ .Single(r => (string)r.Cells["nameColumn"].Value == "Synthetic kit").Cells[0];
+ Field(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(view, "filterLabel").Text);
+ Assert.Equal("Quantity", releases.Columns["multiplierColumn"]!.HeaderText);
+
+ Field(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(form, "tabs").SelectedIndex = 1;
+ PumpUntil(() => releases.Rows.Count == 3, "the re-read releases");
+ Assert.Equal("Releases 1-3 of 3", Field(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);
diff --git a/OpenNest/Controls/DrawingSetBrowser.cs b/OpenNest/Controls/DrawingSetBrowser.cs
index 27f602ba..87b3f70d 100644
--- a/OpenNest/Controls/DrawingSetBrowser.cs
+++ b/OpenNest/Controls/DrawingSetBrowser.cs
@@ -12,7 +12,8 @@ namespace OpenNest.Controls
///
/// Browses the server's drawing sets one bounded page at a time through
/// , and creates, edits, archives or restores them and
- /// shows their history. hosts it to manage and release sets. In pick mode
+ /// shows their history. hosts it on its Sets tab to manage and
+ /// release sets, and shows a set's Releases on its Releases tab. In pick mode
/// ( ) only active sets are listed, and choosing a set or
/// New Set raises or instead.
///
@@ -38,7 +39,7 @@ namespace OpenNest.Controls
/// Shows a set's revision history; replaced by tests.
internal Action ShowHistory { get; set; }
- /// Shows a set's releases; replaced by tests.
+ /// Shows a set's releases; set by the host (Releases is disabled without it) and replaced by tests.
internal Action ShowReleases { get; set; }
/// Shows the release dialog for a set read with its members; replaced by tests.
@@ -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 };
}
}
diff --git a/OpenNest/Forms/SetReleasesForm.Designer.cs b/OpenNest/Controls/SetReleasesView.Designer.cs
similarity index 82%
rename from OpenNest/Forms/SetReleasesForm.Designer.cs
rename to OpenNest/Controls/SetReleasesView.Designer.cs
index fe2cab72..ca62f8cc 100644
--- a/OpenNest/Forms/SetReleasesForm.Designer.cs
+++ b/OpenNest/Controls/SetReleasesView.Designer.cs
@@ -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;
}
}
diff --git a/OpenNest/Forms/SetReleasesForm.cs b/OpenNest/Controls/SetReleasesView.cs
similarity index 71%
rename from OpenNest/Forms/SetReleasesForm.cs
rename to OpenNest/Controls/SetReleasesView.cs
index ace2c57d..925a0a8f 100644
--- a/OpenNest/Forms/SetReleasesForm.cs
+++ b/OpenNest/Controls/SetReleasesView.cs
@@ -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
{
///
- /// File > 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 > Releases and Drawings tab >
- /// 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 : 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.
///
- 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
///
internal Func ShowNewRelease { get; set; }
- /// Every release on the server, newest first.
- 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}'";
- }
-
- /// The releases that had on its own, outside any set.
- 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();
+ }
+
+ /// True after a page of releases has been requested at least once.
+ public bool HasLoaded { get; private set; }
+
+ /// The set the list is narrowed to, or null.
+ public DrawingSetRecord FilterSet => set;
+
+ /// The drawing the list is narrowed to, or null.
+ 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);
}
///
@@ -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(),
Array.Empty(), 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);
+ ///
+ /// Narrows the list to the releases that include , or that had
+ /// on its own outside any set; both null lists every release.
+ /// The next read starts at the newest page.
+ ///
+ 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();
+ }
+
+ /// Re-reads the current page (the first call reads the newest).
+ public Task RefreshAsync() => LoadPageAsync(offset);
+
+ /// Lists every release again, from the newest.
+ 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
/// Reads one page of releases; a later request discards an earlier one's result.
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}";
}
diff --git a/OpenNest/Forms/DrawingLibraryForm.cs b/OpenNest/Forms/DrawingLibraryForm.cs
index 96b51c6e..1bc7a97e 100644
--- a/OpenNest/Forms/DrawingLibraryForm.cs
+++ b/OpenNest/Forms/DrawingLibraryForm.cs
@@ -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 .
+ /// the releases that had it on its own (the Releases tab of ,
+ /// filtered to it) and, once release is enabled, releases it; sets themselves are browsed
+ /// and released in .
/// 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 previewLoad;
private bool populatingNest;
private bool adding;
+ private Action enableSetsRelease = _ => { };
///
/// 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;
}
diff --git a/OpenNest/Forms/DrawingSetsForm.Designer.cs b/OpenNest/Forms/DrawingSetsForm.Designer.cs
index f1d69d6e..fa539879 100644
--- a/OpenNest/Forms/DrawingSetsForm.Designer.cs
+++ b/OpenNest/Forms/DrawingSetsForm.Designer.cs
@@ -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;
}
diff --git a/OpenNest/Forms/DrawingSetsForm.cs b/OpenNest/Forms/DrawingSetsForm.cs
index 0638b4d1..cd3baf93 100644
--- a/OpenNest/Forms/DrawingSetsForm.cs
+++ b/OpenNest/Forms/DrawingSetsForm.cs
@@ -1,34 +1,91 @@
using System;
+using System.Threading.Tasks;
using System.Windows.Forms;
+using OpenNest.Controls;
using OpenNest.Data;
namespace OpenNest.Forms
{
///
- /// File > Drawing Sets (Database mode): browses, edits and releases the server's drawing
- /// sets with . It needs no open nest, since a
- /// release creates new nests or merges into draft ones and opens each in its own tab.
+ /// File > Drawing Sets and File > Releases (Database mode), one window with two tabs.
+ /// Sets browses, edits and releases the server's drawing sets with
+ /// ; Releases lists the releases with
+ /// , 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.
///
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;
+
+ /// True while the Releases tab is showing.
+ public bool ShowsReleases => tabs.SelectedTab == releasesTab;
/// Used by the set editor to pick drawings.
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}";
}
///
- /// Shows Release: nests are saved through and each is opened
- /// with ; a nest reports as open
- /// in a tab is never merged into.
+ /// Shows Release, and New Release on the Releases tab: nests are saved through
+ /// and each is opened with ; a nest
+ /// reports as open in a tab is never merged into.
///
- public void EnableRelease(INestRepository nests, Action openNest, Func isNestOpen = null) =>
+ public void EnableRelease(INestRepository nests, Action openNest, Func 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();
+ ///
+ /// Switches to the Releases tab, listing the releases that include ,
+ /// that had 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.
+ ///
+ 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;
+ }
+
+ /// Switches to the Sets tab.
+ 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();
+ }
}
}
diff --git a/OpenNest/Forms/EditDrawingSetForm.Designer.cs b/OpenNest/Forms/EditDrawingSetForm.Designer.cs
index ea731681..bae93a29 100644
--- a/OpenNest/Forms/EditDrawingSetForm.Designer.cs
+++ b/OpenNest/Forms/EditDrawingSetForm.Designer.cs
@@ -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;
diff --git a/OpenNest/Forms/EditDrawingSetForm.cs b/OpenNest/Forms/EditDrawingSetForm.cs
index d1cebb54..5fea9c1d 100644
--- a/OpenNest/Forms/EditDrawingSetForm.cs
+++ b/OpenNest/Forms/EditDrawingSetForm.cs
@@ -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.
///
public partial class EditDrawingSetForm : Form
{
@@ -39,6 +40,9 @@ namespace OpenNest.Forms
/// Shows Edit Drawing and returns the saved record, or null; replaced by tests.
internal Func EditDrawing { get; set; }
+ /// Shows a set's revision history; replaced by tests.
+ internal Action ShowHistory { get; set; }
+
/// The saved revision once the dialog closes with OK; otherwise null.
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)
diff --git a/OpenNest/Forms/MainForm.File.cs b/OpenNest/Forms/MainForm.File.cs
index a9633040..ea814cb6 100644
--- a/OpenNest/Forms/MainForm.File.cs
+++ b/OpenNest/Forms/MainForm.File.cs
@@ -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.
///
- private void DrawingSets_Click(object sender, EventArgs e)
+ private void DrawingSets_Click(object sender, EventArgs e) => ShowDrawingSets(releases: false);
+
+ /// A shortcut to the Drawing Sets window's Releases tab, listing every release.
+ 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);
- }
-
- ///
- /// 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.
- ///
- 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);
}
diff --git a/docs/nest-storage.md b/docs/nest-storage.md
index c0ea6c24..2ec046d8 100644
--- a/docs/nest-storage.md
+++ b/docs/nest-storage.md
@@ -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.
+
+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