From cf0166f1b25516e51be6da6becfda80e2c94582d Mon Sep 17 00:00:00 2001 From: ericek111 Date: Fri, 28 Aug 2026 08:13:24 +0000 Subject: [PATCH] Edit the function key messages as their file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The messages were twelve boxes in a dialog, which meant no labels, no search-and-pounce set, and no way to read a file somebody published. They are now the file itself: right-click any function key button under the entry window, or the two buttons in Config ▸ Keyer and messages, and the text of an N1MM .mc file opens in a plain editor with Import, Export and Back to the defaults. MessageFile reads that format by N1MM's rules. A line starting with # is a comment; every other line is a label, a comma and the message, with && standing for one & in a label. Which key a line belongs to is decided by where it is and nothing else: the first twelve lines are F1 to F12 while running, the next twelve the same keys while searching, and a file that stops part way through the second twelve leaves the rest sending what they send while running. A blank line in the middle is a key with nothing in it, which is what the manual says; the newline that ends the last line is not, which it does not say, but no .mc file in the world means its final newline as a message. Two things fall out of this. The buttons now say what the file says, and they change when the operator moves between running and searching, so the labels are the documentation N1MM's manual suggests writing them as. And the search set is real: ESM's F2 while searching can be a different message from the one it sends while running, which is how the file was always meant to be used. Settings keep the file text and carry the twelve stored messages into it on first read, with the labels this program used then, so nobody loses what they had typed. The editor is deliberately a text box rather than a grid of fields: importing, exporting and editing are then the same thing, and the comments an operator writes in the file survive being edited here. The telnet window's buttons can use the same editor later. Running it caught two: Save as the default button ate the Enter key that a multi-line editor needs, and the button labels did not follow the move between run and search until they were redrawn rather than only rebuilt. Co-Authored-By: Claude Opus 5 --- README.md | 16 +++- src/Nonemm.App/AppSession.cs | 8 +- src/Nonemm.App/Configuration/Settings.cs | 55 +++++++++-- src/Nonemm.App/Dialogs/KeyerDialog.axaml | 14 +-- src/Nonemm.App/Dialogs/KeyerDialog.axaml.cs | 64 +++---------- src/Nonemm.App/Dialogs/MessagesDialog.axaml | 24 +++++ .../Dialogs/MessagesDialog.axaml.cs | 82 ++++++++++++++++ src/Nonemm.App/Messages.cs | 93 +++++++++--------- src/Nonemm.App/Windows/EntryWindow.Esm.cs | 11 ++- src/Nonemm.App/Windows/EntryWindow.Macros.cs | 43 +++++++-- src/Nonemm.App/Windows/EntryWindow.axaml.cs | 17 +++- src/Nonemm.Session/MessageFile.cs | 77 +++++++++++++++ tests/Nonemm.App.Tests/SettingsTests.cs | 28 ++++++ .../Nonemm.Session.Tests/MessageFileTests.cs | 95 +++++++++++++++++++ 14 files changed, 498 insertions(+), 129 deletions(-) create mode 100644 src/Nonemm.App/Dialogs/MessagesDialog.axaml create mode 100644 src/Nonemm.App/Dialogs/MessagesDialog.axaml.cs create mode 100644 src/Nonemm.Session/MessageFile.cs create mode 100644 tests/Nonemm.Session.Tests/MessageFileTests.cs diff --git a/README.md b/README.md index 80c3430..2d137c8 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,20 @@ mode stay where they were last typed. ### CW **Config → Keyer and messages** picks `cwdaemon` (a UDP port, usually 6789) or a -WinKeyer (a serial port), sets the speed, and edits the twelve function key -messages for CW and for phone. Escape stops sending. +WinKeyer (a serial port) and sets the speed. Escape stops sending. + +The messages are edited as their file, in a plain text editor: right-click any +of the function key buttons under the entry window, or use the two buttons in +Config → Keyer and messages. One line per key — the button label, a comma, then +the message — and a line starting with `#` is a comment. Which key a line +belongs to is decided by where it is in the file: the first twelve lines are F1 +to F12 while running, and twelve more give the same keys while searching, with +the ones left out keeping their running message. The buttons say what the file +says, and they change as you move between running and searching. + +This is N1MM's `.mc` format. **Import…** reads a file somebody published for +N1MM, **Export…** writes one out, and **Back to the defaults** puts the built-in +messages back. The macros are N1MM's, spelled the way N1MM's function-key documentation spells them, so a `.mc` file written for either program says the same thing. The text diff --git a/src/Nonemm.App/AppSession.cs b/src/Nonemm.App/AppSession.cs index 8191331..b5035ef 100644 --- a/src/Nonemm.App/AppSession.cs +++ b/src/Nonemm.App/AppSession.cs @@ -361,10 +361,10 @@ public sealed class AppSession : IDisposable { throw new InvalidOperationException("there is no keyer"); } - string template = Messages.For( - position.Mode.Category, - Settings.CwMessages, - Settings.PhoneMessages)[0]; + string template = Messages + .For(position.Mode.Category, Settings.CwMessageFile, Settings.PhoneMessageFile) + .Keys(position.IsRunning)[Esm.CallCq] + .Message; if (template.Length == 0) { throw new InvalidOperationException("F1 has no message — Config ▸ Keyer and messages"); diff --git a/src/Nonemm.App/Configuration/Settings.cs b/src/Nonemm.App/Configuration/Settings.cs index 429212d..5274deb 100644 --- a/src/Nonemm.App/Configuration/Settings.cs +++ b/src/Nonemm.App/Configuration/Settings.cs @@ -140,7 +140,15 @@ public sealed record Settings /// in `BandPlan.Default`; an entry replaces one band's boundaries. public IReadOnlyList SubBands { get; init; } = []; - /// The twelve function key messages, keyed F1 to F12. + /// The function key messages as the text of an N1MM `.mc` file, which is + /// what the editor edits and what import and export read and write. Empty + /// means the built-in messages. + public string CwMessageFile { get; init; } = ""; + + public string PhoneMessageFile { get; init; } = ""; + + /// Written by versions that kept twelve messages and no labels. Read once, + /// to fill the file text in, and never written again. public IReadOnlyList CwMessages { get; init; } = []; public IReadOnlyList PhoneMessages { get; init; } = []; @@ -232,11 +240,31 @@ public sealed record Settings } } - /// Carries the single radio an older settings file holds into the list. - private static Settings Migrated(Settings settings) => - settings.Radios.Count > 0 || settings.RigctldHost.Length == 0 - ? settings - : settings with + /// The twelve messages an older settings file holds, written out as the + /// text of a function key file with the labels this program used then. + private static string FileFrom(IReadOnlyList messages) + { + IReadOnlyList<(string Key, string Label)> keys = + [ + ("F1", "CQ"), ("F2", "Exch"), ("F3", "TU"), ("F4", "MyCall"), + ("F5", "HisCall"), ("F6", "QSO B4"), ("F7", "?"), ("F8", "Agn"), + ("F9", "Nr?"), ("F10", "Call?"), ("F11", "Spot"), ("F12", "Wipe"), + ]; + return string.Join( + '\n', + messages.Select((message, at) => at < keys.Count + ? $"{keys[at].Key} {keys[at].Label},{message}" + : $",{message}")); + } + + /// Carries what older settings files hold into the shape this one uses: the + /// single radio into the list of radios, and the twelve messages into the + /// text of a function key file. + private static Settings Migrated(Settings settings) + { + if (settings.RigctldHost.Length > 0 && settings.Radios.Count == 0) + { + settings = settings with { Radios = [new StoredRadio { @@ -248,6 +276,21 @@ public sealed record Settings RigctldPort = 0, RadioEnabled = false, }; + } + if (settings.CwMessages.Count > 0 && settings.CwMessageFile.Length == 0) + { + settings = settings with { CwMessageFile = FileFrom(settings.CwMessages), CwMessages = [] }; + } + if (settings.PhoneMessages.Count > 0 && settings.PhoneMessageFile.Length == 0) + { + settings = settings with + { + PhoneMessageFile = FileFrom(settings.PhoneMessages), + PhoneMessages = [], + }; + } + return settings; + } } /// One band's sub-band boundaries as they are stored. Kilohertz, because that diff --git a/src/Nonemm.App/Dialogs/KeyerDialog.axaml b/src/Nonemm.App/Dialogs/KeyerDialog.axaml index 8c882cc..834debb 100644 --- a/src/Nonemm.App/Dialogs/KeyerDialog.axaml +++ b/src/Nonemm.App/Dialogs/KeyerDialog.axaml @@ -1,7 +1,7 @@ @@ -23,9 +23,11 @@ Text="Ctrl+B starts calling CQ on one radio and then the other. Needs two radios." /> - - - + + +