diff --git a/README.md b/README.md index 7e2ee95..fb72c05 100644 --- a/README.md +++ b/README.md @@ -314,6 +314,25 @@ in colours for a dark screen. Config ▸ Manage Skins, Colors and Fonts picks on and the choice is kept in `settings.json` as `Theme`. Fonts and skins are not part of it: the name is N1MM's, the dialog only changes colours. +### The status bar + +Along the bottom of the entry window is N1MM's status bar, in three parts: + +- On the left, where the call being typed is from, in N1MM's own shape: + `OM: EU -> Slovak Republic, Zn 15`. The zone is the one the contest counts in, + so IARU shows the ITU zone and CQ WW the CQ zone. When something has just + happened — a contact logged, a call stacked, a download finished — that stands + there instead until the next keystroke. +- In the middle, the contacts and multipliers: `10/7/8` is ten contacts, seven + zones and eight countries, one number per multiplier the contest counts. It + reads `No Score` before the first contact, as N1MM's does. +- On the right, the score. + +The numbers were in the line at the top of the window before. They are here now, +where N1MM keeps them, so the top line is the contest name alone. The bar under +the callsign box says what working the call would bring — the points and any new +multiplier — and no longer repeats the country. + ### Where the other station is Under the entry window is the line N1MM writes there: the beam heading, the diff --git a/src/Nonemm.App/Windows/EntryWindow.axaml b/src/Nonemm.App/Windows/EntryWindow.axaml index d176e33..7786e85 100644 --- a/src/Nonemm.App/Windows/EntryWindow.axaml +++ b/src/Nonemm.App/Windows/EntryWindow.axaml @@ -123,6 +123,20 @@ + + + + + + + + + @@ -183,7 +197,6 @@ - diff --git a/src/Nonemm.App/Windows/EntryWindow.axaml.cs b/src/Nonemm.App/Windows/EntryWindow.axaml.cs index f8f9d5b..031c531 100644 --- a/src/Nonemm.App/Windows/EntryWindow.axaml.cs +++ b/src/Nonemm.App/Windows/EntryWindow.axaml.cs @@ -37,6 +37,11 @@ public sealed partial class EntryWindow : Window private readonly Dictionary openWindows = []; private bool updating; + /// What the last thing the operator did had to say. It stands in the status + /// bar until the next keystroke, and then the country of the call being + /// typed takes the place of it. + private string message = ""; + public EntryWindow(AppSession session, int radioNumber = 1) { this.session = session; @@ -315,6 +320,8 @@ public sealed partial class EntryWindow : Window { ResetEsm(); } + // typing answers whatever the status bar was reporting + message = ""; Refresh(); } @@ -737,6 +744,9 @@ public sealed partial class EntryWindow : Window UserTextText.Text = Logging.Session.History.Find(Logging.Entry.Call)?.UserText ?? ""; ShowLights(); + ShowScore(); + StatusText.Text = message.Length > 0 ? message : CountryLine(); + Verdict? verdict = Logging.Verdict(); ShowCallColour(verdict); VerdictBorder.Background = Verdicts.Background(verdict); @@ -783,19 +793,57 @@ public sealed partial class EntryWindow : Window && !call.All(char.IsAsciiDigit); } + /// The contacts and multipliers so far, and the score. N1MM keeps them in + /// the status bar in this shape: the contacts, then one number per + /// multiplier the contest counts, separated by slashes. + private void ShowScore() + { + if (Logging is null) + { + BreakdownText.Text = ""; + ScoreText.Text = ""; + return; + } + if (Logging.Log.Qsos.Count == 0) + { + BreakdownText.Text = "No Score"; + ScoreText.Text = ""; + return; + } + BreakdownText.Text = string.Join( + "/", + new[] { Logging.Log.Tally.Qsos }.Concat( + Logging.Contest.MultiplierNames.Select( + (_, at) => Logging.Log.Tally.MultiplierCount(at + 1)))); + ScoreText.Text = $"{Logging.Log.TotalScore:N0}"; + } + private string ContestLine() { if (Logging is null) { return ""; } - string mults = string.Join( - " ", - Logging.Contest.MultiplierNames.Select( - (name, at) => $"{name} {Logging.Log.Tally.MultiplierCount(at + 1)}")); string radio = session.Radios.Count > 1 ? $" · radio {session.ActiveRadioNumber}" : ""; - return $"{Logging.Contest.DisplayName} · {Logging.Log.Tally.Qsos} Q · " + - $"{Logging.Log.Tally.Points} pts · {mults} · {Logging.Log.TotalScore:N0}{radio}"; + return $"{Logging.Contest.DisplayName}{radio}"; + } + + /// N1MM's status bar says where the call being typed is from, in this + /// shape: `OM: EU -> Slovak Republic, Zn 15`. The zone is the one the + /// contest counts in. A message from something the operator just did takes + /// the place of it until the next keystroke. + private string CountryLine() + { + if (Logging is null || Logging.Entry.Call.Trim().Length == 0) + { + return ""; + } + if (Logging.Country() is not { } found) + { + return "unknown country"; + } + int zone = Logging.Contest.UsesItuZones ? found.ItuZone : found.CqZone; + return $"{found.Entity.PrimaryPrefix}: {found.Continent} -> {found.Entity.Name}, Zn {zone}"; } /// The two lights N1MM has beside the run switch: the left one says a radio @@ -857,21 +905,21 @@ public sealed partial class EntryWindow : Window : "no bearing — the country file does not place this callsign"; } - private string VerdictLine(Verdict? verdict) - { - if (Logging is null || Logging.Entry.Call.Trim().Length == 0) - { - return $"sending {Logging?.SentNumber}"; - } - string country = Logging.Country() is { } found - ? $"{found.Entity.Name} · {found.Continent} · CQ {found.CqZone} · ITU {found.ItuZone}" - : "unknown country"; - return $"{country} — {Verdicts.Describe(verdict)}"; - } + /// What working the call being typed would bring. Where it is from is in + /// the status bar rather than here, so neither says it twice. + private string VerdictLine(Verdict? verdict) => + Logging is null || Logging.Entry.Call.Trim().Length == 0 + ? $"sending {Logging?.SentNumber}" + : Verdicts.Describe(verdict); private void UpdateClock() => ClockText.Text = DateTime.UtcNow.ToString("HH:mm:ss") + "Z"; - private void Status(string text) => StatusText.Text = text; + /// What just happened, in the left of the status bar. + private void Status(string text) + { + message = text; + StatusText.Text = text; + } /// The labels come from the message file, so what the buttons say is what /// the operator wrote. Right-clicking one opens the editor, as it does on diff --git a/src/Nonemm.Contests/Contest.cs b/src/Nonemm.Contests/Contest.cs index 105b78f..5e5f754 100644 --- a/src/Nonemm.Contests/Contest.cs +++ b/src/Nonemm.Contests/Contest.cs @@ -41,6 +41,11 @@ public interface Contest /// WARC bands, so only general logging says yes, as in N1MM. bool ShowsWarcBands => false; + /// True for a contest counted in ITU zones rather than CQ zones, which + /// decides which zone the status bar shows for a call. N1MM asks its + /// contests the same question. + bool UsesItuZones => false; + string SentExchangeFor(StationInfo me); int PointsFor(QsoContext qso); diff --git a/src/Nonemm.Contests/Rules/IaruHf.cs b/src/Nonemm.Contests/Rules/IaruHf.cs index 0aad1ff..a366435 100644 --- a/src/Nonemm.Contests/Rules/IaruHf.cs +++ b/src/Nonemm.Contests/Rules/IaruHf.cs @@ -20,6 +20,8 @@ public sealed class IaruHf : Contest public IReadOnlyList MultiplierNames => ["Zones", "HQ"]; + public bool UsesItuZones => true; + public DupeScope DupeScope => DupeScope.PerBandAndMode; public bool HasSerialNumbers => false;