Files
OpenNest/OpenNest/Forms/MainForm.File.cs
T
ajandClaude Opus 5.5 0d1dade063 feat(desktop): open Edit Drawing from everywhere and warn before duplicates
Builds on the library's Edit Drawing (details as a new revision):
- warns before saving details that would duplicate another active library
  drawing (D4), naming them; a failed lookup also asks;
- File > Drawing Library, a nest-less mode of the library browser that lists
  every active drawing and edits on double-click;
- the set editor's member grid (double-click, right-click, button);
- the release dialog: Fix Drawings on a group refused for missing material or
  thickness or duplicate names, so those refusals are no longer a dead end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 15:33:12 -04:00

349 lines
14 KiB
C#

using System;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using OpenNest.Data;
using OpenNest.Engine.BestFit;
using OpenNest.Forms.Commands;
using OpenNest.IO;
namespace OpenNest.Forms
{
public partial class MainForm
{
private void LoadNest(Nest nest, FormWindowState windowState = FormWindowState.Maximized,
Guid remoteId = default, string serverUrl = null, int remoteRevision = 0)
{
// The view follows the application's scheme, not the one the file was saved under.
ColorSchemeRegistry.RecolorDrawings(nest);
var editForm = new EditNestForm(nest);
if (remoteId != Guid.Empty)
editForm.Document.BindRemote(remoteId, serverUrl, remoteRevision);
editForm.MdiParent = this;
editForm.PlateView.FilesDropped += (sender, paths) => OpenDroppedNestFiles(paths);
editForm.PlateChanged += (sender, e) =>
{
NavigationEnableCheck();
statusBar.UpdatePlate();
mnuPlateRemove.Enabled = activeForm?.PlateManager.CanRemoveCurrent ?? false;
mnuNestClear.Enabled = activeForm?.PlateManager.CanClearAll ?? false;
};
editForm.WindowState = windowState;
editForm.Show();
editForm.PlateView.ZoomToFit();
}
/// <summary>
/// Opens a nest that is already on the nest server (a drawing set release made it) in
/// its own tab, bound to that record so the next Save updates it.
/// </summary>
internal void OpenServerNest(Nest nest, NestRecord record)
{
ArgumentNullException.ThrowIfNull(nest);
ArgumentNullException.ThrowIfNull(record);
var windowState = ActiveMdiChild?.WindowState ?? FormWindowState.Maximized;
LoadNest(nest, windowState, record.Id, NestStorage.Settings.ServerUrl, record.Revision);
}
/// <summary>True when a tab holds the server nest <paramref name="id"/>.</summary>
internal bool IsServerNestOpen(Guid id) =>
id != Guid.Empty && MdiChildren.OfType<EditNestForm>().Any(form => form.Document.RemoteId == id);
#region File Menu Events
private async void New_Click(object sender, EventArgs e)
{
var windowState =
ActiveMdiChild != null ? ActiveMdiChild.WindowState : FormWindowState.Maximized;
LoadNest(await NewNestFactory.CreateAsync(this), windowState);
}
private async void Open_Click(object sender, EventArgs e)
{
if (NestStorage.Settings.Mode == NestStorageMode.File)
{
using var dlg = new OpenFileDialog { Filter = NestFormat.FileFilter, Multiselect = true };
if (dlg.ShowDialog(this) == DialogResult.OK)
{
var reader = new NestReader(dlg.FileName) { BestFitStore = BestFitCacheStore.Shared };
var nest = reader.Read();
LoadNest(nest);
ShowNestWarnings(reader);
}
return;
}
await storage.OpenAsync((nest, reader, snapshot) =>
{
LoadNest(nest, remoteId: snapshot.Metadata.Id, serverUrl: snapshot.ServerUrl,
remoteRevision: snapshot.Metadata.Revision);
ShowNestWarnings(reader);
});
}
private void ShowNestWarnings(NestReader reader)
{
if (reader.Warnings.Count > 0)
MessageBox.Show(this, string.Join(Environment.NewLine, reader.Warnings),
"Nest Load Warnings", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
private void ImportBom_Click(object sender, EventArgs e)
{
var form = new BomImportForm();
form.MdiParentForm = this;
form.ShowDialog(this);
}
private void FileMenu_Opening(object sender, EventArgs e)
{
// The drawing library and sets live on the nest server; File mode has nothing to browse.
var databaseMode = NestStorage.Settings.IsDatabaseMode;
mnuFile.DropDown.ShowItemToolTips = true;
mnuFileDrawingLibrary.Enabled = databaseMode;
mnuFileDrawingLibrary.ToolTipText = databaseMode ? "" : NestStorage.DatabaseModeOnly;
mnuFileDrawingSets.Enabled = databaseMode;
mnuFileDrawingSets.ToolTipText = databaseMode ? "" : NestStorage.DatabaseModeOnly;
mnuFileReleases.Enabled = databaseMode;
mnuFileReleases.ToolTipText = databaseMode ? "" : NestStorage.DatabaseModeOnly;
}
/// <summary>
/// Browses the drawing library and edits its drawings' details and settings; needs no
/// open nest. Drawings are added to sets or released from it, never to a nest.
/// </summary>
private void FileDrawingLibrary_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, "Drawing Library", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
using var form = new DrawingLibraryForm(library, NestStorage.Settings.ServerUrl, null, sets);
form.EnableRelease(nests, OpenServerNest, IsServerNestOpen);
form.ShowDialog(this);
}
/// <summary>
/// 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)
{
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, "Drawing Sets", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
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);
form.ShowDialog(this);
}
private async void Save_Click(object sender, EventArgs e)
{
var form = activeForm;
if (form == null || storage.IsSaving)
return;
if (NestStorage.Settings.Mode == NestStorageMode.File)
form.Save();
else
await storage.SaveAsync(form);
}
private async void SaveAs_Click(object sender, EventArgs e)
{
var form = activeForm;
if (form == null || storage.IsSaving)
return;
if (NestStorage.Settings.Mode == NestStorageMode.File)
form.SaveAs();
else
await storage.SaveAsAsync(form);
}
private void Storage_StateChanged(object sender, EventArgs e)
{
storageModeMenu.Enabled = !storage.IsBusy;
mnuToolsOptions.Enabled = !storage.IsBusy;
EnableCheck();
}
private void StorageMode_Click(object sender, EventArgs e)
{
using var dlg = new StorageModeForm();
if (dlg.ShowDialog(this) != DialogResult.OK)
return;
try
{
NestStorage.Save(dlg.SelectedMode, dlg.ServerUrl);
statusLabel1.Text = $"Nest storage: {dlg.SelectedMode}";
foreach (var form in MdiChildren.OfType<EditNestForm>())
form.UpdateDrawingLibraryCommand();
}
catch (Exception ex)
{
storage.ShowError("change storage mode", ex);
}
}
private void ExportNest_Click(object sender, EventArgs e)
{
var form = activeForm;
if (form == null)
return;
using var dlg = new SaveFileDialog { Filter = NestFormat.FileFilter, FileName = form.Nest.Name };
if (dlg.ShowDialog(this) != DialogResult.OK)
return;
try
{
// Export never changes the document's save path, remote id, or nest name.
new NestWriter(form.Nest) { BestFitStore = BestFitCacheStore.Shared }.Write(dlg.FileName);
statusLabel1.Text = $"Exported .nest to {dlg.FileName}";
}
catch (Exception ex)
{
storage.ShowError("export", ex);
}
}
private void Export_Click(object sender, EventArgs e) => WithActive(f => f.Export());
private void ExportAll_Click(object sender, EventArgs e) => WithActive(f => f.ExportAll());
private void ExportNestReport_Click(object sender, EventArgs e)
{
var form = activeForm;
if (form == null)
return;
// The report never depends on a selected post or NC output; it only needs a
// quiescent nest so the snapshot is coherent.
Func<bool> isJobBusy = IsNestJobBusy;
EditNestForm.NestReportTargets targets;
try
{
targets = form.CaptureReportTargets(isJobBusy);
}
catch (Exception ex) when (ex is InvalidOperationException or ArgumentException)
{
ReportFailure(null, ex);
return;
}
var destination = ShowReportSaveDialog(targets.SuggestedFileName);
if (destination == null)
return; // Cancel: nothing written, job untouched.
try
{
form.WriteNestReport(targets, destination, isJobBusy);
}
catch (Exception ex)
when (ex is InvalidOperationException
or NotSupportedException
or InvalidDataException
or IOException
or UnauthorizedAccessException
or ArgumentException)
{
// Failures describe the captured target; an active-document switch is irrelevant.
ReportFailure(targets, ex);
return;
}
statusLabel1.Text = $"Saved nest report to {destination}";
ReportSuccess(destination);
}
/// <summary>Returns the chosen path, or null on cancel. Overwrite consent is the dialog prompt.</summary>
internal virtual string ShowReportSaveDialog(string suggestedFileName)
{
using var dlg = new SaveFileDialog
{
Filter = "PDF report (*.pdf)|*.pdf",
FileName = suggestedFileName,
AddExtension = true,
DefaultExt = ".pdf",
OverwritePrompt = true,
};
return dlg.ShowDialog(this) == DialogResult.OK ? dlg.FileName : null;
}
/// <summary>Failure notice naming the fixed target; internal so tests observe it without dialogs.</summary>
internal virtual void ReportFailure(
EditNestForm.NestReportTargets targets,
Exception error) =>
MessageBox.Show(this,
$"Could not export the nest report{(targets == null ? "" : $" for '{targets.Nest.Name}'")}: {error.Message}",
"Export Nest Report", MessageBoxButtons.OK, MessageBoxIcon.Warning);
/// <summary>Success notice; internal so tests observe completion without dialogs.</summary>
internal virtual void ReportSuccess(string destination) =>
MessageBox.Show(this, $"Nest report saved to:\n\n{destination}", "Export Nest Report",
MessageBoxButtons.OK, MessageBoxIcon.Information);
private void Exit_Click(object sender, EventArgs e)
{
Close();
}
#endregion File Menu Events
}
}