mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 19:45:09 +00:00
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:
@@ -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
|
||||
|
||||
@@ -275,6 +275,7 @@ type
|
||||
function SliceAtPixel(X: Integer): Integer;
|
||||
// SliceId флага под курсором мыши (прямоугольник оверлея; 0 = нет).
|
||||
function SliceUnderCursor: Integer;
|
||||
function CursorOverDisplay: Boolean; // курсор над спектром/водопадом пана
|
||||
// Единый packing всех флагов (A+B+) в один ряд, без наездов.
|
||||
procedure LayoutFlags;
|
||||
// Флаг A виден: панель хозяина скрыта (MainFlagPinned) ИЛИ есть слайсы B+.
|
||||
@@ -1142,6 +1143,22 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
// Курсор над спектром/водопадом этого пана? (колесо крутит «его» частоту)
|
||||
function TPanafallPanel.CursorOverDisplay: Boolean;
|
||||
|
||||
function Over(C: TControl): Boolean;
|
||||
var P: TPoint;
|
||||
begin
|
||||
Result := False;
|
||||
if (C = nil) or (not C.Visible) then Exit;
|
||||
P := C.ScreenToClient(Mouse.CursorPos);
|
||||
Result := (P.X >= 0) and (P.Y >= 0) and (P.X < C.Width) and (P.Y < C.Height);
|
||||
end;
|
||||
|
||||
begin
|
||||
Result := Over(FPbSpectrum) or Over(FPbWaterfall);
|
||||
end;
|
||||
|
||||
// Флаг слайса под курсором мыши (в клиентских координатах спектра)?
|
||||
function TPanafallPanel.SliceUnderCursor: Integer;
|
||||
var P: TPoint; i: Integer; O: TVfoOverlay;
|
||||
@@ -1321,6 +1338,7 @@ begin
|
||||
O.SetExtState(Round(S.Volume * 100), S.Muted, False,
|
||||
FController.FTransmitting and (FController.FTxSliceId = SliceId),
|
||||
FController.FTxSliceId = SliceId);
|
||||
O.SetRxMuteTxState(S.RxMuteOnTx); // бейдж RXM
|
||||
// Список аудио-устройств (общий PA-список) + текущее устройство слайса.
|
||||
if Assigned(FController.FAudioOut) then
|
||||
begin
|
||||
|
||||
@@ -158,6 +158,7 @@ type
|
||||
AGC: TWDSPAGCMode;
|
||||
Volume: Double; // 0..1
|
||||
Muted: Boolean;
|
||||
RxMuteOnTx: Boolean; // True (умолч.) = динамик слайса молчит на передаче
|
||||
FMSQOn: Boolean; // FM-squelch включён (слайс в режиме FM)
|
||||
FMSQLevel: Integer; // порог squelch 0..100
|
||||
DeviceIndex: Integer; // PortAudio-индекс (-1 = default)
|
||||
@@ -187,6 +188,7 @@ type
|
||||
const Left, Right: array of Single; Count: Integer);
|
||||
procedure OnSliceDMRAudioReady(SliceId: Integer;
|
||||
const PCM: array of Single; Count: Integer);
|
||||
function SliceMutedByTx(Idx: Integer): Boolean; // слайс молчит на передаче?
|
||||
procedure RefreshSliceShifts; // пересчёт ShiftHz слайсов пана 0 при смене центра
|
||||
// --- Панадаптеры на аппаратных DDC (этап 3.1/3.2) ---
|
||||
procedure ResetPanDDCMaps; // все пан-слоты свободны, DDC→Pan пуст
|
||||
@@ -592,6 +594,8 @@ type
|
||||
procedure SetSliceAGCMode(Id: Integer; AGC: TWDSPAGCMode);
|
||||
procedure SetSliceVolume(Id: Integer; Vol: Double); // 0..1
|
||||
procedure SetSliceMute(Id: Integer; Mute: Boolean);
|
||||
procedure SetSliceRxMuteOnTx(Id: Integer; On_: Boolean); // self-monitor слайса на TX
|
||||
function PanPrimarySliceId(PanId: Integer): Integer; // первый созданный слайс пана
|
||||
procedure SetSliceDSP(Id, NRMode, NBMode: Integer; SNBOn, ANFOn: Boolean);
|
||||
procedure SetSliceFMSquelch(Id: Integer; On_: Boolean; Level: Integer);
|
||||
function SliceSMeter(Id: Integer): Double; // dBm, из DSP-потока
|
||||
@@ -1334,10 +1338,19 @@ begin
|
||||
idx := FindSliceIndex(SliceId);
|
||||
if idx < 0 then Exit;
|
||||
if FSlices[idx].Mode = MODE_DMR then Exit;
|
||||
if SliceMutedByTx(idx) then Exit;
|
||||
if FSlices[idx].Audio <> nil then
|
||||
FSlices[idx].Audio.Write(Left, Right, Count);
|
||||
end;
|
||||
|
||||
// Слайс глушится на передаче? В full-duplex (Pluto/QO-100) RX-тракт живёт и во
|
||||
// время TX, поэтому иначе оператор слышит сам себя на КАЖДОМ слайсе. У главного
|
||||
// приёмника за это отвечает FRxMuteOnTx (кнопка RX MUTE), у слайса — свой флаг.
|
||||
function TRadioController.SliceMutedByTx(Idx: Integer): Boolean;
|
||||
begin
|
||||
Result := FTransmitting and FSlices[Idx].RxMuteOnTx;
|
||||
end;
|
||||
|
||||
// DSP thread: flat, volume-independent discriminator of the corresponding
|
||||
// WDSP slice channel. FeedAudio only copies it into that decoder's ring.
|
||||
procedure TRadioController.OnSliceDemodAudioReady(SliceId: Integer;
|
||||
@@ -1362,6 +1375,7 @@ begin
|
||||
idx := FindSliceIndex(SliceId);
|
||||
if (idx < 0) or (FSlices[idx].Mode <> MODE_DMR) or
|
||||
FSlices[idx].Muted or not Assigned(FSlices[idx].Audio) then Exit;
|
||||
if SliceMutedByTx(idx) then Exit;
|
||||
N := Min(Count, Length(PCM));
|
||||
SetLength(Left, N * 6);
|
||||
SetLength(Right, N * 6);
|
||||
@@ -1783,6 +1797,7 @@ begin
|
||||
FSlices[slot].AGC := AGC;
|
||||
FSlices[slot].Volume := Vol;
|
||||
FSlices[slot].Muted := False;
|
||||
FSlices[slot].RxMuteOnTx := True; // себя на передаче по умолчанию не слышим
|
||||
FSlices[slot].FMSQOn := False; // squelch по умолчанию выключен
|
||||
FSlices[slot].FMSQLevel := 40;
|
||||
FSlices[slot].DeviceIndex := DevIndex;
|
||||
@@ -1878,6 +1893,26 @@ begin
|
||||
if Assigned(FDSPEngine) then FDSPEngine.SetSliceMute(Id, Mute);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetSliceRxMuteOnTx(Id: Integer; On_: Boolean);
|
||||
var idx: Integer;
|
||||
begin
|
||||
idx := FindSliceIndex(Id);
|
||||
if idx < 0 then Exit;
|
||||
FSlices[idx].RxMuteOnTx := On_;
|
||||
end;
|
||||
|
||||
// «Главный» слайс пана = созданный на нём первым. Id монотонно растёт
|
||||
// (FNextSliceId), поэтому первый = слайс с минимальным Id. 0 = слайсов нет.
|
||||
function TRadioController.PanPrimarySliceId(PanId: Integer): Integer;
|
||||
var i: Integer;
|
||||
begin
|
||||
Result := 0;
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
if FSlices[i].Active and (FSlices[i].PanId = PanId) and
|
||||
((Result = 0) or (FSlices[i].Id < Result)) then
|
||||
Result := FSlices[i].Id;
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetSliceDSP(Id, NRMode, NBMode: Integer;
|
||||
SNBOn, ANFOn: Boolean);
|
||||
var idx: Integer;
|
||||
|
||||
@@ -104,6 +104,7 @@ type
|
||||
AGC: Integer; // Ord(TWDSPAGCMode)
|
||||
Volume: Double; // 0..1
|
||||
Muted: Boolean;
|
||||
RxMuteOnTx: Boolean; // True = слайс молчит на передаче (self-monitor выкл)
|
||||
FMSQOn: Boolean;
|
||||
FMSQLevel: Integer;
|
||||
DevName: string; // имя PortAudio-устройства ('' = default)
|
||||
@@ -1783,6 +1784,7 @@ begin
|
||||
SliceObj.Add('agc', P.Pans[i].Slices[j].AGC);
|
||||
SliceObj.Add('volume', P.Pans[i].Slices[j].Volume);
|
||||
SliceObj.Add('muted', P.Pans[i].Slices[j].Muted);
|
||||
SliceObj.Add('rx_mute_on_tx', P.Pans[i].Slices[j].RxMuteOnTx);
|
||||
SliceObj.Add('fmsq_on', P.Pans[i].Slices[j].FMSQOn);
|
||||
SliceObj.Add('fmsq_level', P.Pans[i].Slices[j].FMSQLevel);
|
||||
SliceObj.Add('dev_name', P.Pans[i].Slices[j].DevName);
|
||||
@@ -1860,6 +1862,7 @@ begin
|
||||
P.Pans[id].Slices[n].AGC := JI(SliceObj, 'agc', 1);
|
||||
P.Pans[id].Slices[n].Volume := EnsureRange(JD(SliceObj, 'volume', 0.7), 0.0, 1.0);
|
||||
P.Pans[id].Slices[n].Muted := JB(SliceObj, 'muted', False);
|
||||
P.Pans[id].Slices[n].RxMuteOnTx := JB(SliceObj, 'rx_mute_on_tx', True);
|
||||
P.Pans[id].Slices[n].FMSQOn := JB(SliceObj, 'fmsq_on', False);
|
||||
P.Pans[id].Slices[n].FMSQLevel := JI(SliceObj, 'fmsq_level', 30);
|
||||
P.Pans[id].Slices[n].DevName := JS(SliceObj, 'dev_name', '');
|
||||
|
||||
+31
-8
@@ -77,6 +77,7 @@ type
|
||||
FSplit: Boolean;
|
||||
FTx: Boolean; // передача идёт (красный)
|
||||
FTxSel: Boolean; // этот слайс выбран для передачи (янтарный)
|
||||
FRxMuteTx: Boolean; // RXM: приём слайса глушится на передаче (умолч. True)
|
||||
FSlice: Char; // буква слайса ('A')
|
||||
FSMeterThreshold: Double; // мин. изменение S-метра (дБ) для перерисовки
|
||||
FDMRText: string; // стабильный краткий статус рядом с полосой
|
||||
@@ -105,6 +106,7 @@ type
|
||||
FOnAudioDevice: TAudioDeviceEvent;
|
||||
FOnSplit: TNotifyEvent;
|
||||
FOnTxSelect: TNotifyEvent;
|
||||
FOnRxMuteTx: TNotifyEvent;
|
||||
FOnInvalidate: TNotifyEvent;
|
||||
|
||||
FTheme: TAppTheme; // тема приложения (цвета кнопок и т.п.)
|
||||
@@ -183,6 +185,7 @@ type
|
||||
AAGCMode: Integer);
|
||||
procedure SetSquelchState(AOn: Boolean; ALevel: Integer);
|
||||
procedure SetExtState(AVolume: Integer; AMuted, ASplit, ATx, ATxSel: Boolean);
|
||||
procedure SetRxMuteTxState(AOn: Boolean); // бейдж RXM (self-monitor на TX)
|
||||
procedure SetAudioDevices(Names: TStrings; const Indices: array of Integer;
|
||||
const CurName: string);
|
||||
procedure UpdateSMeter(DB: Double);
|
||||
@@ -218,6 +221,7 @@ type
|
||||
property OnAudioDevice: TAudioDeviceEvent read FOnAudioDevice write FOnAudioDevice;
|
||||
property OnSplit: TNotifyEvent read FOnSplit write FOnSplit;
|
||||
property OnTxSelect: TNotifyEvent read FOnTxSelect write FOnTxSelect;
|
||||
property OnRxMuteTx: TNotifyEvent read FOnRxMuteTx write FOnRxMuteTx;
|
||||
property OnSliceSelect: TNotifyEvent read FOnSliceSelect write FOnSliceSelect;
|
||||
property OnClose: TNotifyEvent read FOnClose write FOnClose;
|
||||
property OnInvalidate: TNotifyEvent read FOnInvalidate write FOnInvalidate;
|
||||
@@ -258,9 +262,10 @@ const
|
||||
HIT_CLOSE = -30; // × закрытия слайса (неглавные)
|
||||
HIT_SQL = -31; // тумблер FM-squelch (в DSP-панели)
|
||||
HIT_SQL_SLIDER = -32; // трек порога FM-squelch
|
||||
HIT_RXMUTE = -33; // бейдж RXM: приём слайса на своей передаче
|
||||
HIT_AGC_PICK_BASE = 100000;
|
||||
HIT_DEV_BASE = 200000;
|
||||
// Свои диапазоны, чтобы не пересекаться с тумблерами (-9..-32) и с базами выше.
|
||||
// Свои диапазоны, чтобы не пересекаться с тумблерами (-9..-33) и с базами выше.
|
||||
HIT_MODE_BASE = -1000; // -1000 .. -1000-(OVL_MODE_COUNT-1)
|
||||
HIT_FILT_BASE = 1000; // 1000 + индекс кнопки в ряду фильтров
|
||||
|
||||
@@ -455,7 +460,7 @@ begin
|
||||
FNRMode := 0; FNBMode := 0; FSNBOn := False; FANFOn := False; FAGCMode := 0;
|
||||
FSQLOn := False; FSQLLevel := 40; FSqlDrag := False;
|
||||
FVolume := 70; FMuted := False; FSplit := False; FTx := False;
|
||||
FTxSel := True; FSlice := 'A';
|
||||
FTxSel := True; FSlice := 'A'; FRxMuteTx := True;
|
||||
FDMRText := ''; FDMRSynced := False;
|
||||
FDMRCandidateText := ''; FDMRCandidateSynced := False;
|
||||
FDMRCandidateSince := 0;
|
||||
@@ -987,7 +992,7 @@ end;
|
||||
procedure TVfoOverlay.DrawSelf(C: TCanvas; W, H: Integer);
|
||||
var
|
||||
FreqStr, SStr, BWStr, DMRStr: string;
|
||||
CurY, X, RW, i, DMRX, DMRRight: Integer;
|
||||
CurY, X, RW, i, DMRX, DMRRight, TxRight: Integer;
|
||||
R: TRect;
|
||||
BarL, BarW, MeterX: Integer;
|
||||
begin
|
||||
@@ -1005,16 +1010,25 @@ begin
|
||||
RW := W - PAD*2;
|
||||
CurY := PAD;
|
||||
|
||||
{ --- Шапка: главный [A] BW ... SPLIT [TX]; слайс [B] BW ... [×] [TX] --- }
|
||||
{ --- Шапка: главный [A] BW ... SPLIT [TX]; слайс [B] BW ... [RXM] [TX] [×] --- }
|
||||
DrawBadge(C, Rect(X, CurY, X+16, CurY+HDR_BADGE_H), FSlice, SliceColor(FSlice), CLR_BADGE_TXT);
|
||||
RegisterHit(Rect(X, CurY, X+16, CurY+HDR_BADGE_H), HIT_SLICE_SEL); // бейдж = выбор слайса
|
||||
// Кнопка закрытия × — только у неглавных слайсов (главный приёмник не закрыть).
|
||||
// Место освободилось от SPLIT (его у слайсов нет) — ставим вплотную к TX.
|
||||
// Она крайняя справа, TX и RXM выстраиваются левее неё.
|
||||
TxRight := W - PAD;
|
||||
if not FIsMain then
|
||||
begin
|
||||
R := Rect(W - PAD - 26 - 6 - 14, CurY, W - PAD - 26 - 6, CurY + HDR_BADGE_H);
|
||||
R := Rect(W - PAD - 14, CurY, W - PAD, CurY + HDR_BADGE_H);
|
||||
DrawBadge(C, R, #$C3#$97, CLR_BTN_NORM, CLR_DIM); // UTF-8 '×'
|
||||
RegisterHit(R, HIT_CLOSE);
|
||||
TxRight := W - PAD - 14 - 6;
|
||||
// RXM — приём слайса на СВОЕЙ передаче. Подсвечен (умолч.) = глушим, т.е.
|
||||
// себя не слышим; погашен = self-monitor (свой downlink через транспондер).
|
||||
// У главного приёмника то же самое делает кнопка RX MUTE на левой панели.
|
||||
R := Rect(TxRight - 26 - 4 - 32, CurY, TxRight - 26 - 4, CurY + HDR_BADGE_H);
|
||||
if FRxMuteTx then DrawBadge(C, R, 'RXM', CLR_ACCENT, CLR_BADGE_TXT)
|
||||
else DrawBadge(C, R, 'RXM', CLR_BTN_NORM, CLR_DIM);
|
||||
RegisterHit(R, HIT_RXMUTE);
|
||||
end;
|
||||
// ширина фильтра
|
||||
if FFilterBW >= 1000 then BWStr := Format('%.1fk', [FFilterBW/1000.0])
|
||||
@@ -1029,7 +1043,7 @@ begin
|
||||
begin
|
||||
DMRStr := FDMRText;
|
||||
DMRX := X + 22 + C.TextWidth(BWStr) + 8;
|
||||
DMRRight := W - PAD - 26 - 6 - 14 - 5; // до кнопок × и TX
|
||||
DMRRight := TxRight - 26 - 4 - 32 - 5; // до бейджей RXM, TX и ×
|
||||
while (Length(DMRStr) > 1) and
|
||||
(C.TextWidth(DMRStr) > DMRRight - DMRX) do
|
||||
SetLength(DMRStr, Length(DMRStr) - 1);
|
||||
@@ -1038,7 +1052,7 @@ begin
|
||||
C.TextOut(DMRX, CurY + 1, DMRStr);
|
||||
end;
|
||||
// TX badge: a DMR slice is visibly disabled and has no hit target.
|
||||
R := Rect(W - PAD - 26, CurY, W - PAD, CurY + HDR_BADGE_H);
|
||||
R := Rect(TxRight - 26, CurY, TxRight, CurY + HDR_BADGE_H);
|
||||
if not TxSelectionAllowed then
|
||||
DrawBadge(C, R, 'TX', CLR_CARD_BG, CLR_DIM)
|
||||
else if FTx then DrawBadge(C, R, 'TX', CLR_TX_ON, clWhite)
|
||||
@@ -1205,6 +1219,8 @@ begin
|
||||
FOnTxSelect(Self);
|
||||
Exit;
|
||||
end;
|
||||
HIT_RXMUTE: begin FRxMuteTx := not FRxMuteTx; RequestInvalidate;
|
||||
if Assigned(FOnRxMuteTx) then FOnRxMuteTx(Self); Exit; end;
|
||||
HIT_SLICE_SEL: begin if Assigned(FOnSliceSelect) then FOnSliceSelect(Self); Exit; end;
|
||||
HIT_CLOSE: begin if Assigned(FOnClose) then FOnClose(Self); Exit; end;
|
||||
HIT_AUD_UP: begin if FAudScroll > 0 then Dec(FAudScroll);
|
||||
@@ -1487,6 +1503,13 @@ begin
|
||||
RequestInvalidate;
|
||||
end;
|
||||
|
||||
procedure TVfoOverlay.SetRxMuteTxState(AOn: Boolean);
|
||||
begin
|
||||
if FRxMuteTx = AOn then Exit;
|
||||
FRxMuteTx := AOn;
|
||||
RequestInvalidate;
|
||||
end;
|
||||
|
||||
procedure TVfoOverlay.SetSliceInfo(ASliceId: Integer; ALetter: Char; AIsMain: Boolean);
|
||||
begin
|
||||
if (FSliceId = ASliceId) and (FSlice = ALetter) and (FIsMain = AIsMain) then Exit;
|
||||
|
||||
Reference in New Issue
Block a user