Count down the band change timer in the status bar
SOBandChangeTimerDuration and MOBandChangeTimerDuration say how long a station has to stay on a band it has just moved to. Both were read by nothing: N1MM counts them down in its info window, which this program does not have. The countdown now stands in the entry window's status bar, next to the contacts and multipliers, ticking with the clock that is already there. BandChangeRules.StayLeft works it out from the log, starting the stay at the minute after the contact that changed band, which is where N1MM starts it. Which contests show it is decided at runtime rather than by a list: the rules come from the contest for the entry's own category, so a file that asks for no stay, or asks for one only of single-op entries while the station is entered multi-op, shows nothing at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
This commit is contained in:
@@ -123,16 +123,18 @@
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
<!-- N1MM's status strip: what just happened on the left, then the contacts
|
||||
and multipliers, then the score -->
|
||||
<!-- N1MM's status strip: what just happened on the left, then the band
|
||||
change timer, the contacts and multipliers, and the score -->
|
||||
<Border DockPanel.Dock="Bottom" Padding="6,3" BorderThickness="0,1,0,0"
|
||||
BorderBrush="#33808080">
|
||||
<Grid ColumnDefinitions="*,Auto,Auto">
|
||||
<Grid ColumnDefinitions="*,Auto,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=""
|
||||
<TextBlock Grid.Column="1" Name="BandTimerText" FontSize="11" Text=""
|
||||
Margin="12,0,0,0" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="2" Name="BreakdownText" FontSize="11" Opacity="0.8" Text=""
|
||||
Margin="12,0" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="2" Name="ScoreText" FontSize="11" FontWeight="Bold" Text=""
|
||||
<TextBlock Grid.Column="3" Name="ScoreText" FontSize="11" FontWeight="Bold" Text=""
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -747,6 +747,7 @@ public sealed partial class EntryWindow : Window
|
||||
ShowLights();
|
||||
|
||||
ShowScore();
|
||||
ShowBandTimer();
|
||||
StatusText.Text = message.Length > 0 ? message : CountryLine();
|
||||
|
||||
Verdict? verdict = Logging.Verdict();
|
||||
@@ -914,7 +915,31 @@ public sealed partial class EntryWindow : Window
|
||||
? $"sending {Logging?.SentNumber}"
|
||||
: Verdicts.Describe(verdict);
|
||||
|
||||
private void UpdateClock() => ClockText.Text = DateTime.UtcNow.ToString("HH:mm:ss") + "Z";
|
||||
private void UpdateClock()
|
||||
{
|
||||
ClockText.Text = DateTime.UtcNow.ToString("HH:mm:ss") + "Z";
|
||||
ShowBandTimer();
|
||||
}
|
||||
|
||||
/// The countdown N1MM puts in its info window: how long the station still
|
||||
/// has to stay on the band it last moved to. It is here only while the
|
||||
/// contest asks for a stay and the entry's category is one the contest
|
||||
/// counts band changes for, so most contests show nothing.
|
||||
private void ShowBandTimer()
|
||||
{
|
||||
BandChangeRules rules = Logging is null
|
||||
? BandChangeRules.None
|
||||
: Logging.Contest.BandChangesFor(Logging.Log.Entry);
|
||||
if (!rules.HasStayTimer)
|
||||
{
|
||||
BandTimerText.Text = "";
|
||||
return;
|
||||
}
|
||||
TimeSpan left = rules.StayLeft(Logging!.Log.Qsos, DateTime.UtcNow);
|
||||
string what = rules.CountsModeChanges ? "band/mode" : "band";
|
||||
BandTimerText.Text = $"{what} {left.Minutes + (left.Hours * 60):00}:{left.Seconds:00}";
|
||||
BandTimerText.Opacity = left > TimeSpan.Zero ? 1.0 : 0.5;
|
||||
}
|
||||
|
||||
/// What just happened, in the left of the status bar.
|
||||
private void Status(string text)
|
||||
|
||||
@@ -8,6 +8,10 @@ namespace Nonemm.Contests;
|
||||
/// `Max` is how many changes the entry is allowed. The window the count is
|
||||
/// taken over is the clock hour, unless `PerContest` counts the whole contest
|
||||
/// or `PerPeriod` counts the hour up to now.
|
||||
///
|
||||
/// `MinimumStay` is how long the station has to stay on a band it has just
|
||||
/// moved to. Both the count and the stay are worked out from the log, as
|
||||
/// N1MM's are: the contact that lands on another band is the change.
|
||||
public sealed record BandChangeRules(
|
||||
int Max,
|
||||
bool PerContest = false,
|
||||
@@ -20,6 +24,9 @@ public sealed record BandChangeRules(
|
||||
|
||||
public bool IsCounted => Max > 0;
|
||||
|
||||
/// True for a contest that says how long a station has to stay on a band.
|
||||
public bool HasStayTimer => MinimumStay > TimeSpan.Zero;
|
||||
|
||||
/// How many band changes fall in the window that ends at `now`. The first
|
||||
/// contact of the log is not a change.
|
||||
public int CountedIn(IEnumerable<Qso> qsos, DateTime now)
|
||||
@@ -36,6 +43,42 @@ public sealed record BandChangeRules(
|
||||
return changes;
|
||||
}
|
||||
|
||||
/// How much of the stay on this band is left. Zero once the time is up,
|
||||
/// and zero for a log that has not changed band yet.
|
||||
///
|
||||
/// The stay is counted from the minute after the contact that changed
|
||||
/// band, which is where N1MM starts it: a contact at 12:00:40 puts the
|
||||
/// station on the band from 12:01.
|
||||
public TimeSpan StayLeft(IEnumerable<Qso> qsos, DateTime now)
|
||||
{
|
||||
if (!HasStayTimer || LastChange(qsos) is not { } changed)
|
||||
{
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
TimeSpan left = MinimumStay - (now - NextMinute(changed));
|
||||
return left > TimeSpan.Zero ? left : TimeSpan.Zero;
|
||||
}
|
||||
|
||||
/// When the last contact that moved the station to another band was, or
|
||||
/// null while every contact has been on one band.
|
||||
private DateTime? LastChange(IEnumerable<Qso> qsos)
|
||||
{
|
||||
List<Qso> ordered = qsos.OrderBy(q => q.TimestampUtc).ToList();
|
||||
DateTime? found = null;
|
||||
for (int at = 1; at < ordered.Count; at++)
|
||||
{
|
||||
if (IsChange(ordered[at - 1], ordered[at]))
|
||||
{
|
||||
found = ordered[at].TimestampUtc;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
private static DateTime NextMinute(DateTime when) =>
|
||||
new DateTime(when.Year, when.Month, when.Day, when.Hour, when.Minute, 0, when.Kind)
|
||||
.AddMinutes(1);
|
||||
|
||||
private bool IsChange(Qso before, Qso after) =>
|
||||
before.Band != after.Band
|
||||
|| (CountsModeChanges && before.Mode.Category != after.Mode.Category);
|
||||
|
||||
Reference in New Issue
Block a user