Add N1MM's Mark and Store buttons

Both put something on our own bandmap and nothing on the cluster, which is
what separates them from Spot It.

Mark, Alt+M, leaves a mark where the radio is to say the frequency is busy.
The label is N1MM's, Busy@ and the time. It is not a station, so Spot carries
IsStation: nothing judges it, the check window does not offer it as a call,
available mults and Qs leaves it out, space does not take it into the callsign
box, and the bandmap paints it like a station already worked, which is how
N1MM marks it. The station count under the bandmap counts stations.

Store, Alt+O, puts the call being typed on the bandmap at this frequency so it
can be worked later. N1MM writes "Local spot" in the comment and this does the
same.

The action row now holds the eight buttons N1MM has, in N1MM's order and at
N1MM's widths: 23 per cent to Esc: Stop and 11 to each of the rest.

Left out of Store: N1MM also adds the call to what the check window's Master
column offers for the rest of the session. The call is on the bandmap, which
the check window already reads, so it is offered either way.

Driven under Xvfb: Alt+M leaving BUSY@22:32:27 on 14000, Alt+O storing
SP9XYZ there, and both drawn on the bandmap in the worked colour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
This commit is contained in:
2026-08-30 22:34:32 +00:00
parent ec8cdb88d7
commit 9b84b103ac
11 changed files with 135 additions and 16 deletions

View File

@@ -63,7 +63,8 @@ public sealed partial class BandmapWindow : RefreshableWindow
View.Vfos = Vfos();
View.Segments = session.BandPlan.For(band);
StatusText.Text =
$"{View.Spots.Count} stations · {low.Kilohertz:0.#} to {high.Kilohertz:0.#} kHz{Split(band)}";
$"{View.Spots.Count(s => s.IsStation)} stations · " +
$"{low.Kilohertz:0.#} to {high.Kilohertz:0.#} kHz{Split(band)}";
View.InvalidateVisual();
}
@@ -136,6 +137,11 @@ public sealed partial class BandmapWindow : RefreshableWindow
{
return null;
}
if (!spot.IsStation)
{
// N1MM marks a busy frequency as worked, so it is painted like one
return Verdict.Dupe;
}
return session.Position.Log.Judge(new Qso
{
Id = "",

View File

@@ -47,11 +47,15 @@ public sealed partial class EntryWindow
CallFrameText.Text = "";
return;
}
framedCall = spot.Call.Text;
// a mark saying the frequency is busy is shown but never taken into the
// callsign box, because it is not a station
framedCall = spot.IsStation ? spot.Call.Text : "";
CallFrameText.Text = spot.IsSplit
? $"{spot.Call.Text} — listening on {spot.Qsx.Kilohertz:0.0}"
: spot.Call.Text;
CallFrameText.Foreground = Verdicts.Colour(VerdictFor(spot));
CallFrameText.Foreground = spot.IsStation
? Verdicts.Colour(VerdictFor(spot))
: Verdicts.Dupe;
}
private Verdict? VerdictFor(Spot spot) => Logging?.Log.Judge(new Qso

View File

@@ -351,6 +351,50 @@ public sealed partial class EntryWindow
Status($"{call.Text} spotted on {where.Kilohertz:0.0}");
}
/// N1MM's Mark button and Alt+M: leaves a mark on our own bandmap saying
/// this frequency is busy, so the operator does not come back to it. It
/// goes nowhere near the cluster.
private void OnMark(object? sender, RoutedEventArgs e)
{
if (Logging is null)
{
Status("no contest is open");
return;
}
Spot mark = Spot.Mark(Logging.Frequency, DateTime.UtcNow, session.Settings.Station.Callsign);
session.Bandmap.Add(mark);
Status($"{mark.Call.Text} on {mark.Frequency.Kilohertz:0.0}");
boxes[0].Focus();
}
/// N1MM's Store button and Alt+O: puts the call being typed on our own
/// bandmap at this frequency so it can be worked later. Spot It sends the
/// same call to the cluster; this one stays here.
private void OnStore(object? sender, RoutedEventArgs e)
{
if (Logging is null)
{
Status("no contest is open");
return;
}
string typed = Logging.Entry.Call.Trim();
if (typed.Length == 0)
{
Status("nothing to store");
return;
}
Callsign call = Callsign.Parse(typed);
session.Bandmap.Add(new Spot(
call,
Logging.Frequency,
DateTime.UtcNow,
SpotSource.Operator,
session.Settings.Station.Callsign,
"Local spot"));
Status($"{call.Text} stored on {Logging.Frequency.Kilohertz:0.0}");
boxes[0].Focus();
}
private void OnShowLog(object? sender, RoutedEventArgs e) => Show(() => new LogWindow(session));
private void OnShowCallStack(object? sender, RoutedEventArgs e) =>

View File

@@ -164,15 +164,17 @@
<Grid Name="FunctionKeys" Margin="0,6,0,0"
ColumnDefinitions="*,*,*,*,*,*" RowDefinitions="Auto,Auto" />
<!-- the widths N1MM gives the row: 23 per cent to the first and the rest
shared out -->
<Grid ColumnDefinitions="115*,77*,77*,77*,77*,77*">
<!-- the widths N1MM gives the row: 23 per cent to the first button and
11 to each of the other seven -->
<Grid ColumnDefinitions="23*,11*,11*,11*,11*,11*,11*,11*">
<Button Classes="fkey" Content="Esc: Stop" Click="OnStopSending" />
<Button Grid.Column="1" Classes="fkey" Content="Wipe" Click="OnWipe" />
<Button Grid.Column="2" Classes="fkey" Content="Log It" Click="OnLogIt" />
<Button Grid.Column="3" Classes="fkey" Content="Edit" Click="OnEditLastContact" />
<Button Grid.Column="4" Classes="fkey" Content="Spot It" Click="OnSpotIt" />
<Button Grid.Column="5" Classes="fkey" Content="QRZ" Click="OnLookUpCall" />
<Button Grid.Column="4" Classes="fkey" Content="Mark" Click="OnMark" />
<Button Grid.Column="5" Classes="fkey" Content="Store" Click="OnStore" />
<Button Grid.Column="6" Classes="fkey" Content="Spot It" Click="OnSpotIt" />
<Button Grid.Column="7" Classes="fkey" Content="QRZ" Click="OnLookUpCall" />
</Grid>
<TextBlock Name="PathText" FontSize="11" Opacity="0.75" Margin="2,8,0,0" Text=""

View File

@@ -431,6 +431,14 @@ public sealed partial class EntryWindow : Window
e.Handled = true;
DropStacked();
break;
case Key.M when e.KeyModifiers.HasFlag(KeyModifiers.Alt):
e.Handled = true;
OnMark(this, new RoutedEventArgs());
break;
case Key.O when e.KeyModifiers.HasFlag(KeyModifiers.Alt):
e.Handled = true;
OnStore(this, new RoutedEventArgs());
break;
case Key.OemQuestion when e.KeyModifiers.HasFlag(KeyModifiers.Control):
e.Handled = true;
ToggleRun();