Improve the bandmap window
Remove the status bar and the band name, add the 75 and 150 kHz spans, add buttons for the callsign size (saved in the settings), draw the scale in the form's text colour so it shows in the dark theme, mark the carrier on the VFO bar and place a sideband bar on its side of the carrier, and show who spotted a station, when, and the comment when the pointer is on it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -60,6 +60,9 @@ public sealed record Settings
|
|||||||
/// the receiver nears an edge.
|
/// the receiver nears an edge.
|
||||||
public bool BandmapCentred { get; init; }
|
public bool BandmapCentred { get; init; }
|
||||||
|
|
||||||
|
/// The height of the callsigns on the bandmap, in points.
|
||||||
|
public int BandmapTextSize { get; init; } = 12;
|
||||||
|
|
||||||
/// Moves each incoming CW spot by a few tens of hertz, so the operator has
|
/// Moves each incoming CW spot by a few tens of hertz, so the operator has
|
||||||
/// to find the station by ear. N1MM calls it randomising.
|
/// to find the station by ear. N1MM calls it randomising.
|
||||||
public bool RandomizeSpots { get; init; }
|
public bool RandomizeSpots { get; init; }
|
||||||
|
|||||||
@@ -19,10 +19,16 @@ public sealed class BandmapView : Control
|
|||||||
private const double ScaleX = 52;
|
private const double ScaleX = 52;
|
||||||
private const double LabelX = 96;
|
private const double LabelX = 96;
|
||||||
private const double TickLength = 6;
|
private const double TickLength = 6;
|
||||||
private const double LabelHeight = 15;
|
private const double ScaleTextSize = 12;
|
||||||
private const double TextSize = 12;
|
|
||||||
|
/// Callsigns are drawn this much taller than their text, so the lines of
|
||||||
|
/// two neighbours do not touch.
|
||||||
|
private const double LabelPadding = 3;
|
||||||
private const double VfoWidth = 4;
|
private const double VfoWidth = 4;
|
||||||
|
|
||||||
|
/// How far the carrier tick reaches past each side of the bar.
|
||||||
|
private const double CarrierOverhang = 3;
|
||||||
|
|
||||||
/// The scale stops short of the top and bottom edges so the first and last
|
/// The scale stops short of the top and bottom edges so the first and last
|
||||||
/// frequency numbers are not cut in half.
|
/// frequency numbers are not cut in half.
|
||||||
private const double Inset = 9;
|
private const double Inset = 9;
|
||||||
@@ -46,6 +52,11 @@ public sealed class BandmapView : Control
|
|||||||
|
|
||||||
public IReadOnlyList<Spot> Spots { get; set; } = [];
|
public IReadOnlyList<Spot> Spots { get; set; } = [];
|
||||||
|
|
||||||
|
/// The height of the callsigns, in points.
|
||||||
|
public double TextSize { get; set; } = 12;
|
||||||
|
|
||||||
|
private double LabelHeight => TextSize + LabelPadding;
|
||||||
|
|
||||||
public IReadOnlyList<VfoMarker> Vfos { get; set; } = [];
|
public IReadOnlyList<VfoMarker> Vfos { get; set; } = [];
|
||||||
|
|
||||||
public Frequency Low { get; set; } = Bands.Band20M.Low;
|
public Frequency Low { get; set; } = Bands.Band20M.Low;
|
||||||
@@ -121,7 +132,7 @@ public sealed class BandmapView : Control
|
|||||||
|
|
||||||
private void DrawScale(DrawingContext context, IBrush ink)
|
private void DrawScale(DrawingContext context, IBrush ink)
|
||||||
{
|
{
|
||||||
Pen line = new(Opaque(ink, 0.5), 1);
|
Pen line = new(Opaque(ink, 0.7), 1);
|
||||||
context.DrawLine(line, new Point(ScaleX, Inset), new Point(ScaleX, Inset + scale.Height));
|
context.DrawLine(line, new Point(ScaleX, Inset), new Point(ScaleX, Inset + scale.Height));
|
||||||
foreach (Frequency tick in BandmapLayout.Ticks(scale, TickStep))
|
foreach (Frequency tick in BandmapLayout.Ticks(scale, TickStep))
|
||||||
{
|
{
|
||||||
@@ -129,7 +140,8 @@ public sealed class BandmapView : Control
|
|||||||
context.DrawLine(line, new Point(ScaleX - TickLength, y), new Point(ScaleX, y));
|
context.DrawLine(line, new Point(ScaleX - TickLength, y), new Point(ScaleX, y));
|
||||||
FormattedText text = Text(
|
FormattedText text = Text(
|
||||||
tick.Kilohertz.ToString("0.#", CultureInfo.InvariantCulture),
|
tick.Kilohertz.ToString("0.#", CultureInfo.InvariantCulture),
|
||||||
Opaque(ink, 0.6));
|
Opaque(ink, 0.8),
|
||||||
|
ScaleTextSize);
|
||||||
context.DrawText(text, new Point(ScaleX - TickLength - 4 - text.Width, y - (text.Height / 2)));
|
context.DrawText(text, new Point(ScaleX - TickLength - 4 - text.Width, y - (text.Height / 2)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,9 +154,13 @@ public sealed class BandmapView : Control
|
|||||||
double bottom = scale.YFor(vfo.High) + Inset;
|
double bottom = scale.YFor(vfo.High) + Inset;
|
||||||
// a narrow mode would be less than a pixel tall
|
// a narrow mode would be less than a pixel tall
|
||||||
double height = Math.Max(3, bottom - top);
|
double height = Math.Max(3, bottom - top);
|
||||||
|
IBrush brush = BrushFor(vfo.Role);
|
||||||
|
context.FillRectangle(brush, new Rect(ScaleX - (VfoWidth / 2), top, VfoWidth, height));
|
||||||
|
// the carrier, which is not in the middle of a sideband
|
||||||
|
double carrier = Math.Round(scale.YFor(vfo.Carrier) + Inset);
|
||||||
context.FillRectangle(
|
context.FillRectangle(
|
||||||
BrushFor(vfo.Role),
|
brush,
|
||||||
new Rect(ScaleX - (VfoWidth / 2), top, VfoWidth, height));
|
new Rect(ScaleX - (VfoWidth / 2) - CarrierOverhang, carrier - 1, VfoWidth + (2 * CarrierOverhang), 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,10 +168,12 @@ public sealed class BandmapView : Control
|
|||||||
{
|
{
|
||||||
IBrush colour = ColourOf(label.Spot);
|
IBrush colour = ColourOf(label.Spot);
|
||||||
context.DrawLine(
|
context.DrawLine(
|
||||||
new Pen(Opaque(ink, 0.35), 1),
|
new Pen(Opaque(ink, 0.55), 1),
|
||||||
new Point(ScaleX + 2, label.MarkY),
|
new Point(ScaleX + 2, label.MarkY),
|
||||||
new Point(LabelX - 2, label.LabelY + (LabelHeight / 2)));
|
new Point(LabelX - 2, label.LabelY + (LabelHeight / 2)));
|
||||||
context.DrawText(Text(label.Spot.Call.Text, colour), new Point(LabelX, label.LabelY));
|
context.DrawText(
|
||||||
|
Text(label.Spot.Call.Text, colour, TextSize),
|
||||||
|
new Point(LabelX, label.LabelY + (LabelPadding / 2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
|
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
|
||||||
@@ -168,26 +186,37 @@ public sealed class BandmapView : Control
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BandmapLabel? LabelAt(Point at) =>
|
||||||
|
at.X < LabelX
|
||||||
|
? null
|
||||||
|
: placed.FirstOrDefault(l => at.Y >= l.LabelY && at.Y <= l.LabelY + LabelHeight);
|
||||||
|
|
||||||
|
protected override void OnPointerMoved(PointerEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnPointerMoved(e);
|
||||||
|
ToolTip.SetTip(
|
||||||
|
this,
|
||||||
|
LabelAt(e.GetPosition(this)) is { } label ? SpotDescription.Of(label.Spot) : null);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnPointerPressed(e);
|
base.OnPointerPressed(e);
|
||||||
Point at = e.GetPosition(this);
|
Point at = e.GetPosition(this);
|
||||||
foreach (BandmapLabel label in placed)
|
if (LabelAt(at) is { } label)
|
||||||
{
|
{
|
||||||
if (at.X >= LabelX && at.Y >= label.LabelY && at.Y <= label.LabelY + LabelHeight)
|
SpotPicked?.Invoke(this, label.Spot);
|
||||||
{
|
return;
|
||||||
SpotPicked?.Invoke(this, label.Spot);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
FrequencyPicked?.Invoke(this, scale.At(at.Y - Inset));
|
FrequencyPicked?.Invoke(this, scale.At(at.Y - Inset));
|
||||||
}
|
}
|
||||||
|
|
||||||
private FormattedText Text(string text, IBrush brush) =>
|
private FormattedText Text(string text, IBrush brush, double size) =>
|
||||||
new(text, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, TextSize, brush);
|
new(text, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, size, brush);
|
||||||
|
|
||||||
|
/// The form's text colour, which the theme sets in both light and dark.
|
||||||
private IBrush Foreground() =>
|
private IBrush Foreground() =>
|
||||||
this.FindResource("SystemControlForegroundBaseHighBrush") as IBrush ?? Brushes.Gray;
|
this.FindResource("FormForeground") as IBrush ?? Brushes.Gray;
|
||||||
|
|
||||||
private static IBrush Opaque(IBrush ink, double opacity) =>
|
private static IBrush Opaque(IBrush ink, double opacity) =>
|
||||||
ink is ISolidColorBrush solid
|
ink is ISolidColorBrush solid
|
||||||
|
|||||||
@@ -2,15 +2,18 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="using:Nonemm.App.Windows"
|
xmlns:local="using:Nonemm.App.Windows"
|
||||||
x:Class="Nonemm.App.Windows.BandmapWindow"
|
x:Class="Nonemm.App.Windows.BandmapWindow"
|
||||||
Title="Bandmap" Width="280" Height="620">
|
Title="Bandmap" Width="320" Height="620">
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="6,6,6,4" Spacing="8">
|
<DockPanel DockPanel.Dock="Top" Margin="6,6,6,4" LastChildFill="False">
|
||||||
<TextBlock Name="BandText" FontSize="12" VerticalAlignment="Center" />
|
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" Spacing="2">
|
||||||
|
<Button Name="SmallerButton" Content="−" MinWidth="24" Padding="6,0" Height="24"
|
||||||
|
FontSize="12" ToolTip.Tip="Smaller callsigns" />
|
||||||
|
<Button Name="LargerButton" Content="+" MinWidth="24" Padding="6,0" Height="24"
|
||||||
|
FontSize="12" ToolTip.Tip="Larger callsigns" />
|
||||||
|
</StackPanel>
|
||||||
<ComboBox Name="SpanBox" FontSize="11" Width="96" />
|
<ComboBox Name="SpanBox" FontSize="11" Width="96" />
|
||||||
<CheckBox Name="CentreBox" Content="Centre on VFO" FontSize="11" />
|
<CheckBox Name="CentreBox" Content="Centre on VFO" FontSize="11" Margin="8,0,0,0" />
|
||||||
</StackPanel>
|
</DockPanel>
|
||||||
<TextBlock DockPanel.Dock="Bottom" Name="StatusText" Margin="6,2,6,6"
|
|
||||||
FontSize="11" Opacity="0.7" />
|
|
||||||
<local:BandmapView Name="View" />
|
<local:BandmapView Name="View" />
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</local:RefreshableWindow>
|
</local:RefreshableWindow>
|
||||||
|
|||||||
@@ -19,10 +19,15 @@ public sealed partial class BandmapWindow : RefreshableWindow
|
|||||||
("10 kHz", Frequency.FromKilohertz(10)),
|
("10 kHz", Frequency.FromKilohertz(10)),
|
||||||
("20 kHz", Frequency.FromKilohertz(20)),
|
("20 kHz", Frequency.FromKilohertz(20)),
|
||||||
("40 kHz", Frequency.FromKilohertz(40)),
|
("40 kHz", Frequency.FromKilohertz(40)),
|
||||||
|
("75 kHz", Frequency.FromKilohertz(75)),
|
||||||
("100 kHz", Frequency.FromKilohertz(100)),
|
("100 kHz", Frequency.FromKilohertz(100)),
|
||||||
|
("150 kHz", Frequency.FromKilohertz(150)),
|
||||||
("whole band", Frequency.Zero),
|
("whole band", Frequency.Zero),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private const int MinTextSize = 8;
|
||||||
|
private const int MaxTextSize = 24;
|
||||||
|
|
||||||
private readonly AppSession session;
|
private readonly AppSession session;
|
||||||
private readonly Action<Frequency, string> tune;
|
private readonly Action<Frequency, string> tune;
|
||||||
|
|
||||||
@@ -40,6 +45,9 @@ public sealed partial class BandmapWindow : RefreshableWindow
|
|||||||
0,
|
0,
|
||||||
Array.FindIndex(Spans, s => s.Span.Hertz == session.Settings.BandmapSpanHertz));
|
Array.FindIndex(Spans, s => s.Span.Hertz == session.Settings.BandmapSpanHertz));
|
||||||
CentreBox.IsChecked = session.Settings.BandmapCentred;
|
CentreBox.IsChecked = session.Settings.BandmapCentred;
|
||||||
|
View.TextSize = session.Settings.BandmapTextSize;
|
||||||
|
SmallerButton.Click += (_, _) => ResizeText(-1);
|
||||||
|
LargerButton.Click += (_, _) => ResizeText(1);
|
||||||
SpanBox.SelectionChanged += (_, _) => SaveView();
|
SpanBox.SelectionChanged += (_, _) => SaveView();
|
||||||
CentreBox.IsCheckedChanged += (_, _) => SaveView();
|
CentreBox.IsCheckedChanged += (_, _) => SaveView();
|
||||||
View.Zoomed += (_, zoom) => Zoom(zoom);
|
View.Zoomed += (_, zoom) => Zoom(zoom);
|
||||||
@@ -50,10 +58,17 @@ public sealed partial class BandmapWindow : RefreshableWindow
|
|||||||
Refresh();
|
Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ResizeText(int by)
|
||||||
|
{
|
||||||
|
View.TextSize = Math.Clamp(View.TextSize + by, MinTextSize, MaxTextSize);
|
||||||
|
SaveView();
|
||||||
|
}
|
||||||
|
|
||||||
private void SaveView()
|
private void SaveView()
|
||||||
{
|
{
|
||||||
session.Save(session.Settings with
|
session.Save(session.Settings with
|
||||||
{
|
{
|
||||||
|
BandmapTextSize = (int)View.TextSize,
|
||||||
BandmapSpanHertz = (int)Spans[Math.Max(0, SpanBox.SelectedIndex)].Span.Hertz,
|
BandmapSpanHertz = (int)Spans[Math.Max(0, SpanBox.SelectedIndex)].Span.Hertz,
|
||||||
BandmapCentred = CentreBox.IsChecked == true,
|
BandmapCentred = CentreBox.IsChecked == true,
|
||||||
});
|
});
|
||||||
@@ -64,13 +79,11 @@ public sealed partial class BandmapWindow : RefreshableWindow
|
|||||||
{
|
{
|
||||||
session.Bandmap.DropOlderThan(DateTime.UtcNow);
|
session.Bandmap.DropOlderThan(DateTime.UtcNow);
|
||||||
Band? band = session.Position is null ? null : Bands.ForFrequency(session.Position.Frequency);
|
Band? band = session.Position is null ? null : Bands.ForFrequency(session.Position.Frequency);
|
||||||
BandText.Text = band?.Name ?? "no band";
|
|
||||||
if (session.Position is null || band is null)
|
if (session.Position is null || band is null)
|
||||||
{
|
{
|
||||||
View.Spots = [];
|
View.Spots = [];
|
||||||
View.Vfos = [];
|
View.Vfos = [];
|
||||||
View.Segments = null;
|
View.Segments = null;
|
||||||
StatusText.Text = session.Position is null ? "no contest is open" : "off band";
|
|
||||||
View.InvalidateVisual();
|
View.InvalidateVisual();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -82,18 +95,9 @@ public sealed partial class BandmapWindow : RefreshableWindow
|
|||||||
View.Spots = session.Bandmap.On(band);
|
View.Spots = session.Bandmap.On(band);
|
||||||
View.Vfos = Vfos();
|
View.Vfos = Vfos();
|
||||||
View.Segments = session.BandPlan.For(band);
|
View.Segments = session.BandPlan.For(band);
|
||||||
StatusText.Text =
|
|
||||||
$"{View.Spots.Count(s => s.IsStation)} stations · " +
|
|
||||||
$"{low.Kilohertz:0.#} to {high.Kilohertz:0.#} kHz{Split(band)}";
|
|
||||||
View.InvalidateVisual();
|
View.InvalidateVisual();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The band plan boundary, so the two shades behind the scale have a name.
|
|
||||||
private string Split(Band band) =>
|
|
||||||
session.BandPlan.For(band) is { HasPhone: true } segments
|
|
||||||
? $" · CW/phone {segments.CwHigh.Kilohertz:0.#}"
|
|
||||||
: "";
|
|
||||||
|
|
||||||
/// Green for where the operator is listening, red for where the radio
|
/// 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
|
/// transmits when working split, orange for the other radio of a two-radio
|
||||||
/// station.
|
/// station.
|
||||||
@@ -103,22 +107,20 @@ public sealed partial class BandmapWindow : RefreshableWindow
|
|||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
Frequency width = Modes.Bandwidth(session.Position.Mode);
|
|
||||||
List<VfoMarker> markers =
|
List<VfoMarker> markers =
|
||||||
[
|
[
|
||||||
VfoMarker.Centred(session.Position.Frequency, width, VfoRole.Receive),
|
VfoMarker.For(session.Position.Frequency, session.Position.Mode, VfoRole.Receive),
|
||||||
];
|
];
|
||||||
if (session.Position.TransmitFrequency.Hertz > 0)
|
if (session.Position.TransmitFrequency.Hertz > 0)
|
||||||
{
|
{
|
||||||
markers.Add(VfoMarker.Centred(
|
markers.Add(VfoMarker.For(
|
||||||
session.Position.TransmitFrequency, width, VfoRole.Transmit));
|
session.Position.TransmitFrequency, session.Position.Mode, VfoRole.Transmit));
|
||||||
}
|
}
|
||||||
foreach (Radio other in session.Radios.Where(r => r.Number != session.ActiveRadioNumber))
|
foreach (Radio other in session.Radios.Where(r => r.Number != session.ActiveRadioNumber))
|
||||||
{
|
{
|
||||||
if (other.State is { } state)
|
if (other.State is { } state)
|
||||||
{
|
{
|
||||||
markers.Add(VfoMarker.Centred(
|
markers.Add(VfoMarker.For(state.Frequency, state.Mode, VfoRole.Second));
|
||||||
state.Frequency, Modes.Bandwidth(state.Mode), VfoRole.Second));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return markers;
|
return markers;
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Nonemm.Spotting;
|
||||||
|
|
||||||
|
/// What the bandmap shows when the pointer rests on a spot: who spotted it,
|
||||||
|
/// when, and the comment they sent.
|
||||||
|
public static class SpotDescription
|
||||||
|
{
|
||||||
|
public static string Of(Spot spot)
|
||||||
|
{
|
||||||
|
string time = spot.AtUtc.ToString("HH:mm", CultureInfo.InvariantCulture);
|
||||||
|
string kilohertz = spot.Frequency.Kilohertz.ToString("0.0", CultureInfo.InvariantCulture);
|
||||||
|
string who = spot.Spotter.Length > 0
|
||||||
|
? $"Spotted by {spot.Spotter} at {time}Z"
|
||||||
|
: spot.Source switch
|
||||||
|
{
|
||||||
|
SpotSource.Log => $"Worked at {time}Z",
|
||||||
|
SpotSource.Operator => $"Spotted here at {time}Z",
|
||||||
|
_ => $"Spotted at {time}Z",
|
||||||
|
};
|
||||||
|
string text = $"{spot.Call.Text} {kilohertz}\n{who}";
|
||||||
|
return spot.Comment.Trim().Length > 0 ? $"{text}\n{spot.Comment.Trim()}" : text;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,13 +3,26 @@ using Nonemm.Core;
|
|||||||
namespace Nonemm.Spotting;
|
namespace Nonemm.Spotting;
|
||||||
|
|
||||||
/// Where a receiver or transmitter is sitting, drawn on the bandmap as a bar
|
/// Where a receiver or transmitter is sitting, drawn on the bandmap as a bar
|
||||||
/// covering what it hears or occupies rather than a single line.
|
/// covering what it hears or occupies rather than a single line. `Carrier` is
|
||||||
public sealed record VfoMarker(Frequency Low, Frequency High, VfoRole Role)
|
/// the dial frequency, which the bandmap marks with a tick across the bar.
|
||||||
|
public sealed record VfoMarker(Frequency Low, Frequency High, Frequency Carrier, VfoRole Role)
|
||||||
{
|
{
|
||||||
/// The bar for a receiver on `centre`, as wide as the mode it is in.
|
/// The bar for a radio on `frequency` in `mode`. A sideband mode sits to one
|
||||||
public static VfoMarker Centred(Frequency centre, Frequency width, VfoRole role) =>
|
/// side of the carrier: upper sideband above it, lower sideband below it.
|
||||||
new(
|
/// Every other mode is centred on it.
|
||||||
Frequency.FromHertz(centre.Hertz - (width.Hertz / 2)),
|
public static VfoMarker For(Frequency frequency, Mode mode, VfoRole role)
|
||||||
Frequency.FromHertz(centre.Hertz + (width.Hertz / 2)),
|
{
|
||||||
role);
|
long width = Modes.Bandwidth(mode).Hertz;
|
||||||
|
long low = frequency.Hertz - (width / 2);
|
||||||
|
if (mode == Modes.Usb)
|
||||||
|
{
|
||||||
|
low = frequency.Hertz;
|
||||||
|
}
|
||||||
|
else if (mode == Modes.Lsb)
|
||||||
|
{
|
||||||
|
low = frequency.Hertz - width;
|
||||||
|
}
|
||||||
|
return new VfoMarker(
|
||||||
|
Frequency.FromHertz(low), Frequency.FromHertz(low + width), frequency, role);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,14 +82,27 @@ public class BandmapLayoutTests
|
|||||||
Assert.Equal(14_050_000, Scale().At(50).Hertz);
|
Assert.Equal(14_050_000, Scale().At(50).Hertz);
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AReceiverBarIsAsWideAsTheModeItIsIn()
|
public void ACwReceiverBarIsCentredOnTheCarrier()
|
||||||
{
|
{
|
||||||
VfoMarker vfo = VfoMarker.Centred(
|
VfoMarker vfo = VfoMarker.For(Frequency.FromKilohertz(14_025), Modes.Cw, VfoRole.Receive);
|
||||||
Frequency.FromKilohertz(14_025),
|
|
||||||
Frequency.FromHertz(500),
|
|
||||||
VfoRole.Receive);
|
|
||||||
|
|
||||||
Assert.Equal(14_024_750, vfo.Low.Hertz);
|
Assert.Equal((14_024_750, 14_025_250), (vfo.Low.Hertz, vfo.High.Hertz));
|
||||||
Assert.Equal(14_025_250, vfo.High.Hertz);
|
Assert.Equal(14_025_000, vfo.Carrier.Hertz);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AnUpperSidebandBarStartsAtTheCarrier()
|
||||||
|
{
|
||||||
|
VfoMarker vfo = VfoMarker.For(Frequency.FromKilohertz(14_200), Modes.Usb, VfoRole.Receive);
|
||||||
|
|
||||||
|
Assert.Equal((14_200_000, 14_202_700), (vfo.Low.Hertz, vfo.High.Hertz));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ALowerSidebandBarEndsAtTheCarrier()
|
||||||
|
{
|
||||||
|
VfoMarker vfo = VfoMarker.For(Frequency.FromKilohertz(7_100), Modes.Lsb, VfoRole.Receive);
|
||||||
|
|
||||||
|
Assert.Equal((7_097_300, 7_100_000), (vfo.Low.Hertz, vfo.High.Hertz));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using Nonemm.Core;
|
||||||
|
|
||||||
|
namespace Nonemm.Spotting.Tests;
|
||||||
|
|
||||||
|
public class SpotDescriptionTests
|
||||||
|
{
|
||||||
|
private static readonly DateTime At = new(2026, 5, 30, 12, 1, 0, DateTimeKind.Utc);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ASpotNamesTheSpotterTheTimeAndTheComment()
|
||||||
|
{
|
||||||
|
Spot spot = new(
|
||||||
|
Callsign.Parse("JA1XYZ"), Frequency.FromKilohertz(14_025), At, SpotSource.Cluster,
|
||||||
|
"K1TTT", "CW 26 dB");
|
||||||
|
|
||||||
|
Assert.Equal("JA1XYZ 14025.0\nSpotted by K1TTT at 12:01Z\nCW 26 dB", SpotDescription.Of(spot));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ASpotWithoutACommentHasTwoLines()
|
||||||
|
{
|
||||||
|
Spot spot = new(
|
||||||
|
Callsign.Parse("JA1XYZ"), Frequency.FromKilohertz(14_025), At, SpotSource.Log);
|
||||||
|
|
||||||
|
Assert.Equal("JA1XYZ 14025.0\nWorked at 12:01Z", SpotDescription.Of(spot));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user