Put N1MM's status bar along the bottom of the entry window
Three parts, as N1MM has them: what just happened on the left, the contacts and multipliers in the middle, the score on the right. The left says 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 Contest.UsesItuZones answers that the way N1MM's ZoneType does, and IARU overrides it. A message from something the operator just did stands there until the next keystroke. The middle is N1MM's breakdown: the contacts and then one number per multiplier the contest counts, separated by slashes, and No Score before the first contact. Points are not in it, because they are not in N1MM's; the score window has them. Those numbers were in the line at the top of the window, which now carries the contest name alone, and the country was in the bar under the callsign box, which now says only what working the call would bring. Neither is said twice. Looked at under Xvfb: the country line for OM3KFF and then G4AB, 10/7/8 and 300 beside them, and a Mark message standing in the left until the next keystroke. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
This commit is contained in:
19
README.md
19
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
|
||||
|
||||
@@ -123,6 +123,20 @@
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
<!-- N1MM's status strip: what just happened on the left, then the contacts
|
||||
and multipliers, then the score -->
|
||||
<Border DockPanel.Dock="Bottom" Padding="6,3" BorderThickness="0,1,0,0"
|
||||
BorderBrush="#33808080">
|
||||
<Grid ColumnDefinitions="*,Auto,Auto">
|
||||
<TextBlock Name="StatusText" FontSize="11" Opacity="0.8" Text=""
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="1" Name="BreakdownText" FontSize="11" Opacity="0.8" Text=""
|
||||
Margin="12,0" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="2" Name="ScoreText" FontSize="11" FontWeight="Bold" Text=""
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border DockPanel.Dock="Left" Padding="6,6,4,6" BorderThickness="0,0,1,0"
|
||||
BorderBrush="#33808080">
|
||||
<Grid Name="BandButtons" ColumnDefinitions="Auto,Auto" />
|
||||
@@ -183,7 +197,6 @@
|
||||
<TextBlock Name="UserTextText" FontSize="11" Margin="2,2,0,0" Text=""
|
||||
Foreground="#2980B9" TextTrimming="CharacterEllipsis" />
|
||||
|
||||
<TextBlock Name="StatusText" FontSize="11" Opacity="0.7" Margin="2,4,0,0" Text="" />
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
|
||||
@@ -37,6 +37,11 @@ public sealed partial class EntryWindow : Window
|
||||
private readonly Dictionary<Type, Window> 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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -20,6 +20,8 @@ public sealed class IaruHf : Contest
|
||||
|
||||
public IReadOnlyList<string> MultiplierNames => ["Zones", "HQ"];
|
||||
|
||||
public bool UsesItuZones => true;
|
||||
|
||||
public DupeScope DupeScope => DupeScope.PerBandAndMode;
|
||||
|
||||
public bool HasSerialNumbers => false;
|
||||
|
||||
Reference in New Issue
Block a user