Add the Avalonia application and the station network

The entry window types contacts and colours them as they are typed; the log,
check, bandmap, score and packet windows read the same session. Contacts are
shared with the other stations of a multi-operator entry in N1MM's contact
message, so an N1MM station on the same network sees them too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik
2026-08-27 10:56:35 +00:00
co-authored by Claude Opus 5
parent ffeeb2cdc1
commit 2834f63c8e
45 changed files with 2667 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
using Nonemm.Contests;
using Nonemm.Core.Calls;
namespace Nonemm.Session;
/// One callsign the check window offers, with what the log says about it so it
/// is coloured the same as everywhere else.
public sealed record CheckCandidate(string Call, PartialMatchQuality Quality, Verdict? Verdict);
/// Where a column of candidates came from. They stay apart because they answer
/// different questions: the log is what this station copied itself, the
/// database is a guess about the whole world, and the bandmap is somebody
/// else's claim.
public enum CheckSource
{
Log,
Database,
Bandmap,
}
/// One column of the check window.
public sealed record CheckColumn(
CheckSource Source,
int Held,
IReadOnlyList<CheckCandidate> Candidates);
+86
View File
@@ -0,0 +1,86 @@
using Nonemm.Contests;
using Nonemm.Core;
using Nonemm.Core.Calls;
using Nonemm.Spotting;
namespace Nonemm.Session;
/// Answers what the callsign being typed could be, a column per source.
public sealed class CheckWindowSources
{
private readonly LoggingSession session;
private readonly CallDatabase database;
private readonly Bandmap bandmap;
public CheckWindowSources(LoggingSession session, CallDatabase database, Bandmap bandmap)
{
this.session = session;
this.database = database;
this.bandmap = bandmap;
}
public IReadOnlyList<CheckColumn> Columns(string typed, int limit = 24)
{
string query = typed.Trim().ToUpperInvariant();
return
[
Column(CheckSource.Log, LoggedCalls(), query, limit),
new CheckColumn(
CheckSource.Database,
database.Count,
query.Length < PartialMatching.ShortestUsefulQuery
? []
: database.Matches(query, limit).Select(Judged).ToList()),
Column(CheckSource.Bandmap, SpottedCalls(), query, limit),
];
}
private CheckColumn Column(
CheckSource source,
IReadOnlyList<string> calls,
string query,
int limit)
{
if (query.Length < PartialMatching.ShortestUsefulQuery)
{
return new CheckColumn(source, calls.Count, []);
}
List<CheckCandidate> found = [];
foreach (string call in calls)
{
PartialMatchQuality? quality = PartialMatching.Judge(query, call);
if (quality is not null)
{
found.Add(Judged(new PartialMatch(call, quality.Value)));
}
}
return new CheckColumn(
source,
calls.Count,
found.OrderBy(c => c.Quality).ThenBy(c => c.Call, StringComparer.Ordinal).Take(limit).ToList());
}
private CheckCandidate Judged(PartialMatch match) =>
new(match.Call, match.Quality, VerdictFor(match.Call));
/// What logging this call on the current band and mode would do.
private Verdict? VerdictFor(string call)
{
Qso candidate = new()
{
Id = "",
TimestampUtc = DateTime.UtcNow,
Call = Callsign.Parse(call),
Frequency = session.Frequency,
Mode = session.Mode,
ContestName = session.Contest.Name,
};
return session.Log.Judge(candidate);
}
private IReadOnlyList<string> LoggedCalls() =>
session.Log.Qsos.Select(q => q.Call.Text).Distinct(StringComparer.Ordinal).ToList();
private IReadOnlyList<string> SpottedCalls() =>
bandmap.All().Select(s => s.Call.Text).Distinct(StringComparer.Ordinal).ToList();
}
+1
View File
@@ -5,6 +5,7 @@
<ProjectReference Include="..\Nonemm.Contests\Nonemm.Contests.csproj" />
<ProjectReference Include="..\Nonemm.Storage\Nonemm.Storage.csproj" />
<ProjectReference Include="..\Nonemm.Formats\Nonemm.Formats.csproj" />
<ProjectReference Include="..\Nonemm.Spotting\Nonemm.Spotting.csproj" />
</ItemGroup>
<PropertyGroup>