From dfb18fb700d905f3901c0b6b3c957ccaf7efb2f2 Mon Sep 17 00:00:00 2001 From: Szymon Porwolik Date: Wed, 5 Nov 2025 20:51:42 +0100 Subject: [PATCH] Initial commit --- assets/js/cat.js | 34 ++- assets/js/dxwaterfall.js | 380 ++++++++++-------------------- assets/js/sections/qrg_handler.js | 16 +- assets/js/sections/qso.js | 94 +------- 4 files changed, 170 insertions(+), 354 deletions(-) diff --git a/assets/js/cat.js b/assets/js/cat.js index fb2953200..6596bb9b5 100644 --- a/assets/js/cat.js +++ b/assets/js/cat.js @@ -48,6 +48,16 @@ $(document).ready(function() { // Cache for radio names to avoid repeated AJAX calls var radioNameCache = {}; + // Global CAT state - stores last received data from radio + // This allows other components (like DX Waterfall) to read radio state + // without depending on form fields + window.catState = { + frequency: null, // Hz + frequency_rx: null, // Hz (for split operation) + mode: null, // String (USB, LSB, CW, etc.) + lastUpdate: null // Timestamp of last update + }; + /** * Initialize WebSocket connection for real-time radio updates * Handles connection, reconnection logic, and error states @@ -490,6 +500,16 @@ $(document).ready(function() { return; // Exit early - do not update any fields with old data } + // Update global CAT state FIRST before any UI updates + // This allows DX Waterfall and other components to read radio state + // without depending on form fields + if (window.catState) { + window.catState.frequency = data.frequency || null; + window.catState.frequency_rx = data.frequency_rx || null; + window.catState.mode = data.mode ? catmode(data.mode) : null; + window.catState.lastUpdate = new Date(); + } + // Cache frequently used DOM selectors var $frequency = $('#frequency'); var $band = $('#band'); @@ -514,6 +534,7 @@ $(document).ready(function() { // Force update by clearing catValue (prevents cat2UI from blocking updates) $frequency.removeData('catValue'); + $mode.removeData('catValue'); // Also clear mode cache cat_updating_frequency = true; // Set flag before CAT update // Check if DX Waterfall's CAT frequency handler is available @@ -523,22 +544,20 @@ $(document).ready(function() { cat2UI($frequency,data.frequency,false,true,function(d){ $frequency.trigger('change'); // Trigger for other event handlers const newBand = frequencyToBand(d); - // Don't auto-update band if user just manually changed it (prevents race condition) - if ($band.val() != newBand && (typeof dxWaterfall === 'undefined' || !dxWaterfall.userChangedBand)) { + // Auto-update band based on frequency + if ($band.val() != newBand) { $band.val(newBand).trigger('change'); // Trigger band change } - cat_updating_frequency = false; // Clear flag after updates }); }); } else { // Standard frequency update (no DX Waterfall debounce handling) cat2UI($frequency,data.frequency,false,true,function(d){ $frequency.trigger('change'); - // Don't auto-update band if user just manually changed it (prevents race condition) - if ($band.val() != frequencyToBand(d) && (typeof dxWaterfall === 'undefined' || !dxWaterfall.userChangedBand)) { + // Auto-update band based on frequency + if ($band.val() != frequencyToBand(d)) { $band.val(frequencyToBand(d)).trigger('change'); } - cat_updating_frequency = false; // Clear flag after updates }); } @@ -559,6 +578,9 @@ $(document).ready(function() { cat2UI($('#transmit_power'),data.power,false,false); cat2UI($('#selectPropagation'),data.prop_mode,false,false); + // Clear the CAT updating flag AFTER all updates + cat_updating_frequency = false; + // Data is fresh (timeout check already passed at function start) // Set CAT state for waterfall if dxwaterfall_cat_state is available if (typeof dxwaterfall_cat_state !== 'undefined') { diff --git a/assets/js/dxwaterfall.js b/assets/js/dxwaterfall.js index 4defc5a3d..c68714e2b 100644 --- a/assets/js/dxwaterfall.js +++ b/assets/js/dxwaterfall.js @@ -1,7 +1,7 @@ // @ts-nocheck /** * @fileoverview DX WATERFALL for WaveLog - * @version 0.9.1 // also change line 38 + * @version 0.9.2 // also change line 38 * @author Wavelog Team * * @description @@ -36,10 +36,10 @@ var DX_WATERFALL_CONSTANTS = { // Version - VERSION: '0.9.1', // DX Waterfall version (keep in sync with @version in file header) + VERSION: '0.9.2', // DX Waterfall version (keep in sync with @version in file header) // Debug and logging - DEBUG_MODE: false, // Set to true for verbose logging, false for production + DEBUG_MODE: true, // Set to true for verbose logging, false for production // Timing and debouncing DEBOUNCE: { @@ -114,7 +114,6 @@ var DX_WATERFALL_CONSTANTS = { THRESHOLDS: { FREQUENCY_COMPARISON: 0.1, // Frequency comparison tolerance in kHz FT8_FREQUENCY_TOLERANCE: 5, // FT8 frequency detection tolerance in kHz - BAND_CHANGE_THRESHOLD: 1000, // kHz outside band before recalculation MAJOR_TICK_TOLERANCE: 0.05, // Floating point precision for major tick detection SPOT_FREQUENCY_MATCH: 0.01, // Frequency match tolerance for spot navigation (kHz) CAT_FREQUENCY_HZ: 1, // CAT frequency confirmation tolerance (1 Hz for exact tuning) @@ -392,16 +391,9 @@ function handleCATFrequencyUpdate(radioFrequency, updateCallback) { // Only invalidate cache and commit if frequency actually changed if (typeof dxWaterfall !== 'undefined' && (frequencyChanged || isInitialLoad)) { - // Skip frequency commit if: - // 1. User just manually changed the band (2-second cooldown) - // 2. Waiting for frequency update after band change - BUT clear the flag now since CAT confirmed! - // This prevents race condition where CAT sends old frequency before radio catches up - if (dxWaterfall.userChangedBand) { - // Also clear waitingForFrequencyUpdate if it's set - this CAT update is likely stale - if (dxWaterfall.waitingForFrequencyUpdate) { - dxWaterfall.waitingForFrequencyUpdate = false; - } - } else if (dxWaterfall.waitingForFrequencyUpdate) { + // Clear waitingForFrequencyUpdate if band just changed + // This CAT update confirms the new frequency after band change + if (dxWaterfall.waitingForFrequencyUpdate) { // CAT has confirmed the new frequency - clear the waiting flag and commit it dxWaterfall.waitingForFrequencyUpdate = false; dxWaterfall.waitingForData = false; @@ -1417,11 +1409,6 @@ var DX_WATERFALL_UTILS = { DX_WATERFALL_UTILS.qsoForm.clearForm(); } - // Check if frequency is far outside current band and update band if needed - if (waterfallContext.isFrequencyFarOutsideBand(targetSpot.frequency)) { - waterfallContext.updateBandFromFrequency(targetSpot.frequency); - } - // CRITICAL: Set mode FIRST before calling setFrequency // setFrequency reads the mode from $('#mode').val(), so the mode must be set first var radioMode = this.determineRadioMode(targetSpot); @@ -1553,8 +1540,6 @@ var dxWaterfall = { // DATA MANAGEMENT PROPERTIES // ======================================== dxSpots: [], - lastBand: null, - lastMode: null, initialFetchDone: false, totalSpotsCount: 0, @@ -1620,12 +1605,13 @@ var dxWaterfall = { // State flags programmaticModeChange: false, - programmaticBandUpdate: false, - userChangedBand: false, initializationComplete: false, lastPopulatedSpot: null, pendingSpotSelection: null, + // Band tracking - tracks which band we currently have spots for + currentSpotBand: null, // The band we last fetched spots for + // Display configuration displayConfig: { isSplit: false, @@ -1861,9 +1847,7 @@ var dxWaterfall = { _loadSettings: function() { this.loadSettingsFromCookies(); - // Initialize lastBand and lastMode to prevent false change detection - this.lastBand = this.getCurrentBand(); - this.lastMode = this.getCurrentMode(); + // Initialize band cache for edge calculations this.cachedBandForEdges = this.getCurrentBand(); }, @@ -1948,6 +1932,9 @@ var dxWaterfall = { // Commit the current frequency value (called on blur or Enter key) // This prevents the waterfall from shifting while the user is typing commitFrequency: function() { + // This function is primarily for the fallback case when CAT is not available + // When CAT is active, the waterfall reads from window.catState.frequency + // Safety check: return early if waterfall is not initialized (destroyed or not yet ready) if (!this.$freqCalculated || !this.$qrgUnit) { return; @@ -1957,30 +1944,15 @@ var dxWaterfall = { var currentUnit = this.$qrgUnit.text() || 'kHz'; // If this is a valid frequency, save it as the last valid committed frequency + // (used as fallback when CAT not available) var freqValue = parseFloat(currentInput) || 0; if (freqValue > 0) { this.lastValidCommittedFreq = currentInput; this.lastValidCommittedUnit = currentUnit; - // CRITICAL: Update band from frequency BEFORE refresh() is called - // This ensures programmaticBandUpdate flag is set before hasParametersChanged() runs - // BUT only do this if the band actually needs to change (frequency is outside current band) - var currentFreqKhz = DX_WATERFALL_UTILS.frequency.convertToKhz(freqValue, currentUnit); - // Store the committed frequency in kHz for comparison checks + var currentFreqKhz = DX_WATERFALL_UTILS.frequency.convertToKhz(freqValue, currentUnit); this.committedFrequencyKHz = currentFreqKhz; - if (currentFreqKhz > 0) { - var expectedBand = this.getFrequencyBand(currentFreqKhz); - var currentBand = this.getCurrentBand(); - - // Only update band if frequency's band differs from current band - // This prevents overriding manual band changes when frequency is already in that band - // ALSO: Don't auto-update band if user just manually changed it (userChangedBand cooldown active) - // This prevents race condition where CAT sends old frequency before radio catches up to new band - if (expectedBand && currentBand && expectedBand !== currentBand && !this.userChangedBand) { - this.updateBandFromFrequency(currentFreqKhz); - } - } // If we're still waiting for CAT frequency and user manually set a frequency, cancel the wait // Only cancel if initialization is complete (don't cancel during initial page load) @@ -1997,10 +1969,7 @@ var dxWaterfall = { } } - // Invalidate the cached frequency to force recalculation - this.lastQrgUnit = null; - - // Force an immediate refresh to update the display with the new frequency + // Force a refresh to update the display (mainly for non-CAT usage) if (this.canvas && this.ctx) { this.refresh(); } @@ -2011,112 +1980,26 @@ var dxWaterfall = { } }, - // Check if band or mode has changed - hasParametersChanged: function() { - // Get current values from form elements FIRST to detect immediate changes - var currentBand = this.getCurrentBand(); - var currentMode = this.getCurrentMode(); - - // Check if band changed (even during cooldown) and reset dataReceived flag immediately - // This prevents old band data from being displayed while waiting for new band to fetch - var bandChanged = (currentBand !== this.lastBand); - if (bandChanged && this.lastBand !== null) { - // Band changed - immediately mark as waiting for new data - this.dataReceived = false; - this.waitingForData = true; - } - - // Check for invalid states that should prevent spot fetching - var middleFreq = this.getCachedMiddleFreq(); // Returns frequency in kHz - var isFrequencyInvalid = middleFreq <= 0; - var isBandInvalid = !currentBand || currentBand === '' || currentBand.toLowerCase() === 'select'; - - // Early return: If frequency or band is invalid, don't fetch spots - if (isFrequencyInvalid || isBandInvalid) { - this.waitingForData = true; - this.dataReceived = false; - this.relevantSpots = []; - this.currentSpotIndex = 0; - this.lastSpotInfoKey = null; - if (this.spotInfoDiv) { - this.spotInfoDiv.innerHTML = ' '; - } - // Update tracking but don't trigger fetch - this.lastBand = currentBand; - this.lastMode = currentMode; - return false; - } - - // Check for changes - bandChanged = this.lastBand !== currentBand; - var modeChanged = this.lastMode !== currentMode; - - // Update zoom menu when mode changes (but don't trigger spot fetch) - if (modeChanged) { - this.updateZoomMenu(); - } - - // Early return: Only band changes should trigger spot fetching - // Mode changes should NOT trigger fetching (mode is just a display filter) - if (!bandChanged) { - this.lastBand = currentBand; - this.lastMode = currentMode; - return false; - } - - // Band changed - invalidate caches - this.bandLimitsCache = null; - this.cachedBandForEdges = currentBand; - this.cache.visibleSpots = null; - this.cache.visibleSpotsParams = null; - - // Early return: If this is initial load (lastBand is null), don't reset state - if (this.lastBand === null) { - this.lastBand = currentBand; - this.lastMode = currentMode; - return bandChanged; - } - - // Band changed after initial load - reset waiting state - this.waitingForData = true; - this.dataReceived = false; - this.dxSpots = []; - this.totalSpotsCount = 0; - this.relevantSpots = []; - this.currentSpotIndex = 0; - this.lastSpotInfoKey = null; - if (this.spotInfoDiv) { - this.spotInfoDiv.innerHTML = ' '; - } - this.operationStartTime = Date.now(); - this.updateZoomMenu(); - - // Handle programmatic vs manual band changes - if (this.programmaticBandUpdate) { - // Programmatic (CAT) change - reset flag and cancel any manual cooldown - this.programmaticBandUpdate = false; - if (this.userChangedBand) { - this.userChangedBand = false; - } - } else if (!this.userChangedBand) { - // Manual change with no cooldown active - set cooldown - this.userChangedBand = true; - var self = this; - setTimeout(function() { - self.userChangedBand = false; - }, 2000); - } - - this.lastBand = currentBand; - this.lastMode = currentMode; - - return bandChanged; - }, - // Get cached middle frequency to avoid repeated DOM access and parsing // Always returns frequency in kHz for internal calculations getCachedMiddleFreq: function() { - // Use committed frequency values (only updated on blur/Enter) to prevent shifting while typing + // PRIORITY 1: Use CAT state if available (radio controls waterfall) + // The waterfall should display what the radio is tuned to, not what's in the form + if (window.catState && window.catState.frequency && window.catState.frequency > 0) { + var freqHz = window.catState.frequency; + var freqKhz = freqHz / 1000; + + // Cache the CAT frequency + this.cache.middleFreq = freqKhz; + + // Update split operation state and get display configuration + this.updateSplitOperationState(); + + return this.displayConfig.centerFrequency; + } + + // FALLBACK: Use committed frequency values (only updated on blur/Enter) to prevent shifting while typing + // This is used when CAT is not available (no radio connected) // Strategy: // 1. If we have a valid committed frequency from this session, always use last VALID commit // 2. Otherwise use real-time values (initial load before any commits) @@ -2153,9 +2036,13 @@ var dxWaterfall = { // Update split operation state and configure display parameters updateSplitOperationState: function() { - // Check if frequency_rx field exists and has a value + // Prefer CAT state for frequency_rx (radio controls split operation) var frequencyRxValue = null; - if (DX_WATERFALL_UTILS.fieldMapping.hasOptionalField('frequency_rx')) { + + if (window.catState && window.catState.frequency_rx) { + frequencyRxValue = window.catState.frequency_rx; + } else if (DX_WATERFALL_UTILS.fieldMapping.hasOptionalField('frequency_rx')) { + // Fallback to form field if CAT not available var $frequencyRx = DX_WATERFALL_UTILS.fieldMapping.getField('frequency_rx', true); frequencyRxValue = $frequencyRx.val(); } @@ -2264,10 +2151,8 @@ var dxWaterfall = { // Force immediate cache refresh and visual update this.lastFrequencyRefreshTime = 0; - // Only refresh from DOM if CAT is available - otherwise keep waterfall frequency independent - if (isCATAvailable()) { - this.refreshFrequencyCache(); - } + // Note: refreshFrequencyCache() no longer needed here + // Waterfall reads frequency from window.catState (CAT data), not form fields if (this.canvas && this.ctx) { this.refresh(); @@ -3019,7 +2904,9 @@ var dxWaterfall = { // Get band limits for current band and region getBandLimits: function() { - var currentBand = this.getCurrentBand(); + // Use the band we have spots for, not the form selector + // This prevents drawing wrong band limits when form is changed manually + var currentBand = this.currentSpotBand || this.getCurrentBand(); var currentRegion = this.continentToRegion(this.currentContinent); var regionKey = 'region' + currentRegion; @@ -3063,29 +2950,6 @@ var dxWaterfall = { return limits; }, - // Check if frequency is more than 1000 kHz outside current band limits - // Returns true if band should be recalculated - isFrequencyFarOutsideBand: function(frequencyKhz) { - var bandLimits = this.getBandLimits(); - - // If no band limits available, don't trigger recalculation - if (!bandLimits) { - return false; - } - - var threshold = DX_WATERFALL_CONSTANTS.THRESHOLDS.BAND_CHANGE_THRESHOLD; - var lowerThreshold = bandLimits.start_khz - threshold; - var upperThreshold = bandLimits.end_khz + threshold; - - // Check if frequency is more than threshold outside the band - if (frequencyKhz < lowerThreshold || frequencyKhz > upperThreshold) { - return true; - } - - return false; - }, - - // Recalculate and update band based on frequency // Get band name for a given frequency in kHz getFrequencyBand: function(frequencyKhz) { // Check if frequencyToBand function exists @@ -3100,55 +2964,15 @@ var dxWaterfall = { return band && band !== '' ? band : null; }, - updateBandFromFrequency: function(frequencyKhz) { - var newBand = this.getFrequencyBand(frequencyKhz); - - if (newBand) { - // Check if the band exists in the select options - var bandExists = this.$bandSelect.find('option[value="' + newBand + '"]').length > 0; - - if (bandExists) { - // Set flag to prevent band change event handler from running - // This prevents form reset during CAT/WebSocket frequency updates - window.programmaticBandChange = true; - - // CRITICAL: Set waterfall flag IMMEDIATELY before the band changes - // This ensures hasParametersChanged() can detect this was programmatic - this.programmaticBandUpdate = true; - - // Update the band dropdown (in the QSO form) - this.$bandSelect.val(newBand); - - // Reset flags after a short delay to allow event to process - var self = this; - setTimeout(function() { - window.programmaticBandChange = false; - // Keep waterfall flag longer to survive the parameter check - }, 50); - } else { - // Band doesn't exist in dropdown, select the first available option as fallback - var firstOption = this.$bandSelect.find('option:first').val(); - if (firstOption) { - window.programmaticBandChange = true; - this.programmaticBandUpdate = true; - this.$bandSelect.val(firstOption); - var self = this; - setTimeout(function() { - window.programmaticBandChange = false; - }, 50); - } - } - } - }, - // ======================================== // CANVAS DRAWING AND RENDERING FUNCTIONS // ======================================== // Draw band mode indicators (colored lines below ruler showing CW/DIGI/PHONE segments) drawBandModeIndicators: function() { - // Get current region and band - var currentBand = this.getCurrentBand(); + // Use the band we have spots for, not the form selector + // This prevents drawing wrong band mode indicators when form is changed manually + var currentBand = this.currentSpotBand || this.getCurrentBand(); var currentRegion = this.continentToRegion(this.currentContinent); var regionKey = 'region' + currentRegion; @@ -3384,7 +3208,13 @@ var dxWaterfall = { // Set userInitiatedFetch flag this.userInitiatedFetch = userInitiated === true; - var band = this.getCurrentBand(); + // Calculate band from current frequency (independent of form selector) + var currentFreqKhz = this.getCachedMiddleFreq(); + var band = null; + + if (currentFreqKhz > 0) { + band = this.getFrequencyBand(currentFreqKhz); + } // If band is invalid or empty, use a default band for initial fetch if (!band || band === '' || band.toLowerCase() === 'select') { @@ -3483,9 +3313,9 @@ var dxWaterfall = { cache: false, success: function(data) { // Check if band has changed since this fetch was initiated - // If it has, discard this data (it's stale) - var currentBand = self.getCurrentBand(); - if (band !== currentBand) { + // Compare against currentSpotBand (what we're displaying) not form selector + var currentDisplayBand = self.currentSpotBand || band; + if (band !== currentDisplayBand) { // Clear safety timeout even for stale data if (self.safetyTimeoutId) { clearTimeout(self.safetyTimeoutId); @@ -3539,6 +3369,9 @@ var dxWaterfall = { self.lastFetchContinent = de; self.lastFetchAge = age; + // Track which band we currently have spots for + self.currentSpotBand = band; + // Invalidate caches when spots are updated self.cache.visibleSpots = null; self.cache.visibleSpotsParams = null; @@ -3628,11 +3461,16 @@ var dxWaterfall = { // Get current mode from form or default to All getCurrentMode: function() { + // Prefer CAT state if available (radio controls mode) + if (window.catState && window.catState.mode) { + return window.catState.mode; + } + + // Fallback to form field if CAT not available // Safety check: return default if not initialized if (!this.$modeSelect) { return 'All'; } - // Try to get mode from form - adjust selector based on your HTML structure var mode = this.$modeSelect.val() || 'All'; return mode; }, @@ -4865,7 +4703,7 @@ var dxWaterfall = { // NOTE: Removed targetFrequencyHz blocking - waterfall updates immediately on click // Stale CAT updates are ignored in handleCATFrequencyUpdate() instead - // Check if band or mode has changed and fetch new spots if needed + // Check if we need to do initial fetch or band has changed via CAT // Skip during CAT operations to prevent interference if (!this.catTuning && !this.frequencyChanging) { // Force initial fetch if we haven't done one yet (even with invalid frequency/band) @@ -4875,24 +4713,46 @@ var dxWaterfall = { this.initialFetchDone = true; // Set flag BEFORE fetch to prevent duplicate calls this.fetchDxSpots(true, false); // Initial fetch, but not user-initiated (background) } - } else if (this.hasParametersChanged()) { - this.fetchDxSpots(true, true); // User changed band/mode - mark as user-initiated + } else { + // Check if radio has changed to a different band (via CAT frequency updates) + // Calculate band from current frequency, not from form selector + var currentFreqKhz = this.getCachedMiddleFreq(); + var calculatedBand = null; + + if (currentFreqKhz > 0) { + calculatedBand = this.getFrequencyBand(currentFreqKhz); + } + + // If we have a valid calculated band and it differs from what we have spots for, fetch new spots + if (calculatedBand && calculatedBand !== '' && calculatedBand.toLowerCase() !== 'select' && + this.currentSpotBand && calculatedBand !== this.currentSpotBand) { + // Radio frequency changed to different band - fetch spots for new band + DX_WATERFALL_UTILS.log.debug('[DX Waterfall] Band changed via frequency: ' + this.currentSpotBand + ' → ' + calculatedBand); + + // IMMEDIATELY update currentSpotBand to prevent infinite loop + // The refresh() runs 60fps, so we must update this before next cycle + this.currentSpotBand = calculatedBand; + + // Mark that we're waiting for new band data + this.waitingForData = true; + this.dataReceived = false; + this.operationStartTime = Date.now(); // Start timer for visual feedback + + // Fetch spots for new band (not user-initiated, but automatic via CAT) + this.fetchDxSpots(true, false); + + // Invalidate band-related caches + this.bandLimitsCache = null; + this.cachedBandForEdges = calculatedBand; + } } + // NOTE: Removed hasParametersChanged() check - waterfall no longer monitors form band/mode changes + // Waterfall operates independently but will follow radio band changes via CAT + // Spots are fetched only on: initial load, radio band change (CAT), periodic refresh, or explicit user action } - // Periodically refresh frequency cache to catch changes - // Skip during CAT operations to prevent interference - if (!this.catTuning && !this.frequencyChanging) { - this.refreshFrequencyCache(); - } - - // Check if current frequency is far outside band limits and update band if needed - // This handles manual frequency entry in the input field - // Skip if user just manually changed the band to prevent reverting their choice - var currentFreq = this.getCachedMiddleFreq(); - if (currentFreq > 0 && !this.userChangedBand && this.isFrequencyFarOutsideBand(currentFreq)) { - this.updateBandFromFrequency(currentFreq); - } + // Note: refreshFrequencyCache() no longer needed here + // Waterfall reads frequency from window.catState (CAT data), not form fields // Update canvas internal dimensions to match current CSS dimensions this.updateDimensions(); @@ -5526,7 +5386,8 @@ var dxWaterfall = { var hours = String(this.lastUpdateTime.getHours()).padStart(2, '0'); var minutes = String(this.lastUpdateTime.getMinutes()).padStart(2, '0'); var updateTimeStr = hours + ':' + minutes; - var currentBand = this.getCurrentBand(); + // Display the band we have spots for, not the form selector + var currentBand = this.currentSpotBand || this.getCurrentBand(); zoomHTML += ''; zoomHTML += displayedSpotsCount + '/' + this.totalSpotsCount + ' ' + currentBand + ' ' + this.currentContinent + ' ' + lang_dxwaterfall_spots + ' @' + updateTimeStr + 'LT'; @@ -5736,7 +5597,8 @@ var dxWaterfall = { this.lastSpotCollectionTime = currentTime; var currentFreq = this.getCachedMiddleFreq(); - var currentBand = this.getCurrentBand(); + // Use the band we have spots for, not the form selector + var currentBand = this.currentSpotBand || this.getCurrentBand(); var currentMode = this.getCurrentMode(); // Filter spots for current band @@ -6186,7 +6048,6 @@ var dxWaterfall = { this.initialFetchDone = false; this.waitingForCATFrequency = true; this.userEditingFrequency = false; - this.userChangedBand = false; this.programmaticModeChange = false; this.zoomChanging = false; this.spotNavigating = false; @@ -6215,12 +6076,11 @@ var dxWaterfall = { // Reset spot info key this.lastSpotInfoKey = null; - // Clear band/mode tracking - this.lastBand = null; - this.lastMode = null; + // Clear band tracking this.lastFetchBand = null; this.lastFetchContinent = null; this.lastFetchAge = null; + this.currentSpotBand = null; // Reset the band we have spots for // Reset timestamps this.lastUpdateTime = null; @@ -6346,6 +6206,13 @@ function setMode(mode, skipTrigger) { // @param fromWaterfall - True if this change was initiated by waterfall (clicking spot/tune icon), false for external calls function setFrequency(frequencyInKHz, fromWaterfall) { + // PROTECTION: If user is manually updating frequency from form, don't tune radio + // This prevents form changes from controlling the radio (radio should control form) + if (typeof window.user_updating_frequency !== 'undefined' && window.user_updating_frequency) { + DX_WATERFALL_UTILS.log.info('[setFrequency] Skipping radio tune - user manually updating form'); + return; + } + // Input validation if (!frequencyInKHz || typeof frequencyInKHz !== 'number') { DX_WATERFALL_UTILS.log.warn('[setFrequency] Invalid frequency parameter:', frequencyInKHz); @@ -6509,8 +6376,8 @@ function setFrequency(frequencyInKHz, fromWaterfall) { dxWaterfall.frequencyChanging = false; dxWaterfall.catTuningStartTime = null; dxWaterfall.spotNavigating = false; // Clear navigation flag on timeout - // Force immediate cache refresh and visual update when timeout occurs - dxWaterfall.refreshFrequencyCache(); + // Force visual update when timeout occurs + // Note: refreshFrequencyCache() not needed - waterfall reads from catState if (dxWaterfall.canvas && dxWaterfall.ctx) { dxWaterfall.ctx.clearRect(0, 0, dxWaterfall.canvas.width, dxWaterfall.canvas.height); dxWaterfall.refresh(); @@ -6884,10 +6751,6 @@ function setFrequency(frequencyInKHz, fromWaterfall) { window.dxwaterfall_cat_debounce_lock = 1; } - if (dxWaterfall.isFrequencyFarOutsideBand(clickedSpot.frequency)) { - dxWaterfall.updateBandFromFrequency(clickedSpot.frequency); - } - // CRITICAL: Set mode FIRST (without triggering change event), THEN set frequency // This ensures setFrequency() reads the correct mode from the dropdown var radioMode = DX_WATERFALL_UTILS.navigation.determineRadioMode(clickedSpot); @@ -6933,11 +6796,6 @@ function setFrequency(frequencyInKHz, fromWaterfall) { return; // Ignore clicks in invalid frequency range } - // Check if frequency is far outside current band and update band if needed - if (dxWaterfall.isFrequencyFarOutsideBand(clickedFreq)) { - dxWaterfall.updateBandFromFrequency(clickedFreq); - } - // Set the frequency to where user clicked setFrequency(clickedFreq, true); diff --git a/assets/js/sections/qrg_handler.js b/assets/js/sections/qrg_handler.js index 506459f7d..9d1d25e09 100644 --- a/assets/js/sections/qrg_handler.js +++ b/assets/js/sections/qrg_handler.js @@ -63,6 +63,10 @@ async function set_qrg() { async function set_new_qrg() { let new_qrg = $('#freq_calculated').val(); + // Set flag to indicate this is a manual form update (not from CAT/radio) + // This prevents the radio from being tuned when user manually enters frequency + window.user_updating_frequency = true; + // Trim and validate input if (new_qrg !== null && new_qrg !== undefined) { new_qrg = new_qrg.trim(); @@ -77,12 +81,15 @@ async function set_new_qrg() { const result = await $.get(base_url + 'index.php/qso/band_to_freq/' + $('#band').val() + '/' + $('.mode').val()); $('#frequency').val(result); await set_qrg(); + window.user_updating_frequency = false; // Clear flag return; } catch (error) { console.error('Failed to fetch default frequency:', error); + window.user_updating_frequency = false; // Clear flag return; } } + window.user_updating_frequency = false; // Clear flag return; } @@ -134,10 +141,11 @@ async function set_new_qrg() { $('#freq_calculated').val(parsed_qrg); $('#band').val(frequencyToBand(qrg_hz)); - // Tune the radio to the new frequency if CAT is available (using global selectedRadioId) - if (typeof tuneRadioToFrequency === 'function') { - tuneRadioToFrequency(null, qrg_hz); // null = use global selectedRadioId - } + // Clear the manual update flag + window.user_updating_frequency = false; + + // DO NOT tune the radio - let the radio control the frequency + // The form follows the radio, not the other way around } $('#frequency').on('change', function () { diff --git a/assets/js/sections/qso.js b/assets/js/sections/qso.js index 1686bf9fd..464af28e2 100644 --- a/assets/js/sections/qso.js +++ b/assets/js/sections/qso.js @@ -2245,87 +2245,9 @@ $('#band').on('change', function () { // Clear the QSO form when band is manually changed $('#btn_reset').click(); - // Set flag to prevent waterfall from auto-reverting the band change - if (typeof dxWaterfall !== 'undefined') { - dxWaterfall.userChangedBand = true; - if (dxWaterfall.userChangedBandTimer) { - clearTimeout(dxWaterfall.userChangedBandTimer); - } - dxWaterfall.userChangedBandTimer = setTimeout(function() { - dxWaterfall.userChangedBand = false; - }, 10000); - - // Reset operation timer when band is manually changed - dxWaterfall.operationStartTime = Date.now(); - - // Set waiting flags to prevent waterfall from rendering with wrong band edges - // This will be cleared after the frequency is loaded - dxWaterfall.waitingForData = true; - dxWaterfall.dataReceived = false; - dxWaterfall.waitingForFrequencyUpdate = true; // Prevent spot fetch from clearing waitingForData - - // Invalidate frequency cache to prevent using stale frequency during band change - // This forces getCachedMiddleFreq() to wait for new frequency - if (dxWaterfall.lastValidCommittedFreq) { - dxWaterfall.lastValidCommittedFreq = null; - dxWaterfall.cache.middleFreq = null; - } - } - - // Check if current frequency is already in the selected band - const currentFreq = $('#frequency').val(); - const currentBand = currentFreq ? frequencyToBand(currentFreq) : ''; - - // Only fetch default frequency if current frequency is not already in the selected band - if (!currentFreq || currentBand !== selectedBand) { - $.get(base_url + 'index.php/qso/band_to_freq/' + selectedBand + '/' + $('.mode').val(), function (result) { - // Set the frequency - $('#frequency').val(result); - - // Update the displayed frequency field with proper units - set_qrg(); - - // Tune the radio to the new frequency FIRST (using global selectedRadioId) - // Use skipWaterfall=true to force direct radio tuning when band is manually changed - if (typeof tuneRadioToFrequency === 'function') { - tuneRadioToFrequency(null, result, null, null, null, true); // skipWaterfall=true - } - - // DO NOT clear waitingForFrequencyUpdate yet - wait for CAT to confirm the frequency - // The CAT update handler in footer.php will clear it when the radio responds - // This prevents the waterfall from rendering with stale frequency data - }); -} else { - // Frequency is already in the selected band, just update display - set_qrg(); - - // Still tune the radio to the current frequency (user may have changed band but frequency stayed the same) - if (typeof tuneRadioToFrequency === 'function') { - const currentFreqHz = $('#frequency').val(); - if (currentFreqHz) { - tuneRadioToFrequency(null, currentFreqHz, null, null, null, true); // skipWaterfall=true - } - } - - // Clear waiting flags immediately since no frequency change needed - if (typeof dxWaterfall !== 'undefined') { - dxWaterfall.waitingForData = false; - dxWaterfall.waitingForFrequencyUpdate = false; - - // Update zoom menu after band change completes - if (dxWaterfall.updateZoomMenu) { - dxWaterfall.updateZoomMenu(true); - } - } -} - -$('#frequency_rx').val(""); - $('#band_rx').val(""); - $("#selectPropagation").val(""); - $("#sat_name").val(""); - $("#sat_mode").val(""); - $("#callsign").blur(); - stop_az_ele_ticker(); + // Band selector is now display-only - it follows the radio frequency + // Manual band changes do NOT update the frequency or tune the radio + // The radio controls the form, not the other way around }); /* On Key up Calculate Bearing and Distance */ @@ -2944,12 +2866,18 @@ $(document).ready(function () { }); } - // Handle manual frequency entry - tune radio when user changes frequency + // Handle manual frequency entry - DO NOT tune radio (form follows radio, not vice versa) $('#freq_calculated').on('change', function() { + // Skip if CAT is currently updating - don't interfere with radio updates + if (typeof cat_updating_frequency !== 'undefined' && cat_updating_frequency) { + return; + } + // set_new_qrg() is defined in qrg_handler.js and will: // 1. Parse the frequency value and convert to Hz // 2. Update #frequency (hidden field) - // 3. Tune the radio via tuneRadioToFrequency() + // 3. Update #band selector to match the frequency + // NOTE: Does NOT tune the radio - manual form changes are display-only if (typeof set_new_qrg === 'function') { set_new_qrg(); }