From 58f417a1fb4d315a7b49564e03658c664710660e Mon Sep 17 00:00:00 2001 From: ericek111 Date: Sat, 24 Oct 2026 11:51:13 +0000 Subject: [PATCH] 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 --- src/Nonemm.Spotting/SpotDescription.cs | 43 +++++++++++++------ .../SpotDescriptionTests.cs | 41 ++++++++++++++++-- 2 files changed, 67 insertions(+), 17 deletions(-) diff --git a/src/Nonemm.Spotting/SpotDescription.cs b/src/Nonemm.Spotting/SpotDescription.cs index ed64962..462ef7d 100644 --- a/src/Nonemm.Spotting/SpotDescription.cs +++ b/src/Nonemm.Spotting/SpotDescription.cs @@ -2,23 +2,38 @@ using System.Globalization; namespace Nonemm.Spotting; -/// What the bandmap shows when the pointer rests on a spot: who spotted it, -/// when, and the comment they sent. +/// What the bandmap shows when the pointer rests on a spot: the frequency, +/// who spotted it and how long ago, and the comment if it says more than the +/// sideband. 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 who = spot.Spotter.Length > 0 - ? $"Spotted by {spot.Spotter} at {time}Z" - : spot.Source switch - { - SpotSource.Log => $"Worked at {time}Z", - SpotSource.Operator => $"Spotted here at {time}Z", - _ => $"Spotted at {time}Z", - }; - string text = $"{spot.Call.Text} {kilohertz}\n{who}"; - return spot.Comment.Trim().Length > 0 ? $"{text}\n{spot.Comment.Trim()}" : text; + string first = spot.Spotter.Length > 0 + ? $"{kilohertz} by {spot.Spotter}, {age}" + : $"{kilohertz}, {age}"; + string comment = Comment(spot.Comment); + return comment.Length > 0 ? $"{first}\n{comment}" : first; + } + + private static string Age(TimeSpan span) + { + int minutes = Math.Max(0, (int)span.TotalMinutes); + return minutes switch + { + < 60 => $"{minutes}m ago", + _ => $"{minutes / 60}h {minutes % 60}m ago", + }; + } + + /// 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; } } diff --git a/tests/Nonemm.Spotting.Tests/SpotDescriptionTests.cs b/tests/Nonemm.Spotting.Tests/SpotDescriptionTests.cs index a0eea40..caef7e3 100644 --- a/tests/Nonemm.Spotting.Tests/SpotDescriptionTests.cs +++ b/tests/Nonemm.Spotting.Tests/SpotDescriptionTests.cs @@ -5,6 +5,7 @@ namespace Nonemm.Spotting.Tests; public class SpotDescriptionTests { private static readonly DateTime At = new(2026, 5, 30, 12, 1, 0, DateTimeKind.Utc); + private static readonly DateTime Now = At.AddMinutes(41); [Fact] public void ASpotNamesTheSpotterTheTimeAndTheComment() @@ -13,15 +14,49 @@ public class SpotDescriptionTests Callsign.Parse("JA1XYZ"), Frequency.FromKilohertz(14_025), At, SpotSource.Cluster, "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] - public void ASpotWithoutACommentHasTwoLines() + public void ASpotWithoutACommentOrSpotterHasOneLine() { Spot spot = new( 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))); } }