feat(slices): per-pan tuning wheel and per-slice RX mute on TX

Mouse wheel over an extra panadapter now retunes that pan's primary
slice (first created on that receiver) instead of the main VFO; a slice
flag under the cursor still wins, pan 0 keeps driving the VFO.

Slice flags get a clickable RXM badge: RX audio of the slice is muted
while transmitting (default), so the operator no longer hears himself on
every slice in full duplex. State persists with the workspace.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 22:35:19 +03:00
co-authored by Claude Opus 4.8
parent fa5a95ca5d
commit 28ae832b71
5 changed files with 112 additions and 8 deletions
+25
View File
@@ -579,6 +579,7 @@ type
procedure OnVfoOverlayAudioDevice(DevIndex: Integer; const DevName: string);
procedure OnVfoOverlaySplit(Sender: TObject);
procedure OnVfoOverlayTxSelect(Sender: TObject);
procedure OnVfoOverlayRxMuteTx(Sender: TObject); // RXM — приём слайса на TX
procedure RefreshTxIndicators; // TX-бейджи всех флагов
procedure OnVfoOverlaySliceSelect(Sender: TObject); // выбрать слайс активным
procedure OnVfoOverlayClose(Sender: TObject); // × — удалить слайс
@@ -5457,6 +5458,17 @@ begin
FController.SetTxSlice(CurSliceId); // → Changed(rfTransmitting) → RefreshTxIndicators
end;
procedure TMainForm.OnVfoOverlayRxMuteTx(Sender: TObject);
// Бейдж RXM на флаге слайса: слышим ли этот слайс, пока сами передаём.
// У главного приёмника то же делает кнопка RX MUTE на левой панели.
var S: TCtrlSlice;
begin
if CurSliceId = 0 then Exit; // у флага A бейджа RXM нет (страховка)
if not FController.GetSlice(CurSliceId, S) then Exit;
FController.SetSliceRxMuteOnTx(CurSliceId, not S.RxMuteOnTx);
PushFlagStateAllPans(CurSliceId);
end;
procedure TMainForm.RefreshTxIndicators;
// Обновить TX-бейджи (TxSel/Tx) на всех флагах всех панов после смены
// TX-слайса / состояния передачи.
@@ -5539,6 +5551,7 @@ begin
O.OnAudioDevice := OnVfoOverlayAudioDevice;
O.OnSplit := OnVfoOverlaySplit;
O.OnTxSelect := OnVfoOverlayTxSelect;
O.OnRxMuteTx := OnVfoOverlayRxMuteTx;
O.OnSliceSelect := OnVfoOverlaySliceSelect;
O.OnClose := OnVfoOverlayClose;
O.OnInvalidate := OnVfoOverlayInvalidate;
@@ -6477,6 +6490,7 @@ begin
Cfg.Pans[i].Slices[n].AGC := Ord(FController.FSlices[s].AGC);
Cfg.Pans[i].Slices[n].Volume := FController.FSlices[s].Volume;
Cfg.Pans[i].Slices[n].Muted := FController.FSlices[s].Muted;
Cfg.Pans[i].Slices[n].RxMuteOnTx := FController.FSlices[s].RxMuteOnTx;
Cfg.Pans[i].Slices[n].FMSQOn := FController.FSlices[s].FMSQOn;
Cfg.Pans[i].Slices[n].FMSQLevel := FController.FSlices[s].FMSQLevel;
Cfg.Pans[i].Slices[n].DevName := FController.FSlices[s].DevName;
@@ -6501,6 +6515,7 @@ begin
EnsureRange(SC.Volume, 0.0, 1.0), DevIdx, SC.DevName, PanId);
if Id < 0 then Exit;
if SC.Muted then FController.SetSliceMute(Id, True);
FController.SetSliceRxMuteOnTx(Id, SC.RxMuteOnTx);
if SC.FMSQOn then FController.SetSliceFMSquelch(Id, True, SC.FMSQLevel);
P.CreateSliceFlag(Id);
FActiveSliceId := Id;
@@ -7604,6 +7619,16 @@ begin
SliceId := FPans[i].SliceUnderCursor;
if SliceId <> 0 then begin SlicePan := FPans[i]; Break; end;
end;
// Иначе колесо над спектром/водопадом ДОП. пана крутит его главный слайс
// (первый созданный на этом приёмнике), а не общий VFO. Пан 0 — общий VFO.
if SliceId = 0 then
for i := 1 to MAX_PANS - 1 do
if (FPans[i] <> nil) and FPans[i].CursorOverDisplay then
begin
SliceId := FController.PanPrimarySliceId(i);
if SliceId <> 0 then SlicePan := FPans[i];
Break;
end;
if (SliceId <> 0) and FController.GetSlice(SliceId, Sl) then
begin
if (Sl.Mode = MODE_DMR) and