Edit and delete contacts in the log
Double-click a cell in the log window to change it. The columns follow the contest exchange instead of being fixed in the XAML, so CQ WW gets a Zone column and Sweepstakes gets Nr, Prec, Ck and Sec. QsoEditor holds the column list and applies one field edit. Validation comes from the ExchangeFieldKind the contest declared: a CQ zone is 1 to 40, an ITU zone 1 to 90, a section is looked up in the ARRL list, a grid must parse. A refused edit returns the reason and leaves the log alone, so the cell reverts. Changing the callsign runs the country lookup again. Delete, or the right-click menu, removes the selected contact after a confirmation. Either way the log is rescored, so a multiplier the removed contact held passes to the next contact that claims it. Both go out to the other stations in N1MM's own messages: contactreplace carries oldcall and oldtimestamp, contactdelete names the contact. An incoming edit or delete is matched by contact id first, falling back to call plus timestamp because N1MM does not know our ids. Country, continent and the two prefixes were filled in twice, once when logging and once when editing. They now come from CountryFields. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,9 @@ public sealed class AppSession : IDisposable
|
||||
Logging.Logged += (_, qso) => Bandmap.Add(new Spot(
|
||||
qso.Call, qso.Frequency, qso.TimestampUtc, SpotSource.Log));
|
||||
Logging.Logged += (_, qso) => _ = network?.SendAsync(qso, Settings.Station.Callsign);
|
||||
Logging.Edited += (_, change) => _ = network?.SendEditAsync(
|
||||
change.Qso, Settings.Station.Callsign, change.OldCall, change.OldTimestampUtc);
|
||||
Logging.Deleted += (_, qso) => _ = network?.SendDeleteAsync(qso, Settings.Station.Callsign);
|
||||
Check = new CheckWindowSources(Logging, Calls, Bandmap);
|
||||
Save(Settings with { ContestNumber = contestNumber });
|
||||
ContestChanged?.Invoke(this, EventArgs.Empty);
|
||||
@@ -132,29 +135,60 @@ public sealed class AppSession : IDisposable
|
||||
Settings.NetworkPort,
|
||||
Settings.NetworkStationName.Length > 0 ? Settings.NetworkStationName : Environment.MachineName,
|
||||
Settings.NetworkPeers);
|
||||
network.ContactArrived += (_, qso) => TakeFromNetwork(qso);
|
||||
network.UpdateArrived += (_, update) => TakeFromNetwork(update);
|
||||
network.Start();
|
||||
Changed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// A contact another station logged. It goes into the same log under the
|
||||
/// contest that is open, and is scored here rather than trusting the
|
||||
/// points the sender put in the message.
|
||||
private void TakeFromNetwork(Qso qso)
|
||||
/// What another station did to its log, applied to ours. The store is
|
||||
/// written directly rather than through `Logging`, so the change is not
|
||||
/// broadcast back out again. Points and multipliers are worked out here
|
||||
/// from the rules instead of trusting what the sender put in the message.
|
||||
private void TakeFromNetwork(ContactUpdate update)
|
||||
{
|
||||
if (Logging is null || store is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (Logging.Log.Qsos.Any(q => q.Id == qso.Id))
|
||||
int contestNumber = Logging.Instance.ContestNumber;
|
||||
switch (update)
|
||||
{
|
||||
return;
|
||||
case ContactLogged logged when !Logging.Log.Qsos.Any(q => q.Id == logged.Qso.Id):
|
||||
store.Add(logged.Qso with { ContestNumber = contestNumber, IsOriginal = false });
|
||||
break;
|
||||
case ContactReplaced replaced:
|
||||
if (FindLocal(replaced.Qso.Id, replaced.OldCall, replaced.OldTimestampUtc) is not { } old)
|
||||
{
|
||||
return;
|
||||
}
|
||||
store.Update(replaced.Qso with
|
||||
{
|
||||
Id = old.Id,
|
||||
ContestNumber = contestNumber,
|
||||
IsOriginal = false,
|
||||
});
|
||||
break;
|
||||
case ContactDeleted deleted:
|
||||
if (FindLocal(deleted.Id, deleted.Call, deleted.TimestampUtc) is not { } gone)
|
||||
{
|
||||
return;
|
||||
}
|
||||
store.Delete(gone.Id);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
Qso mine = qso with { ContestNumber = Logging.Instance.ContestNumber, IsOriginal = false };
|
||||
store.Add(mine);
|
||||
OpenContest(Logging.Instance.ContestNumber);
|
||||
OpenContest(contestNumber);
|
||||
}
|
||||
|
||||
/// N1MM keys a contact on its call and time, so a message from N1MM carries
|
||||
/// no id we would recognise. Fall back to that pair when the id misses.
|
||||
private Qso? FindLocal(string id, string call, DateTime timestampUtc) =>
|
||||
Logging?.Log.Qsos.FirstOrDefault(q => id.Length > 0 && q.Id == id)
|
||||
?? Logging?.Log.Qsos.FirstOrDefault(q =>
|
||||
string.Equals(q.Call.Text, call, StringComparison.OrdinalIgnoreCase)
|
||||
&& q.TimestampUtc == timestampUtc);
|
||||
|
||||
/// Starts, restarts or stops the keyer, following what the settings say.
|
||||
/// A keyer that will not open is reported; the program keeps running
|
||||
/// without one.
|
||||
|
||||
Reference in New Issue
Block a user