Send WAE QTC traffic on CW
The QTC window took traffic down and read it out of the log, but nothing left the window: on CW the operator had to send the lines by hand. It now puts them on the air through the entry window's keyer, the way N1MM's QTC window sends through its own entry window, so the same macros are expanded and the same transmitter is pointed at first. Sending a series: Send Hdr keys the header, Send1 to Send10 key one line each — the time, the callsign and the serial number — and Close keys the TU message when one is set. Taking one down: Hdr Agn asks for the header again, and shift with Enter in a box asks for that one field again and clears it. The messages and their defaults are N1MM's: AGN TIME, AGN CALL, AGN SERIAL, the hard-coded HDR AGN with its two spaces, and a TU that sends nothing until the operator writes one. So is the field spacing, written N1MM's way with S for a space. All five are in Setup, where the note about the missing settings used to be. Nothing is sent on SSB or RTTY. N1MM plays four recordings on SSB and sends RTTY from its digital window, and there is neither here, so on those modes the buttons move the cursor as before. RX Ready sends nothing on any mode, because a recording is all N1MM ever sends from it. Serial numbers go out as digits: N1MM can send cut numbers and nothing here does, in any message. Driven under Xvfb against a fake cwdaemon, in a WAE CW contest with four contacts logged. Sending, it keyed QTC 1/2, 2257 DL1AAA 01, 2257 DL2BBB 01, then QTC 2/2 with its two lines and TU DE W1ABC on close. Receiving, HDR AGN, AGN TIME, AGN CALL and AGN SERIAL. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
This commit is contained in:
@@ -17,8 +17,10 @@ namespace Nonemm.App.Windows;
|
||||
///
|
||||
/// Receiving, the lines are typed as they arrive. Sending, they are filled from
|
||||
/// the log and cannot be edited — what is being reported is what was worked.
|
||||
/// Nothing here transmits: on CW the operator sends from the entry window, on
|
||||
/// SSB by voice, on RTTY from the digital window there is not yet.
|
||||
///
|
||||
/// On CW it puts the traffic on the air through the entry window's keyer, the
|
||||
/// way N1MM's QTC window sends through its own entry window. On SSB and RTTY
|
||||
/// nothing is sent: one needs a voice keyer and the other a digital window.
|
||||
public sealed partial class QtcWindow : Window
|
||||
{
|
||||
private static IBrush Saved => Themes.Brush(Themes.Current.GoodBackground);
|
||||
@@ -31,19 +33,24 @@ public sealed partial class QtcWindow : Window
|
||||
private readonly OperatingPosition position;
|
||||
private readonly QtcTraffic traffic;
|
||||
private readonly Callsign station;
|
||||
private readonly Func<string, Task> send;
|
||||
private bool isSending;
|
||||
private readonly List<Row> rows = [];
|
||||
private readonly List<WaeQtc> ready = [];
|
||||
|
||||
/// `send` puts a message on the air through the entry window this was
|
||||
/// opened from, so QTC traffic goes out the same way a function key does.
|
||||
public QtcWindow(
|
||||
AppSession session,
|
||||
OperatingPosition position,
|
||||
Callsign station,
|
||||
QtcDirection direction)
|
||||
QtcDirection direction,
|
||||
Func<string, Task> send)
|
||||
{
|
||||
this.session = session;
|
||||
this.position = position;
|
||||
this.station = station;
|
||||
this.send = send;
|
||||
isSending = direction == QtcDirection.Send;
|
||||
traffic = new QtcTraffic(position);
|
||||
InitializeComponent();
|
||||
@@ -65,6 +72,10 @@ public sealed partial class QtcWindow : Window
|
||||
: session.Settings.QtcSkipReady ? HeaderBox
|
||||
: ReadyButton;
|
||||
|
||||
/// Traffic goes out on CW alone. The contest says which mode it is, not
|
||||
/// what the radio happens to be on, which is how N1MM decides too.
|
||||
private bool IsCw => position.Contest.Modes is [ModeCategory.Cw];
|
||||
|
||||
private void SetDirection(bool sending)
|
||||
{
|
||||
if (sending == isSending)
|
||||
@@ -86,6 +97,7 @@ public sealed partial class QtcWindow : Window
|
||||
ReadyButton.IsVisible = !isSending;
|
||||
HeaderAgainButton.IsVisible = !isSending;
|
||||
HeaderCfmButton.IsVisible = !isSending;
|
||||
HeaderSendButton.IsVisible = isSending && IsCw;
|
||||
HeaderBox.IsReadOnly = isSending;
|
||||
BuildRows();
|
||||
Fill();
|
||||
@@ -108,7 +120,11 @@ public sealed partial class QtcWindow : Window
|
||||
TextBox time = Box(at, 0);
|
||||
TextBox call = Box(at, 1);
|
||||
TextBox number = Box(at, 2);
|
||||
Button again = new() { Content = $"Agn{at + 1}", Margin = new Avalonia.Thickness(2, 1, 2, 1) };
|
||||
Button again = new()
|
||||
{
|
||||
Content = isSending && IsCw ? $"Send{at + 1}" : $"Agn{at + 1}",
|
||||
Margin = new Avalonia.Thickness(2, 1, 2, 1),
|
||||
};
|
||||
Button confirm = new() { Content = $"Cfm{at + 1}", Margin = new Avalonia.Thickness(2, 1, 2, 1) };
|
||||
int line = at;
|
||||
again.Click += (_, _) => OnLineAgain(line);
|
||||
@@ -222,6 +238,14 @@ public sealed partial class QtcWindow : Window
|
||||
e.Handled = true;
|
||||
Close(false);
|
||||
break;
|
||||
case Key.Enter when e.Source is TextBox box
|
||||
&& !isSending
|
||||
&& IsCw
|
||||
&& e.KeyModifiers.HasFlag(KeyModifiers.Shift)
|
||||
&& box != HeaderBox:
|
||||
e.Handled = true;
|
||||
SendFieldAgain(box);
|
||||
break;
|
||||
case Key.Enter when e.Source is TextBox:
|
||||
e.Handled = true;
|
||||
MoveOn();
|
||||
@@ -262,12 +286,24 @@ public sealed partial class QtcWindow : Window
|
||||
CloseButton.Focus();
|
||||
}
|
||||
|
||||
/// N1MM's RX Ready tells the other station to go ahead; with no transmit
|
||||
/// path here it only puts the cursor where the first line will land.
|
||||
/// N1MM's RX Ready tells the other station to go ahead, and does it with a
|
||||
/// recording. There is no voice keyer here, so it only puts the cursor
|
||||
/// where the first line will land.
|
||||
private void OnReady(object? sender, RoutedEventArgs e) => rows[0].Time.Focus();
|
||||
|
||||
/// The header of the series we are about to read out: `QTC 3/10`.
|
||||
private void OnHeaderSend(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
_ = send(HeaderBox.Text ?? "");
|
||||
rows[0].Again.Focus();
|
||||
}
|
||||
|
||||
private void OnHeaderAgain(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (IsCw)
|
||||
{
|
||||
_ = send(QtcMessages.HeaderAgain);
|
||||
}
|
||||
if (session.Settings.QtcHeaderAgainClears)
|
||||
{
|
||||
HeaderBox.Text = "";
|
||||
@@ -283,6 +319,11 @@ public sealed partial class QtcWindow : Window
|
||||
{
|
||||
if (isSending)
|
||||
{
|
||||
if (IsCw)
|
||||
{
|
||||
SendLine(at);
|
||||
return;
|
||||
}
|
||||
rows[at].Time.Focus();
|
||||
return;
|
||||
}
|
||||
@@ -294,6 +335,45 @@ public sealed partial class QtcWindow : Window
|
||||
Paint();
|
||||
}
|
||||
|
||||
/// Reads one line out and moves to the next: the time, the callsign and the
|
||||
/// serial number, with the operator's spacing between them. When the last
|
||||
/// line has gone the cursor lands on Close, which sends the TU message.
|
||||
private void SendLine(int at)
|
||||
{
|
||||
_ = send(QtcMessages.Line(
|
||||
rows[at].Time.Text ?? "",
|
||||
rows[at].Call.Text ?? "",
|
||||
rows[at].Number.Text ?? "",
|
||||
session.Settings.QtcCwFieldSpacing));
|
||||
for (int next = at + 1; next < rows.Count; next++)
|
||||
{
|
||||
if (rows[next].Again.IsEffectivelyEnabled)
|
||||
{
|
||||
rows[next].Again.Focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
CloseButton.Focus();
|
||||
}
|
||||
|
||||
/// N1MM's Shift+Enter: ask for one field of the line being taken down
|
||||
/// again, and clear it. One message per column, all three in the settings.
|
||||
private void SendFieldAgain(TextBox box)
|
||||
{
|
||||
int column = rows.Select(r => r.Time).Contains(box) ? 0
|
||||
: rows.Select(r => r.Call).Contains(box) ? 1
|
||||
: 2;
|
||||
_ = send(column switch
|
||||
{
|
||||
0 => session.Settings.QtcCwTimeAgain,
|
||||
1 => session.Settings.QtcCwCallAgain,
|
||||
_ => session.Settings.QtcCwNumberAgain,
|
||||
});
|
||||
box.Text = "";
|
||||
box.Focus();
|
||||
Paint();
|
||||
}
|
||||
|
||||
private void OnLineConfirm(int at)
|
||||
{
|
||||
Control next = at + 1 < rows.Count ? rows[at + 1].Time : CloseButton;
|
||||
@@ -328,6 +408,10 @@ public sealed partial class QtcWindow : Window
|
||||
}
|
||||
}
|
||||
Save(lines);
|
||||
if (isSending && IsCw && session.Settings.QtcCwTu.Trim().Length > 0)
|
||||
{
|
||||
_ = send(session.Settings.QtcCwTu);
|
||||
}
|
||||
Close(true);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user