diff --git a/src/Nonemm.Session/OperatingPosition.cs b/src/Nonemm.Session/OperatingPosition.cs index 8239a63..f78d705 100644 --- a/src/Nonemm.Session/OperatingPosition.cs +++ b/src/Nonemm.Session/OperatingPosition.cs @@ -341,20 +341,24 @@ public sealed class OperatingPosition return filled; } - /// Fills what the call history knows into the exchange boxes that are still - /// empty. What the other station actually sends wins, so a box with - /// something in it is left alone. Returns true when anything was filled. + /// Fills what is already known about the station into the exchange boxes + /// that are still empty. What the other station actually sends wins, so a + /// 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() { CallHistoryEntry? known = Session.History.Find(Entry.Call); - if (known is null) - { - return false; - } bool filled = false; 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) { continue; @@ -369,6 +373,27 @@ public sealed class OperatingPosition 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 /// 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. diff --git a/tests/Nonemm.Session.Tests/OperatingPositionTests.cs b/tests/Nonemm.Session.Tests/OperatingPositionTests.cs index 6ffae66..0505866 100644 --- a/tests/Nonemm.Session.Tests/OperatingPositionTests.cs +++ b/tests/Nonemm.Session.Tests/OperatingPositionTests.cs @@ -482,6 +482,32 @@ public class OperatingPositionTests 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. [Fact] public void TheStateFillsASectionWhenTheFileHasNoSection()