Files
Nonemm/src/Nonemm.Contests/Rules/WaeQtc.cs
T
erikbrockoandClaude Opus 5 9ae5078c84 Take WAE QTC traffic down
Ctrl+Z opens the QTC window for the station being worked, which is the key N1MM
puts it on, and the window is laid out the way N1MM lays it out: the header,
ten lines of time, callsign and serial number, an Agn and a Cfm button beside
each, Clear, Close and Cancel underneath, and the same colours — green for a
line that will be saved, red for one still empty, yellow for one that cannot be
read. Enter and Tab move forward and space moves within a line, so a line can be
walked while listening to it.

Sending, the lines are filled from the log and cannot be edited: the contacts
not reported to anybody yet, oldest first, never the station being worked, and
never past the ten any station may have. The rule for what has been reported is
N1MM's — a contact counts as reported when a QTC row carries its call and the
serial we sent it — so the pair is kept in the row the way N1MM keeps it.

Which way traffic goes is not the operator's choice on CW and SSB: Europe
receives and the rest of the world sends. On RTTY it goes both ways, and there
the window carries a switch. That is the one piece of the workflow that is ours
rather than N1MM's, which toggles a QTC mode outside the window instead.

Nothing here transmits. N1MM sends the lines on CW and RTTY and plays recorded
voice messages on SSB; we have no QTC message templates, no voice keyer and no
digital window, so Agn, Cfm and RX Ready move the cursor and the operator sends
by hand.

Running it caught what the tests could not: focus set in the constructor does
not stick, so the first thing typed into a freshly opened window was dropped and
every field after it took the value of the one before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 00:02:47 +00:00

85 lines
2.8 KiB
C#

using Nonemm.Core;
namespace Nonemm.Contests.Rules;
/// One line of WAE QTC traffic: a contact one station reports to the other.
/// Ten of them make a series, and the series is written `3/10` — the third
/// series, ten lines in it.
///
/// A QTC lives in the log as an ordinary row with `Exchange1` set to `SQTC` or
/// `RQTC`, which is where N1MM keeps it: the time goes in the sent report
/// column, the callsign in the received report column, and the serial number in
/// the sent number column. Reading it back out is `From`.
public sealed record WaeQtc(
Callsign Station,
bool IsSent,
int Series,
int CountInSeries,
string TimeUtc,
Callsign Call,
int Number,
int OwnNumber = 0)
{
public const string Contact = "QSO";
public const string Sent = "SQTC";
public const string Received = "RQTC";
/// `Number` is the serial that station sent us in the contact being
/// reported, and it is what goes out on the air. `OwnNumber` is the serial
/// we sent them, kept only so the same contact is not reported twice;
/// N1MM keeps it in the `NR` column for the same reason.
///
/// Ten lines to a series, and ten to any one station for the whole contest.
public const int MostPerSeries = 10;
public const int MostPerStation = 10;
public string SeriesText => $"{Series}/{CountInSeries}";
public static bool IsQtc(Qso qso) =>
qso.Exchange1.Equals(Sent, StringComparison.OrdinalIgnoreCase)
|| qso.Exchange1.Equals(Received, StringComparison.OrdinalIgnoreCase);
/// Null for a row that is an ordinary contact.
public static WaeQtc? From(Qso qso)
{
if (!IsQtc(qso))
{
return null;
}
(int series, int count) = ParseSeries(qso.MiscText);
return new WaeQtc(
qso.Call,
qso.Exchange1.Equals(Sent, StringComparison.OrdinalIgnoreCase),
series,
count,
qso.SentReport,
Callsign.Parse(qso.ReceivedReport),
qso.SentNumber,
qso.ReceivedNumber);
}
/// Fills this QTC into a log row. The caller supplies the row so the
/// frequency, mode and time of the traffic come from wherever it happened.
public Qso ApplyTo(Qso qso) => qso with
{
Call = Station,
Exchange1 = IsSent ? Sent : Received,
MiscText = SeriesText,
SentReport = TimeUtc,
ReceivedReport = Call.Text,
SentNumber = Number,
ReceivedNumber = OwnNumber,
};
private static (int Series, int Count) ParseSeries(string text)
{
string[] parts = text.Split('/', StringSplitOptions.TrimEntries);
return parts.Length == 2
&& int.TryParse(parts[0], out int series)
&& int.TryParse(parts[1], out int count)
? (series, count)
: (0, 0);
}
}