Keep an operator's windows, colours and recordings
Ctrl+O asks who is at the radio, as it does in N1MM, and the callsign goes on
every contact logged from then on. A multi-operator station changes hands every
few hours, so each operator also keeps what he wants the program to look like:
where the windows sit, the theme, and the folder his recordings are in.
The windows on the screen are stored against the operator leaving, before the
dialog opens, and the operator taking over gets his own back. A window he opens
later is placed as it appears. An operator who is new to the list starts with
whatever is on the screen.
Nothing plays the recordings yet, so the folder is stored and waits on voice
keying. {OPERATOR} in a message sends the callsign.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
This commit is contained in:
@@ -674,6 +674,7 @@ public sealed partial class EntryWindow
|
||||
return (T)existing;
|
||||
}
|
||||
T window = create();
|
||||
Place(window);
|
||||
openWindows[typeof(T)] = window;
|
||||
window.Closed += (_, _) => openWindows.Remove(typeof(T));
|
||||
window.Show(this);
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Nonemm.App.Configuration;
|
||||
using Nonemm.App.Dialogs;
|
||||
using Nonemm.App.Theming;
|
||||
|
||||
namespace Nonemm.App.Windows;
|
||||
|
||||
/// Who is at the radio. A multi-operator station changes hands every few hours,
|
||||
/// and each operator gets his own windows, colours and recordings back. N1MM
|
||||
/// asks the same question with the same key, and writes the callsign against
|
||||
/// every contact logged from then on.
|
||||
public sealed partial class EntryWindow
|
||||
{
|
||||
private async void OnChangeOperator(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
// what is on the screen now belongs to whoever is leaving
|
||||
Settings settings = WithLayout(session.Settings);
|
||||
if (await new OperatorDialog(settings).ShowDialog<OperatorChoice?>(this) is not { } choice)
|
||||
{
|
||||
session.Save(settings);
|
||||
return;
|
||||
}
|
||||
if (choice.IsRemoval)
|
||||
{
|
||||
session.Save(settings with
|
||||
{
|
||||
Operators = [.. settings.Operators.Where(o => !Is(o, choice.Operator.Callsign))],
|
||||
CurrentOperator = Is(choice.Operator, settings.CurrentOperator) ? "" : settings.CurrentOperator,
|
||||
});
|
||||
Status($"{choice.Operator.Callsign} dropped");
|
||||
return;
|
||||
}
|
||||
ChangeTo(settings, choice.Operator);
|
||||
}
|
||||
|
||||
private void ChangeTo(Settings settings, StoredOperator wanted)
|
||||
{
|
||||
StoredOperator stored = settings.Operators.FirstOrDefault(o => Is(o, wanted.Callsign)) is { } known
|
||||
? known with { WavPath = wanted.WavPath }
|
||||
// a new operator starts with the windows and colours on the screen
|
||||
: wanted with { Theme = settings.Theme, Windows = Layout() };
|
||||
session.Save(settings with
|
||||
{
|
||||
Operators = [.. settings.Operators.Where(o => !Is(o, stored.Callsign)), stored],
|
||||
CurrentOperator = stored.Callsign,
|
||||
Theme = stored.Theme.Length > 0 ? stored.Theme : settings.Theme,
|
||||
});
|
||||
if (stored.Theme.Length > 0)
|
||||
{
|
||||
Themes.Use(stored.Theme);
|
||||
}
|
||||
if (session.Logging is { } logging)
|
||||
{
|
||||
logging.Operator = stored.Callsign;
|
||||
}
|
||||
foreach (Window window in Placed())
|
||||
{
|
||||
Place(window, stored);
|
||||
}
|
||||
Status($"operator {stored.Callsign}");
|
||||
}
|
||||
|
||||
/// Puts a window where this operator last had it. Windows are opened one at
|
||||
/// a time, so each one is placed as it appears rather than all at once.
|
||||
private void Place(Window window, StoredOperator? stored = null)
|
||||
{
|
||||
StoredOperator? whose = stored ?? Current();
|
||||
if (whose?.Windows.FirstOrDefault(w => w.Name == window.GetType().Name) is not { } where)
|
||||
{
|
||||
return;
|
||||
}
|
||||
window.Position = new PixelPoint(where.X, where.Y);
|
||||
// a window that sizes itself to what is in it is left the size it wants
|
||||
if (window.CanResize && window.SizeToContent == SizeToContent.Manual
|
||||
&& where.Width > 0 && where.Height > 0)
|
||||
{
|
||||
window.Width = where.Width;
|
||||
window.Height = where.Height;
|
||||
}
|
||||
}
|
||||
|
||||
/// The settings with the windows on the screen stored against the operator
|
||||
/// at the radio. With nobody named there is nowhere to keep them.
|
||||
private Settings WithLayout(Settings settings)
|
||||
{
|
||||
if (Current() is not { } stored)
|
||||
{
|
||||
return settings;
|
||||
}
|
||||
StoredOperator updated = stored with { Theme = settings.Theme, Windows = Layout() };
|
||||
return settings with
|
||||
{
|
||||
Operators = [.. settings.Operators.Select(o => Is(o, updated.Callsign) ? updated : o)],
|
||||
};
|
||||
}
|
||||
|
||||
private StoredOperator? Current() =>
|
||||
session.Settings.Operators.FirstOrDefault(o => Is(o, session.Settings.CurrentOperator));
|
||||
|
||||
private IReadOnlyList<StoredWindow> Layout() =>
|
||||
[.. Placed().Select(w => new StoredWindow
|
||||
{
|
||||
Name = w.GetType().Name,
|
||||
X = w.Position.X,
|
||||
Y = w.Position.Y,
|
||||
Width = w.Bounds.Width,
|
||||
Height = w.Bounds.Height,
|
||||
})];
|
||||
|
||||
/// This window and every window it has opened.
|
||||
private IEnumerable<Window> Placed() => new Window[] { this }.Concat(openWindows.Values);
|
||||
|
||||
private static bool Is(StoredOperator stored, string callsign) =>
|
||||
string.Equals(stored.Callsign, callsign.Trim(), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
@@ -97,6 +97,7 @@
|
||||
<MenuItem Header="Configure Radios…" Click="OnRadioSettings" />
|
||||
<MenuItem Header="Configure Cluster…" Click="OnClusterSettings" />
|
||||
<MenuItem Header="Change Your Station Data…" Click="OnStationSettings" />
|
||||
<MenuItem Header="Change Operator…" Click="OnChangeOperator" InputGesture="Ctrl+O" />
|
||||
<Separator />
|
||||
<MenuItem Name="EsmItem" Header="Enter Sends Message (current mode)" ToggleType="CheckBox"
|
||||
Click="OnToggleEsm" InputGesture="Ctrl+M" />
|
||||
|
||||
@@ -56,6 +56,9 @@ public sealed partial class EntryWindow : Window
|
||||
});
|
||||
Themes.Changed += OnThemeChanged;
|
||||
Closed += (_, _) => Themes.Changed -= OnThemeChanged;
|
||||
// the windows are where this operator left them, and that is what he
|
||||
// gets back next time he takes the radio
|
||||
Closing += (_, _) => session.Save(WithLayout(session.Settings));
|
||||
clock.Tick += (_, _) => UpdateClock();
|
||||
clock.Start();
|
||||
session.ActiveRadioChanged += (_, number) =>
|
||||
@@ -423,6 +426,10 @@ public sealed partial class EntryWindow : Window
|
||||
e.Handled = true;
|
||||
OnEditLastContact(this, new RoutedEventArgs());
|
||||
break;
|
||||
case Key.O when e.KeyModifiers.HasFlag(KeyModifiers.Control):
|
||||
e.Handled = true;
|
||||
OnChangeOperator(this, new RoutedEventArgs());
|
||||
break;
|
||||
case Key.P when e.KeyModifiers.HasFlag(KeyModifiers.Alt):
|
||||
e.Handled = true;
|
||||
OnSpotIt(this, new RoutedEventArgs());
|
||||
|
||||
Reference in New Issue
Block a user