Paint the band the way N1MM paints it
Behind the bandmap scale, the CW part of the band is one shade and the phone part another, with a third for a digital block. That is what N1MM's graphical bandmap does: ResizeBandPanels puts a CW, a Digi and an SSB panel behind the scale, sized from its CWBands, DigiBands and SSBBands tables. Anything past a band edge is shaded red, which is the only place the edges themselves show, and it only appears now that the slice can reach past an edge. The defaults are N1MM's: CW ends at 1840, 3600, 7125, 14150 and so on, and the digital pair is empty until somebody fills it in, exactly as N1MM ships. They are one worldwide set rather than one per ITU region, which is also N1MM's answer — the reason recorded for leaving this out, that the segments differ by region, was wrong about what N1MM holds. 60M and everything above 2M get no entry, because N1MM's own numbers there contradict themselves: its 1.25M CW top is below its phone start, and its 70CM CW top is above the band. Config ▸ Sub bands edits them, one row per band in kilohertz, with a Defaults button. Only the rows that differ are written to the settings file, so a plan nobody has touched leaves nothing behind. BandPlan is an object rather than a static table, so the stored changes layer over the defaults and the bandmap picks up a save without a restart. The numbers are a drawing aid, not a licence. Nothing stops a contact being logged either side of a boundary. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
20
src/Nonemm.App/Dialogs/SubBandsDialog.axaml
Normal file
20
src/Nonemm.App/Dialogs/SubBandsDialog.axaml
Normal file
@@ -0,0 +1,20 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="Nonemm.App.Dialogs.SubBandsDialog"
|
||||
Title="Sub bands" Width="460" Height="460"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<DockPanel Margin="14">
|
||||
<TextBlock DockPanel.Dock="Top" TextWrapping="Wrap" FontSize="11" Opacity="0.7"
|
||||
Margin="0,0,0,10"
|
||||
Text="Kilohertz. CW runs from the bottom of the band to the CW end, phone from there to the top. Leave the digital pair empty for no digital block. Blank the CW end to go back to the default." />
|
||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal"
|
||||
HorizontalAlignment="Right" Spacing="6" Margin="0,10,0,0">
|
||||
<Button Content="Defaults" Click="OnDefaults" />
|
||||
<Button Content="Cancel" Click="OnCancel" />
|
||||
<Button Content="Save" Click="OnSave" IsDefault="True" />
|
||||
</StackPanel>
|
||||
<ScrollViewer>
|
||||
<Grid Name="Table" ColumnDefinitions="60,*,*,*" />
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
125
src/Nonemm.App/Dialogs/SubBandsDialog.axaml.cs
Normal file
125
src/Nonemm.App/Dialogs/SubBandsDialog.axaml.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System.Globalization;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Nonemm.App.Configuration;
|
||||
using Nonemm.Core;
|
||||
|
||||
namespace Nonemm.App.Dialogs;
|
||||
|
||||
/// The CW, digital and phone boundaries the bandmap paints, one row per band.
|
||||
/// N1MM has the same editor under Config; the numbers are a drawing aid, so a
|
||||
/// row that cannot be read is dropped rather than refused.
|
||||
public sealed partial class SubBandsDialog : Window
|
||||
{
|
||||
private readonly Settings settings;
|
||||
private readonly List<Row> rows = [];
|
||||
|
||||
public SubBandsDialog(Settings settings)
|
||||
{
|
||||
this.settings = settings;
|
||||
InitializeComponent();
|
||||
Build(settings.ToBandPlan());
|
||||
}
|
||||
|
||||
private void Build(BandPlan plan)
|
||||
{
|
||||
Table.Children.Clear();
|
||||
Table.RowDefinitions.Clear();
|
||||
rows.Clear();
|
||||
AddHeader();
|
||||
int at = 1;
|
||||
foreach (BandSegments segments in BandPlan.Default.All)
|
||||
{
|
||||
BandSegments shown = plan.For(segments.Band) ?? segments;
|
||||
rows.Add(AddRow(at++, shown));
|
||||
}
|
||||
}
|
||||
|
||||
private void AddHeader()
|
||||
{
|
||||
Table.RowDefinitions.Add(new RowDefinition(GridLength.Auto));
|
||||
string[] headings = ["Band", "CW ends", "Digital from", "Digital to"];
|
||||
for (int column = 0; column < headings.Length; column++)
|
||||
{
|
||||
TextBlock text = new()
|
||||
{
|
||||
Text = headings[column],
|
||||
FontSize = 11,
|
||||
Opacity = 0.7,
|
||||
Margin = new Avalonia.Thickness(0, 0, 6, 4),
|
||||
};
|
||||
Grid.SetColumn(text, column);
|
||||
Table.Children.Add(text);
|
||||
}
|
||||
}
|
||||
|
||||
private Row AddRow(int at, BandSegments segments)
|
||||
{
|
||||
Table.RowDefinitions.Add(new RowDefinition(GridLength.Auto));
|
||||
TextBlock name = new()
|
||||
{
|
||||
Text = segments.Band.Name,
|
||||
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
|
||||
Margin = new Avalonia.Thickness(0, 2, 6, 2),
|
||||
};
|
||||
Grid.SetRow(name, at);
|
||||
Table.Children.Add(name);
|
||||
|
||||
TextBox cw = Box(at, 1, segments.CwHigh);
|
||||
TextBox digitalLow = Box(at, 2, segments.HasDigital ? segments.DigitalLow : Frequency.Zero);
|
||||
TextBox digitalHigh = Box(at, 3, segments.HasDigital ? segments.DigitalHigh : Frequency.Zero);
|
||||
return new Row(segments.Band, cw, digitalLow, digitalHigh);
|
||||
}
|
||||
|
||||
private TextBox Box(int at, int column, Frequency value)
|
||||
{
|
||||
TextBox box = new()
|
||||
{
|
||||
Text = value.Hertz == 0 ? "" : value.Kilohertz.ToString("0.#", CultureInfo.InvariantCulture),
|
||||
Margin = new Avalonia.Thickness(0, 2, 6, 2),
|
||||
};
|
||||
Grid.SetRow(box, at);
|
||||
Grid.SetColumn(box, column);
|
||||
Table.Children.Add(box);
|
||||
return box;
|
||||
}
|
||||
|
||||
private void OnDefaults(object? sender, RoutedEventArgs e) => Build(BandPlan.Default);
|
||||
|
||||
private void OnSave(object? sender, RoutedEventArgs e) => Close(settings with
|
||||
{
|
||||
SubBands = rows.Select(r => r.ToStored()).OfType<StoredSubBand>().ToList(),
|
||||
});
|
||||
|
||||
private void OnCancel(object? sender, RoutedEventArgs e) => Close(null);
|
||||
|
||||
private sealed record Row(Band Band, TextBox CwHigh, TextBox DigitalLow, TextBox DigitalHigh)
|
||||
{
|
||||
/// Null when the row is the default for that band, so a plan nobody has
|
||||
/// touched stays out of the settings file.
|
||||
public StoredSubBand? ToStored()
|
||||
{
|
||||
BandSegments? mine = Segments();
|
||||
return mine is null || mine == BandPlan.Default.For(Band)
|
||||
? null
|
||||
: StoredSubBand.From(mine);
|
||||
}
|
||||
|
||||
private BandSegments? Segments()
|
||||
{
|
||||
if (Kilohertz(CwHigh) is not { } cw)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Frequency low = Kilohertz(DigitalLow) ?? Frequency.Zero;
|
||||
Frequency high = Kilohertz(DigitalHigh) ?? Frequency.Zero;
|
||||
return new BandSegments(Band, cw, low, high);
|
||||
}
|
||||
|
||||
private static Frequency? Kilohertz(TextBox box) =>
|
||||
double.TryParse(box.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out double value)
|
||||
&& value > 0
|
||||
? Frequency.FromKilohertz(value)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user