Check the entry boxes by the rules the log window uses

The entry window took any text and only the log window checked it, so the
same value was accepted in one place and refused in the other. The rules
now live in one file, ExchangeCheck, which both windows call.

OperatingPosition.EntryProblem names the first box that cannot be logged
and why. LogContact and SaveQuickEdit throw on one, so a caller that has
not checked cannot write nonsense to the log. The entry window checks
before it logs, puts the reason in the status line and the cursor in the
box to fix, which covers Enter, the Log It button, ESM and {LOG}.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015NZkFxHde1iifAQemFTHxf
This commit is contained in:
2026-09-10 11:13:38 +00:00
co-authored by Claude Opus 5
parent 95e1405be1
commit ab9730b26d
6 changed files with 175 additions and 33 deletions
+17 -2
View File
@@ -209,14 +209,24 @@ public sealed class OperatingPosition
&& !call.All(c => char.IsAsciiDigit(c) || c == '.')
&& !string.Equals(call, Me.Callsign, StringComparison.OrdinalIgnoreCase);
/// The first entry box holding something that cannot be logged, and why.
/// Null when everything typed can go in the log. The log window checks the
/// same way, so a value refused here is refused there.
public EntryProblem? EntryProblem() => ExchangeCheck.Problem(Entry, Mode);
/// Logs what is typed and clears the entry. Throws when the entry is not
/// complete, so a caller that has not checked cannot lose an exchange.
/// complete or holds a value the contest will not take, so a caller that
/// has not checked cannot lose an exchange or log nonsense.
public Qso LogContact()
{
if (!Entry.IsComplete)
{
throw new InvalidOperationException($"the exchange for {Entry.Call} is not complete");
}
if (EntryProblem() is { } problem)
{
throw new InvalidOperationException(problem.Message);
}
StampSentTime();
Qso stored = Session.Add(Noted(BuildQso(Entry.Call.Trim())));
Entry.Clear();
@@ -384,13 +394,18 @@ public sealed class OperatingPosition
}
/// Writes the boxes back to the contact being quick edited and leaves quick
/// edit. Null when no contact is being edited.
/// edit. Null when no contact is being edited. Throws on a value the
/// contest will not take, the same as logging a new contact does.
public Qso? SaveQuickEdit()
{
if (Editing is null)
{
return null;
}
if (EntryProblem() is { } problem)
{
throw new InvalidOperationException(problem.Message);
}
Qso saved = Noted(ApplyExchange(CountryFields.Apply(
Editing with { Call = Callsign.Parse(Entry.Call.Trim()) }, Session.Countries)));
Session.Update(saved);