Colour the callsign the way N1MM colours it, and mark the call

The colours are N1MM's own defaults, read out of ApplicationStyles: blue
for a station worth working, red for one new multiplier, green for more
than one, grey for a dupe, and red on the CQ frequency line. A call under
three characters stays blue, as N1MM leaves it.

The mark at the right-hand end of the callsign box is N1MM's OKCall: a
tick when the call is in MASTER.SCP, a question mark while it is not, in
the colour of the call. Like N1MM it only shows while the check window is
open, for three characters or more, and only while the call is short
enough to leave room for it.

The band under the boxes takes N1MM's background colours instead of white
on the text colours. The two lights, the telnet window and the message
editor were borrowing the verdict brushes for things that are not
verdicts; they have their own colours now.

The callsign and the exchange boxes uppercase what is typed. Name and
comment do not, which is what N1MM does.

Scored contest 63 of om5m.s3db (CQ WW SSB, 3775 QSOs) against the points
and multiplier flags N1MM stored: points identical, multipliers 601
against 602. The difference is B1Z on 10m, which N1MM logged with an
empty country prefix and counted as a country of its own though BY was
already worked on that band.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-28 12:50:56 +00:00
parent 1c118f9ce4
commit e4b3dc9572
5 changed files with 139 additions and 23 deletions

View File

@@ -39,7 +39,7 @@ public sealed partial class EntryWindow
&& Math.Abs(Logging.Frequency.Hertz - cqFrequency.Hertz) <= TuningTolerance.Hertz)
{
CallFrameText.Text = "CQ-Frequency";
CallFrameText.Foreground = Verdicts.Worth;
CallFrameText.Foreground = Verdicts.Cq;
return;
}
if (session.Bandmap.Near(Logging.Frequency, TuningTolerance) is not { } spot)

View File

@@ -24,10 +24,15 @@ public sealed partial class EntryWindow : Window
private static readonly IBrush Dark = new SolidColorBrush(Color.FromArgb(0x33, 0x80, 0x80, 0x80));
private static readonly IBrush GreenLight = new SolidColorBrush(Color.FromRgb(0x0D, 0x9D, 0x0D));
private static readonly IBrush RedLight = new SolidColorBrush(Color.FromRgb(0xFF, 0x00, 0x00));
/// The pale yellow N1MM paints the entry boxes while quick editing.
private static readonly IBrush QuickEditBackground = new SolidColorBrush(Color.FromRgb(0xFF, 0xFF, 0xC0));
private bool sending;
private TextBlock? callMark;
private TextBox? nameBox;
private TextBox? commentBox;
private readonly DispatcherTimer clock = new() { Interval = TimeSpan.FromSeconds(1) };
@@ -176,6 +181,7 @@ public sealed partial class EntryWindow : Window
EntryGrid.Children.Clear();
EntryGrid.ColumnDefinitions.Clear();
boxes.Clear();
callMark = null;
if (Logging is null)
{
ContestText.Text = "no contest — File ▸ New Contest";
@@ -260,6 +266,29 @@ public sealed partial class EntryWindow : Window
Grid.SetRow(box, 1);
EntryGrid.Children.Add(box);
boxes.Add(box);
if (index == 0)
{
AddCallMark(index);
}
}
/// The mark N1MM puts at the right-hand end of the callsign box: a tick
/// when the call is in the check partial file, a question mark while it is
/// not. It sits over the box, in the same colour as the call.
private void AddCallMark(int index)
{
callMark = new TextBlock
{
FontSize = 21,
FontWeight = FontWeight.Bold,
Margin = new Avalonia.Thickness(0, 0, 12, 0),
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Center,
IsHitTestVisible = false,
};
Grid.SetColumn(callMark, index);
Grid.SetRow(callMark, 1);
EntryGrid.Children.Add(callMark);
}
private void OnBoxChanged(int index, TextBox box)
@@ -268,7 +297,19 @@ public sealed partial class EntryWindow : Window
{
return;
}
Logging.Entry[index] = box.Text ?? "";
// the callsign and the exchange go out in upper case, and N1MM puts
// them in the box that way as they are typed
string typed = box.Text ?? "";
string upper = typed.ToUpperInvariant();
if (upper != typed)
{
int caret = box.CaretIndex;
updating = true;
box.Text = upper;
box.CaretIndex = caret;
updating = false;
}
Logging.Entry[index] = upper;
if (index == 0 && Logging.Entry.Call.Trim().Length == 0)
{
ResetEsm();
@@ -675,15 +716,51 @@ public sealed partial class EntryWindow : Window
ShowLights();
Verdict? verdict = Logging.Verdict();
VerdictBorder.Background = Verdicts.Colour(verdict);
ShowCallColour(verdict);
VerdictBorder.Background = Verdicts.Background(verdict);
VerdictText.Text = VerdictLine(verdict);
VerdictText.Foreground = Brushes.White;
VerdictText.Foreground = Brushes.Black;
foreach (Window window in openWindows.Values)
{
(window as RefreshableWindow)?.Refresh();
}
}
/// N1MM colours the callsign itself rather than the box: blue for a station
/// worth working, red for one multiplier, green for more than one, grey for
/// a dupe. A call too short to judge stays blue.
private void ShowCallColour(Verdict? verdict)
{
if (Logging is null || boxes.Count == 0)
{
return;
}
string call = Logging.Entry.Call.Trim();
IBrush colour = call.Length < 3 || call.All(char.IsAsciiDigit)
? Verdicts.Worth
: Verdicts.Colour(verdict);
boxes[0].Foreground = colour;
ShowCallMark(call, colour);
}
/// The tick and the question mark, which N1MM only shows while the check
/// window is open, and only while the call is short enough to leave room
/// for them.
private void ShowCallMark(string call, IBrush colour)
{
if (callMark is null)
{
return;
}
bool known = call.Length > 0 && session.Calls.Holds(call);
callMark.Text = known ? "✓" : "?";
callMark.Foreground = colour;
callMark.IsVisible = Logging?.Editing is null
&& openWindows.ContainsKey(typeof(CheckWindow))
&& call.Length is >= 3 and <= 9
&& !call.All(char.IsAsciiDigit);
}
private string ContestLine()
{
if (Logging is null)
@@ -704,8 +781,8 @@ public sealed partial class EntryWindow : Window
private void ShowLights()
{
bool radio = session.Radios.Any(r => r.Number == radioNumber && r.IsConnected);
RadioLight.Fill = radio ? Verdicts.NewMultiplier : Dark;
TransmitLight.Fill = sending ? Verdicts.Dupe : Dark;
RadioLight.Fill = radio ? GreenLight : Dark;
TransmitLight.Fill = sending ? RedLight : Dark;
}
/// What N1MM writes in its title bar: the frequency, the mode, whether the

View File

@@ -18,9 +18,9 @@ public sealed partial class TelnetWindow : RefreshableWindow
{
private const int LinesKept = 500;
private static readonly IBrush SpotColour = Verdicts.NewMultiplier;
private static readonly IBrush SentColour = Verdicts.Worth;
private static readonly IBrush NoticeColour = Verdicts.Dupe;
private static readonly IBrush SpotColour = new SolidColorBrush(Color.FromRgb(0x27, 0xAE, 0x60));
private static readonly IBrush SentColour = new SolidColorBrush(Color.FromRgb(0x29, 0x80, 0xB9));
private static readonly IBrush NoticeColour = new SolidColorBrush(Color.FromRgb(0x7F, 0x8C, 0x8D));
private readonly AppSession session;
private readonly Action<Frequency, string> tune;