- Turn off scroll bar auto-hide for ScrollViewer and DataGrid.
- Make the splitter between the two log grids 5 px, in the grid line color.
- Remove the log window status bar. The stats are in the entry window.
- Show a refused log edit in an alert dialog.
- Keep the log grid's rows in an ObservableCollection, so a new contact
does not rebuild the list and move the view.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
With a call in the entry window, a move of more than 2 kHz on the same
band puts the call on the bandmap where it was heard and wipes the boxes,
as N1MM does. The spot goes to the other stations as ADDSPOT and to the
Spots broadcast, never to the cluster. ADDSPOT from other stations is
added to the bandmap.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The # blocks in wl_cty.dat list CQ and ITU zones by call area and the
first letter after the digit, written with the main prefix letters. A call
now takes them whatever its own prefix letters are, so RA9Y and RM9I get
what UA9Y gets, as in N1MM. A call whose own prefix rule gives a zone, and
a whole-call entry, are left as they are.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Without a call history file, the CQ zone box stayed empty after the
callsign. It now takes the zone from the call history file, else from the
last logged contact with the call, else from the country file, as N1MM
does. A box that already holds text is left alone.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Listen for Radio broadcast data from another Nonemm or N1MM computer and
record its frequency, mode and split as this computer's own radio. No
radio is controlled. The settings are in the Configure Radios dialog.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Setting Foreground to null is a local value of no brush, so the Record
button, connected devices in the list and the status line had no text
colour. Clear the value instead. The bitrate step is now 4 kbps.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Remove the status bar and the band name, add the 75 and 150 kHz spans,
add buttons for the callsign size (saved in the settings), draw the scale
in the form's text colour so it shows in the dark theme, mark the carrier
on the VFO bar and place a sideband bar on its side of the carrier, and
show who spotted a station, when, and the comment when the pointer is on it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The entry window sizes its height to its content, and Place skipped the
saved size for any window that sizes itself on either side.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The generic name only resolves through fontconfig, so on Windows the
windows fell back to the proportional default.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The wheel steps along the span list and keeps the frequency under the
pointer in place. With Centre on VFO off, the slice stays put until the
receiver nears an edge. The span and the toggle are saved in the settings.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Lines were as tall as a list item, with padding of more than the text. A
line is now as tall as its text, the view always follows the newest line
instead of pausing under the pointer, and a burst of lines is scrolled
once. The scrollback is a thousand lines.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
QsoRecord followed MultiOpManager.QSOString, a helper N1MM does not use
for the network. The messages use Contact.QSOString: 43 fields, the
continent before the contact type, and flags as numbers. N1MM converts
the multiplier flags with ToShort and threw on 'True', so every contact
logged here raised an error at the other computer.
The id now goes out as a GUID and is kept on arrival, which is what the
other station's checksum of a resync block is made over.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The window picked a brush on the socket thread, and Avalonia crashed the
program the next time it painted that line. A line that arrives is now
posted to the window thread first. Spots no longer show the bell
character as a box, and an unexpected error in the cluster loop is
shown instead of ending it silently.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ApplyNetworkSettings disposed the station link without clearing it, so
ApplyStationLinkSettings disposed it a second time and threw. Only the
second function touches the link now, and StationLink.Dispose can be
called twice.
Contacts from the network are queued and applied together on the window
thread. Each one used to rescore the whole log and redraw the log window,
which kept the window thread busy for minutes during a resync.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
publish.sh takes a runtime id and publishes both programs into
dist/nonemm-<rid>, which holds the runtime and the Avalonia files once.
The archive is a zip for Windows and a tar.gz for the rest.
publish-windows.sh and publish-linux.sh call it with their default id.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
nonemm-recorder is a separate program (src/Nonemm.Recorder.App) over a
library (src/Nonemm.Recorder). Each row in its list is one channel of one
device, and each row becomes one channel in an Ogg Opus file: 60 ms frames,
complexity 9, 32 kbps per channel by default.
- A device that is not connected keeps its row, in red, and writes silence.
It is recorded from the moment it returns, also during a recording.
- Output devices are recorded as loopback: WASAPI on Windows, the
"Monitor of" sources on PulseAudio and PipeWire.
- A headphone button plays one channel, in mono, on the default device.
- The rows, bitrate and folder are saved when the window closes.
SoundFlow 1.4.2 numbers its backend enum one higher than the miniaudio it
ships, so asking for PulseAudio gave ALSA and Wasapi gave DirectSound.
SoundFlowBackend lowers the value by one and checks which backend started.
Output devices and live capture are not tested on real hardware. The
tests use a fake sound system and decode the written file.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The bridge launched its helper only through Wine. It now starts any
process, so on Windows it runs the helper directly. WineBridgeChannel is
renamed ProcessBridgeChannel.
WindowsControlSetup sets up the control side on Windows. The digital
dialog, AppSession, README and docs/digital-bridge.md describe the
Windows setup. publish-windows.sh builds a Windows release.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
CQ WW lists LOCATION as required, and the export never wrote it. It now
carries the ARRL section, state or province, or DX.
CQ WW M/2 has no stay on a band: it may make eight band changes in a clock
hour. It was getting the ten-minute stay that M/S has.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Going down N1MM's published list, the ones a contest operator would miss:
| | |
|---|---|
| `{LASTEXCH}` | what the last station sent, built from the boxes the contest asks for |
| `{ROVERQTH}` | where a rover is now |
| `{TIME}`, `{DATE}`, `{TIMEGMT}`, `{DATEGMT}` | the clock and the calendar |
| `{ESC}`, `{CTRL-A}` to `{CTRL-Z}` | the control characters a TNC reads as commands |
| `{CTRLHOME}` | the cursor back in the callsign box |
| `{F1}` to `{F12}` | another of this radio's keys, where `{CTRLF1}` is the other radio's |
| `{CQ}` | makes any key a CQ key: it starts running and carries alternating CQ on |
| `{DIGQTCS}`, `{DIGQTCR}` | opens the WAE QTC window the way asked for |
| `{CLEARRIT}` | the receive offset back to zero, which is rigctld's `J 0` |
| `{FORCELOG}` | logs what is in the boxes, holes and all |
`{FORCELOG}` is the one that gives something up. `LogContact` refuses an entry
that is short or holds a value the contest will not take, so that a caller who
has not checked cannot log nonsense; `LogContactAnyway` is the way past it,
because a contact in the log with a hole in it can be filled in afterwards and
one that was never logged cannot. It still needs a callsign, and the status
line says the row will need fixing before the log goes in.
A key that names itself would send for ever, so a key sending another key
ignores the macro a second time down.
What is still passed over is written down in the README and unfinished.md.
Advanced voicing is the one that matters: N1MM says a callsign or a serial by
playing a recording per letter out of `LettersFiles`, which is why `!` and `#`
say nothing on phone here and why F5 is empty in N1MM's own SSB file too. The
three packet BBS date formats are passed over because there is nothing here to
check a guess at them against.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
N1MM records a phone message from the entry window: Ctrl+Shift+F1 to
Ctrl+Shift+F12 records into the file that key plays, and the same pair again
stops and saves it. Escape and sending anything stop one too — there is one
microphone, and it cannot be recorded and transmitted at the same time. The
status line says what is being recorded and what was saved, which is where
N1MM says it.
The recording being made is the session's rather than the window's, for the
same reason: the voice keyer window and these keys record into the same
microphone, and now each can see what the other started.
Alt+K opens the function key editor for the mode the radio is on, which is
N1MM's shortcut for what right-clicking a key button already did.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
A phone function key names a wav file instead of words, which is what N1MM's
own SSB file holds: `F1 CQ,{WAVDIR}\Cq.wav`. `{WAVDIR}` is the folder named
after whoever is at the microphone, a name is taken relative to `Wav`, and
commas play several files in a row. An SSB `.mc` file written for N1MM plays
here.
Window ▸ Voice Keyer lists every recording the messages name, says which of
them are there for the operator picked at the top, plays one, and records over
one. The recording is written beside the wanted file and moved over it at the
end, so a recorder that will not start takes nothing away.
`CommandSoundCard` runs `aplay` and `arecord`, and both commands are settings:
the audio path belongs to whatever the station runs, and a library would pick
one of them for the operator. A recorder is stopped with an interrupt so it
closes its wav file, and killed only if it ignores that. PTT is cwdaemon's
`<ESC>a` for as long as the recording plays, off for a station on VOX.
Two things follow. The QTC window sends on SSB: N1MM's four recordings on the
Ready, Agn, Cfm and Close buttons, with the lines still read out by the
operator. And alternating CQ duels on phone, because the voice keyer says when
a recording has finished — which it had to, since the CQ message is picked by
the radio's mode and would otherwise have been keyed as Morse.
Nothing here has been played on a radio: this machine has no sound card, so
what is tested is the program being run, the file filled in, the exit code and
the interrupt, with shell commands standing in for the two.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
The scoring audit says which of its findings are fixed and which are still
open, its line numbers point at where the code is now, and the order of what
to do next is what is left rather than what is done.
In unfinished.md: Send All was listed as missing 500 lines above the section
that describes it, three TypeAhead methods had been renamed, the station
number clash is guarded and the paragraph saying it is not was next to the one
saying it is, and the registry holds twenty-six contests rather than
twenty-five. The claim that every instance of the station's logs scores
contact for contact is replaced by what the replay of 2026-09-06 actually
found.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
The score window and the stored Contest.Multiplier1Name row both said
"Prefixes" for an Asian entrant, which counts DXCC entities.
Contest.MultiplierNames is now MultiplierNamesFor(StationInfo me), the
same shape as ExchangeFieldsFor. Every other contest returns the same
list it did before; All Asian returns "Countries" from Asia and
"Prefixes" from everywhere else. ContestDefinitions.For takes the
station as well, because that name is written to the log for N1MM.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
All Asian is two contests in one class and only one of them was written.
An Asian entrant was paid three times the band table for every contact,
including Asian ones, could work its own country, and counted WPX
prefixes where the rules count DXCC entities.
AllAsian now asks where the entrant is. An Asian station works anybody
outside its own country, gets the band table once for Asia and three
times for the rest, and counts entities. Everybody else works Asia only
and counts prefixes, as before.
Maritime mobiles follow the JARL rule rather than N1MM, which applies it
to non-Asian entrants alone: a station at sea makes a valid contact with
Asian stations only, is paid the Asian rate wherever it is, and counts
for no multiplier either way.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
The band table stopped after 40 metres and gave every other band one point.
It should be 20 on 160, 10 on 80, 5 on 40, 1 on 20, 2 on 15 and 3 on 10:
from Oceania the high bands are the hard ones, so they pay more than 20
metres does. The WIA scoring table and N1MM's COceania.ComputePoints agree
on all six.
The catch-all hid a second fault. N1MM's table ends without one, so a band
that is not in the contest — the WARC bands, 6 metres — scores nothing;
here it scored a point.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
A box the walk between the entry boxes steps over cannot be typed in, so it
must not hold the contact back either. `EntryFields.IsComplete` counted every
box marked required, while `MoveFocus` stepped over the ones the contest does
not ask this station for, so a contest with a box for some stations only
could lock the operator out of his own log.
The seven contests the scoring audit named do not do that any more —
`bae40bb` gave each of them the boxes N1MM shows, and fifteen reproductions
across all seven and both station types log cleanly — but the rule they broke
was still there to break again. Completeness now takes the same
applicability rule the cursor uses, and `OperatingPosition` answers it in one
place for the entry window, the ESM window and logging alike.
The audit is marked up for the three findings that are already fixed and for
item 14, which turns out to be what N1MM does rather than a fault.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
N1MM has four colours for a station in WAE rather than the usual three, and
the manual and the RTTY tutorial both spell them out. Red is a new
multiplier, as everywhere else. Green is a new station with QTC traffic
still to exchange, which takes the more-than-one-multiplier colour because
WAE has no second multiplier to use it. Black is a station already worked
that still has traffic left — worth working for the QTC points alone — and
it is a colour of its own, N1MM's "Color of a QTC callsign in the WAE
contest". Grey is a dupe with nothing left, the same as in other contests.
The verdict carries how much traffic is left with the station, so the
callsign box, the check window, the bandmap and everything else that colours
a station say the same thing. The contest works the number out: ten with a
station on the other side of the contest, none with one on our own
continent, and none once the ten have gone. Every other contest answers
nought. The log counts the QTC rows per station as they are indexed rather
than counting them again for each station judged, because the bandmap judges
every spot it holds on every pass.
The verdict line says it in words as well, so a dupe reads `dupe · 7 QTCs
left` rather than `dupe`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
The QTC window put nothing on the air on RTTY except the lines themselves,
so the operator typed the rest into the digital window's transmit pane and
copied the lines by hand. N1MM sends a message for each step and fills the
lines in from a click. Both are here now, with N1MM's messages, N1MM's
defaults and N1MM's settings behind them.
The messages, all of them settings in the QTC window's setup area:
- RX Ready, `WAERXReadyText`, `{ENTERLF}I AM QRV BK`
- All Agn, `WAEAllAgnText`, `{ENTERLF}ALL AGN ALL AGN BK`
- Agn n, `WAEAGNText`, `{ENTERLF}AGN QTC: $ $ $ `, where `$` is the line
- Save, `WAESaveQTCText`, `{ENTERLF}R R R ALL OK BK`
- R U QRV, `WAERUQRVText`, `{ENTERLF}R U QRV BK`
- Save, reading traffic out, `WAESSaveQTCText`, `{ENTERLF}GL DE {MYCALL}`
Each keys the transmitter, sends the text and drops it again, and an empty
one sends nothing. The line spacing default is N1MM's `{ENTERLF}` rather
than `{ENTER}`, which was wrong.
The buttons: R U QRV and Snd Hdr were hidden while reading traffic out on
RTTY, though that is where N1MM starts — R U QRV is the first button
pressed. All Agn is new, and so is the S button on each line, which strikes
a line through a QTC that arrived too broken to read so the rows after it
keep their places; Agn n clears the strike and asks for that line again.
Clicking a line in the digital window fills the next row, which is how the
series is copied on RTTY: the other station sends the header and all ten
lines in one transmission. A click anywhere in a line hands the whole line
to the QTC window, which reads the time, the callsign and the serial number
out of it by shape and flashes the line to show it was taken. A line that is
not a QTC is left to mean what a click always means there.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
N1MM writes a contact type on every contact it logs: `D` for a station
already worked, `N` for one the entry may not work at all, and empty for an
ordinary contact. Nothing here wrote the column, so a dupe counted as a
contact worth no points instead of dropping out of the score, and the two
places that read the column did nothing: the QTC pile offered dupes that
N1MM leaves out, and the statistics window could not leave dupes out either.
The marking is in one place, `ContestLog.ApplyVerdict`, and every contest is
marked the same way. What changes between contests is what counts as a dupe
and which stations are workable, and both come from the contest, as they do
in N1MM's `Contact.IsDupe`. No contest module in N1MM marks differently. Our
own callsign is not workable either, which is what N1MM adds on top of the
contest rule.
Two details follow N1MM as well: QTC traffic keeps an empty column, because
its `IsDupe` only marks a row while the program is logging contacts; and a
mark that came with the contact stands, because N1MM marks a contact when it
logs or edits one and never clears marks in a pass over the log. Editing a
contact clears the column so it is worked out again, which is what N1MM does
at the same point: an edit can move a contact to a band where it is no
longer a dupe.
A dupe is now out of the score altogether — contacts, points and multipliers
— which is N1MM's `ScoreClass.AddContact`. The QSO count still counts WAE
QTC rows as contacts; that is the other half of the scoring audit's item 14.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
ADIF has no record for QTC traffic, which is why N1MM leaves it out and why
a WAE log cannot be moved between databases as ADIF. The plain export now
writes the contacts alone, as N1MM does: it used to write the QTC rows as
contacts, with the time of the traffic in RST_SENT and the reported callsign
in RST_RCVD.
File > Export > Export ADIF with QTC traffic writes the whole log instead.
A QTC row goes out with the station, time, band and mode, and the traffic in
application-defined fields — APP_NONEMM_QTC and friends, the prefix the ADIF
specification keeps for a program's own data — with the line in the comment
as well so the file can be read without this program. The report columns are
left off that record, because on a QTC row they hold the time and callsign
of the contact being reported. Reading such a file back rebuilds the rows,
so a WAE log survives the round trip whole. The item is only in the menu for
a contest with traffic.
Another program reading that file sees each QTC row as an ordinary contact
with the station the traffic was exchanged with and ignores the extra
fields. There is no way around that in ADIF, which is why the plain export
is still the one to hand over.
Contacts now also carry their row kind in APP_NONEMM_EXCHANGE1. Without it
a WAE log read back holds contacts with no kind at all, which N1MM's own
queries do not find.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
Ctrl+Z logged the contact by calling the Enter handler. With ESM switched
on, Enter sends the next message of the contact instead of logging it, so
the QTC window opened over an unlogged contact and the traffic went into the
log in front of the contact it belongs to.
It now calls LogContact, which is what N1MM calls at the same point. A
station already worked is logged again as a dupe, worth no points, which is
what the WAE rules expect: the second contact does not count but the traffic
does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
An operator copying a header off the air often types the series number and
nothing else. That read as a header the program could not parse, so the box
went yellow and Close refused to log the series.
N1MM fills the count in: "If he only types the batch number without the
forward slash and count, the program will assume the count is 10."
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
A QTC row holds the serial of the contact it reports, which the station
being reported sent, not us. The serial pool was filled from every row in
the log, so reporting a station that sent us 1800 pushed our own next serial
to 1801, and reopening the log read it back from the rows again.
The pool now takes the sent number of contacts alone, when a contact is
logged, when one arrives from another station of the entry, and when the
numbers are seeded from the log at startup. N1MM leaves both QTC kinds out
of the same query, in QSONrsByBand.
The test opens a real log file, sends a series, closes the store and opens
it again, so the series numbering and the contacts already reported are
checked across a restart as well.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
WAE gives a point for every QTC line, so an operator needs to know what is
still there to exchange. The entry window now carries N1MM's QTCinfo line in
WAE: how many QTC lines have already gone with the station in the callsign
box, how many more may go, and how many contacts are left to report to
anybody. The QTC window shows the same line.
The numbers come from the log rows, the way N1MM works them out:
- exchanged is every QTC row with that call, both directions and all bands,
and ten is the lot
- available is every contact not reported to anybody yet, without the dupes
and without the station itself, which is N1MM's QTCRecordsetCount
- what is left with the station is what is free of its ten, and no more than
the contacts there are to report when we are the one reporting
Europe on CW and SSB only takes traffic down, so its line leaves the last
number out, as N1MM's does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
A marked line in a window behind says nothing about where the keyboard
is. The log takes the marks off as another window is activated and puts
them back when it comes forward; the contacts stay picked in between, so
"Change Operator…" and Delete still act on what the operator picked.
The frame around the cell the keyboard is on goes with them. The theme
draws that frame with a rectangle inside the cell, not with the cell's
own border, so it takes a style on the template.
Quick edit moves the picked line: Ctrl+Q and Ctrl+A step through the log
from the entry window, and the contact in the entry boxes is the picked
line here and is scrolled to.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
The values sit closer to the line between the columns, and a column that
is as wide as its values is fifteen pixels narrower.
A header takes much more room besides its text than a cell does — its
own padding, and the room the theme keeps for the sort arrow — so the
two are now measured with an allowance each. One allowance for both
clipped "Mode" and "CQ-Zone" to the column width the values needed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
A multi-operator station logs a whole shift under whoever was at the
radio, so a shift logged under the wrong callsign had to be put right a
contact at a time. "Change Operator…" in the log's right-click menu asks
for one callsign and writes it against every contact picked in the grid.
The dialog offers the operators the program already knows in a drop-down
and takes any other callsign typed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
The theme gave each cell 32 pixels of height, which fits about seven
contacts in the window. A row is now as tall as its text and one pixel
either side, so about fifteen fit.
Editing a cell:
- the columns bind one way, and the value is written back when the edit
is taken, on Enter or a click elsewhere. A two-way binding wrote on
every key press, which took the edit as the first character was typed
and closed the box.
- Enter on the current cell opens the box. The handler runs before the
grid's own, which reads Enter as "move down a line".
- Left, Right, Home and End stay in the box. The grid read a key the box
left alone as "go to the next cell", and the box leaves a key alone
when the caret cannot move; End also landed at the start of the text.
- the box takes the height and the left inset of the text it covers, so
nothing moves as it opens.
An edit no longer rebuilds the list. The lines on screen are given the
contact again, so the view stays where the operator put it instead of
jumping to the top and back.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
The program stored an operator's window positions but never used them at
startup: the entry window was not placed and no window was reopened. It
now restores the whole screen both at startup and on a change of
operator: the windows that operator had open, where he had them, and the
ones he did not have closed.
The second radio's entry window no longer saves the layout. It has none
of the other windows open, so saving from it dropped every window the
first one had.
Ctrl+O gets six buttons for the operators last at the radio, newest
first. One click changes to that operator.
The recordings folder is no longer asked for. It is `Wav/<callsign>`
under the data folder, which is where N1MM plays them from, so
`StoredOperator.WavPath` is gone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ