Start the transmit pane again on every new message
The macro buttons went wrong at the end of a transmission, both faults in
DigitalEngineSender:
- The engine reports the transmitter drop on its own thread.
WhenTransmitChanged took the state lock, decided the message had ended,
released the lock, and only then called Buffer.Ended(), which clears
what has gone to the engine. A macro pressed on the last character got
through StartAsync in that gap and had already flushed its own text into
Sent, so Ended() wiped the new text off the pane while the engine
transmitted it. Ended() is now called inside the same lock.
- The pane only started again when the engine reported a drop. Two macros
in a row keep the transmitter up, so that report never came and Sent
grew with every press. Everything in Sent is locked, because it is in
the engine and cannot be taken back, so the whole pane became
read-only. TypeAhead.Started() drops the last message's sent text and
keeps what was typed ahead, and StartAsync calls it whenever it keys a
new transmission.
The rest of this commit is the digital transmit work these fixes sit on:
the pane as one coloured box, the sender's three keying states, the
type-ahead feeder paced by the clock with the engine's count as a brake,
{RX} flushing what is left in one piece, and the entry window's function
keys reading the digital macros on a digital mode.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdAYHcdRktqKry7nk414TU
This commit is contained in:
@@ -59,6 +59,15 @@ public sealed partial class DigitalWindow : RefreshableWindow
|
||||
/// redraw is not read back as an edit by the operator.
|
||||
private bool showingBuffer;
|
||||
|
||||
/// The transmit pane as it was last drawn, so an edit can be told from a
|
||||
/// redraw and the changed character found.
|
||||
private string pane = "";
|
||||
|
||||
/// How much of the pane is in the engine's hands and cannot be edited. It
|
||||
/// is more than what is coloured, which is only what has gone out over the
|
||||
/// air.
|
||||
private int locked;
|
||||
|
||||
/// The buffer this window is drawing, or null while no engine is running.
|
||||
private TypeAhead? buffer;
|
||||
|
||||
@@ -82,9 +91,6 @@ public sealed partial class DigitalWindow : RefreshableWindow
|
||||
entry.Activated += WhenEntryActivated;
|
||||
BuildMacros();
|
||||
ApplySettings();
|
||||
// the cursor holds the pump back, so it has to follow the caret as it
|
||||
// moves, not only as the text changes
|
||||
TransmitBox.GetObservable(TextBox.CaretIndexProperty).Subscribe(new Watcher(ShowCursor));
|
||||
Attach(session.Digital);
|
||||
Refresh();
|
||||
Closed += (_, _) =>
|
||||
@@ -104,8 +110,8 @@ public sealed partial class DigitalWindow : RefreshableWindow
|
||||
{
|
||||
ReceiveScroller.Background = Themes.Brush(Themes.Current.FieldBackground);
|
||||
GrabScroller.Background = Themes.Brush(Themes.Current.FieldBackground);
|
||||
SentText.Foreground = Transmitted;
|
||||
SentText.FontSize = Settings.DigitalFontSize;
|
||||
TransmitBox.Background = Themes.Brush(Themes.Current.FieldBackground);
|
||||
TransmitBox.SentBrush = Transmitted;
|
||||
TransmitBox.FontSize = Settings.DigitalFontSize;
|
||||
if (buffer is not null)
|
||||
{
|
||||
@@ -188,21 +194,16 @@ public sealed partial class DigitalWindow : RefreshableWindow
|
||||
private void WhenBufferChanged(object? sender, EventArgs e) =>
|
||||
Dispatcher.UIThread.Post(ShowBuffer);
|
||||
|
||||
/// The pane starts empty for the next message: the transmitter dropping
|
||||
/// with nothing left to send is the end of this one. An engine that keys
|
||||
/// itself drops between two characters as well, and that text is left
|
||||
/// where it is.
|
||||
/// The transmitter dropping ends the message. The buffer clears what has
|
||||
/// gone out and keeps what the operator typed ahead, so the pane is left
|
||||
/// with the next message in it rather than empty.
|
||||
private void WhenTransmitChanged(object? sender, bool transmitting) =>
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
TransmitDot.Background = transmitting
|
||||
? Themes.Brush(Themes.Current.TransmitLight)
|
||||
: Brushes.Transparent;
|
||||
if (!transmitting && buffer is { IsSending: false })
|
||||
{
|
||||
buffer.Clear();
|
||||
ShowBuffer();
|
||||
}
|
||||
ShowBuffer();
|
||||
});
|
||||
|
||||
private void WhenConnectionChanged(object? sender, bool connected) =>
|
||||
@@ -568,18 +569,3 @@ public sealed partial class DigitalWindow : RefreshableWindow
|
||||
_ => StackOrder.Disabled,
|
||||
};
|
||||
}
|
||||
|
||||
/// Watches one property. Avalonia hands out observables and this program has no
|
||||
/// other use for Rx, so a handler that takes no value is enough.
|
||||
internal sealed class Watcher(Action changed) : IObserver<int>
|
||||
{
|
||||
public void OnCompleted()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnError(Exception error)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnNext(int value) => changed();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user