Say how many QTCs are left with the station being worked
WAE gives a point for every QTC line, so an operator needs to know what is still there to exchange. The entry window now carries N1MM's QTCinfo line in WAE: how many QTC lines have already gone with the station in the callsign box, how many more may go, and how many contacts are left to report to anybody. The QTC window shows the same line. The numbers come from the log rows, the way N1MM works them out: - exchanged is every QTC row with that call, both directions and all bands, and ten is the lot - available is every contact not reported to anybody yet, without the dupes and without the station itself, which is N1MM's QTCRecordsetCount - what is left with the station is what is free of its ten, and no more than the contacts there are to report when we are the one reporting Europe on CW and SSB only takes traffic down, so its line leaves the last number out, as N1MM's does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
This commit is contained in:
@@ -211,6 +211,11 @@
|
||||
<TextBlock Name="PathText" FontSize="11" Opacity="0.75" Margin="2,8,0,0" Text=""
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
|
||||
<!-- WAE only: N1MM's QTCinfo label, which says how much traffic may
|
||||
still be exchanged with the station being worked -->
|
||||
<TextBlock Name="QtcInfoText" FontSize="11" Margin="2,2,0,0" Text="" IsVisible="False"
|
||||
Foreground="#2980B9" TextTrimming="CharacterEllipsis" />
|
||||
|
||||
<TextBlock Name="UserTextText" FontSize="11" Margin="2,2,0,0" Text=""
|
||||
Foreground="#2980B9" TextTrimming="CharacterEllipsis" />
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using Nonemm.Contests;
|
||||
using Nonemm.Contests.Rules;
|
||||
using Nonemm.Core;
|
||||
using Nonemm.App.Theming;
|
||||
using Nonemm.Session;
|
||||
@@ -799,6 +800,7 @@ public sealed partial class EntryWindow : Window
|
||||
Title = TitleLine();
|
||||
ShowCallFrame();
|
||||
PathText.Text = PathLine();
|
||||
ShowQtcInfo();
|
||||
UserTextText.Text = Logging.Session.History.Find(Logging.Entry.Call)?.UserText ?? "";
|
||||
ShowLights();
|
||||
|
||||
@@ -950,6 +952,24 @@ public sealed partial class EntryWindow : Window
|
||||
+ $"{path.DistanceKm:N0} km · {path.DistanceKm * MilesPerKilometre:N0} mi{sun}";
|
||||
}
|
||||
|
||||
/// WAE's QTC tally for the station being worked, which N1MM keeps in a
|
||||
/// blue label under the entry boxes: how many lines have gone with it, how
|
||||
/// many more may go, and how many contacts are left to report to anybody.
|
||||
/// The line is only there in WAE, because no other contest has traffic.
|
||||
private void ShowQtcInfo()
|
||||
{
|
||||
if (Logging is null || Logging.Contest is not Wae)
|
||||
{
|
||||
QtcInfoText.IsVisible = false;
|
||||
return;
|
||||
}
|
||||
string call = Logging.Entry.Call.Trim();
|
||||
QtcCounts counts = new QtcTraffic(Logging)
|
||||
.CountsFor(Callsign.Parse(call.Length >= 3 ? call : ""));
|
||||
QtcInfoText.Text = counts.IsFull ? $"{counts.Text} — at the limit" : counts.Text;
|
||||
QtcInfoText.IsVisible = true;
|
||||
}
|
||||
|
||||
/// A blank line where a bearing should be is a puzzle. This says which of
|
||||
/// the two ends could not be placed and what to do about it.
|
||||
private string WhyNoPath()
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
<MenuItem Header="Setup…" Click="OnSetup" />
|
||||
</ContextMenu>
|
||||
</StackPanel.ContextMenu>
|
||||
<!-- the same tally the entry window keeps, so the operator can see how
|
||||
much traffic is left with this station without leaving the window -->
|
||||
<TextBlock Name="CountsText" FontSize="11" Foreground="#2980B9" Text="" />
|
||||
<StackPanel Name="DirectionPanel" Orientation="Horizontal" Spacing="8" Margin="0,0,0,4">
|
||||
<RadioButton Name="ReceiveButton" Content="Receive" GroupName="direction" IsChecked="True" />
|
||||
<RadioButton Name="SendButton" Content="Send" GroupName="direction" />
|
||||
|
||||
@@ -98,7 +98,7 @@ public sealed partial class QtcWindow : Window
|
||||
return isSending
|
||||
? IsRtty
|
||||
? "Send All reads the whole series out — Snd n sends one QTC again"
|
||||
: $"{traffic.Remaining(station)} of the ten QTCs for {station.Text} are still free"
|
||||
: "Nothing goes out on SSB: read the lines out yourself, then Close to log them."
|
||||
: "Type the header, then a line per QTC: time, callsign, serial number.";
|
||||
}
|
||||
return isSending
|
||||
@@ -132,6 +132,7 @@ public sealed partial class QtcWindow : Window
|
||||
private void Reset()
|
||||
{
|
||||
Title = isSending ? $"Send QTC · {station.Text}" : $"Receive QTC · {station.Text}";
|
||||
CountsText.Text = traffic.CountsFor(station).Text;
|
||||
KeysText.Text = KeysLine();
|
||||
// N1MM's labels: the buttons say what they do in the direction the
|
||||
// traffic is going, and the ones with nothing to do are hidden
|
||||
|
||||
@@ -15,6 +15,28 @@ public enum QtcDirection
|
||||
Either,
|
||||
}
|
||||
|
||||
/// The QTC tally for one station, as N1MM writes it beside the callsign:
|
||||
/// how many lines have already gone with it, how many more may go, and how
|
||||
/// many logged contacts are left to report to anybody.
|
||||
public sealed record QtcCounts(QtcDirection Role, int Exchanged, int LeftWith, int Available)
|
||||
{
|
||||
/// N1MM's wording, one line per role. Europe on CW and SSB only takes
|
||||
/// traffic down, so there is no pile of contacts to report and the line
|
||||
/// leaves that number out.
|
||||
public string Text => Role switch
|
||||
{
|
||||
QtcDirection.Receive => $"QTCs: {Exchanged} rcvd/{LeftWith} left to exch.",
|
||||
QtcDirection.Send =>
|
||||
$"QTCs: {Exchanged} sent/{LeftWith} left to exch/{Available} total QTCs avail",
|
||||
QtcDirection.Either =>
|
||||
$"QTCs: {Exchanged} exch/{LeftWith} left to exch/{Available} total QTCs avail",
|
||||
_ => "",
|
||||
};
|
||||
|
||||
/// N1MM puts this in the status bar as well when a station has had its ten.
|
||||
public bool IsFull => Exchanged >= WaeQtc.MostPerStation;
|
||||
}
|
||||
|
||||
/// The WAE QTC rules that do not need a window: who may exchange traffic with
|
||||
/// whom, which contacts are still worth reporting, how the series are numbered,
|
||||
/// and whether a line an operator has typed reads as a QTC.
|
||||
@@ -87,6 +109,46 @@ public sealed class QtcTraffic
|
||||
public int Remaining(Callsign other) =>
|
||||
Math.Max(0, WaeQtc.MostPerStation - ExchangedWith(other));
|
||||
|
||||
/// Which way traffic goes here whoever is worked: Europe takes it down on
|
||||
/// CW and SSB, the rest of the world reads it out, and on RTTY it goes
|
||||
/// both ways. `DirectionFor` answers the same question about one station;
|
||||
/// this one ignores the station, so the counts still read out when the
|
||||
/// callsign box is empty or holds a station with nothing left.
|
||||
public QtcDirection Role
|
||||
{
|
||||
get
|
||||
{
|
||||
if (position.Contest is not Wae contest)
|
||||
{
|
||||
return QtcDirection.None;
|
||||
}
|
||||
if (contest.Modes.Contains(ModeCategory.Digital))
|
||||
{
|
||||
return QtcDirection.Either;
|
||||
}
|
||||
return position.Me.Continent == "EU" ? QtcDirection.Receive : QtcDirection.Send;
|
||||
}
|
||||
}
|
||||
|
||||
/// How many logged contacts are still worth reporting to this station: the
|
||||
/// whole pile rather than one series of ten. This is N1MM's
|
||||
/// `QTCRecordsetCount`, which is zero for a European station on CW and SSB
|
||||
/// because it only takes traffic down.
|
||||
public int Available(Callsign other) =>
|
||||
Role == QtcDirection.Receive ? 0 : Reportable(other).Count();
|
||||
|
||||
/// The three numbers the entry window shows. `LeftWith` is what may still
|
||||
/// go with this station: what is free of its ten, and no more than the
|
||||
/// contacts there are to report when we are the one reporting.
|
||||
public QtcCounts CountsFor(Callsign other)
|
||||
{
|
||||
int exchanged = ExchangedWith(other);
|
||||
int free = Math.Max(0, WaeQtc.MostPerStation - exchanged);
|
||||
int available = Available(other);
|
||||
int left = Role == QtcDirection.Send ? Math.Min(free, available) : free;
|
||||
return new QtcCounts(Role, exchanged, left, available);
|
||||
}
|
||||
|
||||
/// Series are numbered across the whole contest, not per station: the
|
||||
/// hundredth series a station sends is `100/10` whoever gets it.
|
||||
public int NextSeries()
|
||||
@@ -111,18 +173,7 @@ public sealed class QtcTraffic
|
||||
{
|
||||
return [];
|
||||
}
|
||||
HashSet<string> reported = position.Log.Qsos
|
||||
.Where(q => q.Exchange1.Equals(WaeQtc.Sent, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(q => ReportedKey(q.ReceivedReport, q.ReceivedNumber))
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
List<Qso> worth = position.Log.Qsos
|
||||
.Where(q => !WaeQtc.IsQtc(q))
|
||||
.Where(q => q.ContactType.Length == 0)
|
||||
.Where(q => !q.Call.Text.Equals(other.Text, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(q => !reported.Contains(ReportedKey(q.Call.Text, q.SentNumber)))
|
||||
.OrderBy(q => q.TimestampUtc)
|
||||
.Take(room)
|
||||
.ToList();
|
||||
List<Qso> worth = [.. Reportable(other).OrderBy(q => q.TimestampUtc).Take(room)];
|
||||
int series = NextSeries();
|
||||
return [.. worth.Select(q => new WaeQtc(
|
||||
other,
|
||||
@@ -196,6 +247,22 @@ public sealed class QtcTraffic
|
||||
private static bool IsClockTime(string text) =>
|
||||
int.Parse(text[..2]) < 24 && int.Parse(text[2..]) < 60;
|
||||
|
||||
/// The contacts not reported to anybody yet, without the dupes and without
|
||||
/// the station in front of us. The caller puts them in order. What N1MM's
|
||||
/// `QTCRecordsetCount` leaves out is the dupes.
|
||||
private IEnumerable<Qso> Reportable(Callsign other)
|
||||
{
|
||||
HashSet<string> reported = position.Log.Qsos
|
||||
.Where(q => q.Exchange1.Equals(WaeQtc.Sent, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(q => ReportedKey(q.ReceivedReport, q.ReceivedNumber))
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
return position.Log.Qsos
|
||||
.Where(q => !WaeQtc.IsQtc(q))
|
||||
.Where(q => q.ContactType.Length == 0)
|
||||
.Where(q => !q.Call.Text.Equals(other.Text, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(q => !reported.Contains(ReportedKey(q.Call.Text, q.SentNumber)));
|
||||
}
|
||||
|
||||
private static string ReportedKey(string call, int number) => $"{call}|{number}";
|
||||
|
||||
private string ContinentOf(Callsign call) =>
|
||||
|
||||
Reference in New Issue
Block a user