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;