From 845a629e3781efe9e4ae2d91fa8be0052fa0b54d Mon Sep 17 00:00:00 2001 From: ericek111 Date: Sun, 25 Oct 2026 11:27:46 +0000 Subject: [PATCH] Stop raising from restoring the entry window and focus the callsign box The window manager activates another window when the entry window minimizes, and raising the windows then restored the entry window. Raising is now skipped while the entry window is minimized and for a second after its state changes. Restoring the entry window puts focus in the callsign box. Co-Authored-By: Claude Sonnet 5.5 --- src/Nonemm.App/Windows/CheckWindow.axaml.cs | 2 + src/Nonemm.App/Windows/EntryWindow.axaml.cs | 49 +++++++++++++++---- .../Windows/GraylineWindow.axaml.cs | 2 + src/Nonemm.App/Windows/LogWindow.axaml.cs | 2 + src/Nonemm.App/Windows/RefreshableWindow.cs | 6 +++ src/Nonemm.Core/Calls/PartialMatching.cs | 10 ++-- 6 files changed, 57 insertions(+), 14 deletions(-) diff --git a/src/Nonemm.App/Windows/CheckWindow.axaml.cs b/src/Nonemm.App/Windows/CheckWindow.axaml.cs index b137e36..de8a092 100644 --- a/src/Nonemm.App/Windows/CheckWindow.axaml.cs +++ b/src/Nonemm.App/Windows/CheckWindow.axaml.cs @@ -27,6 +27,8 @@ public sealed partial class CheckWindow : RefreshableWindow } + public override void RefreshForEntry() => Refresh(); + public override void Refresh() { Columns.Children.Clear(); diff --git a/src/Nonemm.App/Windows/EntryWindow.axaml.cs b/src/Nonemm.App/Windows/EntryWindow.axaml.cs index 0a435af..bdde515 100644 --- a/src/Nonemm.App/Windows/EntryWindow.axaml.cs +++ b/src/Nonemm.App/Windows/EntryWindow.axaml.cs @@ -83,8 +83,16 @@ public sealed partial class EntryWindow : Window Closing += (_, _) => session.Save(WithLayout(session.Settings)); PropertyChanged += (_, e) => { - if (e.Property == WindowStateProperty - && session.Settings.MinimizeWindowsTogether + if (e.Property != WindowStateProperty) + { + return; + } + lastStateChange = Environment.TickCount64; + if (WindowState == WindowState.Normal) + { + Dispatcher.UIThread.Post(() => boxes.ElementAtOrDefault(0)?.Focus(), DispatcherPriority.Background); + } + if (session.Settings.MinimizeWindowsTogether && WindowState is WindowState.Minimized or WindowState.Normal) { FollowWindowState(WindowState); @@ -195,13 +203,19 @@ public sealed partial class EntryWindow : Window } private long lastRaise; + private long lastStateChange; /// Brings the other windows that are not minimized above whatever else is /// on screen, then puts `front` back on top. Activating them fires their /// own Activated events, so raises within half a second are ignored. private void RaiseTogether(Window front) { - if (!session.Settings.RaiseWindowsTogether || Environment.TickCount64 - lastRaise < 500) + // The window manager activates another window when the entry window + // minimizes. Raising then would restore the entry window. + if (!session.Settings.RaiseWindowsTogether + || Environment.TickCount64 - lastRaise < 500 + || Environment.TickCount64 - lastStateChange < 1000 + || WindowState == WindowState.Minimized) { return; } @@ -380,7 +394,8 @@ public sealed partial class EntryWindow : Window private bool typingRefreshPosted; /// Refresh redraws this window and every open tool window, which is too - /// slow to run between two key presses. It runs once the key presses + /// slow to run between two key presses. Typing refreshes this window and + /// the tool windows that show the call, and it runs once the key presses /// waiting in the queue have been handled, so fast typing costs one /// refresh instead of one per letter. private void RefreshAfterTyping() @@ -394,7 +409,7 @@ public sealed partial class EntryWindow : Window () => { typingRefreshPosted = false; - Refresh(); + RefreshForTyping(); }, DispatcherPriority.Background); } @@ -919,6 +934,26 @@ public sealed partial class EntryWindow : Window } internal void Refresh() + { + RefreshSelf(); + foreach (Window window in openWindows.Values) + { + (window as RefreshableWindow)?.Refresh(); + } + } + + /// What a typed letter changes: this window, and the tool windows that + /// show something about the call being typed. + private void RefreshForTyping() + { + RefreshSelf(); + foreach (Window window in openWindows.Values) + { + (window as RefreshableWindow)?.RefreshForEntry(); + } + } + + private void RefreshSelf() { ShowFunctionKeys(); ShowBandButtons(); @@ -967,10 +1002,6 @@ public sealed partial class EntryWindow : Window VerdictBorder.Background = Verdicts.Background(verdict); VerdictText.Text = VerdictLine(verdict); VerdictText.Foreground = Themes.Brush(Themes.Current.FormForeground); - foreach (Window window in openWindows.Values) - { - (window as RefreshableWindow)?.Refresh(); - } } /// N1MM colours the callsign itself rather than the box: blue for a station diff --git a/src/Nonemm.App/Windows/GraylineWindow.axaml.cs b/src/Nonemm.App/Windows/GraylineWindow.axaml.cs index b99e679..0d3259f 100644 --- a/src/Nonemm.App/Windows/GraylineWindow.axaml.cs +++ b/src/Nonemm.App/Windows/GraylineWindow.axaml.cs @@ -36,6 +36,8 @@ public sealed partial class GraylineWindow : RefreshableWindow private OperatingPosition? Radio => session.Position; + public override void RefreshForEntry() => Refresh(); + public override void Refresh() { Map.Moment = DateTime.UtcNow; diff --git a/src/Nonemm.App/Windows/LogWindow.axaml.cs b/src/Nonemm.App/Windows/LogWindow.axaml.cs index fbcafd8..a5e3aca 100644 --- a/src/Nonemm.App/Windows/LogWindow.axaml.cs +++ b/src/Nonemm.App/Windows/LogWindow.axaml.cs @@ -204,6 +204,8 @@ public sealed partial class LogWindow : RefreshableWindow return new ContextMenu { ItemsSource = new List { edit, operators, delete } }; } + public override void RefreshForEntry() => ShowEarlier(); + public override void Refresh() { if (session.Logging is null) diff --git a/src/Nonemm.App/Windows/RefreshableWindow.cs b/src/Nonemm.App/Windows/RefreshableWindow.cs index b4da26b..07df951 100644 --- a/src/Nonemm.App/Windows/RefreshableWindow.cs +++ b/src/Nonemm.App/Windows/RefreshableWindow.cs @@ -6,4 +6,10 @@ namespace Nonemm.App.Windows; public abstract class RefreshableWindow : Window { public abstract void Refresh(); + + /// Called instead of `Refresh` when the entry window's call or exchange + /// changes. A window that shows nothing about them does nothing. + public virtual void RefreshForEntry() + { + } } diff --git a/src/Nonemm.Core/Calls/PartialMatching.cs b/src/Nonemm.Core/Calls/PartialMatching.cs index 6226cd7..ef8faa9 100644 --- a/src/Nonemm.Core/Calls/PartialMatching.cs +++ b/src/Nonemm.Core/Calls/PartialMatching.cs @@ -14,20 +14,20 @@ public static class PartialMatching { return null; } - if (Same(query, candidate)) + if (Same(query, candidate.AsSpan())) { return PartialMatchQuality.Exact; } if (query.Length < candidate.Length) { - if (Same(query, candidate[..query.Length]) || - Same(query, candidate[^query.Length..])) + if (Same(query, candidate.AsSpan(0, query.Length)) || + Same(query, candidate.AsSpan(candidate.Length - query.Length))) { return PartialMatchQuality.StartsOrEnds; } for (int at = 1; at + query.Length < candidate.Length; at++) { - if (Same(query, candidate.Substring(at, query.Length))) + if (Same(query, candidate.AsSpan(at, query.Length))) { return PartialMatchQuality.Contains; } @@ -69,7 +69,7 @@ public static class PartialMatching SameCharacter(query[firstDifference + 1], candidate[firstDifference]); } - private static bool Same(string query, string candidate) + private static bool Same(string query, ReadOnlySpan candidate) { if (query.Length != candidate.Length) {