Add the Broadcast Data dialog and send N1MM's score breakdown
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
using Avalonia.Threading;
|
||||
using Nonemm.Contests;
|
||||
using Nonemm.Core;
|
||||
using Nonemm.Network;
|
||||
using Nonemm.Rig;
|
||||
using Nonemm.Session;
|
||||
using Nonemm.Spotting;
|
||||
using Nonemm.Storage;
|
||||
|
||||
namespace Nonemm.App;
|
||||
|
||||
/// The data N1MM sends to other programs besides the contacts: what the
|
||||
/// Broadcast Data tab turns on.
|
||||
public sealed partial class AppSession
|
||||
{
|
||||
/// N1MM sends the score from the timer of the first entry window, every ten
|
||||
/// seconds.
|
||||
private static readonly TimeSpan ScoreInterval = TimeSpan.FromSeconds(10);
|
||||
|
||||
private DataBroadcaster? data;
|
||||
private DispatcherTimer? broadcasting;
|
||||
private readonly Dictionary<int, RadioReport> radiosSent = [];
|
||||
private DateTime scoreSent;
|
||||
private string lookupSent = "";
|
||||
|
||||
/// Starts, restarts or stops the broadcasts, following what the settings
|
||||
/// now say. Rows whose addresses cannot be read are reported and left off.
|
||||
public void ApplyBroadcastSettings()
|
||||
{
|
||||
broadcasting?.Stop();
|
||||
data?.Dispose();
|
||||
data = new DataBroadcaster(Settings.BroadcastChannels());
|
||||
data.Failed += (_, problem) => Reported?.Invoke(this, problem);
|
||||
foreach (string problem in data.Problems)
|
||||
{
|
||||
Reported?.Invoke(this, problem);
|
||||
}
|
||||
radiosSent.Clear();
|
||||
scoreSent = DateTime.UtcNow;
|
||||
lookupSent = "";
|
||||
SendAppInfo();
|
||||
broadcasting = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
|
||||
broadcasting.Tick += (_, _) =>
|
||||
{
|
||||
SendRadios();
|
||||
SendScore();
|
||||
};
|
||||
broadcasting.Start();
|
||||
}
|
||||
|
||||
/// Which database and contest this station has open. It goes out when a
|
||||
/// contest is opened.
|
||||
private void SendAppInfo()
|
||||
{
|
||||
if (data is null || !data.IsSending(BroadcastKind.AppInfo) || Logging is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
data.Send(
|
||||
BroadcastKind.AppInfo,
|
||||
BroadcastMessages.AppInfo(
|
||||
Settings.DatabasePath,
|
||||
Logging.Instance.ContestNumber,
|
||||
Logging.Contest.Name,
|
||||
ComputerName,
|
||||
Settings.Station.Callsign));
|
||||
}
|
||||
|
||||
/// Each radio, when something about it has changed since the last time.
|
||||
private void SendRadios()
|
||||
{
|
||||
if (data is null || !data.IsSending(BroadcastKind.Radio))
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (OperatingPosition position in positions)
|
||||
{
|
||||
Radio? radio = radios.FirstOrDefault(r => r.Number == position.RadioNumber);
|
||||
long transmit = position.TransmitFrequency.Hertz > 0
|
||||
? position.TransmitFrequency.Hertz
|
||||
: position.Frequency.Hertz;
|
||||
RadioReport report = new(
|
||||
position.RadioNumber,
|
||||
position.Frequency.Hertz,
|
||||
transmit,
|
||||
position.Mode.Name,
|
||||
Settings.Station.Callsign,
|
||||
Settings.CurrentOperator,
|
||||
position.IsRunning,
|
||||
ActiveRadioNumber,
|
||||
position.TransmitFrequency.Hertz > 0,
|
||||
ActiveRadioNumber,
|
||||
false,
|
||||
"",
|
||||
radio?.IsConnected ?? false);
|
||||
if (!radiosSent.TryGetValue(position.RadioNumber, out RadioReport? last) || last != report)
|
||||
{
|
||||
radiosSent[position.RadioNumber] = report;
|
||||
data.Send(BroadcastKind.Radio, BroadcastMessages.Radio(report, ComputerName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SendSpots(IEnumerable<Spot> spots)
|
||||
{
|
||||
if (data is null || !data.IsSending(BroadcastKind.Spots))
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (Spot spot in spots.Where(s => s.IsStation))
|
||||
{
|
||||
data.Send(
|
||||
BroadcastKind.Spots,
|
||||
BroadcastMessages.Spot(
|
||||
new SpotReport(
|
||||
spot.Call.Text,
|
||||
spot.Frequency.Hertz,
|
||||
spot.Spotter,
|
||||
spot.AtUtc,
|
||||
SpotMode(spot.Frequency),
|
||||
spot.Comment,
|
||||
SpotStatus(spot)),
|
||||
"add",
|
||||
ComputerName));
|
||||
}
|
||||
}
|
||||
|
||||
/// The colour the bandmap would give the station, as N1MM names it.
|
||||
private string SpotStatus(Spot spot) => JudgeSpot(spot) switch
|
||||
{
|
||||
{ IsDupe: true } => "dupe",
|
||||
{ NewMultipliers.Count: 1 } => "single mult",
|
||||
{ NewMultipliers.Count: > 1 } => "double mult",
|
||||
_ => "new qso",
|
||||
};
|
||||
|
||||
private string SpotMode(Frequency frequency) => BandPlan.ModeAt(frequency) switch
|
||||
{
|
||||
ModeCategory.Phone => frequency.Megahertz < 10 ? "LSB" : "USB",
|
||||
ModeCategory.Digital => "RTTY",
|
||||
_ => "CW",
|
||||
};
|
||||
|
||||
private void SendScore()
|
||||
{
|
||||
if (data is null
|
||||
|| !data.IsSending(BroadcastKind.Score)
|
||||
|| Logging is null
|
||||
|| DateTime.UtcNow - scoreSent < ScoreInterval)
|
||||
{
|
||||
return;
|
||||
}
|
||||
scoreSent = DateTime.UtcNow;
|
||||
ContestInstance instance = Logging.Instance;
|
||||
StationInfo me = Settings.Station.ToStationInfo();
|
||||
IReadOnlyList<string> multiplierNames = N1mmMultiplierNames.For(Logging.Contest, me);
|
||||
List<ScoreReportRow> rows = ScoreBreakdown.For(Logging.Log.Qsos, Logging.Contest)
|
||||
.Select(r => new ScoreReportRow(r.Band, r.Mode, r.Qsos, r.Multipliers, r.Points))
|
||||
.ToList();
|
||||
data.Send(
|
||||
BroadcastKind.Score,
|
||||
BroadcastMessages.Score(new ScoreReport(
|
||||
ScoreContestName(instance),
|
||||
Settings.Station.Callsign,
|
||||
instance.Operators,
|
||||
instance.PowerCategory,
|
||||
instance.AssistedCategory,
|
||||
instance.TransmitterCategory,
|
||||
instance.OperatorCategory,
|
||||
instance.BandCategory,
|
||||
instance.ModeCategory,
|
||||
instance.OverlayCategory,
|
||||
instance.StationCategory,
|
||||
Settings.Station.Club,
|
||||
Settings.Station.CountryPrefix,
|
||||
Settings.Station.CqZone,
|
||||
Settings.Station.ItuZone,
|
||||
Settings.Station.ArrlSection,
|
||||
Settings.Station.State.Length > 0 ? Settings.Station.State : Settings.Station.Province,
|
||||
Settings.Station.GridSquare,
|
||||
multiplierNames,
|
||||
rows,
|
||||
Logging.Log.TotalScore,
|
||||
DateTime.UtcNow)));
|
||||
}
|
||||
|
||||
/// The state QSO parties carry the state in front of the Cabrillo name.
|
||||
private string ScoreContestName(ContestInstance instance) =>
|
||||
Logging!.Contest.Name == "QSOPARTY"
|
||||
? $"{instance.SubType}-{Logging.Contest.CabrilloName}"
|
||||
: Logging.Contest.CabrilloName;
|
||||
|
||||
/// The callsign being typed, for a program that looks it up. It goes out
|
||||
/// when the operator leaves the callsign box with a call that has not been
|
||||
/// sent yet.
|
||||
public void SendLookup(OperatingPosition position)
|
||||
{
|
||||
string call = position.Entry.Call.Trim();
|
||||
if (data is null || !data.IsSending(BroadcastKind.Lookup) || call.Length == 0 || call == lookupSent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lookupSent = call;
|
||||
data.Send(
|
||||
BroadcastKind.Lookup,
|
||||
ContactMessage.WriteLookup(position.InProgress(), Settings.Station.Callsign, ComputerName));
|
||||
}
|
||||
|
||||
/// A contact another logging computer made, passed on to the programs that
|
||||
/// read the contact broadcast. Edits and deletes are not passed on.
|
||||
private void ForwardToPrograms(ContactUpdate update)
|
||||
{
|
||||
if (Settings.BroadcastAllComputers && update is ContactLogged logged)
|
||||
{
|
||||
_ = network?.SendForwardedAsync(logged.Qso, Settings.Station.Callsign);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ namespace Nonemm.App;
|
||||
/// Everything the running program owns: the log store, the contest in progress,
|
||||
/// the bandmap, the radio and the cluster. The windows read from this and one
|
||||
/// `Changed` event tells them all to redraw.
|
||||
public sealed class AppSession : IDisposable
|
||||
public sealed partial class AppSession : IDisposable
|
||||
{
|
||||
/// How often this station tells the others how it is doing. N1MM sends its
|
||||
/// status on the same sort of interval; every second would read the log
|
||||
@@ -350,6 +350,7 @@ public sealed class AppSession : IDisposable
|
||||
Available = new AvailableStations(positions[0], Bandmap);
|
||||
Save(Settings with { ContestNumber = contestNumber });
|
||||
ContestChanged?.Invoke(this, EventArgs.Empty);
|
||||
SendAppInfo();
|
||||
}
|
||||
|
||||
/// A frequency on the band a number was taken on. The message carries a
|
||||
@@ -412,6 +413,7 @@ public sealed class AppSession : IDisposable
|
||||
// the socket thread must not touch the log: every other change to it is
|
||||
// made where the windows read it
|
||||
link.UpdateArrived += (_, update) => QueueFromNetwork(update);
|
||||
link.UpdateArrived += (_, update) => ForwardToPrograms(update);
|
||||
// the resync reads the log rather than being handed it, and another
|
||||
// station can ask for a rescore when its resync is done
|
||||
link.Log = new SessionContactLog(this);
|
||||
@@ -967,13 +969,17 @@ public sealed class AppSession : IDisposable
|
||||
if (keeping.Count > 0)
|
||||
{
|
||||
Bandmap.AddRange(keeping);
|
||||
SendSpots(keeping);
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the station has already been worked, which decides whether a
|
||||
/// spot is moved about: N1MM leaves a dupe where it is.
|
||||
private bool IsDupe(Spot spot) =>
|
||||
Logging is not null && Logging.Log.Judge(new Qso
|
||||
private bool IsDupe(Spot spot) => JudgeSpot(spot) is { IsDupe: true };
|
||||
|
||||
/// What logging the station of a spot would do, or null with no contest open.
|
||||
private Verdict? JudgeSpot(Spot spot) =>
|
||||
Logging?.Log.Judge(new Qso
|
||||
{
|
||||
Id = "",
|
||||
TimestampUtc = spot.AtUtc,
|
||||
@@ -981,7 +987,7 @@ public sealed class AppSession : IDisposable
|
||||
Frequency = spot.Frequency,
|
||||
Mode = Position?.Mode ?? Modes.Cw,
|
||||
ContestName = Logging.Contest.Name,
|
||||
}) is { IsDupe: true };
|
||||
});
|
||||
|
||||
private void ApplySpotSettings()
|
||||
{
|
||||
@@ -1010,6 +1016,8 @@ public sealed class AppSession : IDisposable
|
||||
DisposeRadios();
|
||||
cluster?.Dispose();
|
||||
announcing?.Stop();
|
||||
broadcasting?.Stop();
|
||||
data?.Dispose();
|
||||
network?.Dispose();
|
||||
link?.Dispose();
|
||||
alternating?.Dispose();
|
||||
|
||||
@@ -130,6 +130,35 @@ public sealed record Settings
|
||||
|
||||
public IReadOnlyList<string> NetworkPeers { get; init; } = [];
|
||||
|
||||
/// Also send the contacts the other logging computers make to the programs
|
||||
/// that read the contact broadcast. N1MM's All Computers.
|
||||
public bool BroadcastAllComputers { get; init; }
|
||||
|
||||
/// The other rows of Config > Broadcast Data. The contacts row is
|
||||
/// `NetworkEnabled` and `NetworkPeers`.
|
||||
public BroadcastChannel BroadcastAppInfo { get; init; } = new();
|
||||
|
||||
public BroadcastChannel BroadcastRadio { get; init; } = new();
|
||||
|
||||
public BroadcastChannel BroadcastSpots { get; init; } = new();
|
||||
|
||||
public BroadcastChannel BroadcastRotor { get; init; } = new();
|
||||
|
||||
public BroadcastChannel BroadcastScore { get; init; } = new();
|
||||
|
||||
public BroadcastChannel BroadcastLookup { get; init; } = new();
|
||||
|
||||
public IReadOnlyDictionary<BroadcastKind, BroadcastChannel> BroadcastChannels() =>
|
||||
new Dictionary<BroadcastKind, BroadcastChannel>
|
||||
{
|
||||
[BroadcastKind.AppInfo] = BroadcastAppInfo,
|
||||
[BroadcastKind.Radio] = BroadcastRadio,
|
||||
[BroadcastKind.Spots] = BroadcastSpots,
|
||||
[BroadcastKind.Rotor] = BroadcastRotor,
|
||||
[BroadcastKind.Score] = BroadcastScore,
|
||||
[BroadcastKind.Lookup] = BroadcastLookup,
|
||||
};
|
||||
|
||||
/// The link to the other logging computers of a multi-operator entry, on
|
||||
/// N1MM's port 12070. It is not the same thing as `NetworkEnabled`, which
|
||||
/// is the XML broadcast on 12060 that other programs read; a station can
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="Nonemm.App.Dialogs.BroadcastDataDialog"
|
||||
Title="Broadcast Data" Width="680" SizeToContent="Height"
|
||||
WindowStartupLocation="CenterOwner" CanResize="False">
|
||||
<StackPanel Margin="14" Spacing="6">
|
||||
<TextBlock TextWrapping="Wrap" FontSize="11" Opacity="0.75"
|
||||
Text="Select the type of data you wish to broadcast, and the IP address(es) and port(s) for the receiver(s) of the data. Use 127.0.0.1 for the local machine. Use 12060 as the port unless the receiving application requires a different port. 255 in the low order octet will broadcast to your current subnet." />
|
||||
<TextBlock Text="Type of data IP Addr:Port IP Addr:Port..."
|
||||
FontSize="11" Opacity="0.7" Margin="0,6,0,0" />
|
||||
<Grid ColumnDefinitions="150,8,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto" RowSpacing="6">
|
||||
<CheckBox Name="AppInfoCheck" Content="Application Info" />
|
||||
<TextBox Name="AppInfoBox" Grid.Column="2" />
|
||||
<CheckBox Name="RadioCheck" Grid.Row="1" Content="Radio" />
|
||||
<TextBox Name="RadioBox" Grid.Row="1" Grid.Column="2" />
|
||||
<CheckBox Name="ContactsCheck" Grid.Row="2" Content="Contacts" />
|
||||
<TextBox Name="ContactsBox" Grid.Row="2" Grid.Column="2"
|
||||
PlaceholderText="empty: broadcast to the whole network" />
|
||||
<CheckBox Name="AllComputersCheck" Grid.Row="3" Content="All Computers" Margin="20,0,0,0" />
|
||||
<CheckBox Name="SpotsCheck" Grid.Row="4" Content="Spots" />
|
||||
<TextBox Name="SpotsBox" Grid.Row="4" Grid.Column="2" />
|
||||
<CheckBox Name="RotorCheck" Grid.Row="5" Content="Rotor" />
|
||||
<TextBox Name="RotorBox" Grid.Row="5" Grid.Column="2" />
|
||||
<CheckBox Name="ScoreCheck" Grid.Row="6" Content="Score" />
|
||||
<TextBox Name="ScoreBox" Grid.Row="6" Grid.Column="2" />
|
||||
<CheckBox Name="LookupCheck" Grid.Row="7" Content="External Callsign Lookup" />
|
||||
<TextBox Name="LookupBox" Grid.Row="7" Grid.Column="2" />
|
||||
</Grid>
|
||||
<TextBlock Name="ProblemText" TextWrapping="Wrap" Foreground="OrangeRed" IsVisible="False" />
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="6" Margin="0,8,0,0">
|
||||
<Button Content="Cancel" Click="OnCancel" />
|
||||
<Button Content="Save" Click="OnSave" IsDefault="True" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Window>
|
||||
@@ -0,0 +1,82 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Nonemm.App.Configuration;
|
||||
using Nonemm.Network;
|
||||
|
||||
namespace Nonemm.App.Dialogs;
|
||||
|
||||
/// N1MM's Broadcast Data tab: what is sent to other programs, and where.
|
||||
public sealed partial class BroadcastDataDialog : Window
|
||||
{
|
||||
private readonly Settings settings;
|
||||
|
||||
public BroadcastDataDialog(Settings settings)
|
||||
{
|
||||
this.settings = settings;
|
||||
InitializeComponent();
|
||||
Fill(AppInfoCheck, AppInfoBox, BroadcastKind.AppInfo, settings.BroadcastAppInfo);
|
||||
Fill(RadioCheck, RadioBox, BroadcastKind.Radio, settings.BroadcastRadio);
|
||||
Fill(SpotsCheck, SpotsBox, BroadcastKind.Spots, settings.BroadcastSpots);
|
||||
Fill(RotorCheck, RotorBox, BroadcastKind.Rotor, settings.BroadcastRotor);
|
||||
Fill(ScoreCheck, ScoreBox, BroadcastKind.Score, settings.BroadcastScore);
|
||||
Fill(LookupCheck, LookupBox, BroadcastKind.Lookup, settings.BroadcastLookup);
|
||||
ContactsCheck.IsChecked = settings.NetworkEnabled;
|
||||
ContactsBox.Text = string.Join(' ', settings.NetworkPeers);
|
||||
AllComputersCheck.IsChecked = settings.BroadcastAllComputers;
|
||||
}
|
||||
|
||||
/// An empty list shows the default, as N1MM does.
|
||||
private static void Fill(CheckBox check, TextBox box, BroadcastKind kind, BroadcastChannel channel)
|
||||
{
|
||||
check.IsChecked = channel.IsEnabled;
|
||||
box.Text = channel.Addresses.Count > 0
|
||||
? string.Join(' ', channel.Addresses)
|
||||
: BroadcastTargets.DefaultFor(kind).ToString();
|
||||
}
|
||||
|
||||
private void OnSave(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
List<string> problems = [];
|
||||
BroadcastChannel Read(string name, CheckBox check, TextBox box, int defaultPort)
|
||||
{
|
||||
string text = (box.Text ?? "").Trim();
|
||||
if (BroadcastTargets.Parse(text, defaultPort, out string problem) is null)
|
||||
{
|
||||
problems.Add($"{name}: {problem}");
|
||||
}
|
||||
return new BroadcastChannel
|
||||
{
|
||||
IsEnabled = check.IsChecked == true,
|
||||
Addresses = text.Split([' ', ',', ';', '\t', '\r', '\n'], StringSplitOptions.RemoveEmptyEntries),
|
||||
};
|
||||
}
|
||||
|
||||
BroadcastChannel appInfo = Read("Application Info", AppInfoCheck, AppInfoBox, BroadcastTargets.DefaultPort);
|
||||
BroadcastChannel radio = Read("Radio", RadioCheck, RadioBox, BroadcastTargets.DefaultPort);
|
||||
BroadcastChannel contacts = Read("Contacts", ContactsCheck, ContactsBox, settings.NetworkPort);
|
||||
BroadcastChannel spots = Read("Spots", SpotsCheck, SpotsBox, BroadcastTargets.DefaultPort);
|
||||
BroadcastChannel rotor = Read("Rotor", RotorCheck, RotorBox, BroadcastTargets.RotorPort);
|
||||
BroadcastChannel score = Read("Score", ScoreCheck, ScoreBox, BroadcastTargets.ScorePort);
|
||||
BroadcastChannel lookup = Read("External Callsign Lookup", LookupCheck, LookupBox, BroadcastTargets.DefaultPort);
|
||||
if (problems.Count > 0)
|
||||
{
|
||||
ProblemText.Text = string.Join("\n", problems);
|
||||
ProblemText.IsVisible = true;
|
||||
return;
|
||||
}
|
||||
Close(settings with
|
||||
{
|
||||
BroadcastAppInfo = appInfo,
|
||||
BroadcastRadio = radio,
|
||||
NetworkEnabled = contacts.IsEnabled,
|
||||
NetworkPeers = contacts.Addresses,
|
||||
BroadcastAllComputers = AllComputersCheck.IsChecked == true,
|
||||
BroadcastSpots = spots,
|
||||
BroadcastRotor = rotor,
|
||||
BroadcastScore = score,
|
||||
BroadcastLookup = lookup,
|
||||
});
|
||||
}
|
||||
|
||||
private void OnCancel(object? sender, RoutedEventArgs e) => Close(null);
|
||||
}
|
||||
@@ -5,16 +5,13 @@
|
||||
WindowStartupLocation="CenterOwner" CanResize="False">
|
||||
<StackPanel Margin="14" Spacing="6">
|
||||
<TextBlock TextWrapping="Wrap" FontSize="11" Opacity="0.75"
|
||||
Text="Each contact is sent to the other stations of the entry as it is logged, in N1MM's contact message, so an N1MM station on the same network sees them too. With no addresses listed the contacts are broadcast." />
|
||||
Text="This computer's name on the network and the port it listens on for contacts. Where contacts are sent is set in Config ▸ Configure Broadcast Data." />
|
||||
<Grid ColumnDefinitions="*,8,110" RowDefinitions="Auto,Auto">
|
||||
<TextBlock Text="This station's name" FontSize="11" Opacity="0.7" Margin="0,6,0,1" />
|
||||
<TextBox Name="NameBox" Grid.Row="1" />
|
||||
<TextBlock Grid.Column="2" Text="Port" FontSize="11" Opacity="0.7" Margin="0,6,0,1" />
|
||||
<TextBox Name="PortBox" Grid.Row="1" Grid.Column="2" />
|
||||
</Grid>
|
||||
<TextBlock Text="Other stations, one address per line" FontSize="11" Opacity="0.7" Margin="0,6,0,1" />
|
||||
<TextBox Name="PeersBox" AcceptsReturn="True" Height="90" PlaceholderText="192.168.1.11" />
|
||||
<CheckBox Name="EnabledBox" Content="Broadcast contacts to other programs (port 12060)" Margin="0,6,0,0" />
|
||||
<TextBlock Text="The other logging computers" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<TextBlock TextWrapping="Wrap" FontSize="11" Opacity="0.75"
|
||||
Text="This is N1MM's own link between the computers of one entry, on port 12070: contacts, edits, deletes and chat, over a connection to each station. Stations are found by broadcast, so nothing has to be listed." />
|
||||
|
||||
@@ -16,8 +16,6 @@ public sealed partial class NetworkDialog : Window
|
||||
? settings.NetworkStationName
|
||||
: Environment.MachineName;
|
||||
PortBox.Text = settings.NetworkPort.ToString();
|
||||
PeersBox.Text = string.Join("\n", settings.NetworkPeers);
|
||||
EnabledBox.IsChecked = settings.NetworkEnabled;
|
||||
StationNumberBox.Text = settings.StationNumber.ToString();
|
||||
LinkPortBox.Text = settings.StationLinkPort.ToString();
|
||||
VersionBox.Text = settings.StationLinkVersion;
|
||||
@@ -30,12 +28,6 @@ public sealed partial class NetworkDialog : Window
|
||||
{
|
||||
NetworkStationName = (NameBox.Text ?? "").Trim(),
|
||||
NetworkPort = int.TryParse(PortBox.Text, out int port) ? port : 12060,
|
||||
NetworkPeers = (PeersBox.Text ?? "")
|
||||
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(l => l.Trim())
|
||||
.Where(l => l.Length > 0)
|
||||
.ToList(),
|
||||
NetworkEnabled = EnabledBox.IsChecked == true,
|
||||
StationNumber = int.TryParse(StationNumberBox.Text, out int number)
|
||||
? Math.Clamp(number, 1, 99)
|
||||
: settings.StationNumber,
|
||||
|
||||
@@ -568,6 +568,19 @@ public sealed partial class EntryWindow
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnBroadcastSettings(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
BroadcastDataDialog dialog = new(session.Settings);
|
||||
Settings? updated = await dialog.ShowDialog<Settings?>(this);
|
||||
if (updated is not null)
|
||||
{
|
||||
session.Save(updated);
|
||||
session.ApplyNetworkSettings();
|
||||
session.ApplyBroadcastSettings();
|
||||
Status("broadcast data saved");
|
||||
}
|
||||
}
|
||||
|
||||
/// What the two networks are now doing, for the status line.
|
||||
private static string Networking(Settings settings) =>
|
||||
(settings.NetworkEnabled, settings.StationLinkEnabled) switch
|
||||
|
||||
@@ -114,6 +114,7 @@
|
||||
<MenuItem Header="Change Band Plan…" Click="OnSubBandSettings" />
|
||||
<MenuItem Header="Manage Skins, Colors and Fonts…" Click="OnThemeSettings" />
|
||||
<MenuItem Header="Edit Networked-Computer Names…" Click="OnNetworkSettings" />
|
||||
<MenuItem Header="Configure Broadcast Data…" Click="OnBroadcastSettings" />
|
||||
<MenuItem Header="WAE">
|
||||
<MenuItem Header="Open QTC Window setup area" Click="OnQtcSetup" />
|
||||
</MenuItem>
|
||||
|
||||
@@ -202,6 +202,10 @@ public sealed partial class EntryWindow : Window
|
||||
{
|
||||
yield return ("station link", session.ApplyStationLinkSettings);
|
||||
}
|
||||
if (session.Settings.BroadcastChannels().Values.Any(c => c.IsEnabled))
|
||||
{
|
||||
yield return ("data broadcast", session.ApplyBroadcastSettings);
|
||||
}
|
||||
// always, whatever the CW keyer is: this is also what builds the voice
|
||||
// keyer, which plays out of the sound card rather than a keying port
|
||||
yield return ("keyer", session.ApplyKeyerSettings);
|
||||
@@ -363,6 +367,10 @@ public sealed partial class EntryWindow : Window
|
||||
break;
|
||||
case Key.Tab when FocusedIsEntryBox():
|
||||
e.Handled = true;
|
||||
if (Logging is not null && boxes[0].IsFocused)
|
||||
{
|
||||
session.SendLookup(Logging);
|
||||
}
|
||||
MoveToNextBox(forward: !e.KeyModifiers.HasFlag(KeyModifiers.Shift));
|
||||
break;
|
||||
case Key.Z when e.KeyModifiers.HasFlag(KeyModifiers.Control):
|
||||
|
||||
Reference in New Issue
Block a user