diff --git a/CATEngine.pas b/CATEngine.pas index a19a97f..6dd82d4 100644 --- a/CATEngine.pas +++ b/CATEngine.pas @@ -88,7 +88,7 @@ type FCtx: TCATContext; FLock: TCriticalSection; - function Pad(V, W: Integer): string; + function Pad(V: Int64; W: Integer): string; function FreqToStr(Hz: Double): string; function StrToFreq(const S: string): Double; function ModeToK(M: Integer): Integer; @@ -311,7 +311,7 @@ begin if B then Result := '1' else Result := '0'; end; { ── Helpers ──────────────────────────────────────────────────────────────── } -function TCATEngine.Pad(V, W: Integer): string; +function TCATEngine.Pad(V: Int64; W: Integer): string; begin Result := IntToStr(Abs(V)); while Length(Result) < W do Result := '0' + Result; diff --git a/FreqDisplay.pas b/FreqDisplay.pas index 926229a..9b9833d 100644 --- a/FreqDisplay.pas +++ b/FreqDisplay.pas @@ -36,8 +36,8 @@ type FCenterText: Boolean; FOnChange: TFreqChangeEvent; - FHoverDigit: Integer; // 0=единицы .. 8=100МГц, -1=нет - FDigitX: array[0..8] of Integer; // X левого края каждой цифры + FHoverDigit: Integer; // 0=единицы .. 10=10ГГц, -1=нет + FDigitX: array[0..10] of Integer; // X левого края каждой цифры FDigitW: Integer; FCharH: Integer; @@ -125,7 +125,7 @@ var begin Result := -1; if FDigitW = 0 then Exit; - for i := 0 to 8 do + for i := 0 to 10 do if (X >= FDigitX[i]) and (X < FDigitX[i] + FDigitW) then begin Result := i; @@ -137,14 +137,14 @@ end; // Цифры нумеруются справа налево: digit0=единицы, digit8=сотни млн procedure TFreqDisplay.BuildDigitMap(const S: string; StartX: Integer); var - Xs: array[0..11] of Integer; // X каждого символа строки (макс 12 симв) + Xs: array[0..13] of Integer; // X каждого символа строки (макс 14 симв) ci: Integer; xi: Integer; dIdx: Integer; len: Integer; begin len := Length(S); - if len > 12 then len := 12; + if len > 13 then len := 13; xi := StartX; for ci := 0 to len - 1 do @@ -159,7 +159,7 @@ begin begin if S[ci + 1] <> '.' then begin - if dIdx <= 8 then + if dIdx <= 10 then FDigitX[dIdx] := Xs[ci]; Inc(dIdx); end; @@ -185,7 +185,7 @@ var ch: Char; dIdx: Integer; col: TColor; - DiChar: array[0..11] of Integer; + DiChar: array[0..13] of Integer; tmpIdx: Integer; len: Integer; begin @@ -234,7 +234,7 @@ begin // Строим обратную карту символ → digit index len := Length(S); - if len > 12 then len := 12; + if len > 13 then len := 13; tmpIdx := 0; for ci := len - 1 downto 0 do begin @@ -385,7 +385,7 @@ begin VK_DOWN: begin ChangeByDigit(d, -1); Key := 0; end; VK_LEFT: begin if FHoverDigit < 0 then FHoverDigit := 3; - if FHoverDigit < 8 then Inc(FHoverDigit); + if FHoverDigit < FMinMhzDigits + 5 then Inc(FHoverDigit); Invalidate; Key := 0; end; VK_RIGHT: begin diff --git a/MainForm.pas b/MainForm.pas index 5e7a529..45302af 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -278,6 +278,7 @@ type FBandCache: array[0..CFG_BAND_COUNT-1] of TBandSettings; // кэш диапазонов FDevConnected: Boolean; // True после первого подключения FLightTheme: Boolean; // True = светлая тема + FFreqMhzDigits: Integer; // 3=999MHz, 4=9.999GHz, 5=99.999GHz FCenterFreq: Double; // центр спектра = LO (DDC). При CTUN ON не меняется при кручении VFO FSpanHz: Double; FSampleRate: Integer; // текущий DDC sample rate (Hz) @@ -630,6 +631,7 @@ type function BuildMicLineSelectByte: Byte; procedure ApplyVisibility(ShowSpectrum, ShowWaterfall: Boolean); procedure ApplyFPS(FPS: Integer); + procedure ApplyFreqMhzDigits(Digits: Integer); published // Обработчики событий формы — должны быть в published для RTTI/LFM @@ -919,6 +921,7 @@ begin Result.DisplayDuplex := FDisplayDuplex; Result.DisplayFPS := FDisplayFPS; Result.LightTheme := FLightTheme; + Result.FreqMhzDigits := FFreqMhzDigits; // Audio device names Result.AudioOutDevice := FAudioOutDevName; Result.AudioInDevice := FAudioInDevName; @@ -1182,6 +1185,7 @@ begin FShowSpectrum := True; FShowWaterfall := True; FDisplayFPS := 60; + FFreqMhzDigits := 3; FSplitterRatio := 0.40; FSplitterDrag := False; @@ -1992,7 +1996,7 @@ begin Canvas.Font.Style := [fsBold]; FreqDispA.Left := VFO_BTN_W + VFO_BTN_FREQ_GAP - VFO_FREQ_LEFT_PAD; FreqDispB.Left := VFO_B_LEFT_PAD + VFO_BTN_W + VFO_BTN_FREQ_GAP - VFO_FREQ_LEFT_PAD; - FreqTextW := Canvas.TextWidth('000.000.000'); + FreqTextW := Canvas.TextWidth(StringOfChar('0', Max(3, FFreqMhzDigits)) + '.000.000'); NeedFreqAW := FreqDispA.Left + FreqTextW + VFO_FREQ_LEFT_PAD + VFO_FREQ_RIGHT_PAD; NeedFreqBW := FreqDispB.Left + FreqTextW + VFO_FREQ_LEFT_PAD + VFO_FREQ_RIGHT_PAD; TopVfoAW := NeedFreqAW; @@ -2448,12 +2452,13 @@ end; function TMainForm.FormatFreq(Hz: Double): string; var - Mhz, KHz, Rest: Integer; + Mhz: Int64; + KHz, Rest: Integer; begin Mhz := Trunc(Hz / 1000000); KHz := Trunc((Hz - Mhz * 1000000) / 1000); Rest := Trunc(Hz) mod 1000; - Result := Format('%3d.%3.3d.%3.3d', [Mhz, KHz, Rest]); + Result := Format('%d.%3.3d.%3.3d', [Mhz, KHz, Rest]); end; procedure TMainForm.UpdateVfoDisplay; @@ -3568,6 +3573,7 @@ begin if BtnDUP <> nil then StyleButton(BtnDUP, FDisplayDuplex); if G_Settings.DisplayFPS > 0 then ApplyFPS(G_Settings.DisplayFPS); + ApplyFreqMhzDigits(EnsureRange(G_Settings.FreqMhzDigits, 3, 5)); // Настройки дисплея и сетки if G_Settings.FFTSize > 0 then begin @@ -6116,6 +6122,21 @@ begin FDSPEngine.SetDisplayFPS(FPS); end; +procedure TMainForm.ApplyFreqMhzDigits(Digits: Integer); +const + MaxFreqs: array[3..5] of Int64 = (999999999, 9999999999, 99999999999); +begin + Digits := EnsureRange(Digits, 3, 5); + FFreqMhzDigits := Digits; + FreqDispA.MinMhzDigits := Digits; + FreqDispB.MinMhzDigits := Digits; + FreqDispA.MaxFreq := MaxFreqs[Digits]; + FreqDispB.MaxFreq := MaxFreqs[Digits]; + if Assigned(FWebServer) then + FWebServer.FreqMhzDigits := Digits; + LayoutTopVfoBlock; +end; + procedure TMainForm.BtnSettingsClick(Sender: TObject); var SF: TSettingsForm; @@ -6133,8 +6154,9 @@ begin SF.OnFPSChange := ApplyFPS; SF.OnPAChange := OnPASettingsChange; SF.OnCATChange := OnCATSettingsChange; - SF.OnThemeChange := SetLightTheme; - SF.OnTXChange := OnTXSettingsChange; + SF.OnThemeChange := SetLightTheme; + SF.OnFreqMhzDigitsChange := ApplyFreqMhzDigits; + SF.OnTXChange := OnTXSettingsChange; SF.OnAlexChange := OnAlexSettingsChange; SF.OnXvtrChange := OnXvtrSettingsChange; end; @@ -6158,6 +6180,7 @@ begin SF.LoadVisibility(FShowSpectrum, FShowWaterfall); SF.LoadFPS(FDisplayFPS); SF.LoadLightTheme(FLightTheme); + SF.LoadFreqMhzDigits(FFreqMhzDigits); // Загружаем текущие значения if FWDSPReady then SF.LoadValues( diff --git a/Settings.pas b/Settings.pas index 7336759..839b393 100644 --- a/Settings.pas +++ b/Settings.pas @@ -194,6 +194,8 @@ type DisplayFPS: Integer; // spectrum/waterfall timer fps (1..100) // --- Theme --- LightTheme: Boolean; // False = dark (default), True = light + // --- VFO display digits --- + FreqMhzDigits: Integer; // minimum MHz digit count: 3=999MHz, 4=9.999GHz, 5=99.999GHz // --- PA (Power Amplifier) settings --- PAMaxPower: Double; // максимальная выходная мощность, Вт (5..200) PABandCal: array[0..CFG_BAND_COUNT-1] of Double; // калибровка на диапазон 38.8..100.0 @@ -346,6 +348,7 @@ begin G.ShowWaterfall := True; G.DisplayDuplex := False; G.DisplayFPS := 60; + G.FreqMhzDigits := 3; // CAT defaults for i := 0 to 3 do begin G.CATSerialEnabled[i] := False; @@ -557,6 +560,7 @@ begin G.DisplayDuplex := JB(GObj,'display_duplex',False); G.DisplayFPS := JI(GObj,'display_fps',60); G.LightTheme := JB(GObj,'light_theme',False); + G.FreqMhzDigits := EnsureRange(JI(GObj,'freq_mhz_digits',3), 3, 5); // PA settings G.PAMaxPower := JD(GObj,'pa_max_power',100.0); for i := 0 to CFG_BAND_COUNT-1 do @@ -636,6 +640,7 @@ begin JW(O,'display_duplex',G.DisplayDuplex); JW(O,'display_fps',G.DisplayFPS); JW(O,'light_theme',G.LightTheme); + JW(O,'freq_mhz_digits',G.FreqMhzDigits); // PA settings JW(O,'pa_max_power',G.PAMaxPower); for i := 0 to CFG_BAND_COUNT-1 do diff --git a/SettingsForm.pas b/SettingsForm.pas index e144aea..501b341 100644 --- a/SettingsForm.pas +++ b/SettingsForm.pas @@ -36,6 +36,7 @@ type // TX-настройки целиком (любая правка → один callback с актуальной структурой) TOnTXSettingsChange = procedure(const T: TTXSettings) of object; + TOnFreqMhzDigitsChange = procedure(Digits: Integer) of object; TOnDisplayParamChange = procedure(FFTSize, WindowType, SpecDetector, SpecAvgMode: Integer; SpecAvgTimeMS: Double) of object; TOnWaterfallParamChange = procedure(WfDetector, WfAvgMode: Integer; @@ -109,6 +110,7 @@ type FChkShowWaterfall: TCheckBox; FCmbFPS: TComboBox; FChkLightTheme: TCheckBox; + FCmbFreqMhzDigits: TComboBox; // ---- PA Settings tab controls ---- FEdMaxPower: TSpinEdit; @@ -243,8 +245,9 @@ type FOnAudioInDevChange: TOnAudioInDevChange; FOnVisibilityChange: TOnVisibilityChange; FOnFPSChange: TOnFPSChange; - FOnThemeChange: TOnThemeChange; - FOnTXChange: TOnTXSettingsChange; + FOnThemeChange: TOnThemeChange; + FOnFreqMhzDigitsChange: TOnFreqMhzDigitsChange; + FOnTXChange: TOnTXSettingsChange; FLoading: Boolean; @@ -298,6 +301,7 @@ type procedure OnVisibilityChkChange(Sender: TObject); procedure OnFPSCmbChange(Sender: TObject); procedure OnLightThemeChkChange(Sender: TObject); + procedure OnFreqMhzDigitsCmbChange(Sender: TObject); procedure OnMaxPowerChange(Sender: TObject); procedure OnBandCalChange(Sender: TObject); procedure OnCATSerialChange(Sender: TObject); @@ -335,6 +339,7 @@ type procedure LoadVisibility(ShowSpectrum, ShowWaterfall: Boolean); procedure LoadFPS(FPS: Integer); procedure LoadLightTheme(ALight: Boolean); + procedure LoadFreqMhzDigits(Digits: Integer); procedure LoadPASettings(MaxPower: Double; const BandCal: array of Double); procedure LoadCATSettings( const SerEnabled: array of Boolean; @@ -357,6 +362,7 @@ type property OnVisibilityChange: TOnVisibilityChange read FOnVisibilityChange write FOnVisibilityChange; property OnFPSChange: TOnFPSChange read FOnFPSChange write FOnFPSChange; property OnThemeChange: TOnThemeChange read FOnThemeChange write FOnThemeChange; + property OnFreqMhzDigitsChange: TOnFreqMhzDigitsChange read FOnFreqMhzDigitsChange write FOnFreqMhzDigitsChange; property OnTXChange: TOnTXSettingsChange read FOnTXChange write FOnTXChange; end; @@ -783,6 +789,15 @@ begin FChkLightTheme.Font.Name := UI_FONT; FChkLightTheme.Font.Size := 9; FChkLightTheme.OnChange := OnLightThemeChkChange; + + Grp := MakeGroupPanel(FPageDisplay, 'VFO Display', MARGIN, 378, 650, 92); + + MakeLbl(Grp, 'Frequency range', PAD, R1 + 5, LW); + FCmbFreqMhzDigits := MakeCombo(Grp, CX, R1, 240, OnFreqMhzDigitsCmbChange); + FCmbFreqMhzDigits.Items.Add('up to 999 MHz (3 digits)'); + FCmbFreqMhzDigits.Items.Add('up to 9.999 GHz (4 digits)'); + FCmbFreqMhzDigits.Items.Add('up to 99.999 GHz (5 digits)'); + FCmbFreqMhzDigits.ItemIndex := 0; end; // --------------------------------------------------------------------------- @@ -2014,6 +2029,23 @@ begin else ApplyTheme(DarkTheme); end; +procedure TSettingsForm.LoadFreqMhzDigits(Digits: Integer); +begin + FLoading := True; + try + FCmbFreqMhzDigits.ItemIndex := EnsureRange(Digits - 3, 0, 2); + finally + FLoading := False; + end; +end; + +procedure TSettingsForm.OnFreqMhzDigitsCmbChange(Sender: TObject); +begin + if FLoading then Exit; + if Assigned(FOnFreqMhzDigitsChange) then + FOnFreqMhzDigitsChange(FCmbFreqMhzDigits.ItemIndex + 3); +end; + procedure TSettingsForm.LoadPASettings(MaxPower: Double; const BandCal: array of Double); var i, n: Integer; diff --git a/SpectrumView.pas b/SpectrumView.pas index d9a49f7..e256d94 100644 --- a/SpectrumView.pas +++ b/SpectrumView.pas @@ -371,12 +371,12 @@ begin end; function FormatFreqSV(Hz: Double): string; -var Mhz, KHz, Rest: Integer; +var Mhz: Int64; KHz, Rest: Integer; begin Mhz := Trunc(Hz / 1000000); KHz := Trunc((Hz - Mhz * 1000000) / 1000); Rest := Trunc(Hz) mod 1000; - Result := Format('%3d.%3.3d.%3.3d', [Mhz, KHz, Rest]); + Result := Format('%d.%3.3d.%3.3d', [Mhz, KHz, Rest]); end; // ════════════════════════════════════════════════════════════════════════════ diff --git a/WebPageHtml.pas b/WebPageHtml.pas index 8012924..92d96df 100644 --- a/WebPageHtml.pas +++ b/WebPageHtml.pas @@ -323,22 +323,28 @@ begin 'q("muteIc").onclick=()=>{cur.mute=!cur.mute;q("muteIc").textContent=cur.mute?"\uD83D\uDD07":"\uD83D\uDD0A";cmd("set_mute",cur.mute);};' + 'let stA=0,stAmem=100,stB=0,stBmem=100;' + + 'let freqMhzDigits=3,freqMaxHz=999999999;' + 'function fmtVfo(hz,step){' + - 'const s=String(Math.max(0,Math.round(hz||0))).padStart(9,"0");' + + 'const N=freqMhzDigits+6;' + + 'const s=String(Math.max(0,Math.round(hz||0))).padStart(N,"0");' + 'let h="",seenNZ=false;' + - 'for(let i=0;i<6;i++){' + - 'const st=Math.pow(10,8-i);' + + 'for(let i=0;i0&&st===step)?" sel":"";' + - 'h+=""+s[i]+"";' + - 'if(i===2)h+=".";}' + + 'h+=""+s[i]+"";}' + 'h+=".";' + - 'h+="";' + - 'for(let i=6;i<9;i++){const st=Math.pow(10,8-i);' + + 'for(let i=freqMhzDigits;i0&&st===step)?" sel":"";' + - 'h+=""+s[i]+"";}' + - 'return h;}' + + 'h+=""+s[i]+"";}' + + 'h+=".";' + + 'for(let i=freqMhzDigits+3;i0&&st===step)?" sel":"";' + + 'h+=""+s[i]+"";}' + + 'h+="";return h;}' + 'function renderVfos(){q("vDispA").innerHTML=fmtVfo(vfoA,stA);q("vDispB").innerHTML=fmtVfo(vfoB,stB);}' + 'function bindVfo(dispId,getHz,setHz,getSt,setSt,getMem,setMem,onSend){' + @@ -348,12 +354,12 @@ begin 'el.addEventListener("mouseleave",()=>{if(getSt()!==0){setSt(0);renderVfos();}});' + 'el.addEventListener("wheel",e=>{e.preventDefault();e.stopPropagation();audioKick();' + 'const dir=e.deltaY<0?1:-1,st=getSt()||getMem()||100;' + - 'const nf=cl(Math.round(getHz())+st*dir,30000,60000000);' + + 'const nf=cl(Math.round(getHz())+st*dir,30000,freqMaxHz);' + 'setHz(nf);onSend(nf);},{passive:false});' + 'el.addEventListener("dblclick",()=>{' + 'const s=prompt("Set Hz",String(Math.round(getHz())));if(!s)return;' + 'const f=parseInt(s.replace(/[^0-9]/g,""),10);if(!isFinite(f))return;' + - 'const nf=cl(f,30000,60000000);setHz(nf);renderVfos();onSend(nf);' + + 'const nf=cl(f,30000,freqMaxHz);setHz(nf);renderVfos();onSend(nf);' + '});}' + 'bindVfo("vDispA",()=>vfoA,v=>{vfoA=v;},()=>stA,v=>{stA=v;},()=>stAmem,v=>{stAmem=v;},' + 'function(nf){renderVfos();pend("freq");if(activeVfo==="A"){localCenter=nf;ws2({cmd:"freq",hz:nf});}else ws2({cmd:"freq_b",hz:nf});});' + @@ -483,6 +489,7 @@ begin '}' + 'if(!isPending("freq")){vfoA=cur.vfo_a_hz||vfoA;}' + 'if(msg.vfo_b_hz!=null)vfoB=msg.vfo_b_hz;' + + 'if(msg.freq_mhz_digits!=null){freqMhzDigits=Math.max(3,Math.min(5,msg.freq_mhz_digits|0));freqMaxHz=Math.pow(10,freqMhzDigits+6)-1;}' + 'if(msg.active_vfo!=null&&!isPending("freq")){' + 'const sv=msg.active_vfo===0?"A":"B";' + 'if(sv!==activeVfo){activeVfo=sv;' + diff --git a/WebServer.pas b/WebServer.pas index 0ff60a1..5c31186 100644 --- a/WebServer.pas +++ b/WebServer.pas @@ -241,7 +241,8 @@ type FAttnIdx: Integer; FOnTun: TWebCmdTun; FTuning: Boolean; - FDuplex: Boolean; + FDuplex: Boolean; + FFreqMhzDigits: Integer; // 3=999MHz, 4=9.999GHz, 5=99.999GHz FXvtrBands: TWebXvtrArray; // список enabled XVTR (для web-UI) FCurrentXvtr: Integer; // -1 = HF, иначе индекс активного XVTR FOnXvtrBand: TWebCmdXvtrBand; @@ -292,6 +293,7 @@ type AttnIdx: Integer; Tuning: Boolean; Duplex: Boolean); property WebClientActive: Boolean read FWebClientActive; + property FreqMhzDigits: Integer read FFreqMhzDigits write FFreqMhzDigits; property OnFreq: TWebCmdFreq read FOnFreq write FOnFreq; property OnMode: TWebCmdMode read FOnMode write FOnMode; @@ -419,7 +421,8 @@ begin FVolume := 70; FSMeter := -120; FBandIdx := 5; - FCurrentXvtr := -1; + FCurrentXvtr := -1; + FFreqMhzDigits := 3; SetLength(FXvtrBands, 0); end; @@ -1233,7 +1236,7 @@ begin '"running":%s,"mute":%s,"ctun":%s,' + '"nr_mode":%d,"nr":%s,"nb_mode":%d,"nb":%s,"snb":%s,"anf":%s,"connected":%s,' + '"transmitting":%s,"drive":%d,"attn_idx":%d,"tuning":%s,"dup":%s,' + - '"xvtr_current":%d,"xvtr_bands":%s}', + '"xvtr_current":%d,"xvtr_bands":%s,"freq_mhz_digits":%d}', [FFreq, FVfoB, FActiveVfo, FMode, MODE_N[FMode mod 8], FFilterIdx, FFilterBW, @@ -1257,7 +1260,7 @@ begin FAttnIdx, BoolToStr(FTuning, 'true', 'false'), BoolToStr(FDuplex, 'true', 'false'), - FCurrentXvtr, XvtrJson + FCurrentXvtr, XvtrJson, FFreqMhzDigits ], FS); finally FStateLock.Leave;