fix(tci): клик по DX-споту на доп. пане слал номер ПАНА вместо номера приёмника

Приёмником TCI в этой ветке стал слот СЛАЙСА (TCI_MAX_RX = 1 + MAX_SLICES,
«панадаптером приёмник быть перестал»), а PanNSpectrumMouseDown отдавал в
rx_clicked_on_spot P.PanId. Клик по пану 1 уезжал как «rx_clicked_on_spot:1,…»,
то есть адресовал слайс в слоте 0 — возможно, стоящий на ГЛАВНОМ пане, — а то и
приёмник, которого нет вовсе: клиенты, фильтрующие по номеру, такое уведомление
просто теряют. Вызов rx0 с главного пана был и остаётся верным.

Теперь номер берётся у слайса, который на спот и увели: DXTuneSpotOnPan стал
функцией и возвращает его, TTCIAdapter.RxOfSlice переводит id в номер TCI.

Заодно два следствия того же места:

  * Не увели ничего (спот вне полосы захвата зумнутого пана, слоты кончились) —
    молчим. Иначе уведомление уходило под номером 0, то есть от имени главного
    приёмника, который никуда не двигался, и логгер открывал карточку QSO с QSY
    на частоту, где радио не стоит.

  * AddSliceAtFreqPan стал функцией и честно сообщает, что получилось. При
    неудаче (нет свободных слотов, частота вне захвата) он не трогает
    FActiveSliceId, а DXTuneSpotOnPan читал именно его — и уводил на спот ЧУЖОЙ
    слайс, вообще говоря с другого пана, вместе с SetSliceModeBW и
    PushSliceFlagState не на том пане.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 16:06:53 +03:00
co-authored by Claude Opus 5
parent 11d8e11ba5
commit 6e4ba1c1c5
2 changed files with 43 additions and 12 deletions
+33 -12
View File
@@ -520,7 +520,8 @@ type
procedure ApplyDXClusterSettings(const D: TDXClusterSettings);
procedure AttachPanDXSpots(P: TPanafallPanel); // свой оверлей спотов на пан
procedure ApplyDXOverlayCfg(O: TDXSpotOverlay); // настройки вида → один оверлей
procedure DXTuneSpotOnPan(P: TPanafallPanel; FreqHz: Double; Mode: TDXMode);
function DXTuneSpotOnPan(P: TPanafallPanel; FreqHz: Double;
Mode: TDXMode): Integer;
procedure UpdateDXClusterButton;
procedure BtnDXClusterClick(Sender: TObject);
procedure BtnDXClusterMouseDown(Sender: TObject; Button: TMouseButton;
@@ -746,7 +747,7 @@ type
procedure PanFloatResize(Sender: TObject);
function PanFloating(PanId: Integer): Boolean;
procedure ResetPanGLCaches(P: TPanafallPanel); // после смены родителя
procedure AddSliceAtFreqPan(P: TPanafallPanel; Hz: Double);
function AddSliceAtFreqPan(P: TPanafallPanel; Hz: Double): Integer;
procedure PanClickTune(P: TPanafallPanel; X, W: Integer); // клик по фону пана N
procedure PanViewWindow(P: TPanafallPanel; out VC, VS: Double);
procedure SyncPanZoomUI(P: TPanafallPanel); // вьюха+зум-бар ← контроллер
@@ -5303,17 +5304,19 @@ begin
FController.SetMode(NewMode);
end;
procedure TMainForm.DXTuneSpotOnPan(P: TPanafallPanel; FreqHz: Double;
Mode: TDXMode);
function TMainForm.DXTuneSpotOnPan(P: TPanafallPanel; FreqHz: Double;
Mode: TDXMode): Integer;
// QSY по споту на доп. пане. Цель — слайс ЭТОГО пана (активный, если он здесь,
// иначе первый; нет ни одного — создаём в точке), ровно как у клика по фону
// пана: своего VFO у панов N нет, а ретюнить их DDC под спот нельзя — это
// увезло бы весь пан.
// увезло бы весь пан. Возвращает слайс, который увели на спот (0 — не увели):
// его номер и адресуют уведомления TCI.
var
Id, NewMode: Integer;
Hz: Int64;
S: TCtrlSlice;
begin
Result := 0;
if (P = nil) or (FreqHz <= 0) then Exit;
Hz := Round(FreqHz);
// Спот видим в окне пана, но окно может быть шире полосы захвата (зум) —
@@ -5329,8 +5332,11 @@ begin
NewMode := DXSpotRadioMode(FreqHz, Mode);
if Id = 0 then
begin
AddSliceAtFreqPan(P, Hz); // ставит FActiveSliceId
Id := FActiveSliceId;
// ★Берём то, что вернул сам вызов, а не FActiveSliceId: слоты могли
// кончиться, и тогда в нём лежит прежний активный слайс — возможно, вообще
// с другого пана. Раньше его и уводили на спот, а теперь ещё и объявляли
// клиентам TCI приёмником, который «встал на спот».
Id := AddSliceAtFreqPan(P, Hz);
if Id = 0 then Exit;
end
else
@@ -5344,6 +5350,7 @@ begin
FController.FilterBWFor(NewMode, FilterDefIdx(NewMode)));
FActiveSliceId := Id;
Result := Id;
P.PushSliceFlagState(Id);
P.LayoutFlags;
MarkPanDirty(P);
@@ -7015,18 +7022,23 @@ begin
if P.PbPanZoom <> nil then P.PbPanZoom.Invalidate; // событийный путь — можно
end;
procedure TMainForm.AddSliceAtFreqPan(P: TPanafallPanel; Hz: Double);
function TMainForm.AddSliceAtFreqPan(P: TPanafallPanel; Hz: Double): Integer;
// Слайс в доп. пане (режим/полоса копируются с главного, звуковуха default).
// Возвращает созданный слайс, 0 — не создали (вне полосы захвата, слоты
// кончились). Спрашивать об этом FActiveSliceId нельзя: при неудаче там
// остаётся ПРЕЖНИЙ активный слайс, вообще говоря с другого пана.
var Lo, Hi, Id: Integer;
begin
Result := 0;
if not FController.SliceFitsCapture(Hz, P.PanId) then Exit;
TRadioController.FilterEdgesFor(FController.FMode, FController.FFilterBW, Lo, Hi);
Id := FController.AddSlice(Hz, FController.FMode, Lo, Hi,
TRadioController.AGCModeFromUI(FController.FAGCMode), 0.7, -1, '',
P.PanId);
if Id < 0 then Exit;
if Id <= 0 then Exit;
P.CreateSliceFlag(Id);
FActiveSliceId := Id;
Result := Id;
end;
procedure TMainForm.OnPanRateClick(Sender: TObject);
@@ -8031,9 +8043,18 @@ begin
if IsSpec and (Button = mbLeft) and (P.DXOverlay <> nil) and
P.DXOverlay.SpotAtPixel(X, Y, DXSpot) then
begin
DXTuneSpotOnPan(P, DXSpot.FreqHz, DXSpot.Mode);
if FTCIAdapter <> nil then
FTCIAdapter.NotifySpotClicked(DXSpot.Call, DXSpot.FreqHz, P.PanId, 0);
Id := DXTuneSpotOnPan(P, DXSpot.FreqHz, DXSpot.Mode);
// ★Приёмник TCI — это слот СЛАЙСА (панадаптером приёмник быть перестал),
// поэтому спрашиваем номер того слайса, который на спот и увели. Номер
// пана здесь адресовал чужой приёмник, а то и несуществующий — клиенты с
// фильтром по rx уведомление просто теряли.
// ★Ничего не увели (спот вне полосы захвата зумнутого пана, слоты
// кончились) — молчим. Иначе уведомление уезжало бы под номером 0, то есть
// от имени ГЛАВНОГО приёмника, который никуда не двигался: логгер открывал
// бы карточку QSO и тянул за собой VFO на частоту, где радио не стоит.
if (Id <> 0) and (FTCIAdapter <> nil) then
FTCIAdapter.NotifySpotClicked(DXSpot.Call, DXSpot.FreqHz,
FTCIAdapter.RxOfSlice(Id), 0);
Exit;
end;
W := TControl(Sender).Width;
+10
View File
@@ -353,6 +353,10 @@ type
{ Уведомление о клике по споту на панораме (§4.4) — зовёт UI. }
procedure NotifySpotClicked(const Call: string; FreqHz: Double;
Rx: Integer = 0; Ch: Integer = 0);
{ Номер TCI-приёмника, за которым стоит слайс Id. Нужен UI: уведомления
адресуются номерами TCI, а он знает только id слайса. 0 — слайса нет в
снимке (или Id = 0): адресуем главный тракт, он есть всегда. }
function RxOfSlice(Id: Integer): Integer;
{ Статус фокуса главного окна (APP_FOCUS) — зовёт UI. }
procedure NotifyAppFocus(InFocus: Boolean);
@@ -3790,6 +3794,12 @@ end;
Уведомления, которые инициирует UI
═══════════════════════════════════════════════════════════════════════════ }
function TTCIAdapter.RxOfSlice(Id: Integer): Integer;
var Rx, Ch: Integer;
begin
if SliceRxCh(Id, Rx, Ch) then Result := Rx else Result := 0;
end;
procedure TTCIAdapter.NotifySpotClicked(const Call: string; FreqHz: Double;
Rx, Ch: Integer);
begin