Shorten the bandmap hover text to frequency, spotter, age and comment

The first line reads "14025.0 by K1TTT, 41m ago". The comment follows
on a second line and is left out when it is only LSB, USB or SSB.
Control characters are removed from the comment: cluster lines end in
bell and carriage-return characters, which the tooltip drew as boxes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 11:51:13 +00:00
co-authored by Claude Sonnet 5.5
parent 70c16a563f
commit 58f417a1fb
2 changed files with 67 additions and 17 deletions
+27 -12
View File
@@ -2,23 +2,38 @@ using System.Globalization;
namespace Nonemm.Spotting; namespace Nonemm.Spotting;
/// What the bandmap shows when the pointer rests on a spot: who spotted it, /// What the bandmap shows when the pointer rests on a spot: the frequency,
/// when, and the comment they sent. /// who spotted it and how long ago, and the comment if it says more than the
/// sideband.
public static class SpotDescription public static class SpotDescription
{ {
public static string Of(Spot spot) public static string Of(Spot spot, DateTime? nowUtc = null)
{ {
string time = spot.AtUtc.ToString("HH:mm", CultureInfo.InvariantCulture); string age = Age((nowUtc ?? DateTime.UtcNow) - spot.AtUtc);
string kilohertz = spot.Frequency.Kilohertz.ToString("0.0", CultureInfo.InvariantCulture); string kilohertz = spot.Frequency.Kilohertz.ToString("0.0", CultureInfo.InvariantCulture);
string who = spot.Spotter.Length > 0 string first = spot.Spotter.Length > 0
? $"Spotted by {spot.Spotter} at {time}Z" ? $"{kilohertz} by {spot.Spotter}, {age}"
: spot.Source switch : $"{kilohertz}, {age}";
string comment = Comment(spot.Comment);
return comment.Length > 0 ? $"{first}\n{comment}" : first;
}
private static string Age(TimeSpan span)
{ {
SpotSource.Log => $"Worked at {time}Z", int minutes = Math.Max(0, (int)span.TotalMinutes);
SpotSource.Operator => $"Spotted here at {time}Z", return minutes switch
_ => $"Spotted at {time}Z", {
< 60 => $"{minutes}m ago",
_ => $"{minutes / 60}h {minutes % 60}m ago",
}; };
string text = $"{spot.Call.Text} {kilohertz}\n{who}"; }
return spot.Comment.Trim().Length > 0 ? $"{text}\n{spot.Comment.Trim()}" : text;
/// Cluster lines end in bell and carriage-return characters, which a
/// tooltip draws as boxes, so control characters are dropped.
private static string Comment(string text)
{
string clean = new([.. text.Where(c => !char.IsControl(c))]);
clean = clean.Trim();
return clean.ToUpperInvariant() is "LSB" or "USB" or "SSB" ? "" : clean;
} }
} }
@@ -5,6 +5,7 @@ namespace Nonemm.Spotting.Tests;
public class SpotDescriptionTests public class SpotDescriptionTests
{ {
private static readonly DateTime At = new(2026, 5, 30, 12, 1, 0, DateTimeKind.Utc); private static readonly DateTime At = new(2026, 5, 30, 12, 1, 0, DateTimeKind.Utc);
private static readonly DateTime Now = At.AddMinutes(41);
[Fact] [Fact]
public void ASpotNamesTheSpotterTheTimeAndTheComment() public void ASpotNamesTheSpotterTheTimeAndTheComment()
@@ -13,15 +14,49 @@ public class SpotDescriptionTests
Callsign.Parse("JA1XYZ"), Frequency.FromKilohertz(14_025), At, SpotSource.Cluster, Callsign.Parse("JA1XYZ"), Frequency.FromKilohertz(14_025), At, SpotSource.Cluster,
"K1TTT", "CW 26 dB"); "K1TTT", "CW 26 dB");
Assert.Equal("JA1XYZ 14025.0\nSpotted by K1TTT at 12:01Z\nCW 26 dB", SpotDescription.Of(spot)); Assert.Equal("14025.0 by K1TTT, 41m ago\nCW 26 dB", SpotDescription.Of(spot, Now));
} }
[Fact] [Fact]
public void ASpotWithoutACommentHasTwoLines() public void ASpotWithoutACommentOrSpotterHasOneLine()
{ {
Spot spot = new( Spot spot = new(
Callsign.Parse("JA1XYZ"), Frequency.FromKilohertz(14_025), At, SpotSource.Log); Callsign.Parse("JA1XYZ"), Frequency.FromKilohertz(14_025), At, SpotSource.Log);
Assert.Equal("JA1XYZ 14025.0\nWorked at 12:01Z", SpotDescription.Of(spot)); Assert.Equal("14025.0, 41m ago", SpotDescription.Of(spot, Now));
}
[Theory]
[InlineData("USB")]
[InlineData("lsb")]
[InlineData("SSB\a\a")]
public void ASidebandAloneIsLeftOut(string comment)
{
Spot spot = new(
Callsign.Parse("JA1XYZ"), Frequency.FromKilohertz(14_025), At, SpotSource.Cluster,
"K1TTT", comment);
Assert.Equal("14025.0 by K1TTT, 41m ago", SpotDescription.Of(spot, Now));
}
[Fact]
public void ControlCharactersAreDroppedFromTheComment()
{
Spot spot = new(
Callsign.Parse("JA1XYZ"), Frequency.FromKilohertz(14_025), At, SpotSource.Cluster,
"K1TTT", "CW 26 dB\r\a");
Assert.Equal("14025.0 by K1TTT, 41m ago\nCW 26 dB", SpotDescription.Of(spot, Now));
}
[Theory]
[InlineData(20, "14025.0, 0m ago")]
[InlineData(3600 + 300, "14025.0, 1h 5m ago")]
public void TheAgeIsInMinutesThenHours(int seconds, string expected)
{
Spot spot = new(
Callsign.Parse("JA1XYZ"), Frequency.FromKilohertz(14_025), At, SpotSource.Log);
Assert.Equal(expected, SpotDescription.Of(spot, At.AddSeconds(seconds)));
} }
} }