Add channel memory: CH button, dropdown, editor form, smart CTCSS/RPT auto-clear

- New ChannelStore.pas: TChannel record + JSON persistence (channels.json)
- New ChannelsForm.pas: full channel editor (list + fields, pre-fills from current radio state)
- CH button in RX block: left-click opens dropdown, right-click opens editor
- Active channel: button highlights with channel name, deactivates on VFO move
- ApplyChannel: auto-switches XVTR/HF band based on channel frequency; saves old
  band state before switching to prevent cache corruption
- SaveCurrentBand: skip save when VFO > 61 MHz (prevents HF cache corruption
  from VHF channel application without XVTR)
- DeactivateXvtr: save full XVTR state (was only saving LastFreq)
- CTCSS/RPT from channel marked as auto: cleared automatically when tuning away
  from channel frequency; survives band switches via FXvtrSettings auto-flags
- TXvtrEntry: add LastCTCSSAutoActive, LastFMRptAutoActive fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 15:39:37 +03:00
co-authored by Claude Sonnet 4.6
parent d08317e835
commit 1175e42516
7 changed files with 1298 additions and 24 deletions
+14 -6
View File
@@ -152,8 +152,10 @@ type
LastCTun: Boolean;
LastAGCMode: Integer;
LastAGCTop: Integer;
LastFMRptDir: Integer;
LastFMRptOffsetHz: Double;
LastFMRptDir: Integer;
LastFMRptOffsetHz: Double;
LastCTCSSAutoActive: Boolean; // CTCSS был включён каналом (авто)
LastFMRptAutoActive: Boolean; // RPT был включён каналом (авто)
end;
TXvtrSettings = record
@@ -1182,8 +1184,10 @@ begin
X.Entries[i].LastCTun := False;
X.Entries[i].LastAGCMode := 1;
X.Entries[i].LastAGCTop := 90;
X.Entries[i].LastFMRptDir := 0;
X.Entries[i].LastFMRptOffsetHz := 0.0;
X.Entries[i].LastFMRptDir := 0;
X.Entries[i].LastFMRptOffsetHz := 0.0;
X.Entries[i].LastCTCSSAutoActive := False;
X.Entries[i].LastFMRptAutoActive := False;
end;
// Шаблоны (пользователь должен сам Enable, чтобы трансверторы появились)
X.Entries[0].ButtonText := '2m';
@@ -1240,8 +1244,10 @@ begin
X.Entries[i].LastCTun := JB(EObj, 'last_ctun', X.Entries[i].LastCTun);
X.Entries[i].LastAGCMode := EnsureRange(JI(EObj, 'last_agc_mode', X.Entries[i].LastAGCMode), 0, 4);
X.Entries[i].LastAGCTop := EnsureRange(JI(EObj, 'last_agc_top', X.Entries[i].LastAGCTop), 0, 120);
X.Entries[i].LastFMRptDir := EnsureRange(JI(EObj, 'last_fmrpt_dir', X.Entries[i].LastFMRptDir), 0, 2);
X.Entries[i].LastFMRptOffsetHz := JD(EObj, 'last_fmrpt_offset', X.Entries[i].LastFMRptOffsetHz);
X.Entries[i].LastFMRptDir := EnsureRange(JI(EObj, 'last_fmrpt_dir', X.Entries[i].LastFMRptDir), 0, 2);
X.Entries[i].LastFMRptOffsetHz := JD(EObj, 'last_fmrpt_offset', X.Entries[i].LastFMRptOffsetHz);
X.Entries[i].LastCTCSSAutoActive := JB(EObj, 'last_ctcss_auto', X.Entries[i].LastCTCSSAutoActive);
X.Entries[i].LastFMRptAutoActive := JB(EObj, 'last_fmrpt_auto', X.Entries[i].LastFMRptAutoActive);
// Валидация: LastFreq должен быть в [FreqBegin..FreqEnd]
if (X.Entries[i].LastFreq < X.Entries[i].FreqBegin) or
(X.Entries[i].LastFreq > X.Entries[i].FreqEnd) then
@@ -1285,6 +1291,8 @@ begin
JW(EObj, 'last_agc_top', X.Entries[i].LastAGCTop);
JW(EObj, 'last_fmrpt_dir', X.Entries[i].LastFMRptDir);
JW(EObj, 'last_fmrpt_offset', X.Entries[i].LastFMRptOffsetHz);
JW(EObj, 'last_ctcss_auto', X.Entries[i].LastCTCSSAutoActive);
JW(EObj, 'last_fmrpt_auto', X.Entries[i].LastFMRptAutoActive);
end;
end;