mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
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:
+251
-41
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user