Mark the CQ frequency on the bandmap and leave Run when the radio moves off it
Starting Run stores the frequency in the position. The bandmap draws a purple, bold "CQ Frequency" pseudospot there, which is not added to the shared bandmap, and a click on it tunes back and sets Run. A move of more than 3 kHz (a setting on the telnet Filters tab) while running switches to search and pounce. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -313,7 +313,7 @@ public sealed partial class AppSession : IDisposable
|
||||
positions.Clear();
|
||||
for (int number = 1; number <= PositionCount; number++)
|
||||
{
|
||||
positions.Add(new OperatingPosition(Logging, number));
|
||||
positions.Add(new OperatingPosition(Logging, number) { CqLeaveHertz = Settings.CqLeaveHertz });
|
||||
}
|
||||
activeRadio = Math.Min(activeRadio, positions.Count - 1);
|
||||
// the other stations refuse to resynchronise with a station on another
|
||||
@@ -995,6 +995,10 @@ public sealed partial class AppSession : IDisposable
|
||||
{
|
||||
spotFilter = Settings.SpotFilter.ToFilter(BandPlan, Settings.Station, Countries, Calls, History);
|
||||
Bandmap.Lifetime = TimeSpan.FromMinutes(Math.Max(1, Settings.SpotTimeoutMinutes));
|
||||
foreach (OperatingPosition position in positions)
|
||||
{
|
||||
position.CqLeaveHertz = Settings.CqLeaveHertz;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReloadSupportFiles()
|
||||
|
||||
@@ -105,6 +105,10 @@ public sealed record Settings
|
||||
/// boxes are wiped. N1MM uses 2 kHz.
|
||||
public int QsySpotHertz { get; init; } = 2000;
|
||||
|
||||
/// How far the radio can move off the CQ frequency, in hertz, before a
|
||||
/// running position goes back to searching.
|
||||
public int CqLeaveHertz { get; init; } = 3000;
|
||||
|
||||
public int TuningToleranceFor(ModeCategory mode) => mode switch
|
||||
{
|
||||
ModeCategory.Phone => PhoneTuningToleranceHertz,
|
||||
|
||||
@@ -181,7 +181,7 @@ public sealed class BandmapView : Control
|
||||
new Point(ScaleX + 2, label.MarkY),
|
||||
new Point(LabelX - 2, label.LabelY + (LabelHeight / 2)));
|
||||
context.DrawText(
|
||||
Text(label.Spot.Call.Text, colour, TextSize, label.Spot.IsLocal),
|
||||
Text(label.Spot.Call.Text, colour, TextSize, label.Spot.IsLocal || label.Spot.IsCqFrequency),
|
||||
new Point(LabelX, label.LabelY + (LabelPadding / 2)));
|
||||
}
|
||||
|
||||
|
||||
@@ -34,15 +34,19 @@ public sealed partial class BandmapWindow : RefreshableWindow
|
||||
|
||||
private readonly AppSession session;
|
||||
private readonly Action<Frequency, string> tune;
|
||||
private readonly Action<Frequency> returnToCq;
|
||||
|
||||
/// Where the slice was last drawn from, so it stays put until the receiver
|
||||
/// nears an edge.
|
||||
private Frequency? sliceLow;
|
||||
|
||||
public BandmapWindow(AppSession session, Action<Frequency, string> tune)
|
||||
private static readonly IBrush CqBrush = new SolidColorBrush(Color.FromRgb(0x9B, 0x59, 0xB6));
|
||||
|
||||
public BandmapWindow(AppSession session, Action<Frequency, string> tune, Action<Frequency> returnToCq)
|
||||
{
|
||||
this.session = session;
|
||||
this.tune = tune;
|
||||
this.returnToCq = returnToCq;
|
||||
InitializeComponent();
|
||||
SpanBox.ItemsSource = Spans.Select(s => s.Label).ToList();
|
||||
SpanBox.SelectedIndex = Math.Max(
|
||||
@@ -59,7 +63,17 @@ public sealed partial class BandmapWindow : RefreshableWindow
|
||||
CentreBox.IsCheckedChanged += (_, _) => SaveView();
|
||||
View.Zoomed += (_, zoom) => Zoom(zoom);
|
||||
View.ColourOf = Colour;
|
||||
View.SpotPicked += (_, spot) => tune(spot.Frequency, spot.Call.Text);
|
||||
View.SpotPicked += (_, spot) =>
|
||||
{
|
||||
if (spot.IsCqFrequency)
|
||||
{
|
||||
returnToCq(spot.Frequency);
|
||||
}
|
||||
else
|
||||
{
|
||||
tune(spot.Frequency, spot.Call.Text);
|
||||
}
|
||||
};
|
||||
View.FrequencyPicked += (_, frequency) => tune(frequency, "");
|
||||
session.Bandmap.Changed += (_, _) => Dispatcher.UIThread.Post(Refresh);
|
||||
Refresh();
|
||||
@@ -95,12 +109,24 @@ public sealed partial class BandmapWindow : RefreshableWindow
|
||||
long shown = high.Hertz - low.Hertz;
|
||||
View.TickStep = StepFor(shown);
|
||||
View.DenseTicks = shown is > 40_000 and <= 200_000;
|
||||
View.Spots = session.Bandmap.On(band);
|
||||
View.Spots = WithCqFrequency(session.Bandmap.On(band), band);
|
||||
View.Vfos = Vfos();
|
||||
View.Segments = session.BandPlan.For(band);
|
||||
View.InvalidateVisual();
|
||||
}
|
||||
|
||||
/// The bandmap's spots with the pseudospot for the CQ frequency, when the
|
||||
/// position has one on this band. It is added here and not to the bandmap,
|
||||
/// so it does not age out and the other windows do not list it.
|
||||
private IReadOnlyList<Spot> WithCqFrequency(IReadOnlyList<Spot> spots, Band band)
|
||||
{
|
||||
if (session.Position?.CqFrequency is not { } cq || Bands.ForFrequency(cq) != band)
|
||||
{
|
||||
return spots;
|
||||
}
|
||||
return [.. spots.Append(Spot.CqFrequency(cq, DateTime.UtcNow)).OrderBy(s => s.Frequency.Hertz)];
|
||||
}
|
||||
|
||||
/// Green for where the operator is listening, red for where the radio
|
||||
/// transmits when working split, orange for the other radio of a two-radio
|
||||
/// station.
|
||||
@@ -165,7 +191,8 @@ public sealed partial class BandmapWindow : RefreshableWindow
|
||||
_ => Frequency.FromKilohertz(50),
|
||||
};
|
||||
|
||||
private IBrush Colour(Spot spot) => Verdicts.Colour(VerdictFor(spot));
|
||||
private IBrush Colour(Spot spot) =>
|
||||
spot.IsCqFrequency ? CqBrush : Verdicts.Colour(VerdictFor(spot));
|
||||
|
||||
private Verdict? VerdictFor(Spot spot)
|
||||
{
|
||||
|
||||
@@ -12,10 +12,6 @@ namespace Nonemm.App.Windows;
|
||||
/// which is how a station is worked without typing its call.
|
||||
public sealed partial class EntryWindow
|
||||
{
|
||||
/// Where we last called CQ, so the frame can say so when the radio comes
|
||||
/// back to it.
|
||||
private Frequency cqFrequency;
|
||||
|
||||
/// What the frame holds, when it holds a callsign. `CQ-Frequency` is not
|
||||
/// one, and neither is nothing.
|
||||
private string framedCall = "";
|
||||
@@ -35,7 +31,7 @@ public sealed partial class EntryWindow
|
||||
CallFrameText.Text = "";
|
||||
return;
|
||||
}
|
||||
if (cqFrequency.Hertz > 0
|
||||
if (Logging.CqFrequency is { } cqFrequency
|
||||
&& Math.Abs(Logging.Frequency.Hertz - cqFrequency.Hertz) <= TuningTolerance.Hertz)
|
||||
{
|
||||
CallFrameText.Text = "CQ-Frequency";
|
||||
@@ -86,7 +82,7 @@ public sealed partial class EntryWindow
|
||||
{
|
||||
if (Logging is not null)
|
||||
{
|
||||
cqFrequency = Logging.Frequency;
|
||||
Logging.CqFrequency = Logging.Frequency;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,7 +462,7 @@ public sealed partial class EntryWindow
|
||||
|
||||
private void OnShowCheck(object? sender, RoutedEventArgs e) => Show(() => new CheckWindow(session));
|
||||
|
||||
private void OnShowBandmap(object? sender, RoutedEventArgs e) => Show(() => new BandmapWindow(session, Tune));
|
||||
private void OnShowBandmap(object? sender, RoutedEventArgs e) => Show(() => new BandmapWindow(session, Tune, ReturnToCq));
|
||||
|
||||
private void OnShowAvailable(object? sender, RoutedEventArgs e) =>
|
||||
Show(() => new AvailableWindow(session, Tune));
|
||||
@@ -712,6 +712,14 @@ public sealed partial class EntryWindow
|
||||
}
|
||||
}
|
||||
|
||||
/// A click on the CQ frequency on the bandmap: back to where the CQs went
|
||||
/// out, and running again.
|
||||
private void ReturnToCq(Frequency frequency)
|
||||
{
|
||||
Tune(frequency, "");
|
||||
SetRunning(true);
|
||||
}
|
||||
|
||||
private void Tune(Frequency frequency, string call)
|
||||
{
|
||||
if (Logging is null)
|
||||
|
||||
@@ -109,7 +109,7 @@ public sealed partial class EntryWindow
|
||||
[
|
||||
(nameof(LogWindow), () => _ = Show(() => new LogWindow(session), activate: false)),
|
||||
(nameof(CheckWindow), () => _ = Show(() => new CheckWindow(session), activate: false)),
|
||||
(nameof(BandmapWindow), () => _ = Show(() => new BandmapWindow(session, Tune), activate: false)),
|
||||
(nameof(BandmapWindow), () => _ = Show(() => new BandmapWindow(session, Tune, ReturnToCq), activate: false)),
|
||||
(nameof(AvailableWindow), () => _ = Show(() => new AvailableWindow(session, Tune), activate: false)),
|
||||
(nameof(ScoreWindow), () => _ = Show(() => new ScoreWindow(session), activate: false)),
|
||||
(nameof(StatisticsWindow), () => _ = Show(() => new StatisticsWindow(session), activate: false)),
|
||||
|
||||
@@ -50,6 +50,7 @@ public sealed partial class TelnetWindow
|
||||
BlockedCallsBox.Text = string.Join(' ', filter.BlockedCalls);
|
||||
TimeoutBox.Text = session.Settings.SpotTimeoutMinutes.ToString(CultureInfo.InvariantCulture);
|
||||
QsySpotBox.Text = session.Settings.QsySpotHertz.ToString(CultureInfo.InvariantCulture);
|
||||
CqLeaveBox.Text = session.Settings.CqLeaveHertz.ToString(CultureInfo.InvariantCulture);
|
||||
SpotCommentBox.Text = session.Settings.SpotComment;
|
||||
}
|
||||
|
||||
@@ -74,6 +75,7 @@ public sealed partial class TelnetWindow
|
||||
RandomizeSpots = RandomizeBox.IsChecked == true,
|
||||
SpotTimeoutMinutes = Minutes(TimeoutBox.Text, session.Settings.SpotTimeoutMinutes),
|
||||
QsySpotHertz = Minutes(QsySpotBox.Text, session.Settings.QsySpotHertz),
|
||||
CqLeaveHertz = Minutes(CqLeaveBox.Text, session.Settings.CqLeaveHertz),
|
||||
});
|
||||
Show("*** filters applied — they hold for the spots that arrive from now on", NoticeColour);
|
||||
}
|
||||
|
||||
@@ -124,6 +124,8 @@
|
||||
<TextBox Name="TimeoutBox" Width="60" HorizontalAlignment="Left" />
|
||||
<TextBlock Classes="label" Text="Spot a call locally when the radio moves more than (Hz)" />
|
||||
<TextBox Name="QsySpotBox" Width="60" HorizontalAlignment="Left" />
|
||||
<TextBlock Classes="label" Text="Go back to search and pounce when the radio moves off the CQ frequency by more than (Hz)" TextWrapping="Wrap" />
|
||||
<TextBox Name="CqLeaveBox" Width="60" HorizontalAlignment="Left" />
|
||||
<Button Content="Apply" Click="OnApplyFilters" Margin="0,10,0,0" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
Reference in New Issue
Block a user