diff --git a/Makefile b/Makefile index db2620e..fa2d9c1 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,6 @@ test: go test -v -timeout=30s ./... build: - # go build -trimpath -buildmode=pie -mod=readonly -modcacherw -v -ldflags "-linkmode external -extldflags \"${LDFLAGS}\" -X main.version=${VERSION_NUMBER}" -o ${BINARY_NAME} go build -trimpath -buildmode=pie -ldflags "-s -w -extldflags \"${LDFLAGS}\" -X main.version=${VERSION_NUMBER}" -o ${BINARY_NAME} run: build diff --git a/core/actions.go b/core/actions.go index 766055c..9f61604 100644 --- a/core/actions.go +++ b/core/actions.go @@ -36,19 +36,26 @@ const ( ActionEntryClearVFO1 = "entry.clear_vfo1" ActionEntryClearVFO2 = "entry.clear_vfo2" - ActionRadioXITActive = "radio.xit_active" - ActionRadioShiftFrequency = "radio.shift_frequency" - ActionRadioShiftXIT = "radio.shift_xit" - ActionRadioFrequencyUp = "radio.frequency_up" - ActionRadioFrequencyDown = "radio.frequency_down" - ActionRadioXITUp = "radio.xit_up" - ActionRadioXITDown = "radio.xit_down" - ActionRadioMuteAudioVFO1 = "radio.mute_audio_vfo1" - ActionRadioMuteAudioVFO2 = "radio.mute_audio_vfo2" - ActionRadioUnmuteAudioVFO1 = "radio.unmute_audio_vfo1" - ActionRadioUnmuteAudioVFO2 = "radio.unmute_audio_vfo2" - ActionRadioToggleAudioVFO1 = "radio.toggle_audio_vfo1" - ActionRadioToggleAudioVFO2 = "radio.toggle_audio_vfo2" + ActionRadioXITActive = "radio.xit_active" + ActionRadioRITActive = "radio.rit_active" + ActionRadioShiftFrequency = "radio.shift_frequency" + ActionRadioShiftXIT = "radio.shift_xit" + ActionRadioShiftRIT = "radio.shift_rit" + ActionRadioFrequencyUp = "radio.frequency_up" + ActionRadioFrequencyDown = "radio.frequency_down" + ActionRadioXITUp = "radio.xit_up" + ActionRadioXITDown = "radio.xit_down" + ActionRadioRITUp = "radio.rit_up" + ActionRadioRITDown = "radio.rit_down" + ActionIncrementalTuningToggle = "radio.incremental_tuning_toggle" + ActionIncrementalTuningUp = "radio.incremental_tuning_up" + ActionIncrementalTuningDown = "radio.incremental_tuning_down" + ActionRadioMuteAudioVFO1 = "radio.mute_audio_vfo1" + ActionRadioMuteAudioVFO2 = "radio.mute_audio_vfo2" + ActionRadioUnmuteAudioVFO1 = "radio.unmute_audio_vfo1" + ActionRadioUnmuteAudioVFO2 = "radio.unmute_audio_vfo2" + ActionRadioToggleAudioVFO1 = "radio.toggle_audio_vfo1" + ActionRadioToggleAudioVFO2 = "radio.toggle_audio_vfo2" ActionBandmapMark = "bandmap.mark" ActionBandmapGotoHighestValueSpot = "bandmap.goto_highest_value_spot" @@ -83,5 +90,6 @@ const ( const ( DefaultFrequencyShift = Frequency(10) // Hz DefaultXITShift = Frequency(10) // Hz + DefaultRITShift = Frequency(10) // Hz DefaultSpeedShift = 1 // WPM ) diff --git a/core/app/app.go b/core/app/app.go index 3163658..4070ec8 100644 --- a/core/app/app.go +++ b/core/app/app.go @@ -230,10 +230,11 @@ func (c *Controller) Startup() { v.SetClient(c.Radio) c.VFOs[vfoID] = v c.Entry.SetVFO(core.VFOID(vfoID), v) + c.Radio.SetVFO(core.VFOID(vfoID), v) c.Logbook.Notify(v) + c.Workmode.Notify(v) } c.Bandmap.SetVFO(c.VFOs[core.VFO1]) - c.Workmode.Notify(c.VFOs[core.VFO1]) c.Radio.SetSendSpotsToTci(c.session.SendSpotsToTci()) c.Radio.SelectRadio(c.session.Radio1()) @@ -882,24 +883,52 @@ func (c *Controller) RequestQTC() { c.QTCController.RequestQTC() } -func (c *Controller) XITActive() bool { - return c.VFOs[core.VFO1].XITActive() +func (c *Controller) IncrementalTuningActive(vfo core.VFOID, kind core.IncrementalTuningKind) bool { + return c.Radio.IncrementalTuningActive(vfo, kind) } -func (c *Controller) SetXITActive(active bool) { - c.VFOs[core.VFO1].SetXITActive(active) +func (c *Controller) FocusedIncrementalTuningActive(kind core.IncrementalTuningKind) bool { + return c.Radio.FocusedIncrementalTuningActive(kind) } -func (c *Controller) ShiftXIT(delta core.Frequency) { - c.VFOs[core.VFO1].ShiftXITOffset(delta) +func (c *Controller) SetIncrementalTuningActive(vfo core.VFOID, kind core.IncrementalTuningKind, active bool) { + c.Radio.SetIncrementalTuningActive(vfo, kind, active) +} + +func (c *Controller) SetFocusedIncrementalTuningActive(kind core.IncrementalTuningKind, active bool) { + c.Radio.SetFocusedIncrementalTuningActive(kind, active) +} + +func (c *Controller) ShiftIncrementalTuning(kind core.IncrementalTuningKind, delta core.Frequency) { + c.Radio.ShiftIncrementalTuning(kind, delta) } func (c *Controller) XITUp() { - c.ShiftXIT(core.DefaultXITShift) + c.ShiftIncrementalTuning(core.XIT, core.DefaultXITShift) } func (c *Controller) XITDown() { - c.ShiftXIT(-core.DefaultXITShift) + c.ShiftIncrementalTuning(core.XIT, -core.DefaultXITShift) +} + +func (c *Controller) RITUp() { + c.ShiftIncrementalTuning(core.RIT, core.DefaultRITShift) +} + +func (c *Controller) RITDown() { + c.ShiftIncrementalTuning(core.RIT, -core.DefaultRITShift) +} + +func (c *Controller) ToggleIncrementalTuning() { + c.Radio.ToggleIncrementalTuning() +} + +func (c *Controller) IncrementalTuningUp() { + c.Radio.IncrementalTuningUp() +} + +func (c *Controller) IncrementalTuningDown() { + c.Radio.IncrementalTuningDown() } func (c *Controller) MuteAudio(vfo core.VFOID) { @@ -1043,11 +1072,27 @@ func (c *Controller) DoAction(id string, params map[string]string) error { if err != nil { return err } - c.ShiftXIT(core.Frequency(amount)) + c.ShiftIncrementalTuning(core.XIT, core.Frequency(amount)) case core.ActionRadioXITUp: c.XITUp() case core.ActionRadioXITDown: c.XITDown() + case core.ActionRadioShiftRIT: + amount, err := shiftAmount(params, int(core.DefaultRITShift)) + if err != nil { + return err + } + c.ShiftIncrementalTuning(core.RIT, core.Frequency(amount)) + case core.ActionRadioRITUp: + c.RITUp() + case core.ActionRadioRITDown: + c.RITDown() + case core.ActionIncrementalTuningToggle: + c.ToggleIncrementalTuning() + case core.ActionIncrementalTuningUp: + c.IncrementalTuningUp() + case core.ActionIncrementalTuningDown: + c.IncrementalTuningDown() case core.ActionKeyerShiftSpeed: amount, err := shiftAmount(params, core.DefaultSpeedShift) if err != nil { diff --git a/core/bandmap/bandmap.go b/core/bandmap/bandmap.go index dd1b6f0..142247d 100644 --- a/core/bandmap/bandmap.go +++ b/core/bandmap/bandmap.go @@ -407,17 +407,20 @@ func (v *nullView) RevealEntry(core.BandmapEntry) {} type nullVFO struct{} -func (n *nullVFO) Name() string { return "" } -func (n *nullVFO) Notify(any) {} -func (n *nullVFO) Active() bool { return false } -func (n *nullVFO) Refresh() {} -func (n *nullVFO) SetFrequency(core.Frequency) {} -func (n *nullVFO) ShiftFrequency(core.Frequency) {} -func (n *nullVFO) SetBand(core.Band) {} -func (n *nullVFO) SetMode(core.Mode) {} -func (n *nullVFO) SetXIT(bool, core.Frequency) {} -func (n *nullVFO) XITActive() bool { return false } -func (n *nullVFO) SetXITActive(bool) {} +func (n *nullVFO) Name() string { return "" } +func (n *nullVFO) Notify(any) {} +func (n *nullVFO) Active() bool { return false } +func (n *nullVFO) Refresh() {} +func (n *nullVFO) SetFrequency(core.Frequency) {} +func (n *nullVFO) ShiftFrequency(core.Frequency) {} +func (n *nullVFO) SetBand(core.Band) {} +func (n *nullVFO) SetMode(core.Mode) {} +func (n *nullVFO) SetIncrementalTuning(core.IncrementalTuningKind, bool, core.Frequency) {} +func (n *nullVFO) ShiftOffset(core.IncrementalTuningKind, core.Frequency) {} +func (n *nullVFO) ToggleIncrementalTuning() {} +func (n *nullVFO) ShiftAvailableIncrementalTuning(core.Frequency) {} +func (n *nullVFO) IncrementalTuningActive(core.IncrementalTuningKind) bool { return false } +func (n *nullVFO) SetIncrementalTuningActive(core.IncrementalTuningKind, bool) {} type nullCallinfo struct{} diff --git a/core/cfg/cfg.go b/core/cfg/cfg.go index 6a8bb7e..4bb9a87 100644 --- a/core/cfg/cfg.go +++ b/core/cfg/cfg.go @@ -45,7 +45,7 @@ var Default = &Data{ Address: "localhost:40001", Keyer: "radio", Options: map[string]string{ - "single_vfo": "false", + "trx": "0", }, }, }, diff --git a/core/core.go b/core/core.go index 39f4db1..8b9c469 100644 --- a/core/core.go +++ b/core/core.go @@ -1672,9 +1672,9 @@ type BandmapWeights struct { Quality float64 } -type XITControl interface { - XITActive() bool - SetXITActive(bool) +type IncrementalTuningControl interface { + IncrementalTuningActive(kind IncrementalTuningKind) bool + SetIncrementalTuningActive(kind IncrementalTuningKind, active bool) } type VFOID int @@ -1687,7 +1687,7 @@ const ( ) type VFO interface { - XITControl + IncrementalTuningControl Name() string Notify(any) Refresh() @@ -1695,7 +1695,10 @@ type VFO interface { ShiftFrequency(Frequency) SetBand(Band) SetMode(Mode) - SetXIT(bool, Frequency) + SetIncrementalTuning(IncrementalTuningKind, bool, Frequency) + ShiftOffset(IncrementalTuningKind, Frequency) + ToggleIncrementalTuning() + ShiftAvailableIncrementalTuning(Frequency) } type CurrentVFOListener interface { @@ -1722,8 +1725,45 @@ type VFOModeListener interface { VFOModeChanged(VFOID, Mode) } -type VFOXITListener interface { - VFOXITChanged(VFOID, bool, Frequency) +type IncrementalTuningKind int + +const ( + RIT IncrementalTuningKind = iota + XIT +) + +func (k IncrementalTuningKind) String() string { + switch k { + case RIT: + return "RIT" + case XIT: + return "XIT" + default: + return "unknown" + } +} + +func (k IncrementalTuningKind) Workmode() Workmode { + if k == RIT { + return Run + } + return SearchPounce +} + +type VFOIncrementalTuningListener interface { + VFOIncrementalTuningChanged(vfo VFOID, kind IncrementalTuningKind, active bool, offset Frequency) +} + +type IncrementalTuningAvailabilityListener interface { + IncrementalTuningAvailabilityChanged(vfo VFOID, kind IncrementalTuningKind, available bool) +} + +type IncrementalTuningActiveListener interface { + IncrementalTuningActiveChanged(vfo VFOID, kind IncrementalTuningKind, active bool) +} + +type IncrementalTuningVisibilityListener interface { + IncrementalTuningVisibilityChanged(vfo VFOID, kind IncrementalTuningKind, visible bool) } type VFOPTTListener interface { @@ -1744,6 +1784,12 @@ func (f ConnectionChangedFunc) ConnectionChanged(connected bool) { f(connected) } +type CurrentVFOChangedFunc func(VFOID) + +func (f CurrentVFOChangedFunc) CurrentVFOChanged(vfo VFOID) { + f(vfo) +} + type Service int const ( diff --git a/core/entry/entry.go b/core/entry/entry.go index b282862..0b29a12 100644 --- a/core/entry/entry.go +++ b/core/entry/entry.go @@ -26,8 +26,6 @@ type View interface { SetFrequency(core.VFOID, core.Frequency) SetBand(vfo core.VFOID, text string) SetMode(vfo core.VFOID, text string) - SetXITActive(vfo core.VFOID, active bool) - SetXIT(vfo core.VFOID, active bool, offset core.Frequency) SetTXState(vfo core.VFOID, ptt bool, parrotActive bool, parrotTimeLeft time.Duration) SetCallsign(core.VFOID, string) @@ -737,11 +735,6 @@ func (c *Controller) bandEntered(band core.Band) { c.vfos[c.focusedVFO].SetBand(band) } -func (c *Controller) SetXITActive(active bool) { - c.vfos[core.VFO1].SetXITActive(active) - c.view.SetActiveField(c.focusedVFO, c.activeField[c.focusedVFO]) -} - func (c *Controller) VFOFrequencyChanged(vfo core.VFOID, frequency core.Frequency) { c.asyncRunner(func() { if vfo == core.VFO1 && c.editing { @@ -871,19 +864,6 @@ func (c *Controller) VFOModeChanged(vfo core.VFOID, mode core.Mode) { }) } -func (c *Controller) VFOXITChanged(vfo core.VFOID, active bool, offset core.Frequency) { - c.asyncRunner(func() { - c.view.SetXIT(vfo, active, offset) - }) -} - -func (c *Controller) XITActiveChanged(active bool) { - // TODO: add VFO parameter to XITActiveChanged - c.asyncRunner(func() { - c.view.SetXITActive(core.VFO1, active) - }) -} - func (c *Controller) VFOPTTChanged(vfo core.VFOID, active bool) { c.asyncRunner(func() { if vfo != core.VFO1 { diff --git a/core/entry/entry_test.go b/core/entry/entry_test.go index 5226fbb..620834e 100644 --- a/core/entry/entry_test.go +++ b/core/entry/entry_test.go @@ -62,7 +62,6 @@ func TestEntryController_ClearView(t *testing.T) { view.AssertExpectations(t) } - func TestEntryController_EnterNewCallsign(t *testing.T) { _, log, _, view, controller, _ := setupEntryTestWithClassicExchangeFields() log.Activate() @@ -598,10 +597,13 @@ func (v *testVFO) Refresh() { v.controller.VFOBandChanged(core.VFO1, core.Band160m) v.controller.VFOModeChanged(core.VFO1, core.ModeCW) } -func (v *testVFO) SetFrequency(core.Frequency) {} -func (v *testVFO) ShiftFrequency(core.Frequency) {} -func (v *testVFO) SetBand(core.Band) {} -func (v *testVFO) SetMode(core.Mode) {} -func (v *testVFO) SetXIT(bool, core.Frequency) {} -func (v *testVFO) XITActive() bool { return false } -func (v *testVFO) SetXITActive(bool) {} +func (v *testVFO) SetFrequency(core.Frequency) {} +func (v *testVFO) ShiftFrequency(core.Frequency) {} +func (v *testVFO) SetBand(core.Band) {} +func (v *testVFO) SetMode(core.Mode) {} +func (v *testVFO) SetIncrementalTuning(core.IncrementalTuningKind, bool, core.Frequency) {} +func (v *testVFO) ShiftOffset(core.IncrementalTuningKind, core.Frequency) {} +func (v *testVFO) ToggleIncrementalTuning() {} +func (v *testVFO) ShiftAvailableIncrementalTuning(core.Frequency) {} +func (v *testVFO) IncrementalTuningActive(core.IncrementalTuningKind) bool { return false } +func (v *testVFO) SetIncrementalTuningActive(core.IncrementalTuningKind, bool) {} diff --git a/core/entry/harness_test.go b/core/entry/harness_test.go index 45c7533..d932e2d 100644 --- a/core/entry/harness_test.go +++ b/core/entry/harness_test.go @@ -235,14 +235,6 @@ func (s *Scenario) PressEnter() *Scenario { return s } -// SetXITActive toggles XIT active state on VFO1. -func (s *Scenario) SetXITActive(active bool) *Scenario { - s.t.Helper() - s.resetSpies() - s.controller.SetXITActive(active) - return s -} - // Clear invokes controller.Clear(). func (s *Scenario) Clear() *Scenario { s.t.Helper() @@ -514,14 +506,6 @@ func (s *Scenario) AssertVFOFrequency(freq core.Frequency) *Scenario { return s } -// AssertXITActiveCommanded asserts the VFO rig was commanded with the given XIT-active flag. -func (s *Scenario) AssertXITActiveCommanded(active bool) *Scenario { - s.t.Helper() - assert.Equal(s.t, active, s.vfo.xitActive, - "expected VFO XIT-active to be commanded as %v", active) - return s -} - // AssertCallsignView asserts view.SetCallsign(vfo, text) was called. func (s *Scenario) AssertCallsignView(vfo core.VFOID, text string) *Scenario { s.t.Helper() @@ -712,19 +696,17 @@ func (s *scenarioSettings) Contest() core.Contest { // ---- vfoSpy ----------------------------------------------------------------- type vfoSpy struct { - ctrl *entry.Controller - vfoID core.VFOID - lastBand core.Band - lastMode core.Mode - lastFreq core.Frequency - xitActive bool + ctrl *entry.Controller + vfoID core.VFOID + lastBand core.Band + lastMode core.Mode + lastFreq core.Frequency } func (v *vfoSpy) reset() { v.lastBand = core.NoBand v.lastMode = core.NoMode v.lastFreq = 0 - v.xitActive = false } func (v *vfoSpy) Name() string { return "STUB" } @@ -734,13 +716,16 @@ func (v *vfoSpy) Refresh() { v.ctrl.VFOBandChanged(v.vfoID, core.Band20m) v.ctrl.VFOModeChanged(v.vfoID, core.ModeCW) } -func (v *vfoSpy) SetFrequency(f core.Frequency) { v.lastFreq = f } -func (v *vfoSpy) ShiftFrequency(f core.Frequency) { v.lastFreq += f } -func (v *vfoSpy) SetBand(b core.Band) { v.lastBand = b } -func (v *vfoSpy) SetMode(m core.Mode) { v.lastMode = m } -func (v *vfoSpy) SetXIT(bool, core.Frequency) {} -func (v *vfoSpy) XITActive() bool { return v.xitActive } -func (v *vfoSpy) SetXITActive(active bool) { v.xitActive = active } +func (v *vfoSpy) SetFrequency(f core.Frequency) { v.lastFreq = f } +func (v *vfoSpy) ShiftFrequency(f core.Frequency) { v.lastFreq += f } +func (v *vfoSpy) SetBand(b core.Band) { v.lastBand = b } +func (v *vfoSpy) SetMode(m core.Mode) { v.lastMode = m } +func (v *vfoSpy) SetIncrementalTuning(core.IncrementalTuningKind, bool, core.Frequency) {} +func (v *vfoSpy) ShiftOffset(core.IncrementalTuningKind, core.Frequency) {} +func (v *vfoSpy) ToggleIncrementalTuning() {} +func (v *vfoSpy) ShiftAvailableIncrementalTuning(core.Frequency) {} +func (v *vfoSpy) IncrementalTuningActive(core.IncrementalTuningKind) bool { return false } +func (v *vfoSpy) SetIncrementalTuningActive(core.IncrementalTuningKind, bool) {} // ---- qsoListSpy ------------------------------------------------------------- @@ -823,12 +808,6 @@ func (v *viewSpy) SetFrequency(vfo core.VFOID, f core.Frequency) { } func (v *viewSpy) SetBand(vfo core.VFOID, text string) { v.record("SetBand", vfo, text) } func (v *viewSpy) SetMode(vfo core.VFOID, text string) { v.record("SetMode", vfo, text) } -func (v *viewSpy) SetXITActive(vfo core.VFOID, active bool) { - v.record("SetXITActive", vfo, active) -} -func (v *viewSpy) SetXIT(vfo core.VFOID, active bool, offset core.Frequency) { - v.record("SetXIT", vfo, active, offset) -} func (v *viewSpy) SetTXState(vfo core.VFOID, ptt bool, parrotActive bool, parrotTimeLeft time.Duration) { v.record("SetTXState", vfo, ptt, parrotActive, parrotTimeLeft) } @@ -1163,22 +1142,6 @@ func (s *Scenario) CurrentVFOChanged(vfo core.VFOID) *Scenario { return s } -// VFOXITChanged fires the XIT-changed event. -func (s *Scenario) VFOXITChanged(vfo core.VFOID, active bool, offset core.Frequency) *Scenario { - s.t.Helper() - s.resetSpies() - s.controller.VFOXITChanged(vfo, active, offset) - return s -} - -// XITActiveChanged fires the XIT-active event. -func (s *Scenario) XITActiveChanged(active bool) *Scenario { - s.t.Helper() - s.resetSpies() - s.controller.XITActiveChanged(active) - return s -} - // VFOPTTChanged fires the PTT-changed event. func (s *Scenario) VFOPTTChanged(vfo core.VFOID, active bool) *Scenario { s.t.Helper() @@ -1310,20 +1273,6 @@ func (s *Scenario) AssertVFO2Enabled(enabled bool) *Scenario { return s } -// AssertXITView asserts view.SetXIT(vfo, active, offset) was called. -func (s *Scenario) AssertXITView(vfo core.VFOID, active bool, offset core.Frequency) *Scenario { - s.t.Helper() - s.view.assertCalledWith(s.t, "SetXIT", vfo, active, offset) - return s -} - -// AssertXITActiveView asserts view.SetXITActive(vfo, active) was called. -func (s *Scenario) AssertXITActiveView(vfo core.VFOID, active bool) *Scenario { - s.t.Helper() - s.view.assertCalledWith(s.t, "SetXITActive", vfo, active) - return s -} - // AssertTXStateView asserts view.SetTXState(vfo, ptt, parrotActive, parrotTimeLeft) was called. func (s *Scenario) AssertTXStateView(vfo core.VFOID, ptt bool, parrotActive bool, parrotTimeLeft time.Duration) *Scenario { s.t.Helper() diff --git a/core/entry/null.go b/core/entry/null.go index 972d8d9..c634674 100644 --- a/core/entry/null.go +++ b/core/entry/null.go @@ -10,13 +10,11 @@ import ( type nullView struct{} -func (n *nullView) SetMyCall(string) {} -func (n *nullView) SetFrequency(core.VFOID, core.Frequency) {} -func (n *nullView) SetCallsign(core.VFOID, string) {} -func (n *nullView) SetBand(vfo core.VFOID, text string) {} -func (n *nullView) SetMode(vfo core.VFOID, text string) {} -func (n *nullView) SetXITActive(vfo core.VFOID, active bool) {} -func (n *nullView) SetXIT(vfo core.VFOID, active bool, offset core.Frequency) {} +func (n *nullView) SetMyCall(string) {} +func (n *nullView) SetFrequency(core.VFOID, core.Frequency) {} +func (n *nullView) SetCallsign(core.VFOID, string) {} +func (n *nullView) SetBand(vfo core.VFOID, text string) {} +func (n *nullView) SetMode(vfo core.VFOID, text string) {} func (n *nullView) SetTXState(vfo core.VFOID, ptt bool, parrotActive bool, parrotTimeLeft time.Duration) { } func (n *nullView) SetMyExchange(int, string) {} @@ -37,17 +35,20 @@ func (n *nullView) SetTXVFO(core.VFOID) {} type nullVFO struct{} -func (n *nullVFO) Name() string { return "" } -func (n *nullVFO) Notify(any) {} -func (n *nullVFO) Active() bool { return false } -func (n *nullVFO) Refresh() {} -func (n *nullVFO) SetFrequency(core.Frequency) {} -func (n *nullVFO) ShiftFrequency(core.Frequency) {} -func (n *nullVFO) SetBand(core.Band) {} -func (n *nullVFO) SetMode(core.Mode) {} -func (n *nullVFO) SetXIT(bool, core.Frequency) {} -func (n *nullVFO) XITActive() bool { return false } -func (n *nullVFO) SetXITActive(bool) {} +func (n *nullVFO) Name() string { return "" } +func (n *nullVFO) Notify(any) {} +func (n *nullVFO) Active() bool { return false } +func (n *nullVFO) Refresh() {} +func (n *nullVFO) SetFrequency(core.Frequency) {} +func (n *nullVFO) ShiftFrequency(core.Frequency) {} +func (n *nullVFO) SetBand(core.Band) {} +func (n *nullVFO) SetMode(core.Mode) {} +func (n *nullVFO) SetIncrementalTuning(core.IncrementalTuningKind, bool, core.Frequency) {} +func (n *nullVFO) ShiftOffset(core.IncrementalTuningKind, core.Frequency) {} +func (n *nullVFO) ToggleIncrementalTuning() {} +func (n *nullVFO) ShiftAvailableIncrementalTuning(core.Frequency) {} +func (n *nullVFO) IncrementalTuningActive(core.IncrementalTuningKind) bool { return false } +func (n *nullVFO) SetIncrementalTuningActive(core.IncrementalTuningKind, bool) {} type nullLogbook struct{} diff --git a/core/entry/usecases_test.go b/core/entry/usecases_test.go index 1fcc0d8..fdaca14 100644 --- a/core/entry/usecases_test.go +++ b/core/entry/usecases_test.go @@ -356,19 +356,6 @@ func TestB5_JumpToBandmapCall(t *testing.T) { AssertNoLogbookWrite() } -// B6. XIT active toggled by UI -// Pre: none. -// Post: VFO1 rig commanded with new XIT-active flag; view active field reapplied. -// Invariants: input values; VFO2 XIT. - -func TestB6_XITActive_Toggle(t *testing.T) { - NewScenario(t). - WithClassicExchange(). - SetXITActive(true). - AssertXITActiveCommanded(true). - AssertActiveField(core.VFO1, core.CallsignField) -} - // C1. Log valid QSO // Pre: input parses fully (callsign, band, mode, their-exchange complete and valid). // Post: AddQSO called; CallsignLogged listeners notified; row cleared (active=callsign). @@ -1454,28 +1441,6 @@ func TestJ3b_VFOModeChanged_VFO2Event_DoesNotCorruptVFO1(t *testing.T) { AssertViewNotCalledWith("SetMode", core.VFO1, "CW") } -// J4. VFOXITChanged -// Pre: event for vfo. -// Post: view.SetXIT(vfo, active, offset). - -func TestJ4_VFOXITChanged_UpdatesView(t *testing.T) { - NewScenario(t). - WithClassicExchange(). - VFOXITChanged(core.VFO1, true, 500). - AssertXITView(core.VFO1, true, 500) -} - -// J5. XITActiveChanged -// Pre: event arrives. -// Post: view.SetXITActive(VFO1, active). - -func TestJ5_XITActiveChanged_UpdatesView(t *testing.T) { - NewScenario(t). - WithClassicExchange(). - XITActiveChanged(true). - AssertXITActiveView(core.VFO1, true) -} - // J6. VFOPTTChanged // Pre: event for vfo. // Post: if VFO1: ptt updated, view.SetTXState; else: view.SetTXState(vfo, active, false, 0). diff --git a/core/hamlib/hamlib.go b/core/hamlib/hamlib.go index f3e5c30..5f257bb 100644 --- a/core/hamlib/hamlib.go +++ b/core/hamlib/hamlib.go @@ -51,6 +51,9 @@ type vfoState struct { xitActive bool xitOffset core.Frequency xitAvailable bool + ritActive bool + ritOffset core.Frequency + ritAvailable bool } func New(address string, bandplan bandplan.Bandplan, vfo1, vfo2 string) *Client { @@ -289,28 +292,24 @@ func (c *Client) pollDualVFO(state []vfoState) (hl.VFO, []vfoState, error) { // pollVFOAdditional must only be called from the run goroutine! func (c *Client) pollVFOAdditional(vfo hl.VFO, state vfoState) vfoState { result := state - var err error + result.xitActive, result.xitOffset, result.xitAvailable = c.pollIncrementalTuning(vfo, hl.XITFunction, c.client.GetXIT) + result.ritActive, result.ritOffset, result.ritAvailable = c.pollIncrementalTuning(vfo, hl.RITFunction, c.client.GetRIT) + return result +} - result.xitAvailable = true - result.xitActive, err = c.client.GetFunc(vfo, hl.XITFunction) +func (c *Client) pollIncrementalTuning(vfo hl.VFO, function hl.Function, getOffset func(hl.VFO) (hl.Frequency, error)) (bool, core.Frequency, bool) { + active, err := c.client.GetFunc(vfo, function) if err != nil { - result.xitActive = false - result.xitAvailable = false + return false, 0, false } - var xitOffset hl.Frequency - if result.xitActive { - xitOffset, err = c.client.GetXIT(vfo) - if err != nil { - result.xitActive = false - result.xitAvailable = false - xitOffset = 0 - } - } else { - xitOffset = 0 + if !active { + return false, 0, true } - result.xitOffset = core.Frequency(xitOffset) - - return result + offset, err := getOffset(vfo) + if err != nil { + return false, 0, false + } + return true, core.Frequency(offset), true } func (c *Client) doInLoop(f func()) { @@ -461,26 +460,34 @@ func (c *Client) SetBandplan(bandplan bandplan.Bandplan) { }) } -func (c *Client) SetXIT(active bool, offset core.Frequency) { - // TODO: add the VFOID to all VFO-related Setters - vfo := core.VFO1 +func (c *Client) SetIncrementalTuning(vfo core.VFOID, kind core.IncrementalTuningKind, active bool, offset core.Frequency) { + function := hl.XITFunction + setOffset := c.client.SetXIT + if kind == core.RIT { + function = hl.RITFunction + setOffset = c.client.SetRIT + } c.doInLoop(func() { - if active == c.lastState[vfo].xitActive && offset == c.lastState[vfo].xitOffset { + lastActive, lastOffset := c.lastState[vfo].xitActive, c.lastState[vfo].xitOffset + if kind == core.RIT { + lastActive, lastOffset = c.lastState[vfo].ritActive, c.lastState[vfo].ritOffset + } + if active == lastActive && offset == lastOffset { return } - if active != c.lastState[vfo].xitActive { - err := c.client.SetFunc(c.vfos[vfo], hl.XITFunction, active) + if active != lastActive { + err := c.client.SetFunc(c.vfos[vfo], function, active) if err != nil { - log.Printf("hamlib: cannot set XIT function: %v", err) + log.Printf("hamlib: cannot set %s function: %v", kind, err) return } } - if active && (offset != c.lastState[vfo].xitOffset) { - err := c.client.SetXIT(c.vfos[vfo], hl.Frequency(offset)) + if active && (offset != lastOffset) { + err := setOffset(c.vfos[vfo], hl.Frequency(offset)) if err != nil { - log.Printf("hamlib: cannot set XIT offset: %v", err) + log.Printf("hamlib: cannot set %s offset: %v", kind, err) return } } @@ -627,12 +634,20 @@ func (c *Client) emitChangeNotifications(vfo core.VFOID, last, current vfoState) if last.mode != current.mode { c.emitModeChanged(vfo, current.mode) } - if (last.xitActive != current.xitActive) || (current.xitActive && (last.xitOffset != current.xitOffset)) { - c.emitXITChanged(vfo, current.xitActive, current.xitOffset) - } + c.emitIncrementalTuningChanges(vfo, core.XIT, last.xitActive, last.xitOffset, last.xitAvailable, current.xitActive, current.xitOffset, current.xitAvailable) + c.emitIncrementalTuningChanges(vfo, core.RIT, last.ritActive, last.ritOffset, last.ritAvailable, current.ritActive, current.ritOffset, current.ritAvailable) }() } +func (c *Client) emitIncrementalTuningChanges(vfo core.VFOID, kind core.IncrementalTuningKind, lastActive bool, lastOffset core.Frequency, lastAvailable bool, currentActive bool, currentOffset core.Frequency, currentAvailable bool) { + if lastAvailable != currentAvailable { + c.emitIncrementalTuningAvailabilityChanged(vfo, kind, currentAvailable) + } + if (lastActive != currentActive) || (currentActive && (lastOffset != currentOffset)) { + c.emitIncrementalTuningChanged(vfo, kind, currentActive, currentOffset) + } +} + func (c *Client) emitFrequencyChanged(vfo core.VFOID, frequency core.Frequency) { core.Emit(c.listeners, func(listener core.VFOFrequencyListener) { listener.VFOFrequencyChanged(vfo, frequency) @@ -651,9 +666,15 @@ func (c *Client) emitModeChanged(vfo core.VFOID, mode core.Mode) { }) } -func (c *Client) emitXITChanged(vfo core.VFOID, active bool, offset core.Frequency) { - core.Emit(c.listeners, func(listener core.VFOXITListener) { - listener.VFOXITChanged(vfo, active, offset) +func (c *Client) emitIncrementalTuningChanged(vfo core.VFOID, kind core.IncrementalTuningKind, active bool, offset core.Frequency) { + core.Emit(c.listeners, func(listener core.VFOIncrementalTuningListener) { + listener.VFOIncrementalTuningChanged(vfo, kind, active, offset) + }) +} + +func (c *Client) emitIncrementalTuningAvailabilityChanged(vfo core.VFOID, kind core.IncrementalTuningKind, available bool) { + core.Emit(c.listeners, func(listener core.IncrementalTuningAvailabilityListener) { + listener.IncrementalTuningAvailabilityChanged(vfo, kind, available) }) } diff --git a/core/mocked/mocked.go b/core/mocked/mocked.go index cbc7ee5..daef103 100644 --- a/core/mocked/mocked.go +++ b/core/mocked/mocked.go @@ -233,20 +233,6 @@ func (m *EntryView) SetMode(vfo core.VFOID, text string) { m.Called(vfo, text) } -func (m *EntryView) SetXITActive(vfo core.VFOID, active bool) { - if !m.active { - return - } - m.Called(vfo, active) -} - -func (m *EntryView) SetXIT(vfo core.VFOID, active bool, offset core.Frequency) { - if !m.active { - return - } - m.Called(vfo, active, offset) -} - func (m *EntryView) SetTXState(vfo core.VFOID, ptt bool, parrotActive bool, parrotTimeLeft time.Duration) { if !m.active { return diff --git a/core/radio/radio.go b/core/radio/radio.go index 2d13147..6e4717b 100644 --- a/core/radio/radio.go +++ b/core/radio/radio.go @@ -18,6 +18,8 @@ const ( hamlibVFO2Option = "vfo2" tciTRXOption = "trx" tciTRX2Option = "trx2" + + incrementalTuningPerVFOOption = "rit_xit_per_vfo" ) type View interface { @@ -43,6 +45,10 @@ type Controller struct { activeKeyerName string radioAsKeyer bool sendSpotsToTci bool + + vfos []core.VFO + currentVFO core.VFOID + incrementalTuningPerVFO bool } type radio interface { @@ -55,7 +61,7 @@ type radio interface { SetFrequency(core.VFOID, core.Frequency) SetBand(core.VFOID, core.Band) SetMode(core.VFOID, core.Mode) - SetXIT(bool, core.Frequency) + SetIncrementalTuning(core.VFOID, core.IncrementalTuningKind, bool, core.Frequency) MuteAudio(core.VFOID) UnmuteAudio(core.VFOID) ToggleAudio(core.VFOID) @@ -158,6 +164,10 @@ func (c *Controller) emitRadioChanged(name string, singleVFO bool) { core.Emit(c.listeners, func(listener core.RadioChangedListener) { listener.RadioChanged(name, singleVFO) }) + c.incrementalTuningPerVFO = false + if config, ok := c.radioConfig(name); ok { + c.incrementalTuningPerVFO = config.Options[incrementalTuningPerVFOOption] == "true" + } if c.view != nil { c.doIgnoreUpdates(func() { c.view.SetRadioSelected(name) @@ -225,6 +235,7 @@ func (c *Controller) SelectRadio(name string) error { c.activeRadio.Notify(listener) } c.activeRadio.Notify(core.ConnectionChangedFunc(c.onRadioConnectionChanged)) + c.activeRadio.Notify(core.CurrentVFOChangedFunc(c.onCurrentVFOChanged)) c.emitRadioChanged(config.Name, c.activeRadio.SingleVFO()) c.onRadioConnectionChanged(c.activeRadio.IsConnected()) @@ -310,12 +321,78 @@ func (c *Controller) SingleVFO() bool { } func (c *Controller) SetCurrentVFO(vfo core.VFOID) { + c.currentVFO = vfo if c.activeRadio == nil { return } c.activeRadio.SetCurrentVFO(vfo) } +func (c *Controller) onCurrentVFOChanged(vfo core.VFOID) { + c.currentVFO = vfo +} + +func (c *Controller) SetVFO(id core.VFOID, vfo core.VFO) { + if len(c.vfos) == 0 { + c.vfos = make([]core.VFO, core.VFOCount) + } + c.vfos[id] = vfo +} + +func (c *Controller) incrementalTuningAvailableOnVFO(vfo core.VFOID) bool { + return (vfo == core.VFO1) || c.incrementalTuningPerVFO +} + +func (c *Controller) SetIncrementalTuningActive(vfo core.VFOID, kind core.IncrementalTuningKind, active bool) { + if !c.incrementalTuningAvailableOnVFO(vfo) { + return + } + c.vfos[vfo].SetIncrementalTuningActive(kind, active) +} + +func (c *Controller) SetFocusedIncrementalTuningActive(kind core.IncrementalTuningKind, active bool) { + c.SetIncrementalTuningActive(c.currentVFO, kind, active) +} + +func (c *Controller) IncrementalTuningActive(vfo core.VFOID, kind core.IncrementalTuningKind) bool { + if !c.incrementalTuningAvailableOnVFO(vfo) { + return false + } + return c.vfos[vfo].IncrementalTuningActive(kind) +} + +func (c *Controller) FocusedIncrementalTuningActive(kind core.IncrementalTuningKind) bool { + return c.IncrementalTuningActive(c.currentVFO, kind) +} + +func (c *Controller) ShiftIncrementalTuning(kind core.IncrementalTuningKind, delta core.Frequency) { + if !c.incrementalTuningAvailableOnVFO(c.currentVFO) { + return + } + c.vfos[c.currentVFO].ShiftOffset(kind, delta) +} + +func (c *Controller) ToggleIncrementalTuning() { + if !c.incrementalTuningAvailableOnVFO(c.currentVFO) { + return + } + c.vfos[c.currentVFO].ToggleIncrementalTuning() +} + +func (c *Controller) IncrementalTuningUp() { + if !c.incrementalTuningAvailableOnVFO(c.currentVFO) { + return + } + c.vfos[c.currentVFO].ShiftAvailableIncrementalTuning(1) +} + +func (c *Controller) IncrementalTuningDown() { + if !c.incrementalTuningAvailableOnVFO(c.currentVFO) { + return + } + c.vfos[c.currentVFO].ShiftAvailableIncrementalTuning(-1) +} + func (c *Controller) SetTXVFO(vfo core.VFOID) { if c.activeRadio == nil { return @@ -344,11 +421,11 @@ func (c *Controller) SetMode(vfo core.VFOID, mode core.Mode) { c.activeRadio.SetMode(vfo, mode) } -func (c *Controller) SetXIT(active bool, offset core.Frequency) { +func (c *Controller) SetIncrementalTuning(vfo core.VFOID, kind core.IncrementalTuningKind, active bool, offset core.Frequency) { if c.activeRadio == nil { return } - c.activeRadio.SetXIT(active, offset) + c.activeRadio.SetIncrementalTuning(vfo, kind, active, offset) } func (c *Controller) MuteAudio(vfo core.VFOID) { diff --git a/core/radio/radio_test.go b/core/radio/radio_test.go new file mode 100644 index 0000000..b17cf33 --- /dev/null +++ b/core/radio/radio_test.go @@ -0,0 +1,73 @@ +package radio + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ftl/hellocontest/core" +) + +type vfoSpy struct { + setActive bool + activeKind core.IncrementalTuningKind + active bool + toggled bool + shiftedAvail core.Frequency +} + +func (v *vfoSpy) Name() string { return "" } +func (v *vfoSpy) Notify(any) {} +func (v *vfoSpy) Refresh() {} +func (v *vfoSpy) SetFrequency(core.Frequency) {} +func (v *vfoSpy) ShiftFrequency(core.Frequency) {} +func (v *vfoSpy) SetBand(core.Band) {} +func (v *vfoSpy) SetMode(core.Mode) {} +func (v *vfoSpy) SetIncrementalTuning(core.IncrementalTuningKind, bool, core.Frequency) {} +func (v *vfoSpy) ShiftOffset(core.IncrementalTuningKind, core.Frequency) {} +func (v *vfoSpy) IncrementalTuningActive(core.IncrementalTuningKind) bool { return v.active } +func (v *vfoSpy) SetIncrementalTuningActive(kind core.IncrementalTuningKind, active bool) { + v.setActive = true + v.activeKind = kind + v.active = active +} +func (v *vfoSpy) ToggleIncrementalTuning() { v.toggled = true } +func (v *vfoSpy) ShiftAvailableIncrementalTuning(sign core.Frequency) { v.shiftedAvail = sign } + +func newTestController(v1, v2 core.VFO) *Controller { + c := NewController(nil, nil, nil) + c.SetVFO(core.VFO1, v1) + c.SetVFO(core.VFO2, v2) + return c +} + +func TestIncrementalTuningRouting_MainVFOOnly(t *testing.T) { + v1, v2 := &vfoSpy{}, &vfoSpy{} + c := newTestController(v1, v2) + c.SetCurrentVFO(core.VFO2) + + c.SetIncrementalTuningActive(core.VFO2, core.XIT, true) + assert.False(t, v1.setActive, "explicit setting on VFO2 is ignored") + assert.False(t, v2.setActive) + + c.ToggleIncrementalTuning() + assert.False(t, v1.toggled, "toggle on VFO2 is ignored") + assert.False(t, v2.toggled) +} + +func TestIncrementalTuningRouting_PerVFO(t *testing.T) { + v1, v2 := &vfoSpy{}, &vfoSpy{} + c := newTestController(v1, v2) + c.incrementalTuningPerVFO = true + c.SetCurrentVFO(core.VFO2) + + c.SetIncrementalTuningActive(core.VFO2, core.XIT, true) + assert.True(t, v2.setActive, "explicit VFO2 targets VFO2 when flag is on") + assert.False(t, v1.setActive) + + c.ToggleIncrementalTuning() + assert.True(t, v2.toggled, "focused action targets the current VFO") + + c.IncrementalTuningUp() + assert.Equal(t, core.Frequency(1), v2.shiftedAvail) +} diff --git a/core/tci/tci.go b/core/tci/tci.go index c3e38d8..0de1a87 100644 --- a/core/tci/tci.go +++ b/core/tci/tci.go @@ -171,9 +171,15 @@ func (c *Client) emitModeChanged(vfo core.VFOID, mode core.Mode) { }) } -func (c *Client) emitXITChanged(vfo core.VFOID, active bool, offset core.Frequency) { - core.Emit(c.listeners, func(listener core.VFOXITListener) { - listener.VFOXITChanged(vfo, active, offset) +func (c *Client) emitIncrementalTuningChanged(vfo core.VFOID, kind core.IncrementalTuningKind, active bool, offset core.Frequency) { + core.Emit(c.listeners, func(listener core.VFOIncrementalTuningListener) { + listener.VFOIncrementalTuningChanged(vfo, kind, active, offset) + }) +} + +func (c *Client) emitIncrementalTuningAvailabilityChanged(vfo core.VFOID, kind core.IncrementalTuningKind, available bool) { + core.Emit(c.listeners, func(listener core.IncrementalTuningAvailabilityListener) { + listener.IncrementalTuningAvailabilityChanged(vfo, kind, available) }) } @@ -233,16 +239,23 @@ func (c *Client) SetMode(vfo core.VFOID, mode core.Mode) { } } -func (c *Client) SetXIT(active bool, offset core.Frequency) { - err := c.client.SetXITEnable(c.toTCITRX(c.currentVFO), active) +func (c *Client) SetIncrementalTuning(vfo core.VFOID, kind core.IncrementalTuningKind, active bool, offset core.Frequency) { + setEnable := c.client.SetXITEnable + setOffset := c.client.SetXITOffset + if kind == core.RIT { + setEnable = c.client.SetRITEnable + setOffset = c.client.SetRITOffset + } + + err := setEnable(c.toTCITRX(vfo), active) if err != nil { - log.Printf("cannot enable XIT: %v", err) + log.Printf("cannot enable %s: %v", kind, err) return } - err = c.client.SetXITOffset(c.toTCITRX(c.currentVFO), int(offset)) + err = setOffset(c.toTCITRX(vfo), int(offset)) if err != nil { - log.Printf("cannot set XIT offset: %v", err) + log.Printf("cannot set %s offset: %v", kind, err) return } } @@ -329,6 +342,8 @@ type trxListener struct { mode core.Mode xitActive bool xitOffset core.Frequency + ritActive bool + ritOffset core.Frequency ptt bool } @@ -336,7 +351,10 @@ func (l *trxListener) Refresh() { l.client.emitFrequencyChanged(l.vfo, l.frequency) l.client.emitBandChanged(l.vfo, l.band) l.client.emitModeChanged(l.vfo, l.mode) - l.client.emitXITChanged(l.vfo, l.xitActive, l.xitOffset) + l.client.emitIncrementalTuningAvailabilityChanged(l.vfo, core.XIT, true) + l.client.emitIncrementalTuningAvailabilityChanged(l.vfo, core.RIT, true) + l.client.emitIncrementalTuningChanged(l.vfo, core.XIT, l.xitActive, l.xitOffset) + l.client.emitIncrementalTuningChanged(l.vfo, core.RIT, l.ritActive, l.ritOffset) l.client.emitPTTChanged(l.vfo, l.ptt) } @@ -394,8 +412,7 @@ func (l *trxListener) SetXITEnable(trx int, active bool) { return } l.xitActive = incomingActive - l.client.emitXITChanged(l.vfo, l.xitActive, l.xitOffset) - // log.Printf("incoming XIT active %v", incomingActive) + l.client.emitIncrementalTuningChanged(l.vfo, core.XIT, l.xitActive, l.xitOffset) } func (l *trxListener) SetXITOffset(trx int, offset int) { @@ -407,8 +424,30 @@ func (l *trxListener) SetXITOffset(trx int, offset int) { return } l.xitOffset = incomingOffset - l.client.emitXITChanged(l.vfo, l.xitActive, l.xitOffset) - // log.Printf("incoming XIT offset %v", incomingOffset) + l.client.emitIncrementalTuningChanged(l.vfo, core.XIT, l.xitActive, l.xitOffset) +} + +func (l *trxListener) SetRITEnable(trx int, active bool) { + if trx != l.trx { + return + } + if active == l.ritActive { + return + } + l.ritActive = active + l.client.emitIncrementalTuningChanged(l.vfo, core.RIT, l.ritActive, l.ritOffset) +} + +func (l *trxListener) SetRITOffset(trx int, offset int) { + if trx != l.trx { + return + } + incomingOffset := core.Frequency(offset) + if incomingOffset == l.ritOffset { + return + } + l.ritOffset = incomingOffset + l.client.emitIncrementalTuningChanged(l.vfo, core.RIT, l.ritActive, l.ritOffset) } func (l *trxListener) SetTX(trx int, enable bool) { diff --git a/core/vfo/incrementaltuning.go b/core/vfo/incrementaltuning.go new file mode 100644 index 0000000..6d76905 --- /dev/null +++ b/core/vfo/incrementaltuning.go @@ -0,0 +1,122 @@ +package vfo + +import ( + "github.com/ftl/hellocontest/core" +) + +type incrementalTuningState struct { + intentActive bool + actualActive bool + offset core.Frequency +} + +type IncrementalTuningControl struct { + state [2]incrementalTuningState + available [2]bool + workmode core.Workmode + + vfo *VFO +} + +func (x *IncrementalTuningControl) IncrementalTuningActive(kind core.IncrementalTuningKind) bool { + return x.state[kind].intentActive +} + +func (x *IncrementalTuningControl) SetIncrementalTuningActive(kind core.IncrementalTuningKind, active bool) { + x.state[kind].intentActive = active + x.activate() + x.emitIncrementalTuningActiveChanged(kind, active) +} + +func (x *IncrementalTuningControl) ShiftOffset(kind core.IncrementalTuningKind, delta core.Frequency) { + x.state[kind].offset += delta + x.vfo.SetIncrementalTuning(kind, x.state[kind].actualActive, x.state[kind].offset) +} + +func (x *IncrementalTuningControl) ToggleIncrementalTuning() { + kind, ok := x.AvailableIncrementalTuningKind() + if !ok { + return + } + x.SetIncrementalTuningActive(kind, !x.state[kind].intentActive) +} + +func (x *IncrementalTuningControl) ShiftAvailableIncrementalTuning(sign core.Frequency) { + kind, ok := x.AvailableIncrementalTuningKind() + if !ok { + return + } + step := core.DefaultXITShift + if kind == core.RIT { + step = core.DefaultRITShift + } + x.ShiftOffset(kind, sign*step) +} + +func (x *IncrementalTuningControl) AvailableIncrementalTuningKind() (core.IncrementalTuningKind, bool) { + for _, kind := range []core.IncrementalTuningKind{core.RIT, core.XIT} { + if x.available[kind] && kind.Workmode() == x.workmode { + return kind, true + } + } + return 0, false +} + +func (x *IncrementalTuningControl) visible(kind core.IncrementalTuningKind) bool { + return x.available[kind] && kind.Workmode() == x.workmode +} + +func (x *IncrementalTuningControl) WorkmodeChanged(vfo core.VFOID, workmode core.Workmode) { + if vfo != x.vfo.id { + return + } + x.workmode = workmode + x.activate() + for _, kind := range []core.IncrementalTuningKind{core.RIT, core.XIT} { + x.emitIncrementalTuningVisibilityChanged(kind) + } +} + +func (x *IncrementalTuningControl) activate() { + for _, kind := range []core.IncrementalTuningKind{core.RIT, core.XIT} { + x.state[kind].actualActive = x.state[kind].intentActive && kind.Workmode() == x.workmode + x.vfo.SetIncrementalTuning(kind, x.state[kind].actualActive, x.state[kind].offset) + } +} + +func (x *IncrementalTuningControl) VFOIncrementalTuningChanged(vfo core.VFOID, kind core.IncrementalTuningKind, active bool, offset core.Frequency) { + if vfo != x.vfo.id { + return + } + x.state[kind].actualActive = active + x.state[kind].offset = offset +} + +func (x *IncrementalTuningControl) IncrementalTuningAvailabilityChanged(vfo core.VFOID, kind core.IncrementalTuningKind, available bool) { + if vfo != x.vfo.id { + return + } + x.available[kind] = available + x.emitIncrementalTuningVisibilityChanged(kind) +} + +func (x *IncrementalTuningControl) emitIncrementalTuningActiveChanged(kind core.IncrementalTuningKind, active bool) { + for _, listener := range x.vfo.listeners { + if l, ok := listener.(core.IncrementalTuningActiveListener); ok { + x.vfo.asyncRunner(func() { + l.IncrementalTuningActiveChanged(x.vfo.id, kind, active) + }) + } + } +} + +func (x *IncrementalTuningControl) emitIncrementalTuningVisibilityChanged(kind core.IncrementalTuningKind) { + visible := x.visible(kind) + for _, listener := range x.vfo.listeners { + if l, ok := listener.(core.IncrementalTuningVisibilityListener); ok { + x.vfo.asyncRunner(func() { + l.IncrementalTuningVisibilityChanged(x.vfo.id, kind, visible) + }) + } + } +} diff --git a/core/vfo/vfo.go b/core/vfo/vfo.go index 2b0b275..cde1c18 100644 --- a/core/vfo/vfo.go +++ b/core/vfo/vfo.go @@ -18,7 +18,7 @@ type Client interface { SetFrequency(core.VFOID, core.Frequency) SetBand(core.VFOID, core.Band) SetMode(core.VFOID, core.Mode) - SetXIT(bool, core.Frequency) + SetIncrementalTuning(core.VFOID, core.IncrementalTuningKind, bool, core.Frequency) MuteAudio(core.VFOID) UnmuteAudio(core.VFOID) ToggleAudio(core.VFOID) @@ -30,7 +30,7 @@ type Logbook interface { } type VFO struct { - XITControl + IncrementalTuningControl id core.VFOID name string @@ -53,7 +53,7 @@ func NewVFO(id core.VFOID, name string, bandplan bandplan.Bandplan, logbook Logb logbook: logbook, asyncRunner: asyncRunner, } - result.XITControl = XITControl{ + result.IncrementalTuningControl = IncrementalTuningControl{ vfo: result, } result.offlineClient = newOfflineClient(result) @@ -136,11 +136,11 @@ func (v *VFO) SetMode(mode core.Mode) { } } -func (v *VFO) SetXIT(active bool, offset core.Frequency) { +func (v *VFO) SetIncrementalTuning(kind core.IncrementalTuningKind, active bool, offset core.Frequency) { if v.online() { - v.client.SetXIT(active, offset) + v.client.SetIncrementalTuning(v.id, kind, active, offset) } else { - v.offlineClient.SetXIT(active, offset) + v.offlineClient.SetIncrementalTuning(kind, active, offset) } } @@ -209,12 +209,12 @@ func (v *VFO) VFOModeChanged(vfo core.VFOID, mode core.Mode) { v.offlineClient.SetMode(mode) } -func (v *VFO) VFOXITChanged(vfo core.VFOID, active bool, offset core.Frequency) { +func (v *VFO) VFOIncrementalTuningChanged(vfo core.VFOID, kind core.IncrementalTuningKind, active bool, offset core.Frequency) { if vfo != v.id { return } - v.XITControl.VFOXITChanged(vfo, active, offset) - v.offlineClient.SetXIT(active, offset) + v.IncrementalTuningControl.VFOIncrementalTuningChanged(vfo, kind, active, offset) + v.offlineClient.SetIncrementalTuning(kind, active, offset) } func (v *VFO) VFOPTTChanged(vfo core.VFOID, active bool) { @@ -248,10 +248,10 @@ func (v *VFO) emitModeChanged(mode core.Mode) { }) } -func (v *VFO) emitXITChanged(active bool, offset core.Frequency) { - core.Emit(v.listeners, func(listener core.VFOXITListener) { +func (v *VFO) emitIncrementalTuningChanged(kind core.IncrementalTuningKind, active bool, offset core.Frequency) { + core.Emit(v.listeners, func(listener core.VFOIncrementalTuningListener) { v.asyncRunner(func() { - listener.VFOXITChanged(v.id, active, offset) + listener.VFOIncrementalTuningChanged(v.id, kind, active, offset) }) }) } @@ -267,8 +267,8 @@ func (v *VFO) emitPTTChanged(active bool) { type bandState struct { frequency core.Frequency mode core.Mode - xitActive bool - xitOffset core.Frequency + itActive [2]bool + itOffset [2]core.Frequency } type offlineClient struct { @@ -374,15 +374,15 @@ func (c *offlineClient) SetMode(mode core.Mode) { c.vfo.emitModeChanged(mode) } -func (c *offlineClient) SetXIT(active bool, offset core.Frequency) { +func (c *offlineClient) SetIncrementalTuning(kind core.IncrementalTuningKind, active bool, offset core.Frequency) { c.stateLock.Lock() state := c.lastState(c.currentBand) - state.xitActive = active - state.xitOffset = offset + state.itActive[kind] = active + state.itOffset[kind] = offset c.lastStates[c.currentBand] = state c.stateLock.Unlock() - c.vfo.emitXITChanged(state.xitActive, state.xitOffset) + c.vfo.emitIncrementalTuningChanged(kind, state.itActive[kind], state.itOffset[kind]) } func (c *offlineClient) SetPTT(active bool) { diff --git a/core/vfo/vfo_test.go b/core/vfo/vfo_test.go index 40089bb..e866a07 100644 --- a/core/vfo/vfo_test.go +++ b/core/vfo/vfo_test.go @@ -43,12 +43,78 @@ func TestShiftFrequency(t *testing.T) { assert.Equal(t, start, v.currentFrequency()) } -func TestShiftXITOffset(t *testing.T) { +func TestShiftOffset(t *testing.T) { v := NewVFO(core.VFO1, "VFO 1", bandplan.IARURegion1, nil, func(f func()) { f() }) - v.ShiftXITOffset(100) - assert.Equal(t, core.Frequency(100), v.offset) + v.ShiftOffset(core.XIT, 100) + assert.Equal(t, core.Frequency(100), v.state[core.XIT].offset) - v.ShiftXITOffset(-30) - assert.Equal(t, core.Frequency(70), v.offset) + v.ShiftOffset(core.XIT, -30) + assert.Equal(t, core.Frequency(70), v.state[core.XIT].offset) + + v.ShiftOffset(core.RIT, 40) + assert.Equal(t, core.Frequency(40), v.state[core.RIT].offset) + assert.Equal(t, core.Frequency(70), v.state[core.XIT].offset) +} + +type visibilitySpy struct { + visible map[core.IncrementalTuningKind]bool +} + +func (s *visibilitySpy) IncrementalTuningVisibilityChanged(vfo core.VFOID, kind core.IncrementalTuningKind, visible bool) { + if s.visible == nil { + s.visible = make(map[core.IncrementalTuningKind]bool) + } + s.visible[kind] = visible +} + +func TestIncrementalTuningVisibility(t *testing.T) { + v := NewVFO(core.VFO1, "VFO 1", bandplan.IARURegion1, nil, func(f func()) { f() }) + spy := &visibilitySpy{} + v.Notify(spy) + + v.WorkmodeChanged(core.VFO1, core.SearchPounce) + v.IncrementalTuningAvailabilityChanged(core.VFO1, core.XIT, true) + assert.True(t, spy.visible[core.XIT], "available XIT visible in S&P") + assert.False(t, spy.visible[core.RIT], "unavailable RIT hidden") + + v.WorkmodeChanged(core.VFO1, core.Run) + assert.False(t, spy.visible[core.XIT], "XIT hidden in Run") +} + +func TestToggleAndShiftAvailableIncrementalTuning(t *testing.T) { + v := NewVFO(core.VFO1, "VFO 1", bandplan.IARURegion1, nil, func(f func()) { f() }) + v.WorkmodeChanged(core.VFO1, core.SearchPounce) + v.IncrementalTuningAvailabilityChanged(core.VFO1, core.XIT, true) + + kind, ok := v.AvailableIncrementalTuningKind() + assert.True(t, ok) + assert.Equal(t, core.XIT, kind) + + v.ToggleIncrementalTuning() + assert.True(t, v.IncrementalTuningActive(core.XIT), "toggle enables the available kind") + v.ToggleIncrementalTuning() + assert.False(t, v.IncrementalTuningActive(core.XIT), "toggle again disables it") + + v.ShiftAvailableIncrementalTuning(1) + assert.Equal(t, core.DefaultXITShift, v.state[core.XIT].offset) + v.ShiftAvailableIncrementalTuning(-1) + assert.Equal(t, core.Frequency(0), v.state[core.XIT].offset) +} + +func TestWorkmodeGatesIncrementalTuning(t *testing.T) { + v := NewVFO(core.VFO1, "VFO 1", bandplan.IARURegion1, nil, func(f func()) { f() }) + v.SetIncrementalTuningActive(core.XIT, true) + v.SetIncrementalTuningActive(core.RIT, true) + + v.WorkmodeChanged(core.VFO1, core.SearchPounce) + assert.True(t, v.state[core.XIT].actualActive, "XIT active in S&P") + assert.False(t, v.state[core.RIT].actualActive, "RIT forced off in S&P") + + v.WorkmodeChanged(core.VFO1, core.Run) + assert.False(t, v.state[core.XIT].actualActive, "XIT forced off in Run") + assert.True(t, v.state[core.RIT].actualActive, "RIT active in Run") + + assert.True(t, v.IncrementalTuningActive(core.XIT), "XIT intent survives the round-trip") + assert.True(t, v.IncrementalTuningActive(core.RIT), "RIT intent survives the round-trip") } diff --git a/core/vfo/xit.go b/core/vfo/xit.go deleted file mode 100644 index 6d3e8b8..0000000 --- a/core/vfo/xit.go +++ /dev/null @@ -1,76 +0,0 @@ -package vfo - -import ( - "log" - - "github.com/ftl/hellocontest/core" -) - -type XITActiveListener interface { - XITActiveChanged(active bool) -} - -type XITControl struct { - active bool - activeOnVFO bool - workmode core.Workmode - - offset core.Frequency - - vfo *VFO -} - -func (x *XITControl) XITActive() bool { - return x.active -} - -func (x *XITControl) SetXITActive(active bool) { - x.active = active - x.activateOnVFO() - x.emitXITActiveChanged(x.active) -} - -// ShiftXITOffset changes the XIT offset by delta (may be negative), preserving -// the current active state. -func (x *XITControl) ShiftXITOffset(delta core.Frequency) { - x.offset += delta - x.vfo.SetXIT(x.activeOnVFO, x.offset) -} - -func (x *XITControl) emitXITActiveChanged(active bool) { - for _, listener := range x.vfo.listeners { - if l, ok := listener.(XITActiveListener); ok { - x.vfo.asyncRunner(func() { - l.XITActiveChanged(active) - }) - } - } -} - -func (x *XITControl) WorkmodeChanged(vfo core.VFOID, workmode core.Workmode) { - if vfo != x.vfo.id { - return - } - x.workmode = workmode - if x.active { - x.activateOnVFO() - } -} - -func (x *XITControl) activateOnVFO() { - x.activeOnVFO = x.active && (x.workmode == core.SearchPounce) - x.vfo.SetXIT(x.activeOnVFO, x.offset) -} - -func (x *XITControl) VFOXITChanged(vfo core.VFOID, active bool, offset core.Frequency) { - if vfo != x.vfo.id { - return - } - x.activeOnVFO = active - x.offset = offset - - shouldBeActive := x.active && (x.workmode == core.SearchPounce) - if shouldBeActive && !x.activeOnVFO { - log.Printf("XITControl: XIT turned off by user") - } -} diff --git a/core/workmode/workmode.go b/core/workmode/workmode.go index 6703244..9a9ceb1 100644 --- a/core/workmode/workmode.go +++ b/core/workmode/workmode.go @@ -112,10 +112,10 @@ func (c *Controller) FocusChanged(vfo core.VFOID) { } // EffectiveWorkmode returns the workmode for a given VFO. -// In SO2V (vfo2Enabled) with global=Run: VFO1=Run, VFO2=S&P. -// Otherwise: both VFOs use the global workmode. +// In SO2V (vfo2Enabled), VFO2 is always S&P; VFO1 uses the global workmode. +// Otherwise both VFOs use the global workmode. func (c *Controller) EffectiveWorkmode(vfo core.VFOID) core.Workmode { - if c.vfo2Enabled && c.workmode == core.Run && vfo == core.VFO2 { + if c.vfo2Enabled && vfo == core.VFO2 { return core.SearchPounce } return c.workmode diff --git a/ui/actions.go b/ui/actions.go index 7815331..e8c6bc5 100644 --- a/ui/actions.go +++ b/ui/actions.go @@ -16,6 +16,7 @@ type actions struct { controller *app.Controller keybindings map[string]string ignoreInput bool + focusedVFO core.VFOID allInfos []ActionInfo // Action groups @@ -52,7 +53,9 @@ type actions struct { requestQTCAction *qtlib.QAction // Radio menu - xitActiveAction *qtlib.QAction + incrementalTuningToggleAction *qtlib.QAction + xitActiveAction *qtlib.QAction + ritActiveAction *qtlib.QAction // Bandmap menu markBandmapAction *qtlib.QAction @@ -82,27 +85,31 @@ type actions struct { spotSourceActions map[string]*qtlib.QAction // Other actions - sendMacro1Action *qtlib.QAction - sendMacro2Action *qtlib.QAction - sendMacro3Action *qtlib.QAction - sendMacro4Action *qtlib.QAction - selectBestMatchAction *qtlib.QAction - nextESMStepAction *qtlib.QAction - toggleVFOAction *qtlib.QAction - focusVFO1Action *qtlib.QAction - focusVFO2Action *qtlib.QAction - muteAudioVFO1Action *qtlib.QAction - muteAudioVFO2Action *qtlib.QAction - unmuteAudioVFO1Action *qtlib.QAction - unmuteAudioVFO2Action *qtlib.QAction - toggleAudioVFO1Action *qtlib.QAction - toggleAudioVFO2Action *qtlib.QAction - frequencyUpAction *qtlib.QAction - frequencyDownAction *qtlib.QAction - xitUpAction *qtlib.QAction - xitDownAction *qtlib.QAction - speedUpAction *qtlib.QAction - speedDownAction *qtlib.QAction + sendMacro1Action *qtlib.QAction + sendMacro2Action *qtlib.QAction + sendMacro3Action *qtlib.QAction + sendMacro4Action *qtlib.QAction + selectBestMatchAction *qtlib.QAction + nextESMStepAction *qtlib.QAction + toggleVFOAction *qtlib.QAction + focusVFO1Action *qtlib.QAction + focusVFO2Action *qtlib.QAction + muteAudioVFO1Action *qtlib.QAction + muteAudioVFO2Action *qtlib.QAction + unmuteAudioVFO1Action *qtlib.QAction + unmuteAudioVFO2Action *qtlib.QAction + toggleAudioVFO1Action *qtlib.QAction + toggleAudioVFO2Action *qtlib.QAction + frequencyUpAction *qtlib.QAction + frequencyDownAction *qtlib.QAction + xitUpAction *qtlib.QAction + xitDownAction *qtlib.QAction + ritUpAction *qtlib.QAction + ritDownAction *qtlib.QAction + incrementalTuningUpAction *qtlib.QAction + incrementalTuningDownAction *qtlib.QAction + speedUpAction *qtlib.QAction + speedDownAction *qtlib.QAction } func newActions(parent *qtlib.QWidget, controller *app.Controller, keybindings map[string]string) *actions { @@ -154,7 +161,9 @@ func newActions(parent *qtlib.QWidget, controller *app.Controller, keybindings m a.requestQTCAction = a.makeTriggerAction(core.ActionEntryRequestQTC, "Request QTC", "", "F6", controller.RequestQTC) // Radio menu - a.xitActiveAction = a.makeCheckAction(core.ActionRadioXITActive, "XIT Active", "Activate the XIT for the search & pounce workmode", "Ctrl+Shift+X", controller.SetXITActive) + a.incrementalTuningToggleAction = a.makeTriggerAction(core.ActionIncrementalTuningToggle, "Toggle Incremental Tuning", "Toggle the RIT or XIT of the focused VFO", "Ctrl+Shift+X", func() { controller.ToggleIncrementalTuning() }) + a.xitActiveAction = a.makeCheckAction(core.ActionRadioXITActive, "XIT Active", "Activate the XIT for the search & pounce workmode", "", func(active bool) { controller.SetFocusedIncrementalTuningActive(core.XIT, active) }) + a.ritActiveAction = a.makeCheckAction(core.ActionRadioRITActive, "RIT Active", "Activate the RIT for the run workmode", "Ctrl+Shift+R", func(active bool) { controller.SetFocusedIncrementalTuningActive(core.RIT, active) }) // Bandmap menu a.markBandmapAction = a.makeTriggerAction(core.ActionBandmapMark, "Mark In Bandmap", "", "Ctrl+M", controller.MarkInBandmap) @@ -196,8 +205,12 @@ func newActions(parent *qtlib.QWidget, controller *app.Controller, keybindings m a.toggleAudioVFO2Action = a.makeTriggerAction(core.ActionRadioToggleAudioVFO2, "Toggle Audio VFO 2", "Toggle audio on VFO 2", "", func() { controller.ToggleAudio(core.VFO2) }) a.frequencyUpAction = a.makeTriggerAction(core.ActionRadioFrequencyUp, "Frequency Up", "Increase the frequency of the focused VFO", "Alt+Up", func() { controller.FrequencyUp() }) a.frequencyDownAction = a.makeTriggerAction(core.ActionRadioFrequencyDown, "Frequency Down", "Decrease the frequency of the focused VFO", "Alt+Down", func() { controller.FrequencyDown() }) - a.xitUpAction = a.makeTriggerAction(core.ActionRadioXITUp, "XIT Up", "Increase the XIT offset", "Alt+Shift+Up", func() { controller.XITUp() }) - a.xitDownAction = a.makeTriggerAction(core.ActionRadioXITDown, "XIT Down", "Decrease the XIT offset", "Alt+Shift+Down", func() { controller.XITDown() }) + a.xitUpAction = a.makeTriggerAction(core.ActionRadioXITUp, "XIT Up", "Increase the XIT offset", "", func() { controller.XITUp() }) + a.xitDownAction = a.makeTriggerAction(core.ActionRadioXITDown, "XIT Down", "Decrease the XIT offset", "", func() { controller.XITDown() }) + a.ritUpAction = a.makeTriggerAction(core.ActionRadioRITUp, "RIT Up", "Increase the RIT offset", "", func() { controller.RITUp() }) + a.ritDownAction = a.makeTriggerAction(core.ActionRadioRITDown, "RIT Down", "Decrease the RIT offset", "", func() { controller.RITDown() }) + a.incrementalTuningUpAction = a.makeTriggerAction(core.ActionIncrementalTuningUp, "Incremental Tuning Up", "Increase the RIT or XIT offset of the focused VFO", "Alt+Shift+Up", func() { controller.IncrementalTuningUp() }) + a.incrementalTuningDownAction = a.makeTriggerAction(core.ActionIncrementalTuningDown, "Incremental Tuning Down", "Decrease the RIT or XIT offset of the focused VFO", "Alt+Shift+Down", func() { controller.IncrementalTuningDown() }) a.speedUpAction = a.makeTriggerAction(core.ActionKeyerSpeedUp, "Speed Up", "Increase the keyer speed", "Ctrl+Alt+Up", func() { controller.KeyerSpeedUp() }) a.speedDownAction = a.makeTriggerAction(core.ActionKeyerSpeedDown, "Speed Down", "Decrease the keyer speed", "Ctrl+Alt+Down", func() { controller.KeyerSpeedDown() }) a.parent.AddActions([]*qtlib.QAction{ @@ -220,6 +233,11 @@ func newActions(parent *qtlib.QWidget, controller *app.Controller, keybindings m a.frequencyDownAction, a.xitUpAction, a.xitDownAction, + a.ritUpAction, + a.ritDownAction, + a.incrementalTuningToggleAction, + a.incrementalTuningUpAction, + a.incrementalTuningDownAction, a.speedUpAction, a.speedDownAction, }) @@ -299,7 +317,8 @@ func (a *actions) updateFromController() { a.ignoreInput = true a.esmAction.SetChecked(a.controller.ESMEnabled()) - a.xitActiveAction.SetChecked(a.controller.XITActive()) + a.xitActiveAction.SetChecked(a.controller.FocusedIncrementalTuningActive(core.XIT)) + a.ritActiveAction.SetChecked(a.controller.FocusedIncrementalTuningActive(core.RIT)) a.sendSpotsToTciAction.SetChecked(a.controller.SendSpotsToTci()) switch a.controller.Workmode.Workmode() { case core.SearchPounce: @@ -424,12 +443,27 @@ func (a *actions) ESMEnabled(enabled bool) { a.esmAction.SetChecked(enabled) } -func (a *actions) XITActiveChanged(active bool) { +func (a *actions) IncrementalTuningActiveChanged(vfo core.VFOID, kind core.IncrementalTuningKind, active bool) { + if vfo != a.focusedVFO { + return + } a.ignoreInput = true defer func() { a.ignoreInput = false }() + if kind == core.RIT { + a.ritActiveAction.SetChecked(active) + return + } a.xitActiveAction.SetChecked(active) } +func (a *actions) FocusChanged(vfo core.VFOID) { + a.focusedVFO = vfo + a.ignoreInput = true + defer func() { a.ignoreInput = false }() + a.xitActiveAction.SetChecked(a.controller.FocusedIncrementalTuningActive(core.XIT)) + a.ritActiveAction.SetChecked(a.controller.FocusedIncrementalTuningActive(core.RIT)) +} + func (a *actions) ContestPagesChanged(rulesAvailable, uploadAvailable bool) { a.openRulesAction.SetEnabled(rulesAvailable) a.openUploadAction.SetEnabled(uploadAvailable) diff --git a/ui/app.go b/ui/app.go index e1ac625..a6ad47b 100644 --- a/ui/app.go +++ b/ui/app.go @@ -75,7 +75,9 @@ func Run(version string, sponsors string, startupScript Script, args []string) { a.controller.Entry.Notify(a.actions) a.controller.Workmode.Notify(a.actions) a.controller.QTCList.Notify(a.actions) - a.controller.VFOs[core.VFO1].Notify(a.actions) + for _, v := range a.controller.VFOs { + v.Notify(a.actions) + } a.createMenu() a.createCentralWidget() @@ -194,8 +196,12 @@ func (a *application) createCentralWidget() { // setup entry view a.entryView = newEntryView() a.entryView.SetEntryController(a.controller.Entry) + a.entryView.SetIncrementalTuningController(a.controller) a.controller.Entry.SetView(a.entryView) a.controller.Entry.Notify(a.entryView) + for _, v := range a.controller.VFOs { + v.Notify(a.entryView) + } // setup callinfo view a.callinfoView = newCallinfoView() diff --git a/ui/centralArea.go b/ui/centralArea.go index f690bbd..d37fa92 100644 --- a/ui/centralArea.go +++ b/ui/centralArea.go @@ -113,6 +113,7 @@ func (a *centralArea) addVFOWidgetsToLayout(vfo core.VFOID, firstRow int) { if entry.serialClaimLabel != nil { a.entryLayout.AddWidget2(entry.serialClaimLabel.QWidget, firstRow+1, 2) } + a.entryLayout.AddWidget2(entry.rit.QWidget, firstRow+1, lastColumn-1) a.entryLayout.AddWidget2(entry.xit.QWidget, firstRow+1, lastColumn-1) a.entryLayout.AddWidget2(entry.txIndicator.QWidget, firstRow+1, lastColumn) a.entryLayout.AddWidget2(callinfo.callsignLabel.QWidget, firstRow+2, 0) @@ -155,6 +156,7 @@ func (a *centralArea) removeVFOWidgetsFromLayout(vfo core.VFOID) { if entry.serialClaimLabel != nil { a.entryLayout.RemoveWidget(entry.serialClaimLabel.QWidget) } + a.entryLayout.RemoveWidget(entry.rit.QWidget) a.entryLayout.RemoveWidget(entry.xit.QWidget) a.entryLayout.RemoveWidget(entry.txIndicator.QWidget) a.entryLayout.RemoveWidget(callinfo.callsignLabel.QWidget) diff --git a/ui/entryView.go b/ui/entryView.go index ad7bbef..1d05489 100644 --- a/ui/entryView.go +++ b/ui/entryView.go @@ -28,7 +28,6 @@ type EntryController interface { SendQuestion() RepeatLastTransmission() StopTX() - SetXITActive(bool) EnterPressed() Log() @@ -49,6 +48,7 @@ type entryVFOWidgets struct { serialClaimLabel *qtlib.QLabel xit *qtlib.QCheckBox + rit *qtlib.QCheckBox txIndicator *qtlib.QLabel callsign *qtlib.QLineEdit @@ -68,6 +68,7 @@ type entryView struct { vfo [core.VFOCount]entryVFOWidgets vfoWorkmode [core.VFOCount]core.Workmode + itVisible [core.VFOCount][2]bool txVFO core.VFOID vfo2Enabled bool @@ -77,7 +78,12 @@ type entryView struct { isDuplicate bool isEditing bool - controller EntryController + controller EntryController + incrementalTuningInput IncrementalTuningController +} + +type IncrementalTuningController interface { + SetIncrementalTuningActive(core.VFOID, core.IncrementalTuningKind, bool) } func newEntryView() *entryView { @@ -119,14 +125,23 @@ func newEntryView() *entryView { } }) v.vfo[core.VFO1].xit.OnStateChanged(func(state int) { - if v.controller != nil { - v.controller.SetXITActive(state != 0) + if v.incrementalTuningInput != nil { + v.incrementalTuningInput.SetIncrementalTuningActive(core.VFO1, core.XIT, state != 0) } }) v.vfo[core.VFO2].xit.OnStateChanged(func(state int) { - if v.controller != nil { - // TODO: handle different VFOs - v.controller.SetXITActive(state != 0) + if v.incrementalTuningInput != nil { + v.incrementalTuningInput.SetIncrementalTuningActive(core.VFO2, core.XIT, state != 0) + } + }) + v.vfo[core.VFO1].rit.OnStateChanged(func(state int) { + if v.incrementalTuningInput != nil { + v.incrementalTuningInput.SetIncrementalTuningActive(core.VFO1, core.RIT, state != 0) + } + }) + v.vfo[core.VFO2].rit.OnStateChanged(func(state int) { + if v.incrementalTuningInput != nil { + v.incrementalTuningInput.SetIncrementalTuningActive(core.VFO2, core.RIT, state != 0) } }) @@ -165,6 +180,11 @@ func newEntryVFOWidgets(prefix string, vfoName string) entryVFOWidgets { w.xit = qtlib.NewQCheckBox3("XIT") w.xit.SetObjectName(*qtlib.NewQAnyStringView3(prefix + "XIT")) + w.xit.SetVisible(false) + + w.rit = qtlib.NewQCheckBox3("RIT") + w.rit.SetObjectName(*qtlib.NewQAnyStringView3(prefix + "RIT")) + w.rit.SetVisible(false) w.txIndicator = qtlib.NewQLabel3("") w.txIndicator.SetObjectName(*qtlib.NewQAnyStringView3(prefix + "TX")) @@ -288,6 +308,10 @@ func (v *entryView) SetEntryController(controller EntryController) { v.controller = controller } +func (v *entryView) SetIncrementalTuningController(controller IncrementalTuningController) { + v.incrementalTuningInput = controller +} + func (v *entryView) SetMyCall(text string) { v.myCallLabel.SetText(text) } @@ -352,8 +376,15 @@ func (v *entryView) SetMode(vfo core.VFOID, text string) { } } -func (v *entryView) SetXITActive(vfo core.VFOID, active bool) { - widget := v.vfo[vfo].xit +func (v *entryView) incrementalTuningWidget(vfo core.VFOID, kind core.IncrementalTuningKind) *qtlib.QCheckBox { + if kind == core.RIT { + return v.vfo[vfo].rit + } + return v.vfo[vfo].xit +} + +func (v *entryView) IncrementalTuningActiveChanged(vfo core.VFOID, kind core.IncrementalTuningKind, active bool) { + widget := v.incrementalTuningWidget(vfo, kind) if widget == nil { return } @@ -363,19 +394,33 @@ func (v *entryView) SetXITActive(vfo core.VFOID, active bool) { widget.SetChecked(active) } -func (v *entryView) SetXIT(vfo core.VFOID, active bool, offset core.Frequency) { - widget := v.vfo[vfo].xit +func (v *entryView) VFOIncrementalTuningChanged(vfo core.VFOID, kind core.IncrementalTuningKind, active bool, offset core.Frequency) { + widget := v.incrementalTuningWidget(vfo, kind) if widget == nil { return } if active { - widget.SetText(fmt.Sprintf("XIT %s", offset)) + widget.SetText(fmt.Sprintf("%s %s", kind, offset)) } else { - widget.SetText("XIT") + widget.SetText(kind.String()) } } +func (v *entryView) IncrementalTuningVisibilityChanged(vfo core.VFOID, kind core.IncrementalTuningKind, visible bool) { + v.itVisible[vfo][kind] = visible + v.applyIncrementalTuningVisibility(vfo, kind) +} + +func (v *entryView) applyIncrementalTuningVisibility(vfo core.VFOID, kind core.IncrementalTuningKind) { + widget := v.incrementalTuningWidget(vfo, kind) + if widget == nil { + return + } + vfoEnabled := vfo == core.VFO1 || v.vfo2Enabled + widget.SetVisible(v.itVisible[vfo][kind] && vfoEnabled) +} + func (v *entryView) SetTXState(vfo core.VFOID, ptt bool, parrotActive bool, parrotTimeLeft time.Duration) { widget := v.vfo[vfo].txIndicator if widget == nil { @@ -671,9 +716,8 @@ func (v *entryView) setVFO2Enabled(enabled bool) { if widgets.vfoContainer != nil { widgets.vfoContainer.SetVisible(enabled) } - if widgets.xit != nil { - widgets.xit.SetVisible(enabled) - } + v.applyIncrementalTuningVisibility(core.VFO2, core.XIT) + v.applyIncrementalTuningVisibility(core.VFO2, core.RIT) if widgets.txIndicator != nil { widgets.txIndicator.SetVisible(enabled) } diff --git a/ui/mainMenu.go b/ui/mainMenu.go index fd57a6b..2b68424 100644 --- a/ui/mainMenu.go +++ b/ui/mainMenu.go @@ -64,7 +64,9 @@ func newMainMenu( // ── Radio ──────────────────────────────────────────────────────────── radioSubmenu := m.menuBar.AddMenuWithTitle("&Radio") + radioSubmenu.AddAction(a.incrementalTuningToggleAction) radioSubmenu.AddAction(a.xitActiveAction) + radioSubmenu.AddAction(a.ritActiveAction) radioSubmenu.AddSeparator() radioMenu.menu = radioSubmenu