mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-26 23:11:07 -04:00
refactor(ui): extract MainForm startup and settings services
Moves startup and settings code out of MainForm: - PostProcessorLoader discovers posts and registers their materials. Each DLL now loads in isolation; a failing one is named in a startup warning instead of throwing out of the MainForm constructor. - MachineSettings owns the clamp-settings and nest-defaults entry points and their once-per-session warnings; BomImportForm calls it directly. - NewNestFactory creates File > New nests and their daily names; GetNestName is now a pure static the characterization test calls. - MainWindowSettings restores/saves window placement and migrates the legacy nest template. - UpdaterStatus owns the updater and its status-bar item. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3cae257fb1
commit
9b46db4891
13 files changed
+377
-282
No files matched your search
@@ -49,7 +49,7 @@ public sealed class NestDefaults
|
||||
/// <summary>
|
||||
/// The built-in defaults used when no file exists and for every field
|
||||
/// that is missing or invalid. Matches the historical
|
||||
/// MainForm.CreateDefaultNest values (units default to Inches; callers
|
||||
/// File > New values (units default to Inches; callers
|
||||
/// may override from their own settings).
|
||||
/// </summary>
|
||||
public static NestDefaults Fallback => new();
|
||||
|
||||
@@ -18,15 +18,7 @@ public class MainFormCharacterizationTests
|
||||
[InlineData(900, "N26-322")]
|
||||
[InlineData(27000, "N26-3222")]
|
||||
public void NestNamesUseTwoDigitYearAndPaddedBase30Sequence(int id, string expected) =>
|
||||
StaTestThread.Run(() =>
|
||||
{
|
||||
using var host = new Host();
|
||||
var name = (string)typeof(MainForm)
|
||||
.GetMethod("GetNestName", BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||
.Invoke(host, [new DateTime(2026, 10, 17), id])!;
|
||||
|
||||
Assert.Equal(expected, name);
|
||||
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
|
||||
Assert.Equal(expected, NewNestFactory.GetNestName(new DateTime(2026, 10, 17), id));
|
||||
|
||||
[Fact]
|
||||
public void BusyGuardsDifferInWhichOperationsTheyCount() => StaTestThread.Run(() =>
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace OpenNest.Forms
|
||||
dgvParts.AutoGenerateColumns = false;
|
||||
dgvParts.DataSource = _parts;
|
||||
_groupSettings = new Dictionary<string, BomGroupPlateSettings>();
|
||||
_defaults = MainForm.LoadSavedNestDefaults(out _);
|
||||
_defaults = MachineSettings.LoadSavedNestDefaults(out _);
|
||||
ApplyDefaults();
|
||||
}
|
||||
|
||||
@@ -414,7 +414,7 @@ namespace OpenNest.Forms
|
||||
var nestsCreated = 0;
|
||||
var importErrors = new List<string>();
|
||||
// Read when the nests are created, warning (once per session) like New does.
|
||||
var clamps = MainForm.LoadClampSettings(this);
|
||||
var clamps = MachineSettings.LoadClampSettings(this);
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Data;
|
||||
|
||||
namespace OpenNest.Forms
|
||||
{
|
||||
/// <summary>
|
||||
/// The desktop entry points for the persisted machine clamp settings and nest defaults.
|
||||
/// The warning variants report an unusable file once per session.
|
||||
/// </summary>
|
||||
internal static class MachineSettings
|
||||
{
|
||||
private static bool clampSettingsWarned;
|
||||
private static bool defaultsWarned;
|
||||
|
||||
/// <summary>
|
||||
/// Loads the machine clamp settings for creating new nests (New, BOM import); an
|
||||
/// unreadable or partly invalid file warns once per session, because the new nest's
|
||||
/// clamps may not match the machine.
|
||||
/// </summary>
|
||||
public static ClampSettings LoadClampSettings(IWin32Window owner)
|
||||
{
|
||||
var settings = LoadSavedClampSettings(out var status);
|
||||
|
||||
if (
|
||||
status is ClampSettingsStatus.Invalid or ClampSettingsStatus.Partial
|
||||
&& !clampSettingsWarned
|
||||
)
|
||||
{
|
||||
clampSettingsWarned = true;
|
||||
var problem =
|
||||
status == ClampSettingsStatus.Invalid
|
||||
? "could not be read, so new nests have no clamps"
|
||||
: "has invalid values that were ignored, so new nests may be missing clamps";
|
||||
MessageBox.Show(
|
||||
owner,
|
||||
$"The clamp settings file {problem}:\n{ClampSettings.DefaultPath}\n\nCheck the clamps in Nest > Clamps and use Save as Machine Defaults to rewrite the file.",
|
||||
"Clamps",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Warning
|
||||
);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
/// <summary>The single desktop entry point for the persisted machine clamp settings.</summary>
|
||||
public static ClampSettings LoadSavedClampSettings(out ClampSettingsStatus status) =>
|
||||
ClampSettings.Load(ClampSettings.DefaultPath, out status);
|
||||
|
||||
/// <summary>
|
||||
/// Loads the persisted nest defaults (see <see cref="LoadSavedNestDefaults"/>);
|
||||
/// a corrupt or unreadable file warns once per session.
|
||||
/// </summary>
|
||||
public static NestDefaults LoadNestDefaults()
|
||||
{
|
||||
var defaults = LoadSavedNestDefaults(out var status);
|
||||
|
||||
if (status == NestDefaultsStatus.Invalid && !defaultsWarned)
|
||||
{
|
||||
defaultsWarned = true;
|
||||
MessageBox.Show(
|
||||
$"The nest defaults file could not be read:\n{NestDefaults.DefaultPath}\n\nBuilt-in defaults will be used. Re-save your defaults from Tools > Nest Defaults.",
|
||||
"Nest Defaults",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Warning
|
||||
);
|
||||
}
|
||||
return defaults;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The single desktop entry point for persisted nest defaults. Units
|
||||
/// fall back to the legacy DefaultUnit setting whenever the file is
|
||||
/// missing or unusable or has no valid unit, so New, BOM import and
|
||||
/// the defaults dialog always agree.
|
||||
/// </summary>
|
||||
public static NestDefaults LoadSavedNestDefaults(out NestDefaultsStatus status) =>
|
||||
NestDefaults.Load(
|
||||
NestDefaults.DefaultPath,
|
||||
Properties.Settings.Default.DefaultUnit,
|
||||
out status
|
||||
);
|
||||
}
|
||||
}
|
||||
Generated
+1
-1
@@ -21,7 +21,7 @@ namespace OpenNest.Forms
|
||||
components.Dispose();
|
||||
}
|
||||
if (disposing)
|
||||
updater?.Dispose();
|
||||
updaterStatus?.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,116 +6,11 @@ using OpenNest.Data;
|
||||
using OpenNest.Engine.BestFit;
|
||||
using OpenNest.Forms.Commands;
|
||||
using OpenNest.IO;
|
||||
using OpenNest.Properties;
|
||||
|
||||
namespace OpenNest.Forms
|
||||
{
|
||||
public partial class MainForm
|
||||
{
|
||||
private Nest CreateDefaultNest()
|
||||
{
|
||||
var nest = new Nest();
|
||||
LoadNestDefaults().ApplyTo(nest);
|
||||
// After the defaults so the rail is converted to the new nest's units.
|
||||
LoadClampSettings(this).ApplyToNewNest(nest);
|
||||
return nest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the machine clamp settings for creating new nests (New, BOM import); an
|
||||
/// unreadable or partly invalid file warns once per session, because the new nest's
|
||||
/// clamps may not match the machine.
|
||||
/// </summary>
|
||||
internal static ClampSettings LoadClampSettings(IWin32Window owner)
|
||||
{
|
||||
var settings = LoadSavedClampSettings(out var status);
|
||||
|
||||
if (
|
||||
status is ClampSettingsStatus.Invalid or ClampSettingsStatus.Partial
|
||||
&& !clampSettingsWarned
|
||||
)
|
||||
{
|
||||
clampSettingsWarned = true;
|
||||
var problem =
|
||||
status == ClampSettingsStatus.Invalid
|
||||
? "could not be read, so new nests have no clamps"
|
||||
: "has invalid values that were ignored, so new nests may be missing clamps";
|
||||
MessageBox.Show(
|
||||
owner,
|
||||
$"The clamp settings file {problem}:\n{ClampSettings.DefaultPath}\n\nCheck the clamps in Nest > Clamps and use Save as Machine Defaults to rewrite the file.",
|
||||
"Clamps",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Warning
|
||||
);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static bool clampSettingsWarned;
|
||||
|
||||
/// <summary>The single desktop entry point for the persisted machine clamp settings.</summary>
|
||||
internal static ClampSettings LoadSavedClampSettings(out ClampSettingsStatus status) =>
|
||||
ClampSettings.Load(ClampSettings.DefaultPath, out status);
|
||||
|
||||
/// <summary>
|
||||
/// Loads the persisted nest defaults (see <see cref="LoadSavedNestDefaults"/>);
|
||||
/// a corrupt or unreadable file warns once per session.
|
||||
/// </summary>
|
||||
private NestDefaults LoadNestDefaults()
|
||||
{
|
||||
var defaults = LoadSavedNestDefaults(out var status);
|
||||
|
||||
if (status == NestDefaultsStatus.Invalid && !defaultsWarned)
|
||||
{
|
||||
defaultsWarned = true;
|
||||
MessageBox.Show(
|
||||
$"The nest defaults file could not be read:\n{NestDefaults.DefaultPath}\n\nBuilt-in defaults will be used. Re-save your defaults from Tools > Nest Defaults.",
|
||||
"Nest Defaults",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Warning
|
||||
);
|
||||
}
|
||||
return defaults;
|
||||
}
|
||||
|
||||
private bool defaultsWarned;
|
||||
|
||||
/// <summary>
|
||||
/// The single desktop entry point for persisted nest defaults. Units
|
||||
/// fall back to the legacy DefaultUnit setting whenever the file is
|
||||
/// missing or unusable or has no valid unit, so New, BOM import and
|
||||
/// the defaults dialog always agree.
|
||||
/// </summary>
|
||||
internal static NestDefaults LoadSavedNestDefaults(out NestDefaultsStatus status) =>
|
||||
NestDefaults.Load(
|
||||
NestDefaults.DefaultPath,
|
||||
Properties.Settings.Default.DefaultUnit,
|
||||
out status
|
||||
);
|
||||
|
||||
private string GetNestName(DateTime date, int id)
|
||||
{
|
||||
var year = (date.Year % 100).ToString("D2");
|
||||
var seq = ToBase36(id).PadLeft(3, '2');
|
||||
|
||||
return $"N{year}-{seq}";
|
||||
}
|
||||
|
||||
private static string ToBase36(int value)
|
||||
{
|
||||
const string chars = "2345679ACDEFGHJKLMNPQRSTUVWXYZ";
|
||||
if (value == 0)
|
||||
return chars[0].ToString();
|
||||
|
||||
var result = "";
|
||||
while (value > 0)
|
||||
{
|
||||
result = chars[value % chars.Length] + result;
|
||||
value /= chars.Length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void LoadNest(Nest nest, FormWindowState windowState = FormWindowState.Maximized,
|
||||
Guid remoteId = default, string serverUrl = null, int remoteRevision = 0)
|
||||
{
|
||||
@@ -142,19 +37,7 @@ namespace OpenNest.Forms
|
||||
var windowState =
|
||||
ActiveMdiChild != null ? ActiveMdiChild.WindowState : FormWindowState.Maximized;
|
||||
|
||||
var nest = CreateDefaultNest();
|
||||
|
||||
nest.DateCreated = DateTime.Now;
|
||||
nest.DateLastModified = DateTime.Now;
|
||||
|
||||
if (DateTime.Now.Date != Settings.Default.LastNestCreatedDate.Date)
|
||||
{
|
||||
Settings.Default.NestNumber = 1;
|
||||
Settings.Default.LastNestCreatedDate = DateTime.Now.Date;
|
||||
}
|
||||
|
||||
nest.Name = GetNestName(DateTime.Now, Settings.Default.NestNumber++);
|
||||
LoadNest(nest, windowState);
|
||||
LoadNest(NewNestFactory.Create(this), windowState);
|
||||
}
|
||||
|
||||
private async void Open_Click(object sender, EventArgs e)
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace OpenNest.Forms
|
||||
return;
|
||||
}
|
||||
|
||||
var defaults = LoadSavedClampSettings(out _);
|
||||
var defaults = MachineSettings.LoadSavedClampSettings(out _);
|
||||
using var form = new ClampsForm(nest.PlateDefaults.ClampRail, nest.Units, defaults);
|
||||
if (form.ShowDialog(this) != DialogResult.OK)
|
||||
return;
|
||||
|
||||
@@ -177,7 +177,7 @@ namespace OpenNest.Forms
|
||||
|
||||
private void NestDefaults_Click(object sender, EventArgs e)
|
||||
{
|
||||
using (var form = new NestDefaultsForm(LoadSavedNestDefaults(out _)))
|
||||
using (var form = new NestDefaultsForm(MachineSettings.LoadSavedNestDefaults(out _)))
|
||||
{
|
||||
if (form.ShowDialog(this) == DialogResult.OK)
|
||||
form.GetDefaults().Save(NestDefaults.DefaultPath);
|
||||
|
||||
+21
-149
@@ -3,10 +3,8 @@ using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Data;
|
||||
using OpenNest.Engine.BestFit;
|
||||
using OpenNest.Engine.Jobs;
|
||||
using OpenNest.Forms.Commands;
|
||||
@@ -110,7 +108,7 @@ namespace OpenNest.Forms
|
||||
mnuFile.DropDownItems.Insert(mnuFile.DropDownItems.IndexOf(mnuFileExport), exportNestMenu);
|
||||
mnuFile.DropDownItems.Insert(mnuFile.DropDownItems.IndexOf(mnuFileExit), storageModeMenu);
|
||||
UpdateOverlapMenu();
|
||||
LoadSettings();
|
||||
MainWindowSettings.Load(this);
|
||||
|
||||
var renderer = new ToolStripRenderer(ToolbarTheme.Toolbar);
|
||||
menuStrip1.Renderer = renderer;
|
||||
@@ -124,7 +122,7 @@ namespace OpenNest.Forms
|
||||
this.SetBevel(false);
|
||||
AllowDrop = true;
|
||||
|
||||
LoadPosts();
|
||||
LoadPosts(PostProcessorLoader.Load(PostProcessorLoader.DefaultDirectory));
|
||||
EnableCheck();
|
||||
UpdateStatus();
|
||||
|
||||
@@ -146,84 +144,11 @@ namespace OpenNest.Forms
|
||||
engineComboBox.SelectedItem = EngineSelection.EngineName;
|
||||
engineComboBox.SelectedIndexChanged += EngineComboBox_SelectedIndexChanged;
|
||||
|
||||
InitializeUpdater();
|
||||
updaterStatus = new UpdaterStatus(this, statusStrip1, statusLabel1.Font);
|
||||
updaterStatus.Start();
|
||||
}
|
||||
|
||||
private global::OpenNest.UpdaterService updater;
|
||||
private ToolStripStatusLabel updateStatusLabel;
|
||||
|
||||
/// <summary>
|
||||
/// Starts background update checks against the per-machine feed URL
|
||||
/// (Options dialog; blank disables checks). Never restarts on its
|
||||
/// own -- shows a clickable status item once an update is ready and
|
||||
/// restarts only when the user clicks it.
|
||||
/// </summary>
|
||||
private void InitializeUpdater()
|
||||
{
|
||||
updateStatusLabel = new ToolStripStatusLabel
|
||||
{
|
||||
Visible = false,
|
||||
ForeColor = Color.DarkGreen,
|
||||
Font = new Font(statusLabel1.Font, FontStyle.Bold),
|
||||
};
|
||||
updateStatusLabel.Click += (_, _) =>
|
||||
{
|
||||
if (!updater.HasPendingUpdate)
|
||||
return;
|
||||
|
||||
var result = MessageBox.Show(
|
||||
this,
|
||||
"An OpenNest update has been downloaded. Restart now to apply it? Any unsaved nest will be lost.",
|
||||
"Update ready",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question
|
||||
);
|
||||
if (result == DialogResult.Yes)
|
||||
updater.ApplyAndRestart();
|
||||
};
|
||||
statusStrip1.Items.Add(updateStatusLabel);
|
||||
|
||||
updater = new global::OpenNest.UpdaterService();
|
||||
updater.UpdateReady += version =>
|
||||
{
|
||||
if (IsHandleCreated)
|
||||
BeginInvoke(new System.Action(() => ShowUpdateReady(version)));
|
||||
};
|
||||
updater.Start();
|
||||
}
|
||||
|
||||
private void ShowUpdateReady(string version)
|
||||
{
|
||||
updateStatusLabel.Text = $"Update {version} ready - click to restart";
|
||||
updateStatusLabel.Visible = true;
|
||||
}
|
||||
|
||||
|
||||
private void LoadSettings()
|
||||
{
|
||||
var screen = Screen.PrimaryScreen.Bounds;
|
||||
|
||||
if (screen.Contains(Settings.Default.MainWindowLocation))
|
||||
Location = Settings.Default.MainWindowLocation;
|
||||
|
||||
if (
|
||||
Settings.Default.MainWindowSize.Width <= screen.Width
|
||||
&& Settings.Default.MainWindowSize.Height <= screen.Height
|
||||
)
|
||||
{
|
||||
Size = Settings.Default.MainWindowSize;
|
||||
}
|
||||
|
||||
WindowState = Settings.Default.MainWindowState;
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
Settings.Default.MainWindowLocation = Location;
|
||||
Settings.Default.MainWindowSize = Size;
|
||||
Settings.Default.MainWindowState = WindowState;
|
||||
Settings.Default.Save();
|
||||
}
|
||||
private UpdaterStatus updaterStatus;
|
||||
|
||||
private static ToolStripButton CreateActionButton(string name, string text, Image image,
|
||||
EventHandler click)
|
||||
@@ -242,40 +167,27 @@ namespace OpenNest.Forms
|
||||
return button;
|
||||
}
|
||||
|
||||
private void LoadPosts()
|
||||
private void LoadPosts(PostProcessorLoader.Result posts)
|
||||
{
|
||||
var exepath = Assembly.GetEntryAssembly().Location;
|
||||
var postprocessordir = Path.Combine(Path.GetDirectoryName(exepath), "Posts");
|
||||
|
||||
if (Directory.Exists(postprocessordir))
|
||||
foreach (var postProcessor in posts.PostProcessors)
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(postprocessordir, "*.dll"))
|
||||
{
|
||||
var types = Assembly.LoadFile(file).GetTypes();
|
||||
var postProcessorMenuItem = new ToolStripMenuItem(postProcessor.Name);
|
||||
postProcessorMenuItem.Tag = postProcessor;
|
||||
postProcessorMenuItem.Click += PostProcessor_Click;
|
||||
mnuNestPost.DropDownItems.Add(postProcessorMenuItem);
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (type.GetInterfaces().Contains(typeof(IPostProcessor)) == false)
|
||||
continue;
|
||||
|
||||
var postProcessor = Activator.CreateInstance(type) as IPostProcessor;
|
||||
var postProcessorMenuItem = new ToolStripMenuItem(postProcessor.Name);
|
||||
postProcessorMenuItem.Tag = postProcessor;
|
||||
postProcessorMenuItem.Click += PostProcessor_Click;
|
||||
mnuNestPost.DropDownItems.Add(postProcessorMenuItem);
|
||||
|
||||
var toolbarPost = new ToolStripMenuItem(postProcessor.Name) { Tag = postProcessor };
|
||||
toolbarPost.Click += PostProcessor_Click;
|
||||
postButton.DropDownItems.Add(toolbarPost);
|
||||
|
||||
if (postProcessor is IMaterialProvidingPostProcessor materialProvider)
|
||||
PostProcessorMaterials.AddFrom(materialProvider);
|
||||
}
|
||||
}
|
||||
var toolbarPost = new ToolStripMenuItem(postProcessor.Name) { Tag = postProcessor };
|
||||
toolbarPost.Click += PostProcessor_Click;
|
||||
postButton.DropDownItems.Add(toolbarPost);
|
||||
}
|
||||
|
||||
mnuNestPost.Visible = mnuNestPost.DropDownItems.Count > 0;
|
||||
postButton.Visible = postButton.DropDownItems.Count > 0;
|
||||
|
||||
if (posts.Failures.Count > 0)
|
||||
Shown += (_, _) => MessageBox.Show(this,
|
||||
"These post processors could not be loaded:\n\n" + string.Join("\n", posts.Failures),
|
||||
"Post Processors", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
|
||||
#region Overrides
|
||||
@@ -344,52 +256,12 @@ namespace OpenNest.Forms
|
||||
{
|
||||
base.OnLoad(e);
|
||||
|
||||
MigrateNestTemplate();
|
||||
MainWindowSettings.MigrateNestTemplate();
|
||||
|
||||
if (Settings.Default.CreateNewNestOnOpen)
|
||||
New_Click(this, new EventArgs());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One-time upgrade: converts a legacy .nstdot nest template
|
||||
/// (NestTemplatePath setting) into defaults.json, then clears the
|
||||
/// setting so the template mechanism is never consulted again.
|
||||
/// </summary>
|
||||
private void MigrateNestTemplate()
|
||||
{
|
||||
var templatePath = Settings.Default.NestTemplatePath;
|
||||
if (string.IsNullOrWhiteSpace(templatePath))
|
||||
return;
|
||||
|
||||
// A failed conversion keeps the setting populated so the user
|
||||
// can still find their template file.
|
||||
var converted = true;
|
||||
if (File.Exists(templatePath) && !File.Exists(NestDefaults.DefaultPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
var nest = new NestReader(templatePath) { BestFitStore = BestFitCacheStore.Shared }.Read();
|
||||
NestDefaults.FromNest(nest).Save(NestDefaults.DefaultPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
converted = false;
|
||||
MessageBox.Show(
|
||||
$"The nest template could not be converted to the new defaults file:\n{templatePath}\n\n{ex.Message}\n\nIt will no longer be loaded automatically. Set defaults under Tools > Nest Defaults.",
|
||||
"Nest Template",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Warning
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!converted)
|
||||
return;
|
||||
|
||||
Settings.Default.NestTemplatePath = "";
|
||||
Settings.Default.Save();
|
||||
}
|
||||
|
||||
protected override void OnDragEnter(DragEventArgs e)
|
||||
{
|
||||
base.OnDragEnter(e);
|
||||
@@ -430,7 +302,7 @@ namespace OpenNest.Forms
|
||||
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
base.OnClosing(e);
|
||||
SaveSettings();
|
||||
MainWindowSettings.Save(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Data;
|
||||
using OpenNest.Engine.BestFit;
|
||||
using OpenNest.IO;
|
||||
using OpenNest.Properties;
|
||||
|
||||
namespace OpenNest.Forms
|
||||
{
|
||||
/// <summary>The main window's persisted placement and its one-time settings upgrades.</summary>
|
||||
internal static class MainWindowSettings
|
||||
{
|
||||
/// <summary>Restores the saved placement when it still fits the primary screen.</summary>
|
||||
public static void Load(Form form)
|
||||
{
|
||||
var screen = Screen.PrimaryScreen.Bounds;
|
||||
|
||||
if (screen.Contains(Settings.Default.MainWindowLocation))
|
||||
form.Location = Settings.Default.MainWindowLocation;
|
||||
|
||||
if (
|
||||
Settings.Default.MainWindowSize.Width <= screen.Width
|
||||
&& Settings.Default.MainWindowSize.Height <= screen.Height
|
||||
)
|
||||
{
|
||||
form.Size = Settings.Default.MainWindowSize;
|
||||
}
|
||||
|
||||
form.WindowState = Settings.Default.MainWindowState;
|
||||
}
|
||||
|
||||
public static void Save(Form form)
|
||||
{
|
||||
Settings.Default.MainWindowLocation = form.Location;
|
||||
Settings.Default.MainWindowSize = form.Size;
|
||||
Settings.Default.MainWindowState = form.WindowState;
|
||||
Settings.Default.Save();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One-time upgrade: converts a legacy .nstdot nest template
|
||||
/// (NestTemplatePath setting) into defaults.json, then clears the
|
||||
/// setting so the template mechanism is never consulted again.
|
||||
/// </summary>
|
||||
public static void MigrateNestTemplate()
|
||||
{
|
||||
var templatePath = Settings.Default.NestTemplatePath;
|
||||
if (string.IsNullOrWhiteSpace(templatePath))
|
||||
return;
|
||||
|
||||
// A failed conversion keeps the setting populated so the user
|
||||
// can still find their template file.
|
||||
var converted = true;
|
||||
if (File.Exists(templatePath) && !File.Exists(NestDefaults.DefaultPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
var nest = new NestReader(templatePath) { BestFitStore = BestFitCacheStore.Shared }.Read();
|
||||
NestDefaults.FromNest(nest).Save(NestDefaults.DefaultPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
converted = false;
|
||||
MessageBox.Show(
|
||||
$"The nest template could not be converted to the new defaults file:\n{templatePath}\n\n{ex.Message}\n\nIt will no longer be loaded automatically. Set defaults under Tools > Nest Defaults.",
|
||||
"Nest Template",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Warning
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!converted)
|
||||
return;
|
||||
|
||||
Settings.Default.NestTemplatePath = "";
|
||||
Settings.Default.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Properties;
|
||||
|
||||
namespace OpenNest.Forms
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates File > New nests: machine defaults and clamps, then the next name in the
|
||||
/// day's sequence (<see cref="Settings.NestNumber"/> restarts at 1 each day).
|
||||
/// </summary>
|
||||
internal static class NewNestFactory
|
||||
{
|
||||
private const string Base30Digits = "2345679ACDEFGHJKLMNPQRSTUVWXYZ";
|
||||
|
||||
public static Nest Create(IWin32Window owner)
|
||||
{
|
||||
var nest = CreateDefaultNest(owner);
|
||||
var now = DateTime.Now;
|
||||
|
||||
nest.DateCreated = now;
|
||||
nest.DateLastModified = now;
|
||||
|
||||
if (now.Date != Settings.Default.LastNestCreatedDate.Date)
|
||||
{
|
||||
Settings.Default.NestNumber = 1;
|
||||
Settings.Default.LastNestCreatedDate = now.Date;
|
||||
}
|
||||
|
||||
nest.Name = GetNestName(now, Settings.Default.NestNumber++);
|
||||
return nest;
|
||||
}
|
||||
|
||||
private static Nest CreateDefaultNest(IWin32Window owner)
|
||||
{
|
||||
var nest = new Nest();
|
||||
MachineSettings.LoadNestDefaults().ApplyTo(nest);
|
||||
// After the defaults so the rail is converted to the new nest's units.
|
||||
MachineSettings.LoadClampSettings(owner).ApplyToNewNest(nest);
|
||||
return nest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// "N" + two-digit year + the sequence in an unambiguous base-30 alphabet, padded to
|
||||
/// three digits with its zero digit ('2').
|
||||
/// </summary>
|
||||
public static string GetNestName(DateTime date, int id)
|
||||
{
|
||||
var year = (date.Year % 100).ToString("D2");
|
||||
var seq = ToBase30(id).PadLeft(3, Base30Digits[0]);
|
||||
|
||||
return $"N{year}-{seq}";
|
||||
}
|
||||
|
||||
private static string ToBase30(int value)
|
||||
{
|
||||
if (value == 0)
|
||||
return Base30Digits[0].ToString();
|
||||
|
||||
var result = "";
|
||||
while (value > 0)
|
||||
{
|
||||
result = Base30Digits[value % Base30Digits.Length] + result;
|
||||
value /= Base30Digits.Length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace OpenNest.Forms
|
||||
{
|
||||
/// <summary>
|
||||
/// Background update checks against the per-machine feed URL (Options dialog; blank
|
||||
/// disables checks) and the status-bar item that announces a downloaded update. Never
|
||||
/// restarts on its own: it restarts only when the user clicks the item and confirms.
|
||||
/// </summary>
|
||||
internal sealed class UpdaterStatus : IDisposable
|
||||
{
|
||||
private readonly Form owner;
|
||||
private readonly UpdaterService updater = new();
|
||||
private readonly ToolStripStatusLabel label;
|
||||
|
||||
public UpdaterStatus(Form owner, StatusStrip statusStrip, Font baseFont)
|
||||
{
|
||||
this.owner = owner;
|
||||
label = new ToolStripStatusLabel
|
||||
{
|
||||
Visible = false,
|
||||
ForeColor = Color.DarkGreen,
|
||||
Font = new Font(baseFont, FontStyle.Bold),
|
||||
};
|
||||
label.Click += Label_Click;
|
||||
statusStrip.Items.Add(label);
|
||||
|
||||
updater.UpdateReady += version =>
|
||||
{
|
||||
if (owner.IsHandleCreated)
|
||||
owner.BeginInvoke(new Action(() => ShowUpdateReady(version)));
|
||||
};
|
||||
}
|
||||
|
||||
public void Start() => updater.Start();
|
||||
|
||||
public void Dispose() => updater.Dispose();
|
||||
|
||||
private void Label_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (!updater.HasPendingUpdate)
|
||||
return;
|
||||
|
||||
var result = MessageBox.Show(
|
||||
owner,
|
||||
"An OpenNest update has been downloaded. Restart now to apply it? Any unsaved nest will be lost.",
|
||||
"Update ready",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question
|
||||
);
|
||||
if (result == DialogResult.Yes)
|
||||
updater.ApplyAndRestart();
|
||||
}
|
||||
|
||||
private void ShowUpdateReady(string version)
|
||||
{
|
||||
label.Text = $"Update {version} ready - click to restart";
|
||||
label.Visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
/// <summary>
|
||||
/// Discovers post processors in the desktop's <c>Posts/</c> directory and registers the
|
||||
/// materials they provide. Each DLL loads in isolation: one that fails is reported in
|
||||
/// <see cref="Result.Failures"/> instead of stopping the others or startup.
|
||||
/// </summary>
|
||||
public static class PostProcessorLoader
|
||||
{
|
||||
public sealed record Result(IReadOnlyList<IPostProcessor> PostProcessors, IReadOnlyList<string> Failures);
|
||||
|
||||
public static string DefaultDirectory =>
|
||||
Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Posts");
|
||||
|
||||
public static Result Load(string directory)
|
||||
{
|
||||
var postProcessors = new List<IPostProcessor>();
|
||||
var failures = new List<string>();
|
||||
if (!Directory.Exists(directory))
|
||||
return new Result(postProcessors, failures);
|
||||
|
||||
foreach (var file in Directory.GetFiles(directory, "*.dll"))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Instantiate the whole DLL before registering any of it.
|
||||
var loaded = Assembly.LoadFile(file).GetTypes()
|
||||
.Where(type => type.GetInterfaces().Contains(typeof(IPostProcessor)))
|
||||
.Select(type => (IPostProcessor)Activator.CreateInstance(type))
|
||||
.ToList();
|
||||
foreach (var postProcessor in loaded)
|
||||
{
|
||||
postProcessors.Add(postProcessor);
|
||||
if (postProcessor is IMaterialProvidingPostProcessor materialProvider)
|
||||
PostProcessorMaterials.AddFrom(materialProvider);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var reason = ex is TargetInvocationException { InnerException: { } inner } ? inner : ex;
|
||||
failures.Add($"{Path.GetFileName(file)}: {reason.Message}");
|
||||
}
|
||||
}
|
||||
return new Result(postProcessors, failures);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user