feat(tx): TX-профиль на слайс — пилюля в шапке флага

Передатчик один, профиль до сих пор был один на устройство: активный профиль
звучал независимо от того, какой слайс взял передачу. С панадаптером на FT8 и
вторым на FM это значит, что компрессия и EQ голосового профиля уходили в
эфир на FT8 (FMRAW прикрыт сам — ApplyTXModeSettings глушит там всю обработку,
а DIGU/DIGL нет). У Flex это решается дисциплиной и внешним API; делаем в радио.

Привязка «источник передачи → профиль» (-1 = не переключать, звучит активный):
TCtrlSlice.TXProfile для B..G, TTXProfileList.MainBind для главного (слайс A).
Персист: ключ tx_profile в слайсе пана и main_bind в секции tx_profiles.
SetTxSlice зовёт ApplyBoundTXProfile ДО пуша режима и цепи, иначе
ApplyTXSettingsToDSP лёг бы на старый режим. Привязали источник, который
передаёт прямо сейчас, — применяем сразу.

Привязки — индексы, поэтому RemapTXProfileBindings: удаление профиля из
середины снимает привязки на него и сдвигает те, что правее; Factory-сброс
снимает все (список заменён целиком).

UI: пилюля с именем профиля в первой строке флага сразу за шириной фильтра
(ЛКМ — fly-out список, первая строка «As active»). Правая граница считается
до SPLIT у главного и до RXM у слайса; не рисуем при пустом списке, в DMR
(передачи нет) и в FMRAW (профиль там ни на что не влияет). Прокрутка списка
общая с AUD — блок стрелок вынесен в DrawScrollArrows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 17:06:08 +03:00
co-authored by Claude Opus 5
parent 8563c4e4cc
commit 013d0406ac
5 changed files with 397 additions and 41 deletions
+251 -41
View File
@@ -57,7 +57,10 @@ type
TVolumeEvent = procedure(Vol: Integer) of object;
TAudioDeviceEvent = procedure(DevIndex: Integer; const DevName: string) of object;
TFlyPanel = (fpNone, fpMode, fpDsp, fpAud);
TFlyPanel = (fpNone, fpMode, fpDsp, fpAud, fpTxProf);
// Выбран TX-профиль для этого флага: Idx = -1 («как активный») или индекс.
TTXProfilePickEvent = procedure(Idx: Integer) of object;
TVfoOverlay = class(TCustomControl)
private
@@ -102,6 +105,13 @@ type
FCurInDev: string; // текущий вход слайса ('' = общий вход)
FAudInTab: Boolean; // AUD-панель показывает вход (IN), а не выход
// TX-профили: список имён (общий на все флаги) + привязка ЭТОГО флага.
// -1 = флаг ничего не переключает, передаёт активный профиль.
FProfNames: TStringList;
FProfBound: Integer;
FProfScroll: Integer; // прокрутка списка профилей
FProfListRect: TRect; // область списка профилей (зона колёсика)
FPanel: TFlyPanel; // открытая fly-out панель
FAudScroll: Integer; // прокрутка списка аудио-устройств (верхний индекс)
FAudListRect: TRect; // область списка AUD (лок. коорд.) — зона колёсика
@@ -121,6 +131,7 @@ type
FOnSplit: TNotifyEvent;
FOnTxSelect: TNotifyEvent;
FOnRxMuteTx: TNotifyEvent;
FOnTXProfile: TTXProfilePickEvent;
FOnInvalidate: TNotifyEvent;
FTheme: TAppTheme; // тема приложения (цвета кнопок и т.п.)
@@ -156,6 +167,13 @@ type
procedure DrawModePanel(C: TCanvas; X, Y, RowW: Integer);
procedure DrawDspPanel(C: TCanvas; X, Y, RowW: Integer);
procedure DrawAudPanel(C: TCanvas; X, Y, RowW: Integer);
procedure DrawProfPanel(C: TCanvas; X, Y, RowW: Integer);
// Стрелки прокрутки справа от списка (общие для AUD и профилей).
procedure DrawScrollArrows(C: TCanvas; X, Y, RowW, RowWidth, PanelH: Integer;
CanUp, CanDn: Boolean; HitUp, HitDn: Integer);
function ProfRowCount: Integer; // «— как активный» + профили
function ProfRowName(I: Integer): string;
procedure ScrollProfToCurrent;
// Строки активной вкладки AUD (OUT — устройства вывода, IN — ввода;
// у IN первая строка псевдо: «общий вход» / «нет»).
function AudRowCount: Integer;
@@ -206,6 +224,8 @@ type
procedure SetDSPState(ANRMode, ANBMode: Integer; ASNBOn, AANFOn: Boolean;
AAGCMode: Integer);
procedure SetSquelchState(AOn: Boolean; ALevel: Integer);
// Список TX-профилей (одинаков у всех флагов) + привязка этого флага.
procedure SetTXProfiles(ANames: TStrings; ABound: Integer);
procedure SetExtState(AVolume: Integer; AMuted, ASplit, ATx, ATxSel: Boolean);
procedure SetRxMuteTxState(AOn: Boolean); // бейдж RXM (self-monitor на TX)
// Auto TX слайса (настройка CAT): бейдж TX превращается в AutoTX.
@@ -257,6 +277,7 @@ type
property OnSplit: TNotifyEvent read FOnSplit write FOnSplit;
property OnTxSelect: TNotifyEvent read FOnTxSelect write FOnTxSelect;
property OnRxMuteTx: TNotifyEvent read FOnRxMuteTx write FOnRxMuteTx;
property OnTXProfile: TTXProfilePickEvent read FOnTXProfile write FOnTXProfile;
property OnSliceSelect: TNotifyEvent read FOnSliceSelect write FOnSliceSelect;
property OnClose: TNotifyEvent read FOnClose write FOnClose;
property OnInvalidate: TNotifyEvent read FOnInvalidate write FOnInvalidate;
@@ -300,8 +321,12 @@ const
HIT_SQL = -31; // тумблер FM-squelch (в DSP-панели)
HIT_SQL_SLIDER = -32; // трек порога FM-squelch
HIT_RXMUTE = -33; // бейдж RXM: приём слайса на своей передаче
HIT_TXPROF = -36; // пилюля TX-профиля в шапке (открыть список)
HIT_PROF_UP = -37; // прокрутка списка профилей
HIT_PROF_DN = -38;
HIT_AGC_PICK_BASE = 100000;
HIT_DEV_BASE = 200000;
HIT_PROF_BASE = 300000; // 300000 + строка списка (0 = «как активный»)
// Свои диапазоны, чтобы не пересекаться с тумблерами (-9..-33) и с базами выше.
HIT_MODE_BASE = -1000; // -1000 .. -1000-(OVL_MODE_COUNT-1)
HIT_FILT_BASE = 1000; // 1000 + индекс кнопки в ряду фильтров
@@ -319,6 +344,9 @@ const
IGAP = 2;
AUD_ROW_H = 17; // высота строки устройства в AUD-панели
AUD_MAX_VIS = 6; // максимум видимых устройств (дальше — прокрутка)
PROF_MAX_VIS = 6; // столько же строк у списка TX-профилей
PROF_PILL_W = 92; // ширина пилюли профиля в шапке
PROF_PILL_MIN = 44; // уже этого не рисуем — шапка занята бейджами
OVL_BASE_H = PAD + HDR_H + MET_H + FRQ_H + BAR_H + PAD; // = 96
// Палитра. Структурные цвета (фон/текст/акценты) берутся из темы —
@@ -519,6 +547,10 @@ begin
FInDevNames := TStringList.Create;
FCurInDev := '';
FAudInTab := False; // AUD открывается на вкладке OUT
FProfNames := TStringList.Create;
FProfBound := -1; // флаг не переключает профиль
FProfScroll := 0;
FProfListRect := Rect(0, 0, 0, 0);
FCacheBitmap := TBitmap.Create;
FCacheBitmap.PixelFormat := pf32bit;
FCacheDirty := True;
@@ -558,6 +590,7 @@ end;
destructor TVfoOverlay.Destroy;
begin
FProfNames.Free;
FInDevNames.Free;
FDevNames.Free;
FCacheBitmap.Free;
@@ -660,6 +693,7 @@ begin
// ряд вкладок OUT/IN + список устройств активной вкладки
fpAud: Result := ROW_H + IGAP +
Min(Max(1, AudRowCount), AUD_MAX_VIS) * AUD_ROW_H;
fpTxProf: Result := Min(ProfRowCount, PROF_MAX_VIS) * AUD_ROW_H;
else Result := 0;
end;
end;
@@ -674,6 +708,7 @@ procedure TVfoOverlay.SetPanel(P: TFlyPanel);
begin
if FPanel = P then FPanel := fpNone else FPanel := P;
if FPanel = fpAud then ScrollAudToCurrent; // сразу показать выбранное
if FPanel = fpTxProf then ScrollProfToCurrent;
Height := ComputeHeight;
RequestInvalidate;
end;
@@ -996,15 +1031,143 @@ begin
end;
end;
function TVfoOverlay.ProfRowCount: Integer;
// Строка 0 — псевдо «— как активный», дальше сами профили.
begin
Result := FProfNames.Count + 1;
end;
function TVfoOverlay.ProfRowName(I: Integer): string;
begin
if I <= 0 then Result := 'As active'
else if I - 1 < FProfNames.Count then Result := FProfNames[I - 1]
else Result := '';
end;
procedure TVfoOverlay.ScrollProfToCurrent;
var Cnt, Cur: Integer;
begin
FProfScroll := 0;
Cnt := ProfRowCount;
if Cnt <= PROF_MAX_VIS then Exit;
Cur := FProfBound + 1; // -1 → строка 0
FProfScroll := Min(Max(0, Cur - PROF_MAX_VIS div 2), Cnt - PROF_MAX_VIS);
end;
procedure TVfoOverlay.SetTXProfiles(ANames: TStrings; ABound: Integer);
var Same: Boolean;
begin
Same := (ABound = FProfBound) and (ANames <> nil) and
(ANames.Count = FProfNames.Count) and (ANames.Text = FProfNames.Text);
if Same then Exit;
if ANames <> nil then FProfNames.Assign(ANames) else FProfNames.Clear;
if (ABound < -1) or (ABound >= FProfNames.Count) then ABound := -1;
FProfBound := ABound;
if FPanel = fpTxProf then
begin
ScrollProfToCurrent;
Height := ComputeHeight; // список мог стать длиннее/короче
end;
RequestInvalidate;
end;
procedure TVfoOverlay.DrawProfPanel(C: TCanvas; X, Y, RowW: Integer);
const
ARROW_W = 15;
var
I, Slot, VisN, RowWidth, MaxScroll, Cnt: Integer;
HR: TRect;
Nm, Full: string;
Cur, Scrollable: Boolean;
begin
C.Font.Name := 'Courier New';
C.Font.Size := 7;
C.Font.Style := [];
Cnt := ProfRowCount;
VisN := Min(Cnt, PROF_MAX_VIS);
FProfListRect := Rect(X, Y, X + RowW, Y + VisN * AUD_ROW_H);
Scrollable := Cnt > PROF_MAX_VIS;
MaxScroll := Cnt - VisN;
if FProfScroll > MaxScroll then FProfScroll := MaxScroll;
if FProfScroll < 0 then FProfScroll := 0;
if Scrollable then RowWidth := RowW - ARROW_W - 2 else RowWidth := RowW;
for Slot := 0 to VisN - 1 do
begin
I := FProfScroll + Slot;
HR := Rect(X, Y + Slot*AUD_ROW_H, X + RowWidth, Y + Slot*AUD_ROW_H + 16);
Full := ProfRowName(I);
Nm := Full;
Cur := (I - 1) = FProfBound;
if Cur then C.Brush.Color := CLR_ACCENT_D
else if (FHotIdx >= 0) and (FHitActions[FHotIdx] = HIT_PROF_BASE + I) then
C.Brush.Color := CLR_BTN_HOT
else C.Brush.Color := CLR_BTN_NORM;
C.Brush.Style := bsSolid;
C.Pen.Color := C.Brush.Color;
C.Pen.Style := psSolid;
C.RoundRect(HR.Left, HR.Top, HR.Right, HR.Bottom, 4, 4);
if Cur then C.Font.Color := CLR_ACCENT else C.Font.Color := CLR_TEXT;
C.Brush.Style := bsClear;
while (Length(Nm) > 1) and (C.TextWidth(Nm + '…') > RowWidth - 8) do
SetLength(Nm, Length(Nm) - 1);
if Nm <> Full then Nm := Nm + '…';
C.TextOut(HR.Left + 4, HR.Top + (16 - C.TextHeight('A')) div 2, Nm);
RegisterHit(HR, HIT_PROF_BASE + I);
end;
if not Scrollable then Exit;
DrawScrollArrows(C, X, Y, RowW, RowWidth, VisN * AUD_ROW_H - 1,
FProfScroll > 0, FProfScroll < MaxScroll,
HIT_PROF_UP, HIT_PROF_DN);
end;
procedure TVfoOverlay.DrawScrollArrows(C: TCanvas; X, Y, RowW, RowWidth,
PanelH: Integer; CanUp, CanDn: Boolean; HitUp, HitDn: Integer);
// Полоса прокрутки справа от списка: ▲ / ▼. Общая для AUD и TX-профилей.
var
HR: TRect;
ArrowX, MidY, CY: Integer;
Tri: array[0..2] of TPoint;
begin
ArrowX := X + RowWidth + 2;
MidY := Y + PanelH div 2;
HR := Rect(ArrowX, Y, X + RowW, MidY - 1);
C.Brush.Color := CLR_BTN_NORM; C.Brush.Style := bsSolid;
C.Pen.Color := CLR_BTN_NORM; C.Pen.Style := psSolid;
C.RoundRect(HR.Left, HR.Top, HR.Right, HR.Bottom, 4, 4);
CY := (HR.Top + HR.Bottom) div 2;
if CanUp then C.Brush.Color := CLR_TEXT else C.Brush.Color := CLR_DIM;
C.Pen.Color := C.Brush.Color;
Tri[0] := Point((HR.Left+HR.Right) div 2, CY - 3);
Tri[1] := Point((HR.Left+HR.Right) div 2 - 4, CY + 2);
Tri[2] := Point((HR.Left+HR.Right) div 2 + 4, CY + 2);
C.Polygon(Tri);
RegisterHit(HR, HitUp);
HR := Rect(ArrowX, MidY + 1, X + RowW, Y + PanelH);
C.Brush.Color := CLR_BTN_NORM; C.Brush.Style := bsSolid;
C.Pen.Color := CLR_BTN_NORM; C.Pen.Style := psSolid;
C.RoundRect(HR.Left, HR.Top, HR.Right, HR.Bottom, 4, 4);
CY := (HR.Top + HR.Bottom) div 2;
if CanDn then C.Brush.Color := CLR_TEXT else C.Brush.Color := CLR_DIM;
C.Pen.Color := C.Brush.Color;
Tri[0] := Point((HR.Left+HR.Right) div 2, CY + 3);
Tri[1] := Point((HR.Left+HR.Right) div 2 - 4, CY - 2);
Tri[2] := Point((HR.Left+HR.Right) div 2 + 4, CY - 2);
C.Polygon(Tri);
RegisterHit(HR, HitDn);
end;
procedure TVfoOverlay.DrawAudPanel(C: TCanvas; X, Y, RowW: Integer);
const
ARROW_W = 15;
var
I, Slot, VisN, RowWidth, MaxScroll, PanelH, ArrowX, MidY, CY, Cnt: Integer;
I, Slot, VisN, RowWidth, MaxScroll, Cnt: Integer;
HR, TabR: TRect;
Nm: string;
Cur, Scrollable, CanUp, CanDn: Boolean;
Tri: array[0..2] of TPoint;
Cur, Scrollable: Boolean;
begin
// Ряд вкладок: OUT (куда отдаём звук) / IN (откуда берём модуляцию).
TabR := Rect(X, Y, X + (RowW - 4) div 2, Y + ROW_H);
@@ -1062,50 +1225,20 @@ begin
end;
if not Scrollable then Exit;
// Полоса прокрутки справа: ▲ (верх) / ▼ (низ) + счётчик позиции.
PanelH := VisN * AUD_ROW_H - 1;
ArrowX := X + RowWidth + 2;
MidY := Y + PanelH div 2;
CanUp := FAudScroll > 0;
CanDn := FAudScroll < MaxScroll;
// верхняя стрелка
HR := Rect(ArrowX, Y, X + RowW, MidY - 1);
C.Brush.Color := CLR_BTN_NORM; C.Brush.Style := bsSolid;
C.Pen.Color := CLR_BTN_NORM; C.Pen.Style := psSolid;
C.RoundRect(HR.Left, HR.Top, HR.Right, HR.Bottom, 4, 4);
CY := (HR.Top + HR.Bottom) div 2;
if CanUp then C.Brush.Color := CLR_TEXT else C.Brush.Color := CLR_DIM;
C.Pen.Color := C.Brush.Color;
Tri[0] := Point((HR.Left+HR.Right) div 2, CY - 3);
Tri[1] := Point((HR.Left+HR.Right) div 2 - 4, CY + 2);
Tri[2] := Point((HR.Left+HR.Right) div 2 + 4, CY + 2);
C.Polygon(Tri);
RegisterHit(HR, HIT_AUD_UP);
// нижняя стрелка
HR := Rect(ArrowX, MidY + 1, X + RowW, Y + PanelH);
C.Brush.Color := CLR_BTN_NORM; C.Brush.Style := bsSolid;
C.Pen.Color := CLR_BTN_NORM; C.Pen.Style := psSolid;
C.RoundRect(HR.Left, HR.Top, HR.Right, HR.Bottom, 4, 4);
CY := (HR.Top + HR.Bottom) div 2;
if CanDn then C.Brush.Color := CLR_TEXT else C.Brush.Color := CLR_DIM;
C.Pen.Color := C.Brush.Color;
Tri[0] := Point((HR.Left+HR.Right) div 2, CY + 3);
Tri[1] := Point((HR.Left+HR.Right) div 2 - 4, CY - 2);
Tri[2] := Point((HR.Left+HR.Right) div 2 + 4, CY - 2);
C.Polygon(Tri);
RegisterHit(HR, HIT_AUD_DN);
DrawScrollArrows(C, X, Y, RowW, RowWidth, VisN * AUD_ROW_H - 1,
FAudScroll > 0, FAudScroll < MaxScroll,
HIT_AUD_UP, HIT_AUD_DN);
end;
{ ---- Основная отрисовка ---- }
procedure TVfoOverlay.DrawSelf(C: TCanvas; W, H: Integer);
var
FreqStr, SStr, BWStr, DMRStr, TxStr: string;
FreqStr, SStr, BWStr, DMRStr, TxStr, PStr: string;
CurY, X, RW, i, DMRX, DMRRight, TxRight, TxW: Integer;
ProfLeft, ProfRight, ProfW: Integer;
R: TRect;
Tri3: array[0..2] of TPoint;
BarL, BarW, MeterX: Integer;
begin
FHitCount := 0;
@@ -1154,6 +1287,7 @@ begin
C.Font.Name := 'Courier New'; C.Font.Size := 8; C.Font.Style := [];
C.Font.Color := CLR_DIM; C.Brush.Style := bsClear;
C.TextOut(X + 22, CurY + 1, BWStr);
ProfLeft := X + 22 + C.TextWidth(BWStr) + 8;
// Краткий статус дополнительного DMR-слайса. Он обновляется только при
// устойчивом изменении sync/slot/CC/TG, поэтому не заставляет GL-текстуру
// перезаливаться на каждом burst.
@@ -1186,6 +1320,45 @@ begin
else DrawBadge(C, R, 'SPLIT', CLR_BTN_NORM, CLR_DIM);
RegisterHit(R, HIT_SPLIT);
end;
// Пилюля TX-профиля этого флага — сразу за шириной фильтра. Правая граница:
// до SPLIT (главный) либо до RXM (слайс). У DMR передачи нет вовсе, у FMRAW
// профиль ни на что не влияет (ApplyTXModeSettings глушит всю обработку —
// компрессор, leveler, ALC, phase rot, EQ, mic gain), у пустого списка
// выбирать нечего — не рисуем. Тесная шапка (узкий флаг) — тоже.
if FIsMain then ProfRight := W - PAD - 26 - 44 - 6
else ProfRight := TxRight - TxW - 4 - 32 - 6;
if (FProfNames.Count > 0) and (FMode <> OVL_MODE_DMR) and
(FMode <> OVL_MODE_FMRAW) and
(ProfRight - ProfLeft >= PROF_PILL_MIN) then
begin
ProfW := Min(PROF_PILL_W, ProfRight - ProfLeft);
R := Rect(ProfLeft, CurY, ProfLeft + ProfW, CurY + HDR_BADGE_H);
if FProfBound >= 0 then C.Brush.Color := CLR_ACCENT_D
else if (FHotIdx >= 0) and (FHitActions[FHotIdx] = HIT_TXPROF) then
C.Brush.Color := CLR_BTN_HOT
else C.Brush.Color := CLR_BTN_NORM;
C.Brush.Style := bsSolid;
C.Pen.Color := C.Brush.Color; C.Pen.Style := psSolid;
C.RoundRect(R.Left, R.Top, R.Right, R.Bottom, 4, 4);
if FProfBound >= 0 then PStr := ProfRowName(FProfBound + 1)
else PStr := 'Profile'; // привязки нет — просто ярлык
C.Font.Size := 7;
if FProfBound >= 0 then C.Font.Color := CLR_ACCENT else C.Font.Color := CLR_DIM;
C.Brush.Style := bsClear;
while (Length(PStr) > 1) and (C.TextWidth(PStr) > ProfW - 14) do
SetLength(PStr, Length(PStr) - 1);
C.TextOut(R.Left + 4, R.Top + (HDR_BADGE_H - C.TextHeight('A')) div 2, PStr);
// Треугольник «список» справа — рисуем, а не пишем: в Courier New
// юникодных стрелок может не оказаться.
C.Brush.Color := C.Font.Color; C.Brush.Style := bsSolid;
C.Pen.Color := C.Font.Color;
Tri3[0] := Point(R.Right - 9, CurY + 5);
Tri3[1] := Point(R.Right - 3, CurY + 5);
Tri3[2] := Point(R.Right - 6, CurY + 9);
C.Polygon(Tri3);
C.Font.Size := 8;
RegisterHit(R, HIT_TXPROF);
end;
Inc(CurY, HDR_H);
{ --- Метр: индикаторы + сигнал-бар --- }
@@ -1259,6 +1432,7 @@ begin
fpMode: DrawModePanel(C, PAD, CurY, W - PAD*2);
fpDsp: DrawDspPanel(C, PAD, CurY, W - PAD*2);
fpAud: DrawAudPanel(C, PAD, CurY, W - PAD*2);
fpTxProf: DrawProfPanel(C, PAD, CurY, W - PAD*2);
end;
end;
end;
@@ -1339,6 +1513,12 @@ begin
end;
HIT_RXMUTE: begin FRxMuteTx := not FRxMuteTx; RequestInvalidate;
if Assigned(FOnRxMuteTx) then FOnRxMuteTx(Self); Exit; end;
HIT_TXPROF: begin SetPanel(fpTxProf); Exit; end;
HIT_PROF_UP: begin if FProfScroll > 0 then Dec(FProfScroll);
RequestInvalidate; Exit; end;
HIT_PROF_DN: begin if FProfScroll < ProfRowCount - PROF_MAX_VIS then
Inc(FProfScroll);
RequestInvalidate; 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);
@@ -1360,6 +1540,18 @@ begin
end;
end;
// Строка списка TX-профилей: 0 = «как активный» (-1), дальше индексы профилей.
if HV >= HIT_PROF_BASE then
begin
NewMode := HV - HIT_PROF_BASE - 1; // -1 = «как активный»
if (NewMode < -1) or (NewMode >= FProfNames.Count) then Exit;
FProfBound := NewMode;
FPanel := fpNone; Height := ComputeHeight;
RequestInvalidate;
if Assigned(FOnTXProfile) then FOnTXProfile(NewMode);
Exit;
end;
// Устройство аудио (строка активной вкладки OUT/IN)
if HV >= HIT_DEV_BASE then
begin
@@ -1533,7 +1725,20 @@ var
MaxScroll, Old: Integer;
begin
Result := False;
if (not Visible) or (FPanel <> fpAud) then Exit;
if not Visible then Exit;
if FPanel = fpTxProf then
begin
if not PtInRect(FProfListRect, Point(X - Left, Y - Top)) then Exit;
Result := True;
MaxScroll := ProfRowCount - PROF_MAX_VIS;
if MaxScroll <= 0 then Exit;
Old := FProfScroll;
if WheelDelta > 0 then Dec(FProfScroll) else Inc(FProfScroll);
FProfScroll := EnsureRange(FProfScroll, 0, MaxScroll);
if FProfScroll <> Old then RequestInvalidate;
Exit;
end;
if FPanel <> fpAud then Exit;
if not PtInRect(FAudListRect, Point(X - Left, Y - Top)) then Exit;
Result := True; // зона наша — колесо в спектр не пускаем
MaxScroll := AudRowCount - AUD_MAX_VIS;
@@ -1635,6 +1840,11 @@ begin
FDMRCandidateText := '';
FDMRCandidateSince := 0;
end;
// Ушли в режим, где профиль ни на что не влияет: пилюли уже нет, открытый
// список закрываем сами — иначе он висел бы без своей кнопки.
if (FPanel = fpTxProf) and
((FMode = OVL_MODE_DMR) or (FMode = OVL_MODE_FMRAW)) then
FPanel := fpNone;
Height := ComputeHeight;
RequestInvalidate;
end;