Fill the zone box from the log and the country file on space

Without a call history file, the CQ zone box stayed empty after the
callsign. It now takes the zone from the call history file, else from the
last logged contact with the call, else from the country file, as N1MM
does. A box that already holds text is left alone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 05:24:56 +00:00
co-authored by Claude Sonnet 5.5
parent 072c04883e
commit 3a39b40edb
2 changed files with 59 additions and 8 deletions
+33 -8
View File
@@ -341,20 +341,24 @@ public sealed class OperatingPosition
return filled; return filled;
} }
/// Fills what the call history knows into the exchange boxes that are still /// Fills what is already known about the station into the exchange boxes
/// empty. What the other station actually sends wins, so a box with /// that are still empty. What the other station actually sends wins, so a
/// something in it is left alone. Returns true when anything was filled. /// box with something in it is left alone. A zone box takes the call
/// history file first, then the zone logged on the last contact with the
/// call, then the zone the country file gives for the callsign, which is
/// what N1MM does. Returns true when anything was filled.
public bool FillFromHistory() public bool FillFromHistory()
{ {
CallHistoryEntry? known = Session.History.Find(Entry.Call); CallHistoryEntry? known = Session.History.Find(Entry.Call);
if (known is null)
{
return false;
}
bool filled = false; bool filled = false;
for (int at = 0; at < Entry.Exchange.Count; at++) for (int at = 0; at < Entry.Exchange.Count; at++)
{ {
string value = ExchangeSlots.ValueFrom(known, Entry.Exchange[at]); ExchangeField field = Entry.Exchange[at];
string value = known is null ? "" : ExchangeSlots.ValueFrom(known, field);
if (value.Length == 0)
{
value = KnownZone(field);
}
if (value.Length == 0 || Entry[at + 1].Trim().Length > 0) if (value.Length == 0 || Entry[at + 1].Trim().Length > 0)
{ {
continue; continue;
@@ -369,6 +373,27 @@ public sealed class OperatingPosition
return filled; return filled;
} }
/// The zone to suggest for a zone box: the one on the newest earlier
/// contact with this call that has one, else the country file's zone.
/// Empty for any other box.
private string KnownZone(ExchangeField field)
{
if (field.Kind is not (ExchangeFieldKind.CqZone or ExchangeFieldKind.ItuZone))
{
return "";
}
string logged = WorkedBefore()
.Select(qso => ExchangeSlots.ValueOf(qso, field.Slot))
.FirstOrDefault(value => value.Length > 0) ?? "";
if (logged.Length > 0)
{
return logged;
}
CountryLookup? their = Country();
int zone = their is null ? 0 : field.Kind == ExchangeFieldKind.CqZone ? their.CqZone : their.ItuZone;
return zone > 0 ? zone.ToString() : "";
}
/// Moves to the next box the operator types in. On top of the report boxes /// Moves to the next box the operator types in. On top of the report boxes
/// the entry itself steps over, a contest can say a box is not for this /// the entry itself steps over, a contest can say a box is not for this
/// station: CQ WW RTTY asks only the US and Canada for a state. /// station: CQ WW RTTY asks only the US and Canada for a state.
@@ -482,6 +482,32 @@ public class OperatingPositionTests
Assert.Equal("25", session.Entry.ValueOf(ExchangeSlot.Zone)); Assert.Equal("25", session.Entry.ValueOf(ExchangeSlot.Zone));
} }
/// N1MM suggests the country's zone when the call is settled.
[Fact]
public void TheCountryFileZoneFillsTheZoneBox()
{
OperatingPosition session = Session();
session.Entry.Call = "JA1XYZ";
Assert.True(session.FillFromHistory());
Assert.Equal("25", session.Entry.ValueOf(ExchangeSlot.Zone));
}
/// A zone copied on an earlier contact beats the country's own.
[Fact]
public void TheZoneLoggedBeforeBeatsTheCountryFileZone()
{
OperatingPosition session = Session();
Type(session, "DL1ABC", "40");
session.LogContact();
session.Wipe();
session.Entry.Call = "DL1ABC";
session.FillFromHistory();
Assert.Equal("40", session.Entry.ValueOf(ExchangeSlot.Zone));
}
/// A file for a section contest often carries the state instead. /// A file for a section contest often carries the state instead.
[Fact] [Fact]
public void TheStateFillsASectionWhenTheFileHasNoSection() public void TheStateFillsASectionWhenTheFileHasNoSection()