Add the button row and the title bar N1MM has

The title bar said Nonemm. It now says what N1MM's says: the frequency, the
mode, whether the frequency came from a radio or was typed, and which radio the
window belongs to when there are two. Reading it off the taskbar is the point.

Under the function keys is N1MM's second row: Esc: Stop, Wipe, Log It, Edit,
Spot It and QRZ. Each one does what its key already did, so the keys and the
buttons cannot disagree. QRZ opens the callsign's page in a browser, and says so
in the status line when there is no browser to open.

Mark and Store are left out. They stack callsigns, and call stacking is not
here, so a button that did nothing would be worse than no button.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-28 09:07:45 +00:00
parent 86d1f71ffe
commit f8e07471a6
3 changed files with 84 additions and 3 deletions

View File

@@ -173,6 +173,16 @@ in again while you are running, and `EsmSendsCorrectedCall` sends the call again
in front of the last message when you have corrected it — copy `SM3AB`, fix it in front of the last message when you have corrected it — copy `SM3AB`, fix it
to `SM3ABC`, and the key sends `SM3ABC TU DL1ABC`. to `SM3ABC`, and the key sends `SM3ABC TU DL1ABC`.
### The buttons and the title bar
Under the function keys is the row N1MM has: Esc: Stop, Wipe, Log It, Edit,
Spot It and QRZ, each doing what its key does. QRZ opens the callsign's page in
a browser. N1MM's Mark and Store are not there, because call stacking is not.
The title bar says what N1MM's says: the frequency, the mode, whether the
frequency came from a radio or was typed, and which radio the window belongs to
when there are two.
### The band buttons ### The band buttons
Down the left of the entry window are the bands, CW in one column and phone in Down the left of the entry window are the bands, CW in one column and phone in

View File

@@ -90,7 +90,17 @@
<Border DockPanel.Dock="Bottom" Padding="6,4" BorderThickness="0,1,0,0" <Border DockPanel.Dock="Bottom" Padding="6,4" BorderThickness="0,1,0,0"
BorderBrush="#33808080"> BorderBrush="#33808080">
<WrapPanel Name="FunctionKeys" /> <StackPanel Spacing="4">
<WrapPanel Name="FunctionKeys" />
<WrapPanel>
<Button Classes="fkey" Content="Esc: Stop" Click="OnStopSending" />
<Button Classes="fkey" Content="Wipe" Click="OnWipe" />
<Button Classes="fkey" Content="Log It" Click="OnLogIt" />
<Button Classes="fkey" Content="Edit" Click="OnEditLastContact" />
<Button Classes="fkey" Content="Spot It" Click="OnSpotIt" />
<Button Classes="fkey" Content="QRZ" Click="OnLookUpCall" />
</WrapPanel>
</StackPanel>
</Border> </Border>
<Border DockPanel.Dock="Left" Padding="6,6,4,6" BorderThickness="0,0,1,0" <Border DockPanel.Dock="Left" Padding="6,6,4,6" BorderThickness="0,0,1,0"

View File

@@ -1,3 +1,4 @@
using System.Diagnostics;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@@ -45,7 +46,6 @@ public sealed partial class EntryWindow : Window
Opened += (_, _) => Reopen(); Opened += (_, _) => Reopen();
if (radioNumber > 1) if (radioNumber > 1)
{ {
Title = "Nonemm — radio 2";
MainMenu.IsVisible = false; MainMenu.IsVisible = false;
} }
AddHandler(KeyDownEvent, OnWindowKeyDown, RoutingStrategies.Tunnel); AddHandler(KeyDownEvent, OnWindowKeyDown, RoutingStrategies.Tunnel);
@@ -97,7 +97,6 @@ public sealed partial class EntryWindow : Window
bool wanted = session.Positions.Count > 1; bool wanted = session.Positions.Count > 1;
if (wanted && secondRadio is null) if (wanted && secondRadio is null)
{ {
Title = "Nonemm — radio 1";
secondRadio = new EntryWindow(session, 2); secondRadio = new EntryWindow(session, 2);
secondRadio.Closed += (_, _) => secondRadio = null; secondRadio.Closed += (_, _) => secondRadio = null;
secondRadio.Show(this); secondRadio.Show(this);
@@ -370,6 +369,51 @@ public sealed partial class EntryWindow : Window
private bool FocusedIsEntryBox() => boxes.Any(b => b.IsFocused); private bool FocusedIsEntryBox() => boxes.Any(b => b.IsFocused);
/// The buttons N1MM puts under its function keys. Each one does what its
/// key does, so there is one place deciding what happens.
private void OnStopSending(object? sender, RoutedEventArgs e)
{
session.Alternating?.Stop();
_ = session.Keyer?.AbortAsync();
Status("stopped sending");
}
private void OnWipe(object? sender, RoutedEventArgs e)
{
if (Logging is null)
{
return;
}
Logging.Wipe();
ResetEsm();
SyncBoxes();
boxes[0].Focus();
Refresh();
}
private void OnLogIt(object? sender, RoutedEventArgs e) => LogContact();
/// Opens the callsign's page in a browser, as N1MM's QRZ button does.
private void OnLookUpCall(object? sender, RoutedEventArgs e)
{
if (Logging is null || Logging.Entry.Call.Trim() is not { Length: > 0 } call)
{
Status("no callsign to look up");
return;
}
try
{
Process.Start(new ProcessStartInfo($"https://www.qrz.com/db/{call}")
{
UseShellExecute = true,
});
}
catch (Exception error) when (error is System.ComponentModel.Win32Exception or IOException)
{
Status($"could not open a browser: {error.Message}");
}
}
private void ToggleRun() private void ToggleRun()
{ {
if (Logging is null) if (Logging is null)
@@ -411,6 +455,7 @@ public sealed partial class EntryWindow : Window
RunBorder.Background = Logging.IsRunning ? Verdicts.Worth : new SolidColorBrush(Color.FromArgb(0x22, 0x80, 0x80, 0x80)); RunBorder.Background = Logging.IsRunning ? Verdicts.Worth : new SolidColorBrush(Color.FromArgb(0x22, 0x80, 0x80, 0x80));
ContestText.Text = ContestLine(); ContestText.Text = ContestLine();
Title = TitleLine();
PathText.Text = PathLine(); PathText.Text = PathLine();
Verdict? verdict = Logging.Verdict(); Verdict? verdict = Logging.Verdict();
@@ -438,6 +483,22 @@ public sealed partial class EntryWindow : Window
$"{Logging.Log.Tally.Points} pts · {mults} · {Logging.Log.TotalScore:N0}{radio}"; $"{Logging.Log.Tally.Points} pts · {mults} · {Logging.Log.TotalScore:N0}{radio}";
} }
/// What N1MM writes in its title bar: the frequency, the mode, whether the
/// frequency came from a radio or was typed, and which radio this window
/// is.
private string TitleLine()
{
if (Logging is null)
{
return "Nonemm";
}
string source = session.Radios.Any(r => r.Number == radioNumber && r.IsConnected)
? "rigctld"
: "manual";
string radio = session.Positions.Count > 1 ? $" — radio {radioNumber}" : "";
return $"{Logging.Frequency.Kilohertz:0.00} {Logging.Mode.Name} {source}{radio}";
}
/// Where the station being worked is: the beam heading, the heading the /// Where the station being worked is: the beam heading, the heading the
/// long way round, the distance, and the sun there. N1MM writes the same /// long way round, the distance, and the sun there. N1MM writes the same
/// line under its entry window. /// line under its entry window.