Lay the station form out the way N1MM lays it out
The dialog now carries N1MM's fields in N1MM's order, with the labels to the left of the boxes and the tip text at the top right. New fields in StoredStation: the address, the licence class, the position, the radio, the antenna and the email address. The grid square and the position fill each other in, as they do in N1MM. Which box has focus decides the direction, because TextChanged arrives too late to tell a typed edit from a programmed one. The fields N1MM's form does not have are gone from it. Continent and the country prefix are looked up from the callsign after Ok; the rest keep the value they had. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -356,6 +356,38 @@ public sealed record StoredStation
|
|||||||
{
|
{
|
||||||
public string Callsign { get; init; } = "";
|
public string Callsign { get; init; } = "";
|
||||||
|
|
||||||
|
public string Address1 { get; init; } = "";
|
||||||
|
|
||||||
|
public string Address2 { get; init; } = "";
|
||||||
|
|
||||||
|
public string City { get; init; } = "";
|
||||||
|
|
||||||
|
public string Zip { get; init; } = "";
|
||||||
|
|
||||||
|
/// The country written out, as it goes on an award application. The prefix
|
||||||
|
/// the contest rules compare against is `CountryPrefix`.
|
||||||
|
public string Country { get; init; } = "";
|
||||||
|
|
||||||
|
public string License { get; init; } = "";
|
||||||
|
|
||||||
|
public double Latitude { get; init; }
|
||||||
|
|
||||||
|
public double Longitude { get; init; }
|
||||||
|
|
||||||
|
/// The radio, as N1MM's "Station" box asks for it.
|
||||||
|
public string Rig { get; init; } = "";
|
||||||
|
|
||||||
|
public string Antenna { get; init; } = "";
|
||||||
|
|
||||||
|
public string AntennaHeight { get; init; } = "";
|
||||||
|
|
||||||
|
/// Height above sea level.
|
||||||
|
public string AboveSeaLevel { get; init; } = "";
|
||||||
|
|
||||||
|
public string RoverQth { get; init; } = "";
|
||||||
|
|
||||||
|
public string Email { get; init; } = "";
|
||||||
|
|
||||||
public int CqZone { get; init; }
|
public int CqZone { get; init; }
|
||||||
|
|
||||||
public int ItuZone { get; init; }
|
public int ItuZone { get; init; }
|
||||||
@@ -401,5 +433,7 @@ public sealed record StoredStation
|
|||||||
Club = Club,
|
Club = Club,
|
||||||
Check = Check,
|
Check = Check,
|
||||||
Precedence = Precedence,
|
Precedence = Precedence,
|
||||||
|
Latitude = Latitude,
|
||||||
|
Longitude = Longitude,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +1,123 @@
|
|||||||
<Window xmlns="https://github.com/avaloniaui"
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
x:Class="Nonemm.App.Dialogs.StationDialog"
|
x:Class="Nonemm.App.Dialogs.StationDialog"
|
||||||
Title="Station" Width="440" SizeToContent="Height"
|
Title="Edit Station Information" Width="545" SizeToContent="Height"
|
||||||
WindowStartupLocation="CenterOwner" CanResize="False">
|
WindowStartupLocation="CenterOwner" CanResize="False">
|
||||||
<Window.Styles>
|
<Window.Styles>
|
||||||
<Style Selector="TextBlock.field">
|
<Style Selector="TextBlock.label">
|
||||||
<Setter Property="FontSize" Value="11" />
|
<Setter Property="FontSize" Value="11" />
|
||||||
<Setter Property="Opacity" Value="0.7" />
|
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||||
<Setter Property="Margin" Value="0,6,0,1" />
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Property="Margin" Value="6,0,6,0" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBlock.first">
|
||||||
|
<Setter Property="Width" Value="95" />
|
||||||
|
<Setter Property="TextAlignment" Value="Right" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBox">
|
||||||
|
<Setter Property="FontSize" Value="11" />
|
||||||
|
<Setter Property="MinHeight" Value="22" />
|
||||||
|
<Setter Property="Padding" Value="4,1" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ComboBox">
|
||||||
|
<Setter Property="FontSize" Value="11" />
|
||||||
|
<Setter Property="MinHeight" Value="22" />
|
||||||
|
<Setter Property="Padding" Value="4,1" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="StackPanel.row">
|
||||||
|
<Setter Property="Orientation" Value="Horizontal" />
|
||||||
|
<Setter Property="Margin" Value="0,0,0,4" />
|
||||||
</Style>
|
</Style>
|
||||||
</Window.Styles>
|
</Window.Styles>
|
||||||
<StackPanel Margin="14" Spacing="0">
|
<StackPanel Margin="10">
|
||||||
<Grid ColumnDefinitions="*,8,*,8,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
<Grid ColumnDefinitions="Auto,*">
|
||||||
<TextBlock Classes="field" Text="Callsign" />
|
<StackPanel>
|
||||||
<TextBox Name="CallsignBox" Grid.Row="1" />
|
<StackPanel Classes="row">
|
||||||
<TextBlock Classes="field" Grid.Column="2" Text="CQ zone" />
|
<TextBlock Classes="label first" Text="Call" />
|
||||||
<TextBox Name="CqZoneBox" Grid.Row="1" Grid.Column="2" />
|
<TextBox Name="CallsignBox" Width="190" />
|
||||||
<TextBlock Classes="field" Grid.Column="4" Text="ITU zone" />
|
</StackPanel>
|
||||||
<TextBox Name="ItuZoneBox" Grid.Row="1" Grid.Column="4" />
|
<StackPanel Classes="row">
|
||||||
|
<TextBlock Classes="label first" Text="Name" />
|
||||||
<TextBlock Classes="field" Grid.Row="2" Text="Continent" />
|
<TextBox Name="NameBox" Width="190" />
|
||||||
<TextBox Name="ContinentBox" Grid.Row="3" />
|
</StackPanel>
|
||||||
<TextBlock Classes="field" Grid.Row="2" Grid.Column="2" Text="Country prefix" />
|
<StackPanel Classes="row">
|
||||||
<TextBox Name="CountryBox" Grid.Row="3" Grid.Column="2" />
|
<TextBlock Classes="label first" Text="Address" />
|
||||||
<TextBlock Classes="field" Grid.Row="2" Grid.Column="4" Text="Grid square" />
|
<TextBox Name="AddressBox" Width="190" />
|
||||||
<TextBox Name="GridBox" Grid.Row="3" Grid.Column="4" />
|
</StackPanel>
|
||||||
|
<StackPanel Classes="row">
|
||||||
<TextBlock Classes="field" Grid.Row="4" Text="State" />
|
<TextBlock Classes="label first" Text="Address" />
|
||||||
<TextBox Name="StateBox" Grid.Row="5" />
|
<TextBox Name="Address2Box" Width="190" />
|
||||||
<TextBlock Classes="field" Grid.Row="4" Grid.Column="2" Text="Province" />
|
</StackPanel>
|
||||||
<TextBox Name="ProvinceBox" Grid.Row="5" Grid.Column="2" />
|
</StackPanel>
|
||||||
<TextBlock Classes="field" Grid.Row="4" Grid.Column="4" Text="ARRL section" />
|
<TextBlock Grid.Column="1" FontSize="11" TextWrapping="Wrap" Margin="12,2,0,0"
|
||||||
<TextBox Name="SectionBox" Grid.Row="5" Grid.Column="4" />
|
Foreground="#0000C0"
|
||||||
|
Text="Tip: You need to fill out this form or the program will not perform properly... Also, make sure your computer date and time are set to the LOCAL date and time zone for your location." />
|
||||||
<TextBlock Classes="field" Grid.Row="6" Text="Name" />
|
|
||||||
<TextBox Name="NameBox" Grid.Row="7" />
|
|
||||||
<TextBlock Classes="field" Grid.Row="6" Grid.Column="2" Text="Power" />
|
|
||||||
<TextBox Name="PowerBox" Grid.Row="7" Grid.Column="2" />
|
|
||||||
<TextBlock Classes="field" Grid.Row="6" Grid.Column="4" Text="Club" />
|
|
||||||
<TextBox Name="ClubBox" Grid.Row="7" Grid.Column="4" />
|
|
||||||
|
|
||||||
<TextBlock Classes="field" Grid.Row="8" Text="Check (year first licensed)" />
|
|
||||||
<TextBox Name="CheckBox" Grid.Row="9" />
|
|
||||||
<TextBlock Classes="field" Grid.Row="8" Grid.Column="2" Text="Precedence" />
|
|
||||||
<TextBox Name="PrecedenceBox" Grid.Row="9" Grid.Column="2" />
|
|
||||||
<TextBlock Classes="field" Grid.Row="8" Grid.Column="4" Text="County" />
|
|
||||||
<TextBox Name="CountyBox" Grid.Row="9" Grid.Column="4" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="6" Margin="0,14,0,0">
|
<StackPanel Classes="row">
|
||||||
<Button Content="Cancel" Click="OnCancel" />
|
<TextBlock Classes="label first" Text="City" />
|
||||||
<Button Content="Save" Click="OnSave" IsDefault="True" />
|
<TextBox Name="CityBox" Width="105" />
|
||||||
|
<TextBlock Classes="label" Text="State" />
|
||||||
|
<TextBox Name="StateBox" Width="45" />
|
||||||
|
<TextBlock Classes="label" Text="Zip" />
|
||||||
|
<TextBox Name="ZipBox" Width="140" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Classes="row">
|
||||||
|
<TextBlock Classes="label first" Text="Country" />
|
||||||
|
<TextBox Name="CountryBox" Width="130" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Classes="row">
|
||||||
|
<TextBlock Classes="label first" Text="Grid Square" />
|
||||||
|
<TextBox Name="GridBox" Width="105" />
|
||||||
|
<TextBlock Classes="label" Text="CQ" />
|
||||||
|
<TextBox Name="CqZoneBox" Width="60" />
|
||||||
|
<TextBlock Classes="label" Text="ITU" />
|
||||||
|
<TextBox Name="ItuZoneBox" Width="90" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Classes="row">
|
||||||
|
<TextBlock Classes="label first" Text="License" />
|
||||||
|
<TextBox Name="LicenseBox" Width="60" />
|
||||||
|
<TextBlock Classes="label" Text="Latitude" />
|
||||||
|
<TextBox Name="LatitudeBox" Width="55" />
|
||||||
|
<ComboBox Name="LatitudeSignBox" Width="55" Margin="4,0,0,0" />
|
||||||
|
<TextBlock Classes="label" Text="Longitude" />
|
||||||
|
<TextBox Name="LongitudeBox" Width="60" />
|
||||||
|
<ComboBox Name="LongitudeSignBox" Width="55" Margin="4,0,0,0" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Classes="row" Margin="0,6,0,4">
|
||||||
|
<TextBlock Classes="label first" Text="Station" />
|
||||||
|
<TextBox Name="RigBox" Width="175" />
|
||||||
|
<TextBlock Classes="label" Text="Power" />
|
||||||
|
<TextBox Name="PowerBox" Width="80" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Classes="row">
|
||||||
|
<TextBlock Classes="label first" Text="Antenna" />
|
||||||
|
<TextBox Name="AntennaBox" Width="120" />
|
||||||
|
<TextBlock Classes="label" Text="Ant. Height" />
|
||||||
|
<TextBox Name="AntennaHeightBox" Width="60" />
|
||||||
|
<TextBlock Classes="label" Text="a.s.l." />
|
||||||
|
<TextBox Name="AboveSeaLevelBox" Width="70" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Classes="row" Margin="0,6,0,4">
|
||||||
|
<TextBlock Classes="label first" Text="ARRL Section" />
|
||||||
|
<TextBox Name="SectionBox" Width="100" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Classes="row">
|
||||||
|
<TextBlock Classes="label first" Text="Rover QTH" />
|
||||||
|
<TextBox Name="RoverQthBox" Width="100" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Classes="row">
|
||||||
|
<TextBlock Classes="label first" Text="Club" />
|
||||||
|
<TextBox Name="ClubBox" Width="235" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Classes="row">
|
||||||
|
<TextBlock Classes="label first" Text="Email address" />
|
||||||
|
<TextBox Name="EmailBox" Width="305" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="30" Margin="0,14,0,4">
|
||||||
|
<Button Content="Ok" Width="85" HorizontalContentAlignment="Center" Click="OnSave" IsDefault="True" />
|
||||||
|
<Button Content="Cancel" Width="85" HorizontalContentAlignment="Center" Click="OnCancel" IsCancel="True" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
using System.Globalization;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Nonemm.Core;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Nonemm.App.Configuration;
|
using Nonemm.App.Configuration;
|
||||||
|
|
||||||
@@ -8,47 +10,120 @@ namespace Nonemm.App.Dialogs;
|
|||||||
/// rules compare a worked station against.
|
/// rules compare a worked station against.
|
||||||
public sealed partial class StationDialog : Window
|
public sealed partial class StationDialog : Window
|
||||||
{
|
{
|
||||||
|
private readonly StoredStation station;
|
||||||
|
|
||||||
public StationDialog(StoredStation station)
|
public StationDialog(StoredStation station)
|
||||||
{
|
{
|
||||||
|
this.station = station;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
LatitudeSignBox.ItemsSource = new[] { "N", "S" };
|
||||||
|
LongitudeSignBox.ItemsSource = new[] { "E", "W" };
|
||||||
|
|
||||||
CallsignBox.Text = station.Callsign;
|
CallsignBox.Text = station.Callsign;
|
||||||
|
NameBox.Text = station.Name;
|
||||||
|
AddressBox.Text = station.Address1;
|
||||||
|
Address2Box.Text = station.Address2;
|
||||||
|
CityBox.Text = station.City;
|
||||||
|
StateBox.Text = station.State;
|
||||||
|
ZipBox.Text = station.Zip;
|
||||||
|
CountryBox.Text = station.Country;
|
||||||
|
GridBox.Text = station.GridSquare;
|
||||||
CqZoneBox.Text = station.CqZone.ToString();
|
CqZoneBox.Text = station.CqZone.ToString();
|
||||||
ItuZoneBox.Text = station.ItuZone.ToString();
|
ItuZoneBox.Text = station.ItuZone.ToString();
|
||||||
ContinentBox.Text = station.Continent;
|
LicenseBox.Text = station.License;
|
||||||
CountryBox.Text = station.CountryPrefix;
|
LatitudeBox.Text = Magnitude(station.Latitude);
|
||||||
GridBox.Text = station.GridSquare;
|
LatitudeSignBox.SelectedIndex = station.Latitude < 0 ? 1 : 0;
|
||||||
StateBox.Text = station.State;
|
LongitudeBox.Text = Magnitude(station.Longitude);
|
||||||
ProvinceBox.Text = station.Province;
|
LongitudeSignBox.SelectedIndex = station.Longitude < 0 ? 1 : 0;
|
||||||
SectionBox.Text = station.ArrlSection;
|
RigBox.Text = station.Rig;
|
||||||
NameBox.Text = station.Name;
|
|
||||||
PowerBox.Text = station.Power;
|
PowerBox.Text = station.Power;
|
||||||
|
AntennaBox.Text = station.Antenna;
|
||||||
|
AntennaHeightBox.Text = station.AntennaHeight;
|
||||||
|
AboveSeaLevelBox.Text = station.AboveSeaLevel;
|
||||||
|
SectionBox.Text = station.ArrlSection;
|
||||||
|
RoverQthBox.Text = station.RoverQth;
|
||||||
ClubBox.Text = station.Club;
|
ClubBox.Text = station.Club;
|
||||||
CheckBox.Text = station.Check.ToString();
|
EmailBox.Text = station.Email;
|
||||||
PrecedenceBox.Text = station.Precedence;
|
|
||||||
CountyBox.Text = station.County;
|
GridBox.TextChanged += (_, _) => FillPositionFromGrid();
|
||||||
|
LatitudeBox.TextChanged += (_, _) => FillGridFromPosition();
|
||||||
|
LongitudeBox.TextChanged += (_, _) => FillGridFromPosition();
|
||||||
|
LatitudeSignBox.SelectionChanged += (_, _) => FillGridFromPosition();
|
||||||
|
LongitudeSignBox.SelectionChanged += (_, _) => FillGridFromPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// N1MM fills each of the grid square and the position in from the other.
|
||||||
|
/// Which one is being typed into decides the direction: the events that
|
||||||
|
/// report a box being written to arrive too late to tell them apart.
|
||||||
|
private void FillPositionFromGrid()
|
||||||
|
{
|
||||||
|
if (!GridBox.IsFocused || !GridSquare.TryParse(GridBox.Text ?? "", out GridSquare grid))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LatitudeBox.Text = Magnitude(grid.Latitude);
|
||||||
|
LatitudeSignBox.SelectedIndex = grid.Latitude < 0 ? 1 : 0;
|
||||||
|
LongitudeBox.Text = Magnitude(grid.Longitude);
|
||||||
|
LongitudeSignBox.SelectedIndex = grid.Longitude < 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnSave(object? sender, RoutedEventArgs e) => Close(new StoredStation
|
private void FillGridFromPosition()
|
||||||
|
{
|
||||||
|
bool typed = LatitudeBox.IsFocused || LongitudeBox.IsFocused ||
|
||||||
|
LatitudeSignBox.IsFocused || LongitudeSignBox.IsFocused;
|
||||||
|
if (!typed || (LatitudeBox.Text ?? "").Trim().Length == 0 || (LongitudeBox.Text ?? "").Trim().Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double latitude = Signed(LatitudeBox, LatitudeSignBox);
|
||||||
|
double longitude = Signed(LongitudeBox, LongitudeSignBox);
|
||||||
|
if (Math.Abs(latitude) > 90.0 || Math.Abs(longitude) > 180.0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GridBox.Text = GridSquare.From(latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The boxes over the record the dialog was opened with, so the fields
|
||||||
|
/// N1MM's form does not show keep the value they had.
|
||||||
|
private void OnSave(object? sender, RoutedEventArgs e) => Close(station with
|
||||||
{
|
{
|
||||||
Callsign = (CallsignBox.Text ?? "").Trim().ToUpperInvariant(),
|
Callsign = (CallsignBox.Text ?? "").Trim().ToUpperInvariant(),
|
||||||
|
Name = (NameBox.Text ?? "").Trim(),
|
||||||
|
Address1 = (AddressBox.Text ?? "").Trim(),
|
||||||
|
Address2 = (Address2Box.Text ?? "").Trim(),
|
||||||
|
City = (CityBox.Text ?? "").Trim(),
|
||||||
|
State = (StateBox.Text ?? "").Trim().ToUpperInvariant(),
|
||||||
|
Zip = (ZipBox.Text ?? "").Trim(),
|
||||||
|
Country = (CountryBox.Text ?? "").Trim(),
|
||||||
|
GridSquare = (GridBox.Text ?? "").Trim(),
|
||||||
CqZone = Number(CqZoneBox),
|
CqZone = Number(CqZoneBox),
|
||||||
ItuZone = Number(ItuZoneBox),
|
ItuZone = Number(ItuZoneBox),
|
||||||
Continent = (ContinentBox.Text ?? "").Trim().ToUpperInvariant(),
|
License = (LicenseBox.Text ?? "").Trim(),
|
||||||
CountryPrefix = (CountryBox.Text ?? "").Trim().ToUpperInvariant(),
|
Latitude = Signed(LatitudeBox, LatitudeSignBox),
|
||||||
GridSquare = (GridBox.Text ?? "").Trim(),
|
Longitude = Signed(LongitudeBox, LongitudeSignBox),
|
||||||
State = (StateBox.Text ?? "").Trim().ToUpperInvariant(),
|
Rig = (RigBox.Text ?? "").Trim(),
|
||||||
Province = (ProvinceBox.Text ?? "").Trim().ToUpperInvariant(),
|
|
||||||
ArrlSection = (SectionBox.Text ?? "").Trim().ToUpperInvariant(),
|
|
||||||
Name = (NameBox.Text ?? "").Trim(),
|
|
||||||
Power = (PowerBox.Text ?? "").Trim(),
|
Power = (PowerBox.Text ?? "").Trim(),
|
||||||
|
Antenna = (AntennaBox.Text ?? "").Trim(),
|
||||||
|
AntennaHeight = (AntennaHeightBox.Text ?? "").Trim(),
|
||||||
|
AboveSeaLevel = (AboveSeaLevelBox.Text ?? "").Trim(),
|
||||||
|
ArrlSection = (SectionBox.Text ?? "").Trim().ToUpperInvariant(),
|
||||||
|
RoverQth = (RoverQthBox.Text ?? "").Trim().ToUpperInvariant(),
|
||||||
Club = (ClubBox.Text ?? "").Trim(),
|
Club = (ClubBox.Text ?? "").Trim(),
|
||||||
Check = Number(CheckBox),
|
Email = (EmailBox.Text ?? "").Trim(),
|
||||||
Precedence = (PrecedenceBox.Text ?? "").Trim().ToUpperInvariant(),
|
|
||||||
County = (CountyBox.Text ?? "").Trim().ToUpperInvariant(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
private void OnCancel(object? sender, RoutedEventArgs e) => Close(null);
|
private void OnCancel(object? sender, RoutedEventArgs e) => Close(null);
|
||||||
|
|
||||||
private static int Number(TextBox box) => int.TryParse(box.Text, out int value) ? value : 0;
|
private static int Number(TextBox box) => int.TryParse(box.Text, out int value) ? value : 0;
|
||||||
|
|
||||||
|
private static string Magnitude(double degrees) =>
|
||||||
|
degrees == 0 ? "" : Math.Abs(degrees).ToString("0.####", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
private static double Signed(TextBox box, ComboBox sign)
|
||||||
|
{
|
||||||
|
double degrees = double.TryParse(
|
||||||
|
box.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out double value) ? Math.Abs(value) : 0;
|
||||||
|
return sign.SelectedIndex == 1 ? -degrees : degrees;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Avalonia.Platform.Storage;
|
|||||||
using Nonemm.App.Configuration;
|
using Nonemm.App.Configuration;
|
||||||
using Nonemm.App.Dialogs;
|
using Nonemm.App.Dialogs;
|
||||||
using Nonemm.Core;
|
using Nonemm.Core;
|
||||||
|
using Nonemm.Core.Country;
|
||||||
using Nonemm.Formats.Adif;
|
using Nonemm.Formats.Adif;
|
||||||
using Nonemm.Formats.Cabrillo;
|
using Nonemm.Formats.Cabrillo;
|
||||||
using Nonemm.Session;
|
using Nonemm.Session;
|
||||||
@@ -244,7 +245,7 @@ public sealed partial class EntryWindow
|
|||||||
StoredStation? updated = await dialog.ShowDialog<StoredStation?>(this);
|
StoredStation? updated = await dialog.ShowDialog<StoredStation?>(this);
|
||||||
if (updated is not null)
|
if (updated is not null)
|
||||||
{
|
{
|
||||||
session.Save(session.Settings with { Station = updated });
|
session.Save(session.Settings with { Station = FromCall(updated) });
|
||||||
if (Logging is not null)
|
if (Logging is not null)
|
||||||
{
|
{
|
||||||
session.OpenContest(Logging.Instance.ContestNumber);
|
session.OpenContest(Logging.Instance.ContestNumber);
|
||||||
@@ -252,6 +253,18 @@ public sealed partial class EntryWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// N1MM's station form has no continent or country box: it looks both up
|
||||||
|
/// from the callsign in the country file.
|
||||||
|
private StoredStation FromCall(StoredStation station)
|
||||||
|
{
|
||||||
|
CountryLookup? country = session.Countries?.Find(station.Callsign);
|
||||||
|
return country is null ? station : station with
|
||||||
|
{
|
||||||
|
Continent = country.Continent,
|
||||||
|
CountryPrefix = country.Entity.PrimaryPrefix,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async void OnRadioSettings(object? sender, RoutedEventArgs e)
|
private async void OnRadioSettings(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
RadioDialog dialog = new(session.Settings);
|
RadioDialog dialog = new(session.Settings);
|
||||||
|
|||||||
@@ -68,6 +68,24 @@ public readonly record struct GridSquare
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The six-character locator of a point, which is what N1MM fills the grid
|
||||||
|
/// square box in with.
|
||||||
|
public static string From(double latitude, double longitude)
|
||||||
|
{
|
||||||
|
double lat = Math.Clamp(latitude, -90.0, 90.0) + 90.0;
|
||||||
|
double lon = Math.Clamp(longitude, -180.0, 180.0) + 180.0;
|
||||||
|
char[] text =
|
||||||
|
[
|
||||||
|
(char)('A' + (int)(lon / 20.0)),
|
||||||
|
(char)('A' + (int)(lat / 10.0)),
|
||||||
|
(char)('0' + (int)(lon % 20.0 / 2.0)),
|
||||||
|
(char)('0' + (int)(lat % 10.0)),
|
||||||
|
(char)('a' + (int)(lon % 2.0 * 12.0)),
|
||||||
|
(char)('a' + (int)(lat % 1.0 * 24.0)),
|
||||||
|
];
|
||||||
|
return new string(text);
|
||||||
|
}
|
||||||
|
|
||||||
/// Great-circle distance in kilometres.
|
/// Great-circle distance in kilometres.
|
||||||
public double DistanceTo(GridSquare other) =>
|
public double DistanceTo(GridSquare other) =>
|
||||||
DistanceKm(Latitude, Longitude, other.Latitude, other.Longitude);
|
DistanceKm(Latitude, Longitude, other.Latitude, other.Longitude);
|
||||||
|
|||||||
@@ -43,4 +43,15 @@ public class GridSquareTests
|
|||||||
Assert.True(GridSquare.TryParse("JP90", out GridSquare north));
|
Assert.True(GridSquare.TryParse("JP90", out GridSquare north));
|
||||||
Assert.Equal(0, south.BearingTo(north), 6);
|
Assert.Equal(0, south.BearingTo(north), 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("JN88md")]
|
||||||
|
[InlineData("FN31pr")]
|
||||||
|
[InlineData("AA00aa")]
|
||||||
|
[InlineData("RR99xx")]
|
||||||
|
public void LocatorOfASquareCentreIsThatSquare(string locator)
|
||||||
|
{
|
||||||
|
Assert.True(GridSquare.TryParse(locator, out GridSquare grid));
|
||||||
|
Assert.Equal(locator, GridSquare.From(grid.Latitude, grid.Longitude));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user