Two entry windows and an SO2R box

LoggingSession mixed two things: the contest, which one station has one of,
and what the operator is typing, which belongs to a radio. It is now
ContestSession and RadioPosition. Two positions share one log, one score and
one run of serial numbers, so a station worked on radio 1 is a dupe on radio 2.
Frequency, mode, split, what is typed and whether you are running belong to
each radio on its own.

A second radio opens a second entry window. It has no menu of its own: it is
another view of the same contest, not another program. Only the window for
radio 1 opens the database and the connections.

Ctrl+Tab moves the operator to the other radio and the keyboard follows.
Ctrl+Shift+Tab puts both radios in the headphones.

OtrspBox speaks OTRSP to an SO2R box over a serial port: TX1/TX2 for the key,
RX1/RX2 and RX1S for the headphones, AUXnnn for an output line. It follows the
active radio, and every message points the box at this radio before keying, so
a message cannot go out of the radio the operator has just left. A command that
would change nothing is not sent, because the box works relays; N1MM does the
same. The box takes a Stream, so what it sends is tested without a serial port.

Still missing: alternating CQ, and voice keying on the second radio.

Looked at under Xvfb with two fake radios, one of them split: both windows up,
radio 1 showing 14008.00 into 14020.0, radio 2 on 14030.00, Ctrl+Tab moving the
keyboard between them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 16:49:24 +00:00
parent 43be3816c3
commit 1185faed78
22 changed files with 714 additions and 208 deletions

View File

@@ -30,11 +30,11 @@ public sealed partial class EditContactDialog : Window
QsoField.CountryPrefix, QsoField.WpxPrefix,
];
private readonly LoggingSession logging;
private readonly ContestSession logging;
private readonly Dictionary<QsoField, TextBox> boxes = [];
private int at;
public EditContactDialog(LoggingSession logging, string contactId)
public EditContactDialog(ContestSession logging, string contactId)
{
this.logging = logging;
InitializeComponent();

View File

@@ -23,8 +23,11 @@
<CheckBox Grid.Row="4" Grid.Column="1" Name="SecondEnabledBox" Content="Follow this radio"
Margin="0,2,0,0" />
</Grid>
<TextBlock Text="SO2R box serial port, or empty for none" FontSize="11" Opacity="0.7"
Margin="0,10,0,1" />
<TextBox Name="BoxPortBox" />
<TextBlock TextWrapping="Wrap" FontSize="11" Opacity="0.75" Margin="0,8,0,0"
Text="With a second radio the station is SO2R: both are read and both show on the bandmap, but only the one you are on drives the entry window. Ctrl+Tab moves you to the other." />
Text="With a second radio the station is SO2R: both are read and both show on the bandmap, but only the one you are on drives the entry window. Ctrl+Tab moves you to the other, and Ctrl+Shift+Tab puts both in the headphones. An OTRSP box follows, routing the key and the headphones." />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="6" Margin="0,8,0,0">
<Button Content="Cancel" Click="OnCancel" />
<Button Content="Save" Click="OnSave" IsDefault="True" />

View File

@@ -17,6 +17,7 @@ public sealed partial class RadioDialog : Window
InitializeComponent();
Show(0, HostBox, PortBox, EnabledBox);
Show(1, SecondHostBox, SecondPortBox, SecondEnabledBox);
BoxPortBox.Text = settings.So2rBoxPort;
}
private void Show(int at, TextBox host, TextBox port, CheckBox enabled)
@@ -36,6 +37,7 @@ public sealed partial class RadioDialog : Window
Read(HostBox, PortBox, EnabledBox),
Read(SecondHostBox, SecondPortBox, SecondEnabledBox),
],
So2rBoxPort = (BoxPortBox.Text ?? "").Trim(),
});
private static StoredRadio Read(TextBox host, TextBox port, CheckBox enabled) => new()