feat(udp): Highlight Callsign and Replay — the decoder becomes log-aware
Message 13 paints callsigns in WSJT-X/JTDX's own Band Activity window with verdicts from the same cluster status cache that colours the spot grid: watchlist pink, new DXCC green, new band for its entity orange. Deduplicated per instance+call+verdict, one datagram each; a verdict that lapses (the operator worked them) clears that call, and turning the option off clears everything via the protocol's CLEARALL!. Off by default, switch in the Connections panel. Message 7 asks a program heard for the first time this session to replay the decodes already on its screen, so the FT decodes panel starts full instead of empty until the next period. Replayed lines arrive marked not-new and are shown but never auto-answered — the auto-caller now checks, on top of its 30-second freshness gate.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
// Log-aware colours in WSJT-X / JTDX's own Band Activity window (message 13),
|
||||
// the way JTAlert paints them: a decode of a watchlist member, a new DXCC or a
|
||||
// new band for its entity is highlighted where the operator is actually
|
||||
// looking. The verdicts come from the same cluster status cache that colours
|
||||
// the spot grid, so the two windows can never disagree.
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"hamlog/internal/applog"
|
||||
"hamlog/internal/dxcc"
|
||||
udp "hamlog/internal/integrations/udp"
|
||||
)
|
||||
|
||||
const keyWsjtHighlight = "udp.wsjt.highlight"
|
||||
|
||||
// The palette. Fixed colours, not theme tokens — they are painted into another
|
||||
// application's window, which has no idea what theme OpsLog wears.
|
||||
var (
|
||||
hlWatchlist = udp.RGB{R: 244, G: 114, B: 182} // the watchlist pink
|
||||
hlNewDXCC = udp.RGB{R: 22, G: 130, B: 60} // green
|
||||
hlNewBand = udp.RGB{R: 226, G: 122, B: 24} // orange
|
||||
hlWhite = udp.RGB{R: 255, G: 255, B: 255}
|
||||
hlBlack = udp.RGB{R: 20, G: 20, B: 20}
|
||||
)
|
||||
|
||||
// GetWsjtHighlight reports whether decode highlighting is on.
|
||||
func (a *App) GetWsjtHighlight() bool {
|
||||
return a.settingOr(keyWsjtHighlight, "0") == "1"
|
||||
}
|
||||
|
||||
// SetWsjtHighlight turns decode highlighting on or off. Turning it OFF also
|
||||
// clears every instruction OpsLog installed in the running applications — a
|
||||
// disabled option that leaves stale colours behind looks broken, not disabled.
|
||||
func (a *App) SetWsjtHighlight(on bool) {
|
||||
v := "0"
|
||||
if on {
|
||||
v = "1"
|
||||
}
|
||||
a.setSetting(keyWsjtHighlight, v)
|
||||
a.wsjtHighlightOn.Store(on)
|
||||
if !on && a.udp != nil {
|
||||
for _, inst := range a.udp.Instances() {
|
||||
_ = a.udp.SendClearHighlights(inst)
|
||||
}
|
||||
a.wsjtHLMu.Lock()
|
||||
a.wsjtHLSent = map[string]string{}
|
||||
a.wsjtHLMu.Unlock()
|
||||
applog.Printf("wsjt highlight: off — cleared in every instance")
|
||||
}
|
||||
}
|
||||
|
||||
// maybeHighlightDecode paints one decoded callsign in the instance that heard
|
||||
// it, when the option is on and the verdict is worth a colour. De-duplicated
|
||||
// per instance+call+verdict: a station CQing all evening is decoded four times
|
||||
// a minute, and the instruction only needs to be said once.
|
||||
func (a *App) maybeHighlightDecode(instance, call, band string) {
|
||||
if !a.wsjtHighlightOn.Load() || a.udp == nil || call == "" || instance == "" {
|
||||
return
|
||||
}
|
||||
bg, fg, verdict := a.decodeHighlightVerdict(call, band)
|
||||
key := instance + "|" + strings.ToUpper(call) + "|" + band
|
||||
a.wsjtHLMu.Lock()
|
||||
if a.wsjtHLSent == nil {
|
||||
a.wsjtHLSent = map[string]string{}
|
||||
}
|
||||
if len(a.wsjtHLSent) > 4000 { // bounded; a long session just re-says a few
|
||||
a.wsjtHLSent = map[string]string{}
|
||||
}
|
||||
prev, had := a.wsjtHLSent[key]
|
||||
if had && prev == verdict {
|
||||
a.wsjtHLMu.Unlock()
|
||||
return
|
||||
}
|
||||
a.wsjtHLSent[key] = verdict
|
||||
a.wsjtHLMu.Unlock()
|
||||
if verdict == "" {
|
||||
// Was highlighted under an earlier verdict and no longer deserves it
|
||||
// (the operator just worked them): clear that one callsign.
|
||||
if had && prev != "" {
|
||||
_ = a.udp.SendHighlight(instance, call, nil, nil, false)
|
||||
}
|
||||
return
|
||||
}
|
||||
_ = a.udp.SendHighlight(instance, call, bg, fg, false)
|
||||
}
|
||||
|
||||
// decodeHighlightVerdict ranks a callsign: watchlist beats new-DXCC beats
|
||||
// new-band; anything else is "no colour". The empty verdict doubles as the
|
||||
// clear signal in maybeHighlightDecode.
|
||||
func (a *App) decodeHighlightVerdict(call, band string) (bg, fg *udp.RGB, verdict string) {
|
||||
if a.watchlist != nil {
|
||||
if _, ok := a.watchlist.Match(call); ok {
|
||||
c := hlWatchlist
|
||||
f := hlBlack
|
||||
return &c, &f, "watchlist"
|
||||
}
|
||||
}
|
||||
c := a.clusterStatusMaps()
|
||||
if a.dxcc != nil {
|
||||
if m, ok := a.dxcc.Lookup(call); ok && m.Entity != nil {
|
||||
num := dxcc.EntityDXCC(m.Entity.Name)
|
||||
ent := c.entities[num]
|
||||
if ent == nil {
|
||||
bgc, fgc := hlNewDXCC, hlWhite
|
||||
return &bgc, &fgc, "new-dxcc"
|
||||
}
|
||||
if band != "" {
|
||||
if _, workedBand := ent.Bands[strings.ToLower(band)]; !workedBand {
|
||||
bgc, fgc := hlNewBand, hlBlack
|
||||
return &bgc, &fgc, "new-band"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil, ""
|
||||
}
|
||||
Reference in New Issue
Block a user