Show what is spotted and not worked yet
The Available Mults and Qs window: a column per band, the multipliers at the top of each column and then the stations that are only points. Clicking one puts the radio there with the call in the entry window, the same as the bandmap does. Every spot on the bandmap goes through ContestLog.Judge, and what comes back a dupe is dropped, so the colours and the ordering agree with every other window. A spot holds a callsign and a frequency and nothing else. For CQ WW that gives the country multiplier, which is derived from the call, but not the zone, which is what the other station sends. Where a call history file has an entry for the call its exchange is filled into the candidate first, so a published CQZone column brings the zone in and a Sect column makes Sweepstakes work at all. Filling the zone from the country file instead was rejected: it is wrong for every large country, and a multiplier that is not there is better than one that is not real. A spot has no mode either, so the candidate is judged in the mode the radio is in. In a mixed-mode contest that means the answer follows the operator. RadioPosition kept two private mappings — exchange value into a contact, and call history field into an exchange box — and both are needed here, so they move to ExchangeSlots and both callers share them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
15
src/Nonemm.App/Windows/AvailableWindow.axaml
Normal file
15
src/Nonemm.App/Windows/AvailableWindow.axaml
Normal file
@@ -0,0 +1,15 @@
|
||||
<local:RefreshableWindow xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Nonemm.App.Windows"
|
||||
x:Class="Nonemm.App.Windows.AvailableWindow"
|
||||
Title="Available mults and Qs" Width="620" Height="340">
|
||||
<DockPanel Margin="6">
|
||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="10" Margin="0,0,0,6">
|
||||
<CheckBox Name="MultsOnlyBox" Content="Mults only" />
|
||||
<TextBlock Name="StatusText" FontSize="11" Opacity="0.7" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<ScrollViewer>
|
||||
<Grid Name="Columns" />
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</local:RefreshableWindow>
|
||||
110
src/Nonemm.App/Windows/AvailableWindow.axaml.cs
Normal file
110
src/Nonemm.App/Windows/AvailableWindow.axaml.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using Nonemm.Core;
|
||||
using Nonemm.Session;
|
||||
|
||||
namespace Nonemm.App.Windows;
|
||||
|
||||
/// What is left to work, a column per band: the multipliers at the top of each
|
||||
/// column, then the stations that are only points. Clicking one puts the radio
|
||||
/// there with the call in the entry window, the same as clicking a spot on the
|
||||
/// bandmap.
|
||||
public sealed partial class AvailableWindow : RefreshableWindow
|
||||
{
|
||||
private readonly AppSession session;
|
||||
private readonly Action<Frequency, string> tune;
|
||||
|
||||
public AvailableWindow(AppSession session, Action<Frequency, string> tune)
|
||||
{
|
||||
this.session = session;
|
||||
this.tune = tune;
|
||||
InitializeComponent();
|
||||
MultsOnlyBox.IsCheckedChanged += (_, _) => Refresh();
|
||||
session.Bandmap.Changed += (_, _) => Dispatcher.UIThread.Post(Refresh);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
public override void Refresh()
|
||||
{
|
||||
Columns.Children.Clear();
|
||||
Columns.ColumnDefinitions.Clear();
|
||||
if (session.Available is null)
|
||||
{
|
||||
StatusText.Text = "no contest is open";
|
||||
return;
|
||||
}
|
||||
|
||||
session.Bandmap.DropOlderThan(DateTime.UtcNow);
|
||||
bool multsOnly = MultsOnlyBox.IsChecked == true;
|
||||
List<AvailableStation> stations = session.Available.All()
|
||||
.Where(s => !multsOnly || s.IsMultiplier)
|
||||
.ToList();
|
||||
List<Band> bands = stations
|
||||
.Select(s => s.Band)
|
||||
.Distinct()
|
||||
.OrderBy(b => b.Low.Hertz)
|
||||
.ToList();
|
||||
|
||||
int at = 0;
|
||||
foreach (Band band in bands)
|
||||
{
|
||||
Columns.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Star));
|
||||
Control column = BuildColumn(band, stations.Where(s => s.Band == band).ToList());
|
||||
Grid.SetColumn(column, at++);
|
||||
Columns.Children.Add(column);
|
||||
}
|
||||
|
||||
int mults = stations.Count(s => s.IsMultiplier);
|
||||
StatusText.Text = stations.Count == 0
|
||||
? "nothing spotted that is not worked"
|
||||
: $"{stations.Count} to work · {mults} multipliers";
|
||||
}
|
||||
|
||||
private Control BuildColumn(Band band, IReadOnlyList<AvailableStation> stations)
|
||||
{
|
||||
StackPanel panel = new() { Margin = new Avalonia.Thickness(0, 0, 8, 0) };
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"{band.Name} {stations.Count}",
|
||||
FontSize = 11,
|
||||
Opacity = 0.7,
|
||||
Margin = new Avalonia.Thickness(0, 0, 0, 4),
|
||||
});
|
||||
foreach (AvailableStation station in stations)
|
||||
{
|
||||
panel.Children.Add(Row(station));
|
||||
}
|
||||
return panel;
|
||||
}
|
||||
|
||||
private Control Row(AvailableStation station)
|
||||
{
|
||||
TextBlock text = new()
|
||||
{
|
||||
Text = Label(station),
|
||||
FontFamily = new FontFamily("monospace"),
|
||||
FontSize = 13,
|
||||
Foreground = Verdicts.Colour(station.Verdict),
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
Cursor = new Cursor(StandardCursorType.Hand),
|
||||
};
|
||||
ToolTip.SetTip(text, Tip(station));
|
||||
text.PointerPressed += (_, _) => tune(station.Spot.Frequency, station.Spot.Call.Text);
|
||||
return text;
|
||||
}
|
||||
|
||||
private static string Label(AvailableStation station)
|
||||
{
|
||||
string call = $"{station.Spot.Frequency.Kilohertz,9:0.0} {station.Spot.Call.Text}";
|
||||
return station.IsMultiplier ? $"{call} · {station.MultiplierText}" : call;
|
||||
}
|
||||
|
||||
private static string Tip(AvailableStation station)
|
||||
{
|
||||
string who = station.Spot.Spotter.Length > 0 ? $" de {station.Spot.Spotter}" : "";
|
||||
return $"{Verdicts.Describe(station.Verdict)}{who}";
|
||||
}
|
||||
}
|
||||
@@ -230,6 +230,9 @@ public sealed partial class EntryWindow
|
||||
|
||||
private void OnShowBandmap(object? sender, RoutedEventArgs e) => Show(() => new BandmapWindow(session, Tune));
|
||||
|
||||
private void OnShowAvailable(object? sender, RoutedEventArgs e) =>
|
||||
Show(() => new AvailableWindow(session, Tune));
|
||||
|
||||
private void OnShowScore(object? sender, RoutedEventArgs e) => Show(() => new ScoreWindow(session));
|
||||
|
||||
private void OnShowPacket(object? sender, RoutedEventArgs e) => Show(() => new PacketWindow(session));
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
<MenuItem Header="_Log" Click="OnShowLog" />
|
||||
<MenuItem Header="_Check" Click="OnShowCheck" />
|
||||
<MenuItem Header="_Bandmap" Click="OnShowBandmap" />
|
||||
<MenuItem Header="_Available Mults and Qs" Click="OnShowAvailable" />
|
||||
<MenuItem Header="_Score Summary" Click="OnShowScore" />
|
||||
<MenuItem Header="_Packet" Click="OnShowPacket" />
|
||||
</MenuItem>
|
||||
|
||||
Reference in New Issue
Block a user