Lay the menu bar out the way N1MM lays it out, and fill in the Edit menu
The menu tree comes from N1MM's EntryWindow.Designer.cs: File, Edit, View, Tools, Config, Window, with the items under the names N1MM gives them. The window list moved out of View into Window; the call history file moved from Config into File > Import. Ctrl+W, Ctrl+L and Ctrl+M now work, so the keys printed in the menu are real. Two commands that had no menu item before: the CW and SSB function key definitions, and the QTC setup area. The rest of the Edit menu: - Ctrl+N asks for a note and puts it in the comment of the contact in the boxes, or of the last logged contact when nothing is typed. - Edit Current Contact opens the Edit Contact form over what is typed but not logged. Update hands the values back to the entry boxes. - Ctrl+Q and Ctrl+A load a logged contact into the boxes and paint them pale yellow. Enter writes the change back, Esc puts back what was being typed, and stepping forward past the newest contact leaves quick edit. - Ctrl+U raises the received serial, or a numeric exchange box when the contest has no serial. - Ctrl+F shows the call being typed in the log window, then the next contact with that call. RadioPosition is now OperatingPosition: it is the operator at one radio, not a place on a band. Looked at under Xvfb on a 3775-contact log: quick edit back and forward, Esc, the note dialog, find, and Edit Current Contact putting a zone back into the entry boxes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,7 @@ public sealed class AppSession : IDisposable
|
||||
{
|
||||
private LogStore? store;
|
||||
private readonly List<RigctldRadio> radios = [];
|
||||
private readonly List<RadioPosition> positions = [];
|
||||
private readonly List<OperatingPosition> positions = [];
|
||||
private int activeRadio;
|
||||
private ClusterClient? cluster;
|
||||
private StationNetwork? network;
|
||||
@@ -88,15 +88,15 @@ public sealed class AppSession : IDisposable
|
||||
|
||||
/// The radio the operator is not on, or null at a one-radio station. The
|
||||
/// message macros that pass a station to the other band need it.
|
||||
public RadioPosition? Other(RadioPosition position) =>
|
||||
public OperatingPosition? Other(OperatingPosition position) =>
|
||||
positions.FirstOrDefault(p => p.RadioNumber != position.RadioNumber);
|
||||
|
||||
/// One per radio, in radio-number order. There is always at least one, so
|
||||
/// the program works with no radio connected.
|
||||
public IReadOnlyList<RadioPosition> Positions => positions;
|
||||
public IReadOnlyList<OperatingPosition> Positions => positions;
|
||||
|
||||
/// The radio the operator is on.
|
||||
public RadioPosition? Position =>
|
||||
public OperatingPosition? Position =>
|
||||
activeRadio < positions.Count ? positions[activeRadio] : null;
|
||||
|
||||
public CheckWindowSources? Check { get; private set; }
|
||||
@@ -185,7 +185,7 @@ public sealed class AppSession : IDisposable
|
||||
positions.Clear();
|
||||
for (int number = 1; number <= PositionCount; number++)
|
||||
{
|
||||
positions.Add(new RadioPosition(Logging, number));
|
||||
positions.Add(new OperatingPosition(Logging, number));
|
||||
}
|
||||
activeRadio = Math.Min(activeRadio, positions.Count - 1);
|
||||
Logging.Changed += (_, _) => Changed?.Invoke(this, EventArgs.Empty);
|
||||
@@ -370,7 +370,7 @@ public sealed class AppSession : IDisposable
|
||||
/// alternating CQ; the operator's own F1 goes through the entry window.
|
||||
private async Task CallCqOnAsync(int radioNumber)
|
||||
{
|
||||
RadioPosition position = positions.FirstOrDefault(p => p.RadioNumber == radioNumber)
|
||||
OperatingPosition position = positions.FirstOrDefault(p => p.RadioNumber == radioNumber)
|
||||
?? throw new InvalidOperationException($"there is no radio {radioNumber}");
|
||||
if (keyer is null)
|
||||
{
|
||||
@@ -438,7 +438,7 @@ public sealed class AppSession : IDisposable
|
||||
/// but it must not drag the entry window off the contact being worked.
|
||||
private void RadioMoved(Radio moved, RadioState state)
|
||||
{
|
||||
RadioPosition? position = positions.FirstOrDefault(p => p.RadioNumber == moved.Number);
|
||||
OperatingPosition? position = positions.FirstOrDefault(p => p.RadioNumber == moved.Number);
|
||||
if (position is null)
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
WindowStartupLocation="CenterOwner" CanResize="False">
|
||||
<DockPanel Margin="14">
|
||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="6" Margin="0,12,0,0">
|
||||
<Button Content="◀" Click="OnPrevious" ToolTip.Tip="Previous contact" />
|
||||
<Button Content="▶" Click="OnNext" ToolTip.Tip="Next contact" />
|
||||
<Button Content="Delete" Click="OnDelete" />
|
||||
<Button Name="PreviousButton" Content="◀" Click="OnPrevious" ToolTip.Tip="Previous contact" />
|
||||
<Button Name="NextButton" Content="▶" Click="OnNext" ToolTip.Tip="Next contact" />
|
||||
<Button Name="DeleteButton" Content="Delete" Click="OnDelete" />
|
||||
<Panel Width="120" />
|
||||
<Button Content="Close" Click="OnClose" IsCancel="True" />
|
||||
<Button Content="Update" Click="OnUpdate" IsDefault="True" />
|
||||
|
||||
@@ -34,18 +34,42 @@ public sealed partial class EditContactDialog : Window
|
||||
private readonly Dictionary<QsoField, TextBox> boxes = [];
|
||||
private int at;
|
||||
|
||||
/// Set when the dialog holds the contact still in the entry boxes rather
|
||||
/// than one from the log. Update hands it back instead of saving it, and
|
||||
/// there is nothing to step to or delete.
|
||||
private readonly Qso? inProgress;
|
||||
|
||||
public EditContactDialog(ContestSession logging, string contactId)
|
||||
{
|
||||
this.logging = logging;
|
||||
InitializeComponent();
|
||||
BuildFields(ContactFields, ContactRow);
|
||||
BuildFields(ExchangeFields, ExchangeRow);
|
||||
AddWideField(ExchangeFields, QsoField.Comment);
|
||||
Build();
|
||||
at = Math.Max(0, IndexOf(contactId));
|
||||
Display(Current);
|
||||
}
|
||||
|
||||
private Qso Current => logging.Log.Qsos[at];
|
||||
/// N1MM's Edit Current Contact: the same form over what is typed but not
|
||||
/// logged yet. Closes with the edited contact, or null when it is cancelled.
|
||||
public EditContactDialog(ContestSession logging, Qso inProgress)
|
||||
{
|
||||
this.logging = logging;
|
||||
this.inProgress = inProgress;
|
||||
InitializeComponent();
|
||||
Build();
|
||||
PreviousButton.IsVisible = false;
|
||||
NextButton.IsVisible = false;
|
||||
DeleteButton.IsVisible = false;
|
||||
Display(Current);
|
||||
}
|
||||
|
||||
private void Build()
|
||||
{
|
||||
BuildFields(ContactFields, ContactRow);
|
||||
BuildFields(ExchangeFields, ExchangeRow);
|
||||
AddWideField(ExchangeFields, QsoField.Comment);
|
||||
}
|
||||
|
||||
private Qso Current => inProgress ?? logging.Log.Qsos[at];
|
||||
|
||||
private int IndexOf(string id)
|
||||
{
|
||||
@@ -113,7 +137,9 @@ public sealed partial class EditContactDialog : Window
|
||||
}
|
||||
RunBox.IsChecked = qso.IsRunQso;
|
||||
ClaimedBox.IsChecked = qso.IsClaimed;
|
||||
PositionText.Text = $"contact {at + 1} of {logging.Log.Qsos.Count}";
|
||||
PositionText.Text = inProgress is null
|
||||
? $"contact {at + 1} of {logging.Log.Qsos.Count}"
|
||||
: "the contact in the entry boxes, not logged yet";
|
||||
DerivedText.Text =
|
||||
$"band {qso.Band?.Name ?? "off band"} · continent {Or(qso.Continent)} · " +
|
||||
$"station prefix {Or(qso.StationPrefix)} · {qso.Points} points · " +
|
||||
@@ -155,11 +181,17 @@ public sealed partial class EditContactDialog : Window
|
||||
}
|
||||
return false;
|
||||
}
|
||||
logging.Update(edit.Result with
|
||||
Qso changed = edit.Result with
|
||||
{
|
||||
IsRunQso = RunBox.IsChecked == true,
|
||||
IsClaimed = ClaimedBox.IsChecked == true,
|
||||
});
|
||||
};
|
||||
if (inProgress is not null)
|
||||
{
|
||||
Close(changed);
|
||||
return true;
|
||||
}
|
||||
logging.Update(changed);
|
||||
// editing the time re-sorts the log, so find the contact again
|
||||
at = IndexOf(before.Id);
|
||||
Display(Current);
|
||||
@@ -228,6 +260,11 @@ public sealed partial class EditContactDialog : Window
|
||||
|
||||
private async void OnClose(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (inProgress is not null)
|
||||
{
|
||||
Close(null);
|
||||
return;
|
||||
}
|
||||
if (await KeepChanges())
|
||||
{
|
||||
Close();
|
||||
|
||||
17
src/Nonemm.App/Dialogs/NoteDialog.axaml
Normal file
17
src/Nonemm.App/Dialogs/NoteDialog.axaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="Nonemm.App.Dialogs.NoteDialog"
|
||||
Title="Current Contact" Width="380" SizeToContent="Height"
|
||||
WindowStartupLocation="CenterOwner" CanResize="False">
|
||||
<DockPanel Margin="14">
|
||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right"
|
||||
Spacing="6" Margin="0,12,0,0">
|
||||
<Button Content="Cancel" Click="OnCancel" IsCancel="True" />
|
||||
<Button Content="Ok" Click="OnOk" IsDefault="True" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock Text="Enter note (comment)" />
|
||||
<TextBox Name="NoteBox" MaxLength="60" />
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
25
src/Nonemm.App/Dialogs/NoteDialog.axaml.cs
Normal file
25
src/Nonemm.App/Dialogs/NoteDialog.axaml.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
|
||||
namespace Nonemm.App.Dialogs;
|
||||
|
||||
/// The note N1MM's Ctrl+N asks for. It goes in the contact's comment, and N1MM
|
||||
/// keeps notes to 60 characters.
|
||||
public sealed partial class NoteDialog : Window
|
||||
{
|
||||
public NoteDialog(string title, string note)
|
||||
{
|
||||
InitializeComponent();
|
||||
Title = title;
|
||||
NoteBox.Text = note;
|
||||
Opened += (_, _) =>
|
||||
{
|
||||
NoteBox.Focus();
|
||||
NoteBox.CaretIndex = NoteBox.Text?.Length ?? 0;
|
||||
};
|
||||
}
|
||||
|
||||
private void OnOk(object? sender, RoutedEventArgs e) => Close(NoteBox.Text ?? "");
|
||||
|
||||
private void OnCancel(object? sender, RoutedEventArgs e) => Close(null);
|
||||
}
|
||||
@@ -28,9 +28,10 @@ public sealed partial class EntryWindow
|
||||
|
||||
/// Right-clicking a function key button opens the messages for the mode the
|
||||
/// radio is in, which is where import and export live too.
|
||||
private async Task EditMessages()
|
||||
private Task EditMessages() => EditMessages(Logging?.Mode.Category ?? ModeCategory.Cw);
|
||||
|
||||
private async Task EditMessages(ModeCategory mode)
|
||||
{
|
||||
ModeCategory mode = Logging?.Mode.Category ?? ModeCategory.Cw;
|
||||
string stored = mode == ModeCategory.Phone
|
||||
? session.Settings.PhoneMessageFile
|
||||
: session.Settings.CwMessageFile;
|
||||
|
||||
@@ -3,6 +3,7 @@ using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Nonemm.App.Configuration;
|
||||
using Nonemm.App.Dialogs;
|
||||
using Nonemm.Contests;
|
||||
using Nonemm.Core;
|
||||
using Nonemm.Core.Country;
|
||||
using Nonemm.Formats.Adif;
|
||||
@@ -189,6 +190,129 @@ public sealed partial class EntryWindow
|
||||
await new EditContactDialog(Logging.Session, Logging.Log.Qsos[^1].Id).ShowDialog(this);
|
||||
}
|
||||
|
||||
/// N1MM's Ctrl+N. The note goes on the contact in the boxes when one is
|
||||
/// being typed, and on the last logged contact otherwise.
|
||||
private async void OnAddNote(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Logging is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (Logging.Entry.Call.Trim().Length > 0)
|
||||
{
|
||||
if (await new NoteDialog("Current Contact", Logging.Comment).ShowDialog<string?>(this) is { } typed)
|
||||
{
|
||||
Logging.Comment = typed;
|
||||
SyncBoxes();
|
||||
Status($"note on {Logging.Entry.Call.Trim()}: {typed}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (Logging.Log.Qsos.Count == 0)
|
||||
{
|
||||
Status("there is nothing in the log yet");
|
||||
return;
|
||||
}
|
||||
Qso last = Logging.Log.Qsos[^1];
|
||||
string title = $"{last.Call.Text} @ {last.TimestampUtc:yyyy-MM-dd HH:mm:ss}";
|
||||
if (await new NoteDialog(title, last.Comment).ShowDialog<string?>(this) is { } note)
|
||||
{
|
||||
Logging.Session.Update(last with { Comment = note });
|
||||
Status($"note on {last.Call.Text}: {note}");
|
||||
}
|
||||
}
|
||||
|
||||
/// N1MM's Edit Current Contact: the same form the log uses, over what is
|
||||
/// typed but not logged yet. What comes back goes into the boxes.
|
||||
private async void OnEditCurrentContact(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Logging is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (Logging.Entry.Call.Trim().Length == 0)
|
||||
{
|
||||
Status("no callsign to edit");
|
||||
return;
|
||||
}
|
||||
EditContactDialog dialog = new(Logging.Session, Logging.InProgress());
|
||||
if (await dialog.ShowDialog<Qso?>(this) is { } edited)
|
||||
{
|
||||
Logging.LoadFrom(edited);
|
||||
SyncBoxes();
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnQuickEditBack(object? sender, RoutedEventArgs e) => QuickEdit(forward: false);
|
||||
|
||||
private void OnQuickEditForward(object? sender, RoutedEventArgs e) => QuickEdit(forward: true);
|
||||
|
||||
/// Loads an earlier contact into the boxes. Enter writes the changes back,
|
||||
/// Esc leaves the boxes as they were.
|
||||
private void QuickEdit(bool forward)
|
||||
{
|
||||
if (Logging is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!Logging.QuickEdit(forward))
|
||||
{
|
||||
Status(forward ? "not in quick edit" : "there is nothing earlier in the log");
|
||||
return;
|
||||
}
|
||||
SyncBoxes();
|
||||
Refresh();
|
||||
boxes[0].Focus();
|
||||
Status(Logging.Editing is null
|
||||
? "back to the contact being typed"
|
||||
: $"quick edit: {Logging.Editing.Call.Text} — Enter saves, Esc leaves");
|
||||
}
|
||||
|
||||
/// N1MM's Ctrl+U: the received serial number goes up by one, for the
|
||||
/// station that says it sent the next number. With no serial box, a
|
||||
/// numeric exchange box is bumped instead, which is what N1MM does for the
|
||||
/// contests that count in the exchange.
|
||||
private void OnBumpNumber(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Logging is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (int at = 0; at < Logging.Entry.Exchange.Count; at++)
|
||||
{
|
||||
ExchangeField field = Logging.Entry.Exchange[at];
|
||||
if ((field.Kind != ExchangeFieldKind.Number && field.Slot != ExchangeSlot.Exchange1) ||
|
||||
!int.TryParse(Logging.Entry[at + 1].Trim(), out int number) || number <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Logging.Entry[at + 1] = (number + 1).ToString();
|
||||
SyncBoxes();
|
||||
Refresh();
|
||||
return;
|
||||
}
|
||||
Status("no number to increase");
|
||||
}
|
||||
|
||||
/// N1MM's Ctrl+F: shows the call being typed in the log window, and again
|
||||
/// for the next contact with the same call.
|
||||
private void OnFind(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Logging is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string call = Logging.Entry.Call.Trim();
|
||||
if (call.Length == 0)
|
||||
{
|
||||
Status("no callsign to find");
|
||||
return;
|
||||
}
|
||||
Qso? found = Show(() => new LogWindow(session)).FindNextCall(call);
|
||||
Status(found is null ? $"{call} not found" : $"{call} at {found.TimestampUtc:HH:mm:ss}");
|
||||
}
|
||||
|
||||
/// Puts the call being typed on the cluster, or the last one logged when
|
||||
/// nothing is typed. That is what N1MM's Spot It button does.
|
||||
private async void OnSpotIt(object? sender, RoutedEventArgs e)
|
||||
@@ -341,6 +465,19 @@ public sealed partial class EntryWindow
|
||||
BuildFunctionKeys();
|
||||
}
|
||||
|
||||
private void OnEditCwMessages(object? sender, RoutedEventArgs e) => _ = EditMessages(ModeCategory.Cw);
|
||||
|
||||
private void OnEditPhoneMessages(object? sender, RoutedEventArgs e) => _ = EditMessages(ModeCategory.Phone);
|
||||
|
||||
private async void OnQtcSetup(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
QtcSetupDialog dialog = new(session.Settings);
|
||||
if (await dialog.ShowDialog<Settings?>(this) is { } updated)
|
||||
{
|
||||
session.Save(updated);
|
||||
}
|
||||
}
|
||||
|
||||
/// Call history files are published per contest, so the operator points at
|
||||
/// one rather than the program looking in a fixed place.
|
||||
private async void OnCallHistoryFile(object? sender, RoutedEventArgs e)
|
||||
|
||||
@@ -64,45 +64,72 @@
|
||||
<DockPanel>
|
||||
<Menu Name="MainMenu" DockPanel.Dock="Top">
|
||||
<MenuItem Header="_File">
|
||||
<MenuItem Header="_New Database…" Click="OnNewDatabase" />
|
||||
<MenuItem Header="New Log in Database…" Click="OnNewContest" />
|
||||
<MenuItem Header="Open Log in Database…" Click="OnOpenContest" />
|
||||
<Separator />
|
||||
<MenuItem Header="New Database…" Click="OnNewDatabase" />
|
||||
<MenuItem Header="_Open Database…" Click="OnOpenDatabase" />
|
||||
<Separator />
|
||||
<MenuItem Header="New _Contest…" Click="OnNewContest" />
|
||||
<MenuItem Header="Open Con_test…" Click="OnOpenContest" />
|
||||
<MenuItem Header="Generate Cabrillo File" Click="OnExportCabrillo" />
|
||||
<MenuItem Header="Import">
|
||||
<MenuItem Header="Import ADIF from file…" Click="OnImportAdif" />
|
||||
<MenuItem Header="Import Call History…" Click="OnCallHistoryFile" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="Export">
|
||||
<MenuItem Header="Export ADIF to file…" Click="OnExportAdif" />
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Export _Cabrillo…" Click="OnExportCabrillo" />
|
||||
<MenuItem Header="Export _ADIF…" Click="OnExportAdif" />
|
||||
<MenuItem Header="_Import ADIF…" Click="OnImportAdif" />
|
||||
<Separator />
|
||||
<MenuItem Header="E_xit" Click="OnExit" />
|
||||
<MenuItem Header="E_xit" Click="OnExit" InputGesture="Alt+F4" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Edit">
|
||||
<MenuItem Header="Edit _Last Contact…" Click="OnEditLastContact" InputGesture="Ctrl+Y" />
|
||||
<MenuItem Header="S_pot It" Click="OnSpotIt" InputGesture="Alt+P" />
|
||||
<MenuItem Header="Wipe Out Entry Fields" Click="OnWipe" InputGesture="Ctrl+W" />
|
||||
<Separator />
|
||||
<MenuItem Header="Edit Last Contact" Click="OnEditLastContact" InputGesture="Ctrl+Y" />
|
||||
<MenuItem Header="Add a Note to Last/Current Contact" Click="OnAddNote" InputGesture="Ctrl+N" />
|
||||
<MenuItem Header="Edit Current Contact" Click="OnEditCurrentContact" />
|
||||
<MenuItem Header="Quick Edit Previous Contacts (Back)" Click="OnQuickEditBack" InputGesture="Ctrl+Q" />
|
||||
<MenuItem Header="Quick Edit Previous Contacts (Forward)" Click="OnQuickEditForward" InputGesture="Ctrl+A" />
|
||||
<MenuItem Header="Increase Received NR by 1" Click="OnBumpNumber" InputGesture="Ctrl+U" />
|
||||
<Separator />
|
||||
<MenuItem Header="Find/Find Again" Click="OnFind" InputGesture="Ctrl+F" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="_View">
|
||||
<MenuItem Header="_Log" Click="OnShowLog" />
|
||||
<MenuItem Header="_Check" Click="OnShowCheck" />
|
||||
<MenuItem Header="_Bandmap" Click="OnShowBandmap" />
|
||||
<MenuItem Header="_Available Mults and Qs" Click="OnShowAvailable" />
|
||||
<MenuItem Header="_Score Summary" Click="OnShowScore" />
|
||||
<MenuItem Header="_Telnet" Click="OnShowTelnet" />
|
||||
<MenuItem Header="Show QRZ" Click="OnLookUpCall" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="Tools">
|
||||
<MenuItem Header="Download and Install Latest Check Partial file (master.scp) (Internet)"
|
||||
Click="OnDownloadCallDatabase" />
|
||||
<MenuItem Header="Download and install latest country file (wl_cty.dat) (Internet)"
|
||||
Click="OnDownloadCountryFile" />
|
||||
<Separator />
|
||||
<MenuItem Header="Reload Support Files" Click="OnReloadSupportFiles" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Config">
|
||||
<MenuItem Header="_Station…" Click="OnStationSettings" />
|
||||
<MenuItem Header="_Radio…" Click="OnRadioSettings" />
|
||||
<MenuItem Header="_Cluster…" Click="OnClusterSettings" />
|
||||
<MenuItem Header="_Network…" Click="OnNetworkSettings" />
|
||||
<MenuItem Header="_Keyer and messages…" Click="OnKeyerSettings" />
|
||||
<MenuItem Name="EsmItem" Header="_ESM — Enter sends message" ToggleType="CheckBox"
|
||||
Click="OnToggleEsm" />
|
||||
<MenuItem Header="Configure Ports, Mode Control, Winkey, etc…" Click="OnKeyerSettings" />
|
||||
<MenuItem Header="Configure Radios…" Click="OnRadioSettings" />
|
||||
<MenuItem Header="Configure Cluster…" Click="OnClusterSettings" />
|
||||
<MenuItem Header="Change Your Station Data…" Click="OnStationSettings" />
|
||||
<Separator />
|
||||
<MenuItem Header="Call _History File…" Click="OnCallHistoryFile" />
|
||||
<MenuItem Header="S_ub bands…" Click="OnSubBandSettings" />
|
||||
<MenuItem Name="EsmItem" Header="Enter Sends Message (current mode)" ToggleType="CheckBox"
|
||||
Click="OnToggleEsm" InputGesture="Ctrl+M" />
|
||||
<Separator />
|
||||
<MenuItem Header="Download Country _File" Click="OnDownloadCountryFile" />
|
||||
<MenuItem Header="Download Check _Partial File" Click="OnDownloadCallDatabase" />
|
||||
<MenuItem Header="Reload Support Files" Click="OnReloadSupportFiles" />
|
||||
<MenuItem Header="Change CW/SSB/Digital Function Key Definitions">
|
||||
<MenuItem Header="Change _CW Function Key Definitions" Click="OnEditCwMessages" />
|
||||
<MenuItem Header="Change _SSB Function Key Definitions" Click="OnEditPhoneMessages" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="Change Band Plan…" Click="OnSubBandSettings" />
|
||||
<MenuItem Header="Edit Networked-Computer Names…" Click="OnNetworkSettings" />
|
||||
<MenuItem Header="WAE">
|
||||
<MenuItem Header="Open QTC Window setup area" Click="OnQtcSetup" />
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Wi_ndow">
|
||||
<MenuItem Header="Available Mult's and Q's" Click="OnShowAvailable" />
|
||||
<MenuItem Header="_Bandmap" Click="OnShowBandmap" />
|
||||
<MenuItem Header="Check" Click="OnShowCheck" />
|
||||
<MenuItem Header="Log" Click="OnShowLog" InputGesture="Ctrl+L" />
|
||||
<MenuItem Header="Score Summary" Click="OnShowScore" />
|
||||
<MenuItem Header="Telnet" Click="OnShowTelnet" />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
|
||||
@@ -24,6 +24,9 @@ public sealed partial class EntryWindow : Window
|
||||
|
||||
private static readonly IBrush Dark = new SolidColorBrush(Color.FromArgb(0x33, 0x80, 0x80, 0x80));
|
||||
|
||||
/// The pale yellow N1MM paints the entry boxes while quick editing.
|
||||
private static readonly IBrush QuickEditBackground = new SolidColorBrush(Color.FromRgb(0xFF, 0xFF, 0xC0));
|
||||
|
||||
private bool sending;
|
||||
private TextBox? nameBox;
|
||||
private TextBox? commentBox;
|
||||
@@ -61,7 +64,7 @@ public sealed partial class EntryWindow : Window
|
||||
|
||||
/// Resolved every time rather than held, because opening a contest builds
|
||||
/// new positions.
|
||||
private RadioPosition? Logging =>
|
||||
private OperatingPosition? Logging =>
|
||||
session.Positions.FirstOrDefault(p => p.RadioNumber == radioNumber);
|
||||
|
||||
/// Reopens the database and contest the operator was last in.
|
||||
@@ -289,6 +292,14 @@ public sealed partial class EntryWindow : Window
|
||||
e.Handled = true;
|
||||
OnEnter();
|
||||
break;
|
||||
case Key.Escape when Logging.Editing is not null:
|
||||
e.Handled = true;
|
||||
Logging.LeaveQuickEdit();
|
||||
SyncBoxes();
|
||||
Refresh();
|
||||
boxes[0].Focus();
|
||||
Status("quick edit left");
|
||||
break;
|
||||
case Key.Escape:
|
||||
e.Handled = true;
|
||||
sending = false;
|
||||
@@ -325,6 +336,38 @@ public sealed partial class EntryWindow : Window
|
||||
e.Handled = true;
|
||||
ToggleAlternatingCq();
|
||||
break;
|
||||
case Key.N when e.KeyModifiers.HasFlag(KeyModifiers.Control):
|
||||
e.Handled = true;
|
||||
OnAddNote(this, new RoutedEventArgs());
|
||||
break;
|
||||
case Key.Q when e.KeyModifiers.HasFlag(KeyModifiers.Control):
|
||||
e.Handled = true;
|
||||
OnQuickEditBack(this, new RoutedEventArgs());
|
||||
break;
|
||||
case Key.A when e.KeyModifiers.HasFlag(KeyModifiers.Control):
|
||||
e.Handled = true;
|
||||
OnQuickEditForward(this, new RoutedEventArgs());
|
||||
break;
|
||||
case Key.U when e.KeyModifiers.HasFlag(KeyModifiers.Control):
|
||||
e.Handled = true;
|
||||
OnBumpNumber(this, new RoutedEventArgs());
|
||||
break;
|
||||
case Key.F when e.KeyModifiers.HasFlag(KeyModifiers.Control):
|
||||
e.Handled = true;
|
||||
OnFind(this, new RoutedEventArgs());
|
||||
break;
|
||||
case Key.W when e.KeyModifiers.HasFlag(KeyModifiers.Control):
|
||||
e.Handled = true;
|
||||
OnWipe(this, new RoutedEventArgs());
|
||||
break;
|
||||
case Key.L when e.KeyModifiers.HasFlag(KeyModifiers.Control):
|
||||
e.Handled = true;
|
||||
OnShowLog(this, new RoutedEventArgs());
|
||||
break;
|
||||
case Key.M when e.KeyModifiers.HasFlag(KeyModifiers.Control):
|
||||
e.Handled = true;
|
||||
OnToggleEsm(this, new RoutedEventArgs());
|
||||
break;
|
||||
case Key.Y when e.KeyModifiers.HasFlag(KeyModifiers.Control):
|
||||
e.Handled = true;
|
||||
OnEditLastContact(this, new RoutedEventArgs());
|
||||
@@ -350,6 +393,11 @@ public sealed partial class EntryWindow : Window
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (Logging.Editing is not null)
|
||||
{
|
||||
SaveQuickEdit();
|
||||
return;
|
||||
}
|
||||
Frequency? qsy = Logging.PendingQsy();
|
||||
if (qsy is not null)
|
||||
{
|
||||
@@ -375,6 +423,18 @@ public sealed partial class EntryWindow : Window
|
||||
|
||||
/// Logs what is in the boxes, which is what Enter and N1MM's `{LOG}` macro
|
||||
/// both do.
|
||||
private void SaveQuickEdit()
|
||||
{
|
||||
if (Logging?.SaveQuickEdit() is not { } saved)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Status($"{saved.Call.Text} updated");
|
||||
SyncBoxes();
|
||||
Refresh();
|
||||
boxes[0].Focus();
|
||||
}
|
||||
|
||||
private void LogContact()
|
||||
{
|
||||
if (Logging is null)
|
||||
@@ -585,6 +645,21 @@ public sealed partial class EntryWindow : Window
|
||||
VerdictText.Text = "";
|
||||
return;
|
||||
}
|
||||
foreach (TextBox box in boxes)
|
||||
{
|
||||
if (Logging.Editing is null)
|
||||
{
|
||||
// clearing rather than assigning null: a null brush would paint
|
||||
// no text at all
|
||||
box.ClearValue(BackgroundProperty);
|
||||
box.ClearValue(ForegroundProperty);
|
||||
}
|
||||
else
|
||||
{
|
||||
box.Background = QuickEditBackground;
|
||||
box.Foreground = Brushes.Black;
|
||||
}
|
||||
}
|
||||
FrequencyText.Text = Logging.TransmitFrequency.Hertz > 0
|
||||
? $"{Logging.Frequency.Kilohertz:0.00} ▸ {Logging.TransmitFrequency.Kilohertz:0.0}"
|
||||
: Logging.Frequency.Kilohertz.ToString("0.00");
|
||||
|
||||
@@ -21,6 +21,7 @@ public sealed partial class LogWindow : RefreshableWindow
|
||||
private readonly AppSession session;
|
||||
private string builtFor = "";
|
||||
private string message = "";
|
||||
private string finding = "";
|
||||
|
||||
public LogWindow(AppSession session)
|
||||
{
|
||||
@@ -105,6 +106,32 @@ public sealed partial class LogWindow : RefreshableWindow
|
||||
private static double MinimumFor(int characters) =>
|
||||
(characters * CharacterWidth) + CellPadding;
|
||||
|
||||
/// N1MM's Ctrl+F: shows the next contact with this call and selects it,
|
||||
/// starting again from the top once the last one has been shown. Null when
|
||||
/// the call is not in the log.
|
||||
public Qso? FindNextCall(string call)
|
||||
{
|
||||
string wanted = call.Trim();
|
||||
if (wanted.Length == 0 || Rows.ItemsSource is not IReadOnlyList<LogRow> rows)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
int from = wanted.Equals(finding, StringComparison.OrdinalIgnoreCase) ? Rows.SelectedIndex + 1 : 0;
|
||||
finding = wanted;
|
||||
for (int step = 0; step < rows.Count; step++)
|
||||
{
|
||||
int at = (from + step) % rows.Count;
|
||||
if (!rows[at].Qso.Call.Text.Equals(wanted, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Rows.SelectedIndex = at;
|
||||
Rows.ScrollIntoView(rows[at], null);
|
||||
return rows[at].Qso;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// True when the edit was taken. A refused edit puts the reason in the
|
||||
/// summary line and the cell falls back to what it held before.
|
||||
private bool Edit(Qso qso, QsoField field, string text)
|
||||
|
||||
@@ -25,7 +25,7 @@ public sealed partial class QtcWindow : Window
|
||||
private static readonly IBrush Malformed = new SolidColorBrush(Color.FromRgb(0xFF, 0xF8, 0xC4));
|
||||
|
||||
private readonly AppSession session;
|
||||
private readonly RadioPosition position;
|
||||
private readonly OperatingPosition position;
|
||||
private readonly QtcTraffic traffic;
|
||||
private readonly Callsign station;
|
||||
private bool isSending;
|
||||
@@ -34,7 +34,7 @@ public sealed partial class QtcWindow : Window
|
||||
|
||||
public QtcWindow(
|
||||
AppSession session,
|
||||
RadioPosition position,
|
||||
OperatingPosition position,
|
||||
Callsign station,
|
||||
QtcDirection direction)
|
||||
{
|
||||
|
||||
@@ -29,10 +29,10 @@ public sealed record AvailableStation(Spot Spot, Band Band, Verdict Verdict)
|
||||
/// in, so in a mixed-mode contest the answer follows the operator.
|
||||
public sealed class AvailableStations
|
||||
{
|
||||
private readonly RadioPosition position;
|
||||
private readonly OperatingPosition position;
|
||||
private readonly Bandmap bandmap;
|
||||
|
||||
public AvailableStations(RadioPosition position, Bandmap bandmap)
|
||||
public AvailableStations(OperatingPosition position, Bandmap bandmap)
|
||||
{
|
||||
this.position = position;
|
||||
this.bandmap = bandmap;
|
||||
|
||||
@@ -8,11 +8,11 @@ namespace Nonemm.Session;
|
||||
/// Answers what the callsign being typed could be, a column per source.
|
||||
public sealed class CheckWindowSources
|
||||
{
|
||||
private readonly RadioPosition session;
|
||||
private readonly OperatingPosition session;
|
||||
private readonly CallDatabase database;
|
||||
private readonly Bandmap bandmap;
|
||||
|
||||
public CheckWindowSources(RadioPosition session, CallDatabase database, Bandmap bandmap)
|
||||
public CheckWindowSources(OperatingPosition session, CallDatabase database, Bandmap bandmap)
|
||||
{
|
||||
this.session = session;
|
||||
this.database = database;
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Nonemm.Session;
|
||||
|
||||
/// The running contest: the log, the score and what happens when a contact is
|
||||
/// logged. One of these per contest, however many radios the station has.
|
||||
/// What the operator is typing lives in `RadioPosition`, one per radio.
|
||||
/// What the operator is typing lives in `OperatingPosition`, one per radio.
|
||||
///
|
||||
/// No UI framework is referenced here, so the behaviour an operator judges the
|
||||
/// logger by is tested with plain unit tests.
|
||||
|
||||
@@ -26,6 +26,26 @@ public static class ExchangeSlots
|
||||
_ => qso,
|
||||
};
|
||||
|
||||
/// What a logged contact holds for one exchange box. The inverse of
|
||||
/// `Apply`, used to load a contact back into the entry boxes.
|
||||
public static string ValueOf(Qso qso, ExchangeSlot slot) => slot switch
|
||||
{
|
||||
ExchangeSlot.ReceivedReport => qso.ReceivedReport,
|
||||
ExchangeSlot.SerialNumber => Digits(qso.ReceivedNumber),
|
||||
ExchangeSlot.Zone => Digits(qso.Zone),
|
||||
ExchangeSlot.Section => qso.Section,
|
||||
ExchangeSlot.Check => Digits(qso.Check),
|
||||
ExchangeSlot.Precedence => qso.Precedence,
|
||||
ExchangeSlot.Exchange1 => qso.Exchange1,
|
||||
ExchangeSlot.MiscText => qso.MiscText,
|
||||
ExchangeSlot.Name => qso.Name,
|
||||
ExchangeSlot.Qth => qso.Qth,
|
||||
ExchangeSlot.GridSquare => qso.GridSquare,
|
||||
ExchangeSlot.Power => qso.Power,
|
||||
ExchangeSlot.Comment => qso.Comment,
|
||||
_ => "",
|
||||
};
|
||||
|
||||
/// What the call history file says this station sends in one exchange box,
|
||||
/// or an empty string when the file holds nothing for it.
|
||||
public static string ValueFrom(CallHistoryEntry known, ExchangeField field) => field.Slot switch
|
||||
|
||||
@@ -23,7 +23,7 @@ public static class MessageExpander
|
||||
/// `other` is the radio the operator is not on, for the macros that pass a
|
||||
/// station to the other band. Null when the station has one radio, and then
|
||||
/// those macros stand for nothing.
|
||||
public static string Expand(string template, RadioPosition session, RadioPosition? other = null)
|
||||
public static string Expand(string template, OperatingPosition session, OperatingPosition? other = null)
|
||||
{
|
||||
StringBuilder text = new();
|
||||
int at = 0;
|
||||
@@ -56,7 +56,7 @@ public static class MessageExpander
|
||||
}
|
||||
|
||||
/// The macros that need no braces: `*`, `!` and `#`.
|
||||
public static string TextMacro(char macro, RadioPosition session) => macro switch
|
||||
public static string TextMacro(char macro, OperatingPosition session) => macro switch
|
||||
{
|
||||
'*' => session.Me.Callsign,
|
||||
'!' => TheirCall(session),
|
||||
@@ -66,14 +66,14 @@ public static class MessageExpander
|
||||
/// The number for this contact, or the one just logged when nothing is
|
||||
/// typed in the callsign box, so a repeat after logging sends the same
|
||||
/// number again.
|
||||
private static string SerialNumber(RadioPosition session) =>
|
||||
private static string SerialNumber(OperatingPosition session) =>
|
||||
session.Entry.Call.Trim().Length == 0 && LastLogged(session) is { } last
|
||||
? last.SentNumber.ToString(CultureInfo.InvariantCulture)
|
||||
: session.SentNumber.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
/// What the macro stands for, or null when it is not a text macro — an
|
||||
/// action macro, or a name this program does not know.
|
||||
public static string? TextMacro(string name, RadioPosition session, RadioPosition? other) =>
|
||||
public static string? TextMacro(string name, OperatingPosition session, OperatingPosition? other) =>
|
||||
name.ToUpperInvariant() switch
|
||||
{
|
||||
"MYCALL" => session.Me.Callsign,
|
||||
@@ -110,19 +110,19 @@ public static class MessageExpander
|
||||
|
||||
/// The call being worked, or the last one logged when the box is empty,
|
||||
/// which is what N1MM sends for both `{CALL}` and `!`.
|
||||
private static string TheirCall(RadioPosition session) =>
|
||||
private static string TheirCall(OperatingPosition session) =>
|
||||
session.Entry.Call.Trim() is { Length: > 0 } typed ? typed : LastLogged(session)?.Call.Text ?? "";
|
||||
|
||||
private static Qso? LastLogged(RadioPosition session) =>
|
||||
private static Qso? LastLogged(OperatingPosition session) =>
|
||||
session.Log.Qsos.Count > 0 ? session.Log.Qsos[^1] : null;
|
||||
|
||||
private static string Name(RadioPosition session) =>
|
||||
private static string Name(OperatingPosition session) =>
|
||||
Theirs(session, ExchangeSlot.Name, known => known.Name);
|
||||
|
||||
/// What the operator has typed in that exchange box, or what the call
|
||||
/// history file says when the contest has no such box or it is empty.
|
||||
private static string Theirs(
|
||||
RadioPosition session,
|
||||
OperatingPosition session,
|
||||
ExchangeSlot slot,
|
||||
Func<CallHistoryEntry, string> fromHistory)
|
||||
{
|
||||
@@ -136,7 +136,7 @@ public static class MessageExpander
|
||||
}
|
||||
|
||||
/// Kilohertz to one decimal, and on CW the decimal point is sent as R.
|
||||
private static string FrequencyText(RadioPosition session, Frequency frequency, bool round)
|
||||
private static string FrequencyText(OperatingPosition session, Frequency frequency, bool round)
|
||||
{
|
||||
if (round)
|
||||
{
|
||||
@@ -146,20 +146,20 @@ public static class MessageExpander
|
||||
}
|
||||
|
||||
/// The band in megahertz, as N1MM sends it: 14, or 3R5 on CW.
|
||||
private static string Megahertz(RadioPosition session, Frequency frequency) =>
|
||||
private static string Megahertz(OperatingPosition session, Frequency frequency) =>
|
||||
Bands.ForFrequency(frequency) is { } band
|
||||
? CwDecimal(session, band.MegahertzLabel.ToString("0.###", CultureInfo.InvariantCulture))
|
||||
: "";
|
||||
|
||||
private static Frequency Radio(RadioPosition session, RadioPosition? other, int number) =>
|
||||
private static Frequency Radio(OperatingPosition session, OperatingPosition? other, int number) =>
|
||||
session.RadioNumber == number ? session.Frequency
|
||||
: other?.RadioNumber == number ? other.Frequency
|
||||
: Frequency.Zero;
|
||||
|
||||
private static string CwDecimal(RadioPosition session, string text) =>
|
||||
private static string CwDecimal(OperatingPosition session, string text) =>
|
||||
session.Mode.Category == ModeCategory.Cw ? text.Replace('.', 'R') : text;
|
||||
|
||||
private static string Bearing(RadioPosition session, bool reverse)
|
||||
private static string Bearing(OperatingPosition session, bool reverse)
|
||||
{
|
||||
if (!Grids(session, out GridSquare mine, out GridSquare theirs))
|
||||
{
|
||||
@@ -169,12 +169,12 @@ public static class MessageExpander
|
||||
return Math.Round(bearing).ToString("0", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static string Distance(RadioPosition session) =>
|
||||
private static string Distance(OperatingPosition session) =>
|
||||
Grids(session, out GridSquare mine, out GridSquare theirs)
|
||||
? Math.Round(mine.DistanceTo(theirs)).ToString("0", CultureInfo.InvariantCulture)
|
||||
: "";
|
||||
|
||||
private static bool Grids(RadioPosition session, out GridSquare mine, out GridSquare theirs)
|
||||
private static bool Grids(OperatingPosition session, out GridSquare mine, out GridSquare theirs)
|
||||
{
|
||||
theirs = default;
|
||||
return GridSquare.TryParse(session.Me.GridSquare, out mine)
|
||||
|
||||
@@ -15,7 +15,7 @@ public sealed record MessagePlan(
|
||||
string Text,
|
||||
IReadOnlyList<MessageAction> After)
|
||||
{
|
||||
public static MessagePlan Read(string template, RadioPosition session, RadioPosition? other = null)
|
||||
public static MessagePlan Read(string template, OperatingPosition session, OperatingPosition? other = null)
|
||||
{
|
||||
List<MessageAction> before = [];
|
||||
List<MessageAction> after = [];
|
||||
|
||||
@@ -9,9 +9,9 @@ namespace Nonemm.Session;
|
||||
/// One radio and what the operator is typing on it. A single-radio station has
|
||||
/// one of these; an SO2R station has two, sharing one `ContestSession` and so
|
||||
/// one log, one score and one run of serial numbers.
|
||||
public sealed class RadioPosition
|
||||
public sealed class OperatingPosition
|
||||
{
|
||||
public RadioPosition(ContestSession session, int radioNumber = 1)
|
||||
public OperatingPosition(ContestSession session, int radioNumber = 1)
|
||||
{
|
||||
Session = session;
|
||||
RadioNumber = radioNumber;
|
||||
@@ -170,8 +170,110 @@ public sealed class RadioPosition
|
||||
}
|
||||
}
|
||||
|
||||
/// The logged contact the boxes are re-editing, or null while a new one is
|
||||
/// being typed. N1MM calls this quick edit: Ctrl+Q steps back through the
|
||||
/// log, Ctrl+A forward, Enter writes the changes back and Esc leaves.
|
||||
public Qso? Editing { get; private set; }
|
||||
|
||||
private (string Call, string[] Values, string Name, string Comment)? held;
|
||||
|
||||
/// Steps one contact back or forward and loads it into the boxes. False
|
||||
/// when the log has nothing that way; stepping forward past the newest
|
||||
/// contact leaves quick edit instead.
|
||||
public bool QuickEdit(bool forward)
|
||||
{
|
||||
IReadOnlyList<Qso> qsos = Log.Qsos;
|
||||
if (qsos.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int at = Editing is null ? qsos.Count : IndexOf(Editing.Id);
|
||||
int wanted = forward ? at + 1 : at - 1;
|
||||
if (wanted < 0 || at < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (wanted >= qsos.Count)
|
||||
{
|
||||
LeaveQuickEdit();
|
||||
return true;
|
||||
}
|
||||
held ??= (Entry.Call, [.. Enumerable.Range(1, Entry.FieldCount - 1).Select(f => Entry[f])], OtherName, Comment);
|
||||
Editing = qsos[wanted];
|
||||
LoadFrom(Editing);
|
||||
Session.NotifyChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Puts the boxes back to the contact that was being typed.
|
||||
public void LeaveQuickEdit()
|
||||
{
|
||||
if (Editing is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Editing = null;
|
||||
if (held is { } typed)
|
||||
{
|
||||
Entry.Call = typed.Call;
|
||||
for (int field = 1; field < Entry.FieldCount; field++)
|
||||
{
|
||||
Entry[field] = typed.Values[field - 1];
|
||||
}
|
||||
OtherName = typed.Name;
|
||||
Comment = typed.Comment;
|
||||
held = null;
|
||||
}
|
||||
Session.NotifyChanged();
|
||||
}
|
||||
|
||||
/// Writes the boxes back to the contact being quick edited and leaves quick
|
||||
/// edit. Null when no contact is being edited.
|
||||
public Qso? SaveQuickEdit()
|
||||
{
|
||||
if (Editing is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Qso saved = Noted(ApplyExchange(CountryFields.Apply(
|
||||
Editing with { Call = Callsign.Parse(Entry.Call.Trim()) }, Session.Countries)));
|
||||
Session.Update(saved);
|
||||
LeaveQuickEdit();
|
||||
return saved;
|
||||
}
|
||||
|
||||
/// The contact as it would be logged now, for the dialog that edits the one
|
||||
/// in progress.
|
||||
public Qso InProgress() => Noted(BuildQso(Entry.Call.Trim()));
|
||||
|
||||
/// Fills the boxes from a contact, as quick edit does.
|
||||
public void LoadFrom(Qso qso)
|
||||
{
|
||||
Entry.Call = qso.Call.Text;
|
||||
for (int at = 0; at < Entry.Exchange.Count; at++)
|
||||
{
|
||||
Entry[at + 1] = ExchangeSlots.ValueOf(qso, Entry.Exchange[at].Slot);
|
||||
}
|
||||
OtherName = Entry.Exchange.Any(f => f.Slot == ExchangeSlot.Name) ? "" : qso.Name;
|
||||
Comment = qso.Comment;
|
||||
}
|
||||
|
||||
private int IndexOf(string id)
|
||||
{
|
||||
for (int at = 0; at < Log.Qsos.Count; at++)
|
||||
{
|
||||
if (Log.Qsos[at].Id == id)
|
||||
{
|
||||
return at;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Wipe()
|
||||
{
|
||||
Editing = null;
|
||||
held = null;
|
||||
Entry.Clear();
|
||||
OtherName = "";
|
||||
Comment = "";
|
||||
@@ -24,9 +24,9 @@ public enum QtcDirection
|
||||
/// only rule left is that the two stations are on different continents.
|
||||
public sealed class QtcTraffic
|
||||
{
|
||||
private readonly RadioPosition position;
|
||||
private readonly OperatingPosition position;
|
||||
|
||||
public QtcTraffic(RadioPosition position) => this.position = position;
|
||||
public QtcTraffic(OperatingPosition position) => this.position = position;
|
||||
|
||||
public bool IsWaeContest => position.Contest is Wae;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ public sealed record StationPath(
|
||||
|
||||
/// Null when there is nothing to work out a path from: no station in the
|
||||
/// callsign box, no country file, or no position for either end.
|
||||
public static StationPath? For(RadioPosition session, DateTime nowUtc)
|
||||
public static StationPath? For(OperatingPosition session, DateTime nowUtc)
|
||||
{
|
||||
if (Mine(session) is not { } here || Theirs(session) is not { } there)
|
||||
{
|
||||
@@ -37,10 +37,10 @@ public sealed record StationPath(
|
||||
sun?.Set);
|
||||
}
|
||||
|
||||
private static (double Latitude, double Longitude)? Mine(RadioPosition session) =>
|
||||
private static (double Latitude, double Longitude)? Mine(OperatingPosition session) =>
|
||||
Place(session.Me.GridSquare) ?? Place(session.Session.Countries?.Find(session.Me.Callsign));
|
||||
|
||||
private static (double Latitude, double Longitude)? Theirs(RadioPosition session)
|
||||
private static (double Latitude, double Longitude)? Theirs(OperatingPosition session)
|
||||
{
|
||||
if (session.Entry.Call.Trim().Length == 0)
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@ public class AvailableStationsTests
|
||||
|
||||
private static readonly Frequency On20 = Frequency.FromKilohertz(14_030);
|
||||
|
||||
private static (RadioPosition Position, Bandmap Bandmap, AvailableStations Available) Station(
|
||||
private static (OperatingPosition Position, Bandmap Bandmap, AvailableStations Available) Station(
|
||||
Contest? contest = null,
|
||||
CallHistory? history = null)
|
||||
{
|
||||
@@ -47,7 +47,7 @@ public class AvailableStationsTests
|
||||
Me,
|
||||
CountryFile.Parse(Countries),
|
||||
history);
|
||||
RadioPosition position = new(session);
|
||||
OperatingPosition position = new(session);
|
||||
Bandmap bandmap = new();
|
||||
return (position, bandmap, new AvailableStations(position, bandmap));
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public class AvailableStationsTests
|
||||
private static Spot At(string call, Frequency where) =>
|
||||
new(Callsign.Parse(call), where, DateTime.UtcNow, SpotSource.Cluster, "OK1TEST");
|
||||
|
||||
private static void Work(RadioPosition position, string call, string zone, Frequency where)
|
||||
private static void Work(OperatingPosition position, string call, string zone, Frequency where)
|
||||
{
|
||||
position.Tune(where);
|
||||
position.Entry.Call = call;
|
||||
@@ -96,7 +96,7 @@ public class AvailableStationsTests
|
||||
[Fact]
|
||||
public void AStationAlreadyWorkedOnThatBandIsLeftOut()
|
||||
{
|
||||
(RadioPosition position, Bandmap bandmap, AvailableStations available) = Station();
|
||||
(OperatingPosition position, Bandmap bandmap, AvailableStations available) = Station();
|
||||
Work(position, "JA1XYZ", "25", On20);
|
||||
bandmap.Add(At("JA1XYZ", On20));
|
||||
|
||||
@@ -106,7 +106,7 @@ public class AvailableStationsTests
|
||||
[Fact]
|
||||
public void TheSameStationOnAnotherBandIsStillWorthWorking()
|
||||
{
|
||||
(RadioPosition position, Bandmap bandmap, AvailableStations available) = Station();
|
||||
(OperatingPosition position, Bandmap bandmap, AvailableStations available) = Station();
|
||||
Work(position, "JA1XYZ", "25", On20);
|
||||
bandmap.Add(At("JA1XYZ", Frequency.FromKilohertz(7_030)));
|
||||
|
||||
@@ -118,7 +118,7 @@ public class AvailableStationsTests
|
||||
[Fact]
|
||||
public void AStationThatBringsNoMultiplierIsStillOfferedForItsPoints()
|
||||
{
|
||||
(RadioPosition position, Bandmap bandmap, AvailableStations available) = Station();
|
||||
(OperatingPosition position, Bandmap bandmap, AvailableStations available) = Station();
|
||||
Work(position, "JA1XYZ", "25", On20);
|
||||
bandmap.Add(At("JA2ABC", On20));
|
||||
|
||||
@@ -130,7 +130,7 @@ public class AvailableStationsTests
|
||||
[Fact]
|
||||
public void MultipliersComeFirst()
|
||||
{
|
||||
(RadioPosition position, Bandmap bandmap, AvailableStations available) = Station();
|
||||
(OperatingPosition position, Bandmap bandmap, AvailableStations available) = Station();
|
||||
Work(position, "JA1XYZ", "25", On20);
|
||||
bandmap.Add(At("JA2ABC", Frequency.FromKilohertz(14_010)));
|
||||
bandmap.Add(At("W1AW", Frequency.FromKilohertz(14_040)));
|
||||
|
||||
@@ -28,7 +28,7 @@ public class ContestSessionTests
|
||||
return new ContestSession(store, new CqWpx(ModeCategory.Cw), instance, Me, null);
|
||||
}
|
||||
|
||||
private static void Type(RadioPosition radio, string call, string number)
|
||||
private static void Type(OperatingPosition radio, string call, string number)
|
||||
{
|
||||
radio.Entry.Call = call;
|
||||
radio.Entry.Set(ExchangeSlot.ReceivedReport, "599");
|
||||
@@ -39,8 +39,8 @@ public class ContestSessionTests
|
||||
public void BothRadiosLogIntoTheSameLog()
|
||||
{
|
||||
ContestSession session = Session();
|
||||
RadioPosition first = new(session, 1);
|
||||
RadioPosition second = new(session, 2);
|
||||
OperatingPosition first = new(session, 1);
|
||||
OperatingPosition second = new(session, 2);
|
||||
|
||||
Type(first, "JA1XYZ", "1");
|
||||
first.LogContact();
|
||||
@@ -57,8 +57,8 @@ public class ContestSessionTests
|
||||
public void SerialNumbersCountUpAcrossBothRadios()
|
||||
{
|
||||
ContestSession session = Session();
|
||||
RadioPosition first = new(session, 1);
|
||||
RadioPosition second = new(session, 2);
|
||||
OperatingPosition first = new(session, 1);
|
||||
OperatingPosition second = new(session, 2);
|
||||
|
||||
Type(first, "JA1XYZ", "1");
|
||||
Assert.Equal(1, first.LogContact().SentNumber);
|
||||
@@ -73,8 +73,8 @@ public class ContestSessionTests
|
||||
public void AStationWorkedOnOneRadioIsADupeOnTheOther()
|
||||
{
|
||||
ContestSession session = Session();
|
||||
RadioPosition first = new(session, 1);
|
||||
RadioPosition second = new(session, 2);
|
||||
OperatingPosition first = new(session, 1);
|
||||
OperatingPosition second = new(session, 2);
|
||||
|
||||
Type(first, "JA1XYZ", "1");
|
||||
first.LogContact();
|
||||
@@ -89,8 +89,8 @@ public class ContestSessionTests
|
||||
public void EachRadioKeepsItsOwnFrequencyAndRunState()
|
||||
{
|
||||
ContestSession session = Session();
|
||||
RadioPosition first = new(session, 1);
|
||||
RadioPosition second = new(session, 2);
|
||||
OperatingPosition first = new(session, 1);
|
||||
OperatingPosition second = new(session, 2);
|
||||
|
||||
first.Tune(Frequency.FromKilohertz(14_025));
|
||||
first.IsRunning = true;
|
||||
@@ -106,8 +106,8 @@ public class ContestSessionTests
|
||||
public void WhatIsTypedOnOneRadioStaysThere()
|
||||
{
|
||||
ContestSession session = Session();
|
||||
RadioPosition first = new(session, 1);
|
||||
RadioPosition second = new(session, 2);
|
||||
OperatingPosition first = new(session, 1);
|
||||
OperatingPosition second = new(session, 2);
|
||||
|
||||
Type(first, "JA1XYZ", "1");
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Nonemm.Session.Tests;
|
||||
|
||||
public class MessageExpanderTests
|
||||
{
|
||||
private static RadioPosition Session(CallHistory? history = null, int radioNumber = 1)
|
||||
private static OperatingPosition Session(CallHistory? history = null, int radioNumber = 1)
|
||||
{
|
||||
FakeLogStore store = new();
|
||||
ContestInstance instance = store.AddContest(new ContestInstance
|
||||
@@ -17,7 +17,7 @@ public class MessageExpanderTests
|
||||
ContestName = "CQWW",
|
||||
SentExchange = "14",
|
||||
});
|
||||
return new RadioPosition(
|
||||
return new OperatingPosition(
|
||||
new ContestSession(
|
||||
store,
|
||||
new CqWorldWide(ModeCategory.Cw),
|
||||
@@ -31,7 +31,7 @@ public class MessageExpanderTests
|
||||
[Fact]
|
||||
public void MyCallAndTheirCallAreFilledIn()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
session.Entry.Call = "JA1XYZ";
|
||||
Assert.Equal("JA1XYZ DE DL1ABC", MessageExpander.Expand("{CALL} DE {MYCALL}", session));
|
||||
}
|
||||
@@ -59,7 +59,7 @@ public class MessageExpanderTests
|
||||
[Fact]
|
||||
public void TheSingleCharacterMacrosAreTheirCallMyCallAndTheSerial()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
session.Entry.Call = "JA1XYZ";
|
||||
|
||||
Assert.Equal("JA1XYZ 599 1 DE DL1ABC", MessageExpander.Expand("! 599 # DE *", session));
|
||||
@@ -68,7 +68,7 @@ public class MessageExpanderTests
|
||||
[Fact]
|
||||
public void TheirCallFallsBackToTheLastOneLogged()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
session.Entry.Call = "JA1XYZ";
|
||||
session.Entry.Set(ExchangeSlot.ReceivedReport, "599");
|
||||
session.Entry.Set(ExchangeSlot.Zone, "25");
|
||||
@@ -80,7 +80,7 @@ public class MessageExpanderTests
|
||||
[Fact]
|
||||
public void TheFrequencySendsRForTheDecimalPointOnCw()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
session.Tune(Frequency.FromKilohertz(14_025.5));
|
||||
|
||||
Assert.Equal("14025R5 14026", MessageExpander.Expand("{FREQ} {FREQROUND}", session));
|
||||
@@ -89,8 +89,8 @@ public class MessageExpanderTests
|
||||
[Fact]
|
||||
public void TheOtherRadioFillsInTheOtherMacros()
|
||||
{
|
||||
RadioPosition here = Session();
|
||||
RadioPosition there = Session(radioNumber: 2);
|
||||
OperatingPosition here = Session();
|
||||
OperatingPosition there = Session(radioNumber: 2);
|
||||
there.Tune(Frequency.FromKilohertz(3_525));
|
||||
|
||||
Assert.Equal("3525R0 3R5 80M", MessageExpander.Expand("{OTHERFREQ} {OTHERMHZ} {OTHERBAND}", here, there));
|
||||
@@ -103,7 +103,7 @@ public class MessageExpanderTests
|
||||
[Fact]
|
||||
public void TheNameComesFromTheCallHistoryWhenTheContestHasNoNameBox()
|
||||
{
|
||||
RadioPosition session = Session(CallHistory.Parse("!!Order!!,Call,Name\nJA1XYZ,Ken"));
|
||||
OperatingPosition session = Session(CallHistory.Parse("!!Order!!,Call,Name\nJA1XYZ,Ken"));
|
||||
session.Entry.Call = "JA1XYZ";
|
||||
|
||||
Assert.Equal("TU Ken GL", MessageExpander.Expand("TU {NAMEANDSPACE}GL", session));
|
||||
@@ -112,7 +112,7 @@ public class MessageExpanderTests
|
||||
[Fact]
|
||||
public void MyGridAndTheirsAreDifferentMacros()
|
||||
{
|
||||
RadioPosition session = Session(CallHistory.Parse("!!Order!!,Call,LOC1\nJA1XYZ,PM95"));
|
||||
OperatingPosition session = Session(CallHistory.Parse("!!Order!!,Call,LOC1\nJA1XYZ,PM95"));
|
||||
session.Entry.Call = "JA1XYZ";
|
||||
|
||||
Assert.Equal("JN88 PM95", MessageExpander.Expand("{GRID} {GRIDSQUARE}", session));
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Nonemm.Session.Tests;
|
||||
|
||||
public class MessagePlanTests
|
||||
{
|
||||
private static RadioPosition Session()
|
||||
private static OperatingPosition Session()
|
||||
{
|
||||
FakeLogStore store = new();
|
||||
ContestInstance instance = store.AddContest(new ContestInstance
|
||||
@@ -16,7 +16,7 @@ public class MessagePlanTests
|
||||
ContestName = "CQWW",
|
||||
SentExchange = "14",
|
||||
});
|
||||
return new RadioPosition(new ContestSession(
|
||||
return new OperatingPosition(new ContestSession(
|
||||
store,
|
||||
new CqWorldWide(ModeCategory.Cw),
|
||||
instance,
|
||||
|
||||
@@ -7,7 +7,7 @@ using Nonemm.Storage;
|
||||
|
||||
namespace Nonemm.Session.Tests;
|
||||
|
||||
public class RadioPositionTests
|
||||
public class OperatingPositionTests
|
||||
{
|
||||
private const string Countries = """
|
||||
Germany: 14: 28: EU: 51.00: -10.00: -1.0: DL:
|
||||
@@ -25,7 +25,7 @@ public class RadioPositionTests
|
||||
CountryPrefix = "DL",
|
||||
};
|
||||
|
||||
private static RadioPosition Session(Contest? contest = null) =>
|
||||
private static OperatingPosition Session(Contest? contest = null) =>
|
||||
new(Shared(new FakeLogStore(), contest));
|
||||
|
||||
private static ContestSession Shared(FakeLogStore store, Contest? contest = null)
|
||||
@@ -43,7 +43,7 @@ public class RadioPositionTests
|
||||
CountryFile.Parse(Countries));
|
||||
}
|
||||
|
||||
private static void Type(RadioPosition session, string call, string zone)
|
||||
private static void Type(OperatingPosition session, string call, string zone)
|
||||
{
|
||||
session.Entry.Call = call;
|
||||
session.Entry.Set(ExchangeSlot.ReceivedReport, "599");
|
||||
@@ -53,7 +53,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void LoggingAContactPutsItInTheLogAndClearsTheEntry()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
Type(session, "JA1XYZ", "25");
|
||||
Qso logged = session.LogContact();
|
||||
|
||||
@@ -67,7 +67,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void CountryAndPrefixAreFilledInFromTheCountryFile()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
Type(session, "JA1XYZ", "25");
|
||||
Qso logged = session.LogContact();
|
||||
|
||||
@@ -79,7 +79,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void AnIncompleteExchangeIsNotLogged()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
session.Entry.Call = "JA1XYZ";
|
||||
Assert.Throws<InvalidOperationException>(() => session.LogContact());
|
||||
}
|
||||
@@ -87,7 +87,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void TheSameStationOnTheSameBandReadsAsADupe()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
Type(session, "JA1XYZ", "25");
|
||||
session.LogContact();
|
||||
Type(session, "JA1XYZ", "25");
|
||||
@@ -97,7 +97,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void MovingBandClearsTheDupe()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
Type(session, "JA1XYZ", "25");
|
||||
session.LogContact();
|
||||
session.Tune(Frequency.FromKilohertz(7_025));
|
||||
@@ -108,7 +108,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void SerialNumbersCountUp()
|
||||
{
|
||||
RadioPosition session = Session(new CqWpx(ModeCategory.Cw));
|
||||
OperatingPosition session = Session(new CqWpx(ModeCategory.Cw));
|
||||
Assert.Equal(1, session.SentNumber);
|
||||
session.Entry.Call = "JA1XYZ";
|
||||
session.Entry.Set(ExchangeSlot.ReceivedReport, "599");
|
||||
@@ -121,7 +121,7 @@ public class RadioPositionTests
|
||||
public void SpaceStepsOverTheReportAndComesBackToTheCall()
|
||||
{
|
||||
// CQ WW's boxes are the call, the report and the zone
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
|
||||
Assert.Equal(0, session.Entry.Focus);
|
||||
session.Entry.Advance();
|
||||
@@ -133,7 +133,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void GoingBackStepsOverTheReportToo()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
session.Entry.FocusOn(2);
|
||||
|
||||
session.Entry.Retreat();
|
||||
@@ -144,7 +144,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void TheReportBoxesAreFilledInWithWhatWouldBeSentAnyway()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
|
||||
Assert.True(session.FillReports());
|
||||
|
||||
@@ -155,7 +155,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void AReportTheOperatorTypedIsLeftAlone()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
session.Entry.Set(ExchangeSlot.ReceivedReport, "559");
|
||||
|
||||
Assert.False(session.FillReports());
|
||||
@@ -168,7 +168,7 @@ public class RadioPositionTests
|
||||
[InlineData("7025.5", 7_025_500)]
|
||||
public void AFrequencyTypedIntoTheCallBoxIsRecognised(string typed, long hertz)
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
session.Entry.Call = typed;
|
||||
Assert.Equal(hertz, session.PendingQsy()?.Hertz);
|
||||
}
|
||||
@@ -178,7 +178,7 @@ public class RadioPositionTests
|
||||
[InlineData("12000")]
|
||||
public void ACallOrAnOutOfBandNumberIsNotAQsy(string typed)
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
session.Entry.Call = typed;
|
||||
Assert.Null(session.PendingQsy());
|
||||
}
|
||||
@@ -186,7 +186,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void AContactDeletedFromTheLogGivesItsMultiplierBack()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
Type(session, "JA1XYZ", "25");
|
||||
Qso first = session.LogContact();
|
||||
Type(session, "JA2XYZ", "25");
|
||||
@@ -202,7 +202,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void AContactWorkedSplitRecordsWhereWeTransmitted()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
session.Tune(
|
||||
Frequency.FromKilohertz(14_025),
|
||||
Modes.Cw,
|
||||
@@ -218,7 +218,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void AContactRecordsWhichRadioMadeIt()
|
||||
{
|
||||
RadioPosition session = new(Shared(new FakeLogStore()), 2);
|
||||
OperatingPosition session = new(Shared(new FakeLogStore()), 2);
|
||||
Type(session, "JA1XYZ", "25");
|
||||
|
||||
Assert.Equal(2, session.LogContact().RadioNumber);
|
||||
@@ -229,7 +229,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void TuningWithoutASplitLeavesTheOneAlreadySet()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
session.Tune(Frequency.FromKilohertz(14_025), null, Frequency.FromKilohertz(14_200));
|
||||
|
||||
session.Tune(Frequency.FromKilohertz(14_030));
|
||||
@@ -240,7 +240,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void EditingAContactRescoresTheLog()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
Type(session, "JA1XYZ", "25");
|
||||
Qso logged = session.LogContact();
|
||||
Assert.Equal(3, session.Log.Tally.Points);
|
||||
@@ -255,7 +255,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void ARefusedEditLeavesTheLogAlone()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
Type(session, "JA1XYZ", "25");
|
||||
Qso logged = session.LogContact();
|
||||
|
||||
@@ -270,7 +270,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void AnEditReportsWhatTheContactUsedToBe()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
Type(session, "JA1XYZ", "25");
|
||||
Qso logged = session.LogContact();
|
||||
QsoChange? change = null;
|
||||
@@ -288,7 +288,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void CorrectingTheCountryChangesTheScore()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
Type(session, "JA1XYZ", "25");
|
||||
Qso logged = session.LogContact();
|
||||
Assert.Equal(3, session.Log.Tally.Points);
|
||||
@@ -301,7 +301,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void ADeleteReportsTheContactThatWent()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
Type(session, "JA1XYZ", "25");
|
||||
Qso logged = session.LogContact();
|
||||
Qso? gone = null;
|
||||
@@ -316,11 +316,11 @@ public class RadioPositionTests
|
||||
public void StoredContactsAreReadBackWhenTheSessionStarts()
|
||||
{
|
||||
FakeLogStore store = new();
|
||||
RadioPosition first = new(Shared(store));
|
||||
OperatingPosition first = new(Shared(store));
|
||||
Type(first, "JA1XYZ", "25");
|
||||
first.LogContact();
|
||||
|
||||
RadioPosition second = new(new ContestSession(
|
||||
OperatingPosition second = new(new ContestSession(
|
||||
store,
|
||||
new CqWorldWide(ModeCategory.Cw),
|
||||
first.Instance,
|
||||
@@ -330,7 +330,7 @@ public class RadioPositionTests
|
||||
Assert.Equal(3, second.Log.Tally.Points);
|
||||
}
|
||||
|
||||
private static RadioPosition WithHistory(Contest contest, string file)
|
||||
private static OperatingPosition WithHistory(Contest contest, string file)
|
||||
{
|
||||
FakeLogStore store = new();
|
||||
ContestInstance instance = store.AddContest(new ContestInstance
|
||||
@@ -338,7 +338,7 @@ public class RadioPositionTests
|
||||
ContestNumber = 0,
|
||||
ContestName = contest.Name,
|
||||
});
|
||||
return new RadioPosition(new ContestSession(
|
||||
return new OperatingPosition(new ContestSession(
|
||||
store,
|
||||
contest,
|
||||
instance,
|
||||
@@ -350,7 +350,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void TheCallHistoryFillsTheExchangeIn()
|
||||
{
|
||||
RadioPosition session = WithHistory(
|
||||
OperatingPosition session = WithHistory(
|
||||
new Sweepstakes(ModeCategory.Cw),
|
||||
"!!Order!!,Call,Sect,CK\nW3LPL,MDC,56");
|
||||
session.Entry.Call = "W3LPL";
|
||||
@@ -365,7 +365,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void WhatIsAlreadyTypedIsNotOverwritten()
|
||||
{
|
||||
RadioPosition session = WithHistory(
|
||||
OperatingPosition session = WithHistory(
|
||||
new Sweepstakes(ModeCategory.Cw),
|
||||
"!!Order!!,Call,Sect\nW3LPL,MDC");
|
||||
session.Entry.Call = "W3LPL";
|
||||
@@ -379,7 +379,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void ACallTheHistoryDoesNotHoldFillsNothing()
|
||||
{
|
||||
RadioPosition session = WithHistory(
|
||||
OperatingPosition session = WithHistory(
|
||||
new Sweepstakes(ModeCategory.Cw),
|
||||
"!!Order!!,Call,Sect\nW3LPL,MDC");
|
||||
session.Entry.Call = "JA1XYZ";
|
||||
@@ -392,7 +392,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void TheZoneComesFromTheColumnTheContestAsksFor()
|
||||
{
|
||||
RadioPosition session = WithHistory(
|
||||
OperatingPosition session = WithHistory(
|
||||
new CqWorldWide(ModeCategory.Cw),
|
||||
"!!Order!!,Call,CqZone,ITUZone\nJA1XYZ,25,45");
|
||||
session.Entry.Call = "JA1XYZ";
|
||||
@@ -406,7 +406,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void TheStateFillsASectionWhenTheFileHasNoSection()
|
||||
{
|
||||
RadioPosition session = WithHistory(
|
||||
OperatingPosition session = WithHistory(
|
||||
new Sweepstakes(ModeCategory.Cw),
|
||||
"!!Order!!,Call,Sect,State\nK1TTT,,CT");
|
||||
session.Entry.Call = "K1TTT";
|
||||
@@ -419,7 +419,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void TheNameAndCommentBoxesReachTheLog()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
session.Entry.Call = "JA1XYZ";
|
||||
session.Entry.Set(ExchangeSlot.ReceivedReport, "599");
|
||||
session.Entry.Set(ExchangeSlot.Zone, "25");
|
||||
@@ -437,7 +437,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void WipingClearsTheNameAndTheComment()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
session.OtherName = "Ken";
|
||||
session.Comment = "up 2";
|
||||
|
||||
@@ -450,7 +450,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void CqWwRttyStepsOverTheStateBoxForAStationOutsideTheUsAndCanada()
|
||||
{
|
||||
RadioPosition session = Session(new CqWorldWide(ModeCategory.Digital));
|
||||
OperatingPosition session = Session(new CqWorldWide(ModeCategory.Digital));
|
||||
session.Entry.Call = "JA1XYZ";
|
||||
|
||||
// the boxes are the call, the report, the zone and the state
|
||||
@@ -463,7 +463,7 @@ public class RadioPositionTests
|
||||
[Fact]
|
||||
public void CqWwRttyStopsOnTheStateBoxForAUsStation()
|
||||
{
|
||||
RadioPosition session = Session(new CqWorldWide(ModeCategory.Digital));
|
||||
OperatingPosition session = Session(new CqWorldWide(ModeCategory.Digital));
|
||||
session.Entry.Call = "K1ABC";
|
||||
|
||||
session.MoveFocus(forward: true);
|
||||
@@ -471,4 +471,72 @@ public class RadioPositionTests
|
||||
session.MoveFocus(forward: true);
|
||||
Assert.Equal(3, session.Entry.Focus);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QuickEditLoadsTheLastContactAndSavesTheChangeBack()
|
||||
{
|
||||
OperatingPosition session = Session();
|
||||
Type(session, "JA1XYZ", "25");
|
||||
Qso logged = session.LogContact();
|
||||
Type(session, "DL9ZZZ", "14");
|
||||
|
||||
Assert.True(session.QuickEdit(forward: false));
|
||||
Assert.Equal(logged.Id, session.Editing?.Id);
|
||||
Assert.Equal("JA1XYZ", session.Entry.Call);
|
||||
Assert.Equal("25", session.Entry.ValueOf(ExchangeSlot.Zone));
|
||||
|
||||
session.Entry.Set(ExchangeSlot.Zone, "24");
|
||||
Qso? saved = session.SaveQuickEdit();
|
||||
|
||||
Assert.Equal(24, saved?.Zone);
|
||||
Assert.Equal(24, session.Log.Qsos[0].Zone);
|
||||
Assert.Null(session.Editing);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LeavingQuickEditPutsBackWhatWasBeingTyped()
|
||||
{
|
||||
OperatingPosition session = Session();
|
||||
Type(session, "JA1XYZ", "25");
|
||||
session.LogContact();
|
||||
Type(session, "DL9ZZZ", "14");
|
||||
|
||||
session.QuickEdit(forward: false);
|
||||
session.LeaveQuickEdit();
|
||||
|
||||
Assert.Null(session.Editing);
|
||||
Assert.Equal("DL9ZZZ", session.Entry.Call);
|
||||
Assert.Equal("14", session.Entry.ValueOf(ExchangeSlot.Zone));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QuickEditForwardPastTheNewestContactLeavesQuickEdit()
|
||||
{
|
||||
OperatingPosition session = Session();
|
||||
Type(session, "JA1XYZ", "25");
|
||||
session.LogContact();
|
||||
Type(session, "DL9ZZZ", "14");
|
||||
|
||||
session.QuickEdit(forward: false);
|
||||
Assert.True(session.QuickEdit(forward: true));
|
||||
|
||||
Assert.Null(session.Editing);
|
||||
Assert.Equal("DL9ZZZ", session.Entry.Call);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QuickEditStepsBackThroughTheLog()
|
||||
{
|
||||
OperatingPosition session = Session();
|
||||
Type(session, "JA1XYZ", "25");
|
||||
session.LogContact();
|
||||
Type(session, "DL9ZZZ", "14");
|
||||
session.LogContact();
|
||||
|
||||
session.QuickEdit(forward: false);
|
||||
Assert.Equal("DL9ZZZ", session.Entry.Call);
|
||||
session.QuickEdit(forward: false);
|
||||
Assert.Equal("JA1XYZ", session.Entry.Call);
|
||||
Assert.False(session.QuickEdit(forward: false));
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ public class QtcTrafficTests
|
||||
CountryPrefix = "K",
|
||||
};
|
||||
|
||||
private static (RadioPosition Position, QtcTraffic Traffic) Station(
|
||||
private static (OperatingPosition Position, QtcTraffic Traffic) Station(
|
||||
ModeCategory mode,
|
||||
StationInfo? me = null)
|
||||
{
|
||||
@@ -49,12 +49,12 @@ public class QtcTrafficTests
|
||||
});
|
||||
ContestSession session = new(
|
||||
store, new Wae(mode), instance, me ?? Slovakia, CountryFile.Parse(Countries));
|
||||
RadioPosition position = new(session);
|
||||
OperatingPosition position = new(session);
|
||||
return (position, new QtcTraffic(position));
|
||||
}
|
||||
|
||||
private static Qso Contact(
|
||||
RadioPosition position,
|
||||
OperatingPosition position,
|
||||
string call,
|
||||
int receivedNumber,
|
||||
int sentNumber,
|
||||
@@ -111,7 +111,7 @@ public class QtcTrafficTests
|
||||
[Fact]
|
||||
public void TenLinesIsAllAnyStationGets()
|
||||
{
|
||||
(RadioPosition position, QtcTraffic traffic) = Station(ModeCategory.Digital);
|
||||
(OperatingPosition position, QtcTraffic traffic) = Station(ModeCategory.Digital);
|
||||
for (int at = 1; at <= 10; at++)
|
||||
{
|
||||
position.Session.Add(new WaeQtc(
|
||||
@@ -127,7 +127,7 @@ public class QtcTrafficTests
|
||||
[Fact]
|
||||
public void WhatIsSentIsTheContactsNotReportedYetOldestFirst()
|
||||
{
|
||||
(RadioPosition position, QtcTraffic traffic) = Station(ModeCategory.Digital);
|
||||
(OperatingPosition position, QtcTraffic traffic) = Station(ModeCategory.Digital);
|
||||
Contact(position, "DL1AAA", receivedNumber: 16, sentNumber: 1, minute: 46);
|
||||
Contact(position, "DL2BBB", receivedNumber: 40, sentNumber: 2, minute: 47);
|
||||
Contact(position, "JA1XYZ", receivedNumber: 7, sentNumber: 3, minute: 48);
|
||||
@@ -143,7 +143,7 @@ public class QtcTrafficTests
|
||||
[Fact]
|
||||
public void AContactAlreadyReportedIsNotReportedAgain()
|
||||
{
|
||||
(RadioPosition position, QtcTraffic traffic) = Station(ModeCategory.Digital);
|
||||
(OperatingPosition position, QtcTraffic traffic) = Station(ModeCategory.Digital);
|
||||
Contact(position, "DL1AAA", receivedNumber: 16, sentNumber: 1, minute: 46);
|
||||
Contact(position, "DL2BBB", receivedNumber: 40, sentNumber: 2, minute: 47);
|
||||
WaeQtc first = traffic.ToSend(Callsign.Parse("JA1XYZ"))[0];
|
||||
@@ -157,7 +157,7 @@ public class QtcTrafficTests
|
||||
[Fact]
|
||||
public void SeriesAreNumberedAcrossTheContest()
|
||||
{
|
||||
(RadioPosition position, QtcTraffic traffic) = Station(ModeCategory.Digital);
|
||||
(OperatingPosition position, QtcTraffic traffic) = Station(ModeCategory.Digital);
|
||||
Assert.Equal(1, traffic.NextSeries());
|
||||
|
||||
position.Session.Add(new WaeQtc(
|
||||
|
||||
@@ -20,7 +20,7 @@ public class StationPathTests
|
||||
JA;
|
||||
""");
|
||||
|
||||
private static RadioPosition Session(string myGrid = "", CallHistory? history = null)
|
||||
private static OperatingPosition Session(string myGrid = "", CallHistory? history = null)
|
||||
{
|
||||
FakeLogStore store = new();
|
||||
ContestInstance instance = store.AddContest(new ContestInstance
|
||||
@@ -29,7 +29,7 @@ public class StationPathTests
|
||||
ContestName = "CQWW",
|
||||
SentExchange = "15",
|
||||
});
|
||||
return new RadioPosition(new ContestSession(
|
||||
return new OperatingPosition(new ContestSession(
|
||||
store,
|
||||
new CqWorldWide(ModeCategory.Cw),
|
||||
instance,
|
||||
@@ -45,7 +45,7 @@ public class StationPathTests
|
||||
[Fact]
|
||||
public void TheCountryFilePlacesAStationWhoseGridIsNotKnown()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
session.Entry.Call = "JA1XYZ";
|
||||
|
||||
StationPath path = Assert.IsType<StationPath>(StationPath.For(session, Now));
|
||||
@@ -59,11 +59,11 @@ public class StationPathTests
|
||||
[Fact]
|
||||
public void AGridFromTheCallHistoryBeatsTheCountryFile()
|
||||
{
|
||||
RadioPosition known = Session(
|
||||
OperatingPosition known = Session(
|
||||
myGrid: "JN88",
|
||||
history: CallHistory.Parse("!!Order!!,Call,LOC1\nJA1XYZ,PM95"));
|
||||
known.Entry.Call = "JA1XYZ";
|
||||
RadioPosition unknown = Session(myGrid: "JN88");
|
||||
OperatingPosition unknown = Session(myGrid: "JN88");
|
||||
unknown.Entry.Call = "JA1XYZ";
|
||||
|
||||
StationPath fromGrid = Assert.IsType<StationPath>(StationPath.For(known, Now));
|
||||
@@ -76,7 +76,7 @@ public class StationPathTests
|
||||
[Fact]
|
||||
public void TheSunTimesAreTheOtherStationsOwn()
|
||||
{
|
||||
RadioPosition session = Session();
|
||||
OperatingPosition session = Session();
|
||||
session.Entry.Call = "JA1XYZ";
|
||||
|
||||
StationPath path = Assert.IsType<StationPath>(StationPath.For(session, Now));
|
||||
|
||||
Reference in New Issue
Block a user