Put the QTC window's settings on its right-click menu
N1MM keeps them on a Setup tab inside the window; here they are a dialog behind a right-click, which suits a window this size better. Four of them, with N1MM's own defaults: how many QTCs a series carries, whether the RX Ready and Cfm steps are stopped at on the way through, and whether Hdr Agn and Agn clear what they are asking for again. Each one changes what the window does. The series count is passed to the search for contacts worth reporting, the two skip settings are the focus order, and the two clear settings are what the Agn buttons do. The Cfm buttons also disappear when a RTTY station is reading its own log out, which is what N1MM does with them. N1MM's remaining QTC settings are all about putting traffic on the air — the CW messages for Agn and TU, the field spacing, the four SSB recordings, the RTTY templates — and nothing here transmits, so they are left out rather than shown as settings that do nothing. The dialog says so where the operator will look. The right-click menu needed a background on the panel to come up anywhere but over a control: a panel with no background is not hit-tested. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -292,9 +292,16 @@ being worked, and never more than the ten any station may have — and cannot be
|
|||||||
edited. On CW and SSB the direction is fixed by the rules; on RTTY traffic goes
|
edited. On CW and SSB the direction is fixed by the rules; on RTTY traffic goes
|
||||||
both ways and the window has a switch.
|
both ways and the window has a switch.
|
||||||
|
|
||||||
|
Right-click the window for **Setup**: how many QTCs a series carries, whether
|
||||||
|
the RX Ready and Cfm steps are stopped at, and whether Hdr Agn and Agn clear
|
||||||
|
what they ask for again. The defaults are N1MM's — both steps skipped, the
|
||||||
|
header cleared, the line left alone.
|
||||||
|
|
||||||
Nothing in the window transmits. On CW the operator sends from the entry window,
|
Nothing in the window transmits. On CW the operator sends from the entry window,
|
||||||
on SSB by voice, and RTTY has no digital window yet, so Agn, Cfm and RX Ready
|
on SSB by voice, and RTTY has no digital window yet, so Agn, Cfm and RX Ready
|
||||||
move the cursor rather than putting anything on the air.
|
move the cursor rather than putting anything on the air. N1MM's QTC settings for
|
||||||
|
sending — the CW messages for Agn and TU, the field spacing, the SSB recordings
|
||||||
|
and the RTTY templates — are left out with them.
|
||||||
|
|
||||||
The scoring was checked against six real WAE logs, and agrees with N1MM on
|
The scoring was checked against six real WAE logs, and agrees with N1MM on
|
||||||
points and on weighted multipliers to the contact.
|
points and on weighted multipliers to the contact.
|
||||||
|
|||||||
@@ -45,9 +45,11 @@ voice messages for QRV, Agn, Cfm and TU on SSB. Here the Agn, Cfm and RX Ready
|
|||||||
buttons only move the cursor. What is missing under it is the message templates
|
buttons only move the cursor. What is missing under it is the message templates
|
||||||
for QTC traffic, a voice keyer, and the digital window.
|
for QTC traffic, a voice keyer, and the digital window.
|
||||||
|
|
||||||
**N1MM's QTC setup tab is not there.** Field spacing, the CW messages for Agn
|
**The QTC setup covers the window, not the sending.** Right-click ▸ Setup has
|
||||||
and TU, and the settings that skip the Cfm and QRV steps are all N1MM options
|
the four settings that change how the window behaves, with N1MM's defaults.
|
||||||
this window does not have.
|
What is not there is everything about putting QTCs on the air: N1MM's CW
|
||||||
|
messages for Agn and TU, its SQTC field spacing, its four SSB recordings, and
|
||||||
|
its RTTY message templates. Those wait on a transmit path.
|
||||||
|
|
||||||
**Two WAE numbers do not agree with N1MM, both from 2020 and 2021 logs.** The
|
**Two WAE numbers do not agree with N1MM, both from 2020 and 2021 logs.** The
|
||||||
disagreements are the country file of the day, not the rules: 4U1A counted as
|
disagreements are the country file of the day, not the rules: 4U1A counted as
|
||||||
|
|||||||
@@ -67,6 +67,20 @@ public sealed record Settings
|
|||||||
/// 100 ms, because an SO2R box works relays.
|
/// 100 ms, because an SO2R box works relays.
|
||||||
public int AlternatingCqGapMs { get; init; } = 100;
|
public int AlternatingCqGapMs { get; init; } = 100;
|
||||||
|
|
||||||
|
/// How the WAE QTC window behaves. The names and the defaults are N1MM's:
|
||||||
|
/// it skips the confirm and ready steps out of the box, clears the header
|
||||||
|
/// when the header is asked for again, and leaves a line alone when the
|
||||||
|
/// line is.
|
||||||
|
public int QtcLinesPerSeries { get; init; } = 10;
|
||||||
|
|
||||||
|
public bool QtcSkipReady { get; init; } = true;
|
||||||
|
|
||||||
|
public bool QtcSkipConfirm { get; init; } = true;
|
||||||
|
|
||||||
|
public bool QtcHeaderAgainClears { get; init; } = true;
|
||||||
|
|
||||||
|
public bool QtcAgainClearsLine { get; init; }
|
||||||
|
|
||||||
/// Sub-band boundaries the operator has changed. Empty means the defaults
|
/// Sub-band boundaries the operator has changed. Empty means the defaults
|
||||||
/// in `BandPlan.Default`; an entry replaces one band's boundaries.
|
/// in `BandPlan.Default`; an entry replaces one band's boundaries.
|
||||||
public IReadOnlyList<StoredSubBand> SubBands { get; init; } = [];
|
public IReadOnlyList<StoredSubBand> SubBands { get; init; } = [];
|
||||||
|
|||||||
28
src/Nonemm.App/Dialogs/QtcSetupDialog.axaml
Normal file
28
src/Nonemm.App/Dialogs/QtcSetupDialog.axaml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="Nonemm.App.Dialogs.QtcSetupDialog"
|
||||||
|
Title="QTC setup" Width="420" SizeToContent="Height"
|
||||||
|
WindowStartupLocation="CenterOwner" CanResize="False">
|
||||||
|
<StackPanel Margin="14" Spacing="8">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock Text="QTCs in a series" VerticalAlignment="Center" />
|
||||||
|
<TextBox Name="CountBox" Width="50" />
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock Text="1 to 10, and fewer when the station has already had some."
|
||||||
|
FontSize="11" Opacity="0.7" TextWrapping="Wrap" Margin="0,-4,0,0" />
|
||||||
|
<CheckBox Name="SkipReadyBox" Content="Skip the RX Ready step" />
|
||||||
|
<TextBlock Text="Off puts the cursor on RX Ready when the window opens, so the other station is told to go ahead before anything is typed."
|
||||||
|
FontSize="11" Opacity="0.7" TextWrapping="Wrap" Margin="24,-6,0,0" />
|
||||||
|
<CheckBox Name="SkipConfirmBox" Content="Skip the Cfm step" />
|
||||||
|
<TextBlock Text="Off stops at each line's Cfm button before moving to the next line."
|
||||||
|
FontSize="11" Opacity="0.7" TextWrapping="Wrap" Margin="24,-6,0,0" />
|
||||||
|
<CheckBox Name="HeaderAgainClearsBox" Content="Hdr Agn clears the header" />
|
||||||
|
<CheckBox Name="AgainClearsBox" Content="Agn clears the line" />
|
||||||
|
<TextBlock Name="MissingText" FontSize="11" Opacity="0.7" TextWrapping="Wrap" Margin="0,6,0,0"
|
||||||
|
Text="N1MM's other QTC settings are for sending: the CW messages for Agn and TU, the field spacing, the SSB recordings and the RTTY message templates. Nothing here transmits, so they are left out rather than shown as settings that do nothing." />
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="6" Margin="0,8,0,0">
|
||||||
|
<Button Content="Cancel" Click="OnCancel" />
|
||||||
|
<Button Content="Save" Click="OnSave" IsDefault="True" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Window>
|
||||||
36
src/Nonemm.App/Dialogs/QtcSetupDialog.axaml.cs
Normal file
36
src/Nonemm.App/Dialogs/QtcSetupDialog.axaml.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Nonemm.App.Configuration;
|
||||||
|
|
||||||
|
namespace Nonemm.App.Dialogs;
|
||||||
|
|
||||||
|
/// The QTC window's own settings, on its right-click menu as N1MM keeps them on
|
||||||
|
/// the window's Setup tab.
|
||||||
|
public sealed partial class QtcSetupDialog : Window
|
||||||
|
{
|
||||||
|
private readonly Settings settings;
|
||||||
|
|
||||||
|
public QtcSetupDialog(Settings settings)
|
||||||
|
{
|
||||||
|
this.settings = settings;
|
||||||
|
InitializeComponent();
|
||||||
|
CountBox.Text = settings.QtcLinesPerSeries.ToString();
|
||||||
|
SkipReadyBox.IsChecked = settings.QtcSkipReady;
|
||||||
|
SkipConfirmBox.IsChecked = settings.QtcSkipConfirm;
|
||||||
|
HeaderAgainClearsBox.IsChecked = settings.QtcHeaderAgainClears;
|
||||||
|
AgainClearsBox.IsChecked = settings.QtcAgainClearsLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSave(object? sender, RoutedEventArgs e) => Close(settings with
|
||||||
|
{
|
||||||
|
QtcLinesPerSeries = int.TryParse(CountBox.Text, out int count)
|
||||||
|
? Math.Clamp(count, 1, 10)
|
||||||
|
: settings.QtcLinesPerSeries,
|
||||||
|
QtcSkipReady = SkipReadyBox.IsChecked == true,
|
||||||
|
QtcSkipConfirm = SkipConfirmBox.IsChecked == true,
|
||||||
|
QtcHeaderAgainClears = HeaderAgainClearsBox.IsChecked == true,
|
||||||
|
QtcAgainClearsLine = AgainClearsBox.IsChecked == true,
|
||||||
|
});
|
||||||
|
|
||||||
|
private void OnCancel(object? sender, RoutedEventArgs e) => Close(null);
|
||||||
|
}
|
||||||
@@ -3,14 +3,21 @@
|
|||||||
x:Class="Nonemm.App.Windows.QtcWindow"
|
x:Class="Nonemm.App.Windows.QtcWindow"
|
||||||
Title="QTC" SizeToContent="WidthAndHeight" CanResize="False"
|
Title="QTC" SizeToContent="WidthAndHeight" CanResize="False"
|
||||||
WindowStartupLocation="CenterOwner">
|
WindowStartupLocation="CenterOwner">
|
||||||
<StackPanel Margin="10" Spacing="4">
|
<!-- the background is what makes the panel hit-testable, so the right-click
|
||||||
|
menu comes up over the whole window rather than only over a control -->
|
||||||
|
<StackPanel Margin="10" Spacing="4" Background="Transparent">
|
||||||
|
<StackPanel.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Header="Setup…" Click="OnSetup" />
|
||||||
|
</ContextMenu>
|
||||||
|
</StackPanel.ContextMenu>
|
||||||
<StackPanel Name="DirectionPanel" Orientation="Horizontal" Spacing="8" Margin="0,0,0,4">
|
<StackPanel Name="DirectionPanel" Orientation="Horizontal" Spacing="8" Margin="0,0,0,4">
|
||||||
<RadioButton Name="ReceiveButton" Content="Receive" GroupName="direction" IsChecked="True" />
|
<RadioButton Name="ReceiveButton" Content="Receive" GroupName="direction" IsChecked="True" />
|
||||||
<RadioButton Name="SendButton" Content="Send" GroupName="direction" />
|
<RadioButton Name="SendButton" Content="Send" GroupName="direction" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<TextBlock Text="QTC Header :" FontSize="11" Opacity="0.7" />
|
<TextBlock Text="QTC Header :" FontSize="11" Opacity="0.7" />
|
||||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||||
<TextBox Name="HeaderBox" Width="110" />
|
<TextBox Name="HeaderBox" Width="170" />
|
||||||
<Button Name="ReadyButton" Content="RX Ready" Click="OnReady" />
|
<Button Name="ReadyButton" Content="RX Ready" Click="OnReady" />
|
||||||
<Button Name="HeaderAgainButton" Content="Hdr Agn" Click="OnHeaderAgain" />
|
<Button Name="HeaderAgainButton" Content="Hdr Agn" Click="OnHeaderAgain" />
|
||||||
<Button Name="HeaderCfmButton" Content="Cfm" Click="OnHeaderConfirm" />
|
<Button Name="HeaderCfmButton" Content="Cfm" Click="OnHeaderConfirm" />
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
|
using Nonemm.App.Configuration;
|
||||||
|
using Nonemm.App.Dialogs;
|
||||||
using Nonemm.Contests.Rules;
|
using Nonemm.Contests.Rules;
|
||||||
using Nonemm.Core;
|
using Nonemm.Core;
|
||||||
using Nonemm.Session;
|
using Nonemm.Session;
|
||||||
@@ -53,9 +55,12 @@ public sealed partial class QtcWindow : Window
|
|||||||
Opened += (_, _) => FirstBox().Focus();
|
Opened += (_, _) => FirstBox().Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Where the cursor goes: the header when it has to be typed, the first
|
/// Where the cursor goes: RX Ready when the operator wants that step, the
|
||||||
/// line when it is already filled in.
|
/// header when it has to be typed, the first line when it is filled in.
|
||||||
private TextBox FirstBox() => isSending ? rows[0].Time : HeaderBox;
|
private Control FirstBox() =>
|
||||||
|
isSending ? rows[0].Time
|
||||||
|
: session.Settings.QtcSkipReady ? HeaderBox
|
||||||
|
: ReadyButton;
|
||||||
|
|
||||||
private void SetDirection(bool sending)
|
private void SetDirection(bool sending)
|
||||||
{
|
{
|
||||||
@@ -105,6 +110,8 @@ public sealed partial class QtcWindow : Window
|
|||||||
int line = at;
|
int line = at;
|
||||||
again.Click += (_, _) => OnLineAgain(line);
|
again.Click += (_, _) => OnLineAgain(line);
|
||||||
confirm.Click += (_, _) => OnLineConfirm(line);
|
confirm.Click += (_, _) => OnLineConfirm(line);
|
||||||
|
// nothing to confirm when a RTTY station is reading its own log out
|
||||||
|
confirm.IsVisible = !(isSending && position.Contest.Modes.Contains(ModeCategory.Digital));
|
||||||
Grid.SetRow(again, at);
|
Grid.SetRow(again, at);
|
||||||
Grid.SetColumn(again, 3);
|
Grid.SetColumn(again, 3);
|
||||||
Grid.SetRow(confirm, at);
|
Grid.SetRow(confirm, at);
|
||||||
@@ -145,7 +152,7 @@ public sealed partial class QtcWindow : Window
|
|||||||
Paint();
|
Paint();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ready.AddRange(traffic.ToSend(station));
|
ready.AddRange(traffic.ToSend(station, session.Settings.QtcLinesPerSeries));
|
||||||
// the contest is what makes it RTTY, not whatever the radio is on now
|
// the contest is what makes it RTTY, not whatever the radio is on now
|
||||||
bool rtty = position.Contest.Modes.Contains(ModeCategory.Digital);
|
bool rtty = position.Contest.Modes.Contains(ModeCategory.Digital);
|
||||||
HeaderBox.Text = ready.Count == 0
|
HeaderBox.Text = ready.Count == 0
|
||||||
@@ -225,7 +232,17 @@ public sealed partial class QtcWindow : Window
|
|||||||
|
|
||||||
private void MoveOn()
|
private void MoveOn()
|
||||||
{
|
{
|
||||||
List<TextBox> order = [HeaderBox, .. rows.SelectMany(r => new[] { r.Time, r.Call, r.Number })];
|
List<Control> order = [HeaderBox];
|
||||||
|
foreach (Row row in rows)
|
||||||
|
{
|
||||||
|
order.Add(row.Time);
|
||||||
|
order.Add(row.Call);
|
||||||
|
order.Add(row.Number);
|
||||||
|
if (!session.Settings.QtcSkipConfirm && row.Confirm.IsVisible)
|
||||||
|
{
|
||||||
|
order.Add(row.Confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
int at = order.FindIndex(b => b.IsFocused);
|
int at = order.FindIndex(b => b.IsFocused);
|
||||||
if (at < 0)
|
if (at < 0)
|
||||||
{
|
{
|
||||||
@@ -248,7 +265,10 @@ public sealed partial class QtcWindow : Window
|
|||||||
|
|
||||||
private void OnHeaderAgain(object? sender, RoutedEventArgs e)
|
private void OnHeaderAgain(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
HeaderBox.Text = "";
|
if (session.Settings.QtcHeaderAgainClears)
|
||||||
|
{
|
||||||
|
HeaderBox.Text = "";
|
||||||
|
}
|
||||||
HeaderBox.Focus();
|
HeaderBox.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +283,10 @@ public sealed partial class QtcWindow : Window
|
|||||||
rows[at].Time.Focus();
|
rows[at].Time.Focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
rows[at].Clear();
|
if (session.Settings.QtcAgainClearsLine)
|
||||||
|
{
|
||||||
|
rows[at].Clear();
|
||||||
|
}
|
||||||
rows[at].Time.Focus();
|
rows[at].Time.Focus();
|
||||||
Paint();
|
Paint();
|
||||||
}
|
}
|
||||||
@@ -307,6 +330,17 @@ public sealed partial class QtcWindow : Window
|
|||||||
|
|
||||||
private void OnCancel(object? sender, RoutedEventArgs e) => Close(false);
|
private void OnCancel(object? sender, RoutedEventArgs e) => Close(false);
|
||||||
|
|
||||||
|
private async void OnSetup(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
QtcSetupDialog dialog = new(session.Settings);
|
||||||
|
if (await dialog.ShowDialog<Settings?>(this) is { } updated)
|
||||||
|
{
|
||||||
|
session.Save(updated);
|
||||||
|
Reset();
|
||||||
|
FirstBox().Focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// One row per line, a second apart, so they keep their order in the log.
|
/// One row per line, a second apart, so they keep their order in the log.
|
||||||
/// That is what N1MM does with them too.
|
/// That is what N1MM does with them too.
|
||||||
private void Save(IReadOnlyList<WaeQtc> lines)
|
private void Save(IReadOnlyList<WaeQtc> lines)
|
||||||
|
|||||||
@@ -100,11 +100,12 @@ public sealed class QtcTraffic
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The contacts to report to this station: the ones not reported to anybody
|
/// The contacts to report to this station: the ones not reported to anybody
|
||||||
/// yet, oldest first, and never the station itself. Ten at a time, fewer
|
/// yet, oldest first, and never the station itself. Ten at a time unless
|
||||||
/// when it has already had some.
|
/// the operator asks for a shorter series, and fewer when the station has
|
||||||
public IReadOnlyList<WaeQtc> ToSend(Callsign other)
|
/// already had some.
|
||||||
|
public IReadOnlyList<WaeQtc> ToSend(Callsign other, int mostPerSeries = WaeQtc.MostPerSeries)
|
||||||
{
|
{
|
||||||
int room = Math.Min(WaeQtc.MostPerSeries, Remaining(other));
|
int room = Math.Min(Math.Clamp(mostPerSeries, 1, WaeQtc.MostPerSeries), Remaining(other));
|
||||||
if (room <= 0)
|
if (room <= 0)
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
Reference in New Issue
Block a user