2018-09-30 17:17:47 -04:00
//---------------------------------------------------------- MainWindow
2018-02-08 21:28:33 -05:00
# include "mainwindow.h"
2024-11-09 20:25:26 -08:00
# include <algorithm>
2024-12-25 22:47:39 -08:00
# include <array>
2024-12-28 09:26:07 -08:00
# include <bitset>
2018-08-27 21:19:38 -04:00
# include <cmath>
2018-02-08 21:28:33 -05:00
# include <cinttypes>
2024-12-23 17:38:07 -08:00
# include <complex>
2024-09-08 15:55:48 -07:00
# include <cstring>
2018-02-08 21:28:33 -05:00
# include <functional>
# include <fstream>
2024-12-25 22:47:39 -08:00
# include <limits>
2024-12-26 10:57:01 -08:00
# include <mutex>
2024-12-25 22:47:39 -08:00
# include <numeric>
2018-02-08 21:28:33 -05:00
# include <iterator>
2024-12-25 22:47:39 -08:00
# include <stdexcept>
# include <string_view>
2024-12-23 17:38:07 -08:00
# include <vector>
2024-12-25 22:47:39 -08:00
# include <boost/crc.hpp>
2018-02-08 21:28:33 -05:00
# include <fftw3.h>
# include <QLineEdit>
2024-08-31 09:40:28 -07:00
# include <QRegularExpressionValidator>
2018-02-08 21:28:33 -05:00
# include <QRegularExpression>
# include <QDesktopServices>
2019-03-31 14:36:40 -04:00
# include <QNetworkAccessManager>
# include <QNetworkRequest>
# include <QNetworkReply>
2018-02-08 21:28:33 -05:00
# include <QUrl>
# include <QStandardPaths>
# include <QDir>
# include <QDebug>
# include <QtConcurrent/QtConcurrentRun>
# include <QProgressDialog>
# include <QHostInfo>
# include <QVector>
# include <QCursor>
# include <QToolTip>
# include <QAction>
# include <QActionGroup>
2024-08-31 13:13:39 -07:00
# include <QSoundEffect>
2019-06-05 11:33:21 -04:00
# include <QUdpSocket>
# include <QVariant>
2019-12-27 19:46:36 -05:00
# include <QPixmap>
2024-08-31 13:13:39 -07:00
# include <QMdiSubWindow>
# include <QFileDialog>
# include <QInputDialog>
# include <QScrollBar>
2024-10-09 06:45:04 -07:00
# include <QVersionNumber>
2024-10-21 08:30:21 -07:00
# include <QTimeZone>
2024-10-26 10:37:04 -07:00
# include <QByteArrayView>
2024-11-09 20:25:26 -08:00
# include <QElapsedTimer>
2018-02-08 21:28:33 -05:00
# include "revision_utils.hpp"
# include "qt_helpers.hpp"
# include "NetworkAccessManager.hpp"
# include "soundout.h"
# include "soundin.h"
# include "Modulator.hpp"
# include "Detector.hpp"
# include "plotter.h"
# include "about.h"
# include "widegraph.h"
# include "sleep.h"
# include "logqso.h"
# include "decodedtext.h"
# include "Radio.hpp"
# include "Bands.hpp"
# include "TransceiverFactory.hpp"
# include "StationList.hpp"
# include "MessageClient.hpp"
2024-11-09 06:15:31 -08:00
# include "SignalMeter.hpp"
2018-02-08 21:28:33 -05:00
# include "HelpTextWindow.hpp"
# include "MultiSettings.hpp"
# include "CallsignValidator.hpp"
2018-08-16 15:19:43 -04:00
# include "SelfDestructMessageBox.h"
2018-09-09 10:04:19 -04:00
# include "messagereplydialog.h"
2018-09-18 17:24:07 -04:00
# include "DriftingDateTime.h"
2018-09-30 17:17:47 -04:00
# include "jsc.h"
2018-12-30 20:18:35 -05:00
# include "jsc_checker.h"
2019-01-02 12:27:16 -05:00
# include "Inbox.h"
2019-02-01 15:53:07 -05:00
# include "messagewindow.h"
2019-10-08 14:33:38 -04:00
# include "NotificationAudio.h"
2024-10-08 06:14:21 -07:00
# include "JS8Submode.hpp"
2024-11-12 07:50:08 -08:00
# include "EventFilter.hpp"
2024-11-17 05:34:54 -08:00
# include "Geodesic.hpp"
2018-02-08 21:28:33 -05:00
# include "ui_mainwindow.h"
# include "moc_mainwindow.cpp"
2024-12-30 07:37:07 -08:00
int volatile itone [ JS8_NUM_SYMBOLS ] ; // Audio tones for all Tx symbols
struct dec_data dec_data ; // for sharing with Fortran
struct specData specData ; // Used by plotter
2025-01-22 17:42:38 -08:00
std : : mutex fftw_mutex ;
2024-12-23 17:38:07 -08:00
2018-02-08 21:28:33 -05:00
namespace
{
2024-10-30 11:47:36 -07:00
namespace Default
{
constexpr Radio : : Frequency DIAL_FREQUENCY = 14078000 ;
constexpr auto FREQUENCY = 1500 ;
2024-10-30 18:44:10 -07:00
constexpr auto DEPTH = 2 ;
2024-10-31 07:29:55 -07:00
constexpr auto SUBMODE = Varicode : : JS8CallNormal ;
2024-10-30 11:47:36 -07:00
}
2024-10-26 07:07:12 -07:00
2024-10-30 11:47:36 -07:00
namespace State
{
constexpr auto RX = 1 ;
constexpr auto TX = 2 ;
}
2018-08-17 09:18:49 -04:00
2018-02-08 21:28:33 -05:00
int ms_minute_error ( )
{
2024-09-28 06:40:57 -07:00
auto const now = DriftingDateTime : : currentDateTime ( ) ;
auto const time = now . time ( ) ;
auto const second = time . second ( ) ;
2018-02-08 21:28:33 -05:00
return now . msecsTo ( now . addSecs ( second > 30 ? 60 - second : - second ) ) - time . msec ( ) ;
}
2018-07-06 02:36:40 -04:00
2024-09-28 13:45:04 -07:00
QString
since ( QDateTime const & time )
{
auto const delta = time . secsTo ( DriftingDateTime : : currentDateTimeUtc ( ) ) ;
2018-07-06 02:36:40 -04:00
2024-09-28 13:45:04 -07:00
if ( delta > = 60 * 60 * 24 ) return QString ( " %1d " ) . arg ( delta / ( 60 * 60 * 24 ) ) ;
else if ( delta > = 60 * 60 ) return QString ( " %1h " ) . arg ( delta / ( 60 * 60 ) ) ;
else if ( delta > = 60 ) return QString ( " %1m " ) . arg ( delta / ( 60 ) ) ;
else if ( delta > = 15 ) return QString ( " %1s " ) . arg ( delta - ( delta % 15 ) ) ;
else return QString ( " now " ) ;
2018-07-06 02:36:40 -04:00
}
2018-07-06 18:25:27 -04:00
2024-09-28 16:18:09 -07:00
namespace State
{
constexpr QStringView Ready = u " Ready " ;
constexpr QStringView Send = u " Send " ;
constexpr QStringView Sending = u " Sending " ;
constexpr QStringView Tuning = u " Tuning " ;
QString
timed ( QStringView const state ,
int const delay )
{
auto time = std : : div ( delay , 60 ) ;
if ( time . quot & & time . rem ) return QString ( " %1 (%2m %3s) " ) . arg ( state ) . arg ( time . quot ) . arg ( time . rem ) ;
else if ( time . quot ) return QString ( " %1 (%2m) " ) . arg ( state ) . arg ( time . quot ) ;
else return QString ( " %1 (%2s) " ).arg(state).arg(time.rem) ;
}
2019-05-26 20:06:28 -04:00
}
2018-07-31 14:34:36 -04:00
#if 0
2018-07-24 09:42:07 -04:00
int round ( int numToRound , int multiple )
{
if ( multiple = = 0 )
{
return numToRound ;
}
int roundDown = ( ( int ) ( numToRound ) / multiple ) * multiple ;
if ( numToRound - roundDown > multiple / 2 ) {
return roundDown + multiple ;
}
return roundDown ;
}
2018-07-31 14:34:36 -04:00
# endif
2018-07-24 09:42:07 -04:00
2018-07-06 20:01:38 -04:00
int roundUp ( int numToRound , int multiple )
{
if ( multiple = = 0 )
{
return numToRound ;
}
2024-09-06 06:58:17 -07:00
int roundDown = ( numToRound / multiple ) * multiple ;
2018-07-06 20:01:38 -04:00
return roundDown + multiple ;
}
2018-07-27 01:24:38 -04:00
2024-11-08 16:16:57 -08:00
auto
replaceMacros ( QString const & text ,
QMap < QString , QString > const & values ,
bool const prune )
{
QString output = text ;
for ( auto const [ key , value ] : values . asKeyValueRange ( ) )
{
output = output . replace ( key , value . toUpper ( ) ) ;
}
return prune ? output . replace ( QRegularExpression ( " [<](?:[^>]+)[>] " ) , " " )
: output ;
}
2024-10-27 04:52:52 -07:00
// Copy at most size bytes into the array, filling any unused size
// with spaces if less than size bytes were available to copy. For
// convenience, return a one past the end iterator, i.e., equal to
// array + size.
auto
copyByteData ( QByteArrayView const bytes ,
char * const array ,
qsizetype const size )
2024-10-09 08:11:07 -07:00
{
2024-10-27 04:52:52 -07:00
return std : : fill_n ( std : : copy_n ( bytes . begin ( ) ,
std : : min ( size , bytes . size ( ) ) ,
array ) , size - bytes . size ( ) , ' ' ) ;
2024-10-09 08:11:07 -07:00
}
2024-10-16 15:28:53 -07:00
// Copy at most size bytes from the string into the array, filling
2024-10-27 04:52:52 -07:00
// the array with spaces at the end if we didn't use up the size.
2024-10-16 15:28:53 -07:00
void
2024-10-17 14:15:25 -07:00
copyStringData ( QStringView const string ,
char * const array ,
qsizetype const size )
2024-10-16 15:28:53 -07:00
{
2024-10-27 04:52:52 -07:00
copyByteData ( string . toLatin1 ( ) ,
array ,
size ) ;
}
// Copy at most size bytes into the array, padding out the message
// with spaces if less than size bytes were available to copy, and
2024-12-25 22:47:39 -08:00
// null-terminate it. Caller is responsible for ensuring that at
2024-10-27 04:52:52 -07:00
// least (size + 1) bytes of space are available.
void
copyMessage ( QStringView const string ,
char * const array ,
qsizetype const size = 28 )
{
* copyByteData ( string . toLocal8Bit ( ) ,
array ,
size ) = ' \0 ' ;
2024-10-16 15:28:53 -07:00
}
2024-12-24 07:06:46 -08:00
// Emulation of the Fortran 'flat1' subroutine.
void
flat1 ( float const * const savg ,
int const iz ,
int const nsmo ,
float * const slin )
{
constexpr int x_size = 8192 ;
constexpr int nstep = 20 ;
constexpr int nh = nstep / 2 ;
// Define bounds for smoothing
int const ia = nh + 1 ;
int const ib = iz - nh - 1 ;
std : : vector < float > x ( x_size , 0.0f ) ;
// Smooth savg using median percentiles
auto const rank = std : : clamp ( static_cast < int > ( std : : round ( 0.5f * nsmo ) ) , 0 , nsmo - 1 ) ;
for ( int i = ia ; i < = ib ; i + = nstep )
{
auto const data = & savg [ i - nsmo / 2 ] ;
auto temp = std : : vector < float > ( data , data + nsmo ) ;
std : : nth_element ( temp . begin ( ) ,
temp . begin ( ) + rank ,
temp . end ( ) ) ;
x [ i ] = temp [ rank ] ;
std : : fill ( x . begin ( ) + ( i - nh ) ,
x . begin ( ) + ( i + nh ) , x [ i ] ) ;
}
// Extend smoothed values to boundaries
std : : fill ( x . begin ( ) , x . begin ( ) + ia , x [ ia ] ) ;
std : : fill ( x . begin ( ) + ib + 1 , x . begin ( ) + iz , x [ ib ] ) ;
// Compute scaling factor
float x0 = 0.001f * * std : : max_element ( x . begin ( ) + iz / 10 ,
x . begin ( ) + ( 9 * iz ) / 10 ) ;
// Normalize savg to compute slin
for ( int i = 0 ; i < iz ; + + i ) slin [ i ] = savg [ i ] / ( x [ i ] + x0 ) ;
}
// Emulation of the Fortran 'smo' subroutine. However, doesn't copy the data
// back from b to a; rather, a is input and, b is output. Since we invariably
// call this twice, we can just swap the order of the arrays to achieve the
// same result without the extra two copy operations.
void
smo ( float const * const a ,
float * const b ,
int const npts ,
int const nadd )
{
auto const nh = nadd / 2 ;
// Smooth the array
for ( int i = nh ; i < npts - nh ; + + i )
{
float sum = 0.0f ;
for ( int j = - nh ; j < = nh ; + + j )
{
sum + = a [ i + j ] ;
}
b [ i ] = sum ;
}
// Set edges to zero
for ( int i = 0 ; i < nh ; + + i ) b [ i ] = 0.0f ; // Zero out leading edge
for ( int i = npts - nh ; i < npts ; + + i ) b [ i ] = 0.0f ; // Zero out trailing edge
}
2018-02-08 21:28:33 -05:00
}
//--------------------------------------------------- MainWindow constructor
2024-11-06 12:22:23 -08:00
MainWindow : : MainWindow ( QString const & program_info ,
QDir const & temp_directory ,
bool const multiple ,
MultiSettings * multi_settings ,
QWidget * parent ) :
2018-02-08 21:28:33 -05:00
QMainWindow ( parent ) ,
m_network_manager { this } ,
m_valid { true } ,
m_multiple { multiple } ,
m_multi_settings { multi_settings } ,
m_configurations_button { 0 } ,
m_settings { multi_settings - > settings ( ) } ,
2019-06-12 23:54:02 -04:00
m_settings_read { false } ,
2018-02-08 21:28:33 -05:00
ui ( new Ui : : MainWindow ) ,
m_config { temp_directory , m_settings , this } ,
m_rigErrorMessageBox { MessageBox : : Critical , tr ( " Rig Control Error " )
, MessageBox : : Cancel | MessageBox : : Ok | MessageBox : : Retry } ,
m_wideGraph ( new WideGraph ( m_settings ) ) ,
2018-03-05 14:49:51 -05:00
// no parent so that it has a taskbar icon
m_logDlg ( new LogQSO ( program_title ( ) , m_settings , & m_config , nullptr ) ) ,
2018-02-08 21:28:33 -05:00
m_lastDialFreq { 0 } ,
2024-11-29 11:45:00 -08:00
m_detector { new Detector { RX_SAMPLE_RATE , NTMAX } } ,
2019-11-01 15:07:24 -04:00
m_FFTSize { 6912 / 2 } , // conservative value to avoid buffer overruns
2018-02-08 21:28:33 -05:00
m_soundInput { new SoundInput } ,
2024-10-28 09:35:08 -07:00
m_modulator { new Modulator } ,
2018-02-08 21:28:33 -05:00
m_soundOutput { new SoundOutput } ,
2019-10-08 14:33:38 -04:00
m_notification { new NotificationAudio } ,
2024-08-31 10:30:59 -07:00
m_decoder { this } ,
2018-02-08 21:28:33 -05:00
m_secBandChanged { 0 } ,
m_freqNominal { 0 } ,
m_freqTxNominal { 0 } ,
m_XIT { 0 } ,
m_sec0 { - 1 } ,
m_RxLog { 1 } , //Write Date and Time to RxLog
m_nutc0 { 999999 } ,
m_TRperiod { 60 } ,
m_inGain { 0 } ,
m_idleMinutes { 0 } ,
2024-10-31 07:29:55 -07:00
m_nSubMode { Default : : SUBMODE } ,
2018-02-08 21:28:33 -05:00
m_frequency_list_fcal_iter { m_config . frequencies ( ) - > begin ( ) } ,
2024-08-31 10:30:59 -07:00
m_i3bit { 0 } ,
2018-02-08 21:28:33 -05:00
m_btxok { false } ,
m_auto { false } ,
m_restart { false } ,
m_currentMessageType { - 1 } ,
m_lastMessageType { - 1 } ,
m_tuneup { false } ,
m_bTxTime { false } ,
m_ihsym { 0 } ,
m_px { 0.0 } ,
m_iptt0 { 0 } ,
m_btxok0 { false } ,
m_onAirFreq0 { 0.0 } ,
m_first_error { true } ,
tx_status_label { " Receiving " } ,
m_appDir { QApplication : : applicationDirPath ( ) } ,
m_palette { " Linrad " } ,
2024-08-31 10:30:59 -07:00
m_txFrameCountEstimate { 0 } ,
m_txFrameCount { 0 } ,
m_txFrameCountSent { 0 } ,
m_txTextDirty { false } ,
m_driftMsMMA { 0 } ,
m_driftMsMMA_N { 0 } ,
m_previousFreq { 0 } ,
m_hbInterval { 0 } ,
m_cqInterval { 0 } ,
m_hbPaused { false } ,
2018-02-08 21:28:33 -05:00
m_msAudioOutputBuffered ( 0u ) ,
m_framesAudioInputBuffered ( RX_SAMPLE_RATE / 10 ) ,
m_audioThreadPriority ( QThread : : HighPriority ) ,
2019-10-08 21:26:17 -04:00
m_notificationAudioThreadPriority ( QThread : : LowPriority ) ,
2019-11-20 00:11:30 -05:00
m_decoderThreadPriority ( QThread : : HighPriority ) ,
2018-02-08 21:28:33 -05:00
m_splitMode { false } ,
m_monitoring { false } ,
m_tx_when_ready { false } ,
m_transmitting { false } ,
m_tune { false } ,
m_tx_watchdog { false } ,
m_block_pwr_tooltip { false } ,
m_PwrBandSetOK { true } ,
2024-10-30 11:47:36 -07:00
m_lastMonitoredFrequency { Default : : DIAL_FREQUENCY } ,
2024-11-02 09:21:43 -07:00
m_messageClient { new MessageClient { m_config . udp_server_name ( ) , m_config . udp_server_port ( ) , this } } ,
m_messageServer { new MessageServer ( ) } ,
m_n3fjpClient { new TCPClient { this } } ,
2024-11-09 10:25:45 -08:00
m_pskReporter { new PSKReporter { & m_config , program_info } } , // UR
2024-11-09 15:07:20 -08:00
m_spotClient { new SpotClient { " spot.js8call.com " , 50000 , program_info } } ,
2024-11-07 13:12:55 -08:00
m_aprsClient { new APRSISClient { " rotate.aprs2.net " , 14580 } } ,
2024-08-31 10:30:59 -07:00
m_manual { & m_network_manager }
2018-02-08 21:28:33 -05:00
{
ui - > setupUi ( this ) ;
2018-07-27 10:57:51 -04:00
2018-02-08 21:28:33 -05:00
createStatusBar ( ) ;
add_child_to_event_filter ( this ) ;
m_baseCall = Radio : : base_callsign ( m_config . my_callsign ( ) ) ;
2018-03-05 14:49:51 -05:00
m_opCall = m_config . opCall ( ) ;
2018-02-08 21:28:33 -05:00
// Closedown.
connect ( ui - > actionExit , & QAction : : triggered , this , & QMainWindow : : close ) ;
// parts of the rig error message box that are fixed
m_rigErrorMessageBox . setInformativeText ( tr ( " Do you want to reconfigure the radio interface? " ) ) ;
m_rigErrorMessageBox . setDefaultButton ( MessageBox : : Ok ) ;
// start audio thread and hook up slots & signals for shutdown management
// these objects need to be in the audio thread so that invoking
// their slots is done in a thread safe way
m_soundOutput - > moveToThread ( & m_audioThread ) ;
m_modulator - > moveToThread ( & m_audioThread ) ;
m_soundInput - > moveToThread ( & m_audioThread ) ;
m_detector - > moveToThread ( & m_audioThread ) ;
2019-10-08 21:26:17 -04:00
// notification audio operates in its own thread at a lower priority
m_notification - > moveToThread ( & m_notificationAudioThread ) ;
2018-02-08 21:28:33 -05:00
2024-11-09 15:07:20 -08:00
// Move the aprs client message server, psk reporter, and spot client
// to the network thread at a lower priority.
2024-11-09 10:25:45 -08:00
2020-03-31 15:22:48 -04:00
m_aprsClient - > moveToThread ( & m_networkThread ) ;
2020-04-03 22:49:28 -04:00
m_messageServer - > moveToThread ( & m_networkThread ) ;
2024-11-09 10:25:45 -08:00
m_pskReporter - > moveToThread ( & m_networkThread ) ;
2024-11-09 15:07:20 -08:00
m_spotClient - > moveToThread ( & m_networkThread ) ;
2020-04-03 22:49:28 -04:00
// hook up the message server slots and signals and disposal
2024-11-04 13:36:42 -08:00
connect ( m_messageServer , & MessageServer : : message , this , & MainWindow : : tcpNetworkMessage ) ;
2020-04-04 14:58:30 -04:00
connect ( this , & MainWindow : : apiSetMaxConnections , m_messageServer , & MessageServer : : setMaxConnections ) ;
2024-11-09 15:07:20 -08:00
connect ( this , & MainWindow : : apiSetServer , m_messageServer , & MessageServer : : setServer ) ;
connect ( this , & MainWindow : : apiStartServer , m_messageServer , & MessageServer : : start ) ;
connect ( this , & MainWindow : : apiStopServer , m_messageServer , & MessageServer : : stop ) ;
connect ( & m_config , & Configuration : : tcp_server_changed , m_messageServer , & MessageServer : : setServerHost ) ;
connect ( & m_config , & Configuration : : tcp_server_port_changed , m_messageServer , & MessageServer : : setServerPort ) ;
2020-04-04 14:58:30 -04:00
connect ( & m_config , & Configuration : : tcp_max_connections_changed , m_messageServer , & MessageServer : : setMaxConnections ) ;
2020-04-03 22:49:28 -04:00
connect ( & m_networkThread , & QThread : : finished , m_messageServer , & QObject : : deleteLater ) ;
2020-03-31 15:22:48 -04:00
// hook up the aprs client slots and signals and disposal
2024-11-09 15:07:20 -08:00
connect ( this , & MainWindow : : aprsClientEnqueueSpot , m_aprsClient , & APRSISClient : : enqueueSpot ) ;
2020-03-31 15:22:48 -04:00
connect ( this , & MainWindow : : aprsClientEnqueueThirdParty , m_aprsClient , & APRSISClient : : enqueueThirdParty ) ;
2024-11-09 15:07:20 -08:00
connect ( this , & MainWindow : : aprsClientSendReports , m_aprsClient , & APRSISClient : : sendReports ) ;
connect ( this , & MainWindow : : aprsClientSetLocalStation , m_aprsClient , & APRSISClient : : setLocalStation ) ;
connect ( this , & MainWindow : : aprsClientSetPaused , m_aprsClient , & APRSISClient : : setPaused ) ;
connect ( this , & MainWindow : : aprsClientSetServer , m_aprsClient , & APRSISClient : : setServer ) ;
connect ( this , & MainWindow : : aprsClientSetSkipPercent , m_aprsClient , & APRSISClient : : setSkipPercent ) ;
2020-03-31 15:22:48 -04:00
connect ( & m_networkThread , & QThread : : finished , m_aprsClient , & QObject : : deleteLater ) ;
2024-11-09 10:25:45 -08:00
// hook up the psk reporter slots and signals and disposal
connect ( m_pskReporter , & PSKReporter : : errorOccurred , this , & MainWindow : : pskReporterError ) ;
connect ( this , & MainWindow : : pskReporterSendReport , m_pskReporter , & PSKReporter : : sendReport ) ;
connect ( this , & MainWindow : : pskReporterAddRemoteStation , m_pskReporter , & PSKReporter : : addRemoteStation ) ;
connect ( this , & MainWindow : : pskReporterSetLocalStation , m_pskReporter , & PSKReporter : : setLocalStation ) ;
2024-11-09 19:09:42 -08:00
connect ( & m_networkThread , & QThread : : started , m_pskReporter , & PSKReporter : : start ) ;
2024-11-09 10:25:45 -08:00
connect ( & m_networkThread , & QThread : : finished , m_pskReporter , & QObject : : deleteLater ) ;
2024-11-09 15:07:20 -08:00
// hook up the spot client signals and disposal
connect ( this , & MainWindow : : spotClientEnqueueCmd , m_spotClient , & SpotClient : : enqueueCmd ) ;
connect ( this , & MainWindow : : spotClientEnqueueSpot , m_spotClient , & SpotClient : : enqueueSpot ) ;
connect ( this , & MainWindow : : spotClientSetLocalStation , m_spotClient , & SpotClient : : setLocalStation ) ;
connect ( & m_networkThread , & QThread : : started , m_spotClient , & SpotClient : : start ) ;
connect ( & m_networkThread , & QThread : : finished , m_spotClient , & QObject : : deleteLater ) ;
2018-02-08 21:28:33 -05:00
// hook up sound output stream slots & signals and disposal
connect ( this , & MainWindow : : initializeAudioOutputStream , m_soundOutput , & SoundOutput : : setFormat ) ;
connect ( m_soundOutput , & SoundOutput : : error , this , & MainWindow : : showSoundOutError ) ;
2024-09-11 05:58:28 -07:00
connect ( m_soundOutput , & SoundOutput : : error , & m_config , & Configuration : : invalidate_audio_output_device ) ;
2018-02-08 21:28:33 -05:00
connect ( this , & MainWindow : : outAttenuationChanged , m_soundOutput , & SoundOutput : : setAttenuation ) ;
connect ( & m_audioThread , & QThread : : finished , m_soundOutput , & QObject : : deleteLater ) ;
2019-10-15 13:52:30 -04:00
connect ( this , & MainWindow : : initializeNotificationAudioOutputStream , m_notification , & NotificationAudio : : setDevice ) ;
2019-10-11 20:09:47 -04:00
connect ( & m_config , & Configuration : : test_notify , this , & MainWindow : : tryNotify ) ;
connect ( this , & MainWindow : : playNotification , m_notification , & NotificationAudio : : play ) ;
2019-10-08 21:26:17 -04:00
connect ( & m_notificationAudioThread , & QThread : : finished , m_notification , & QObject : : deleteLater ) ;
2019-10-08 14:33:38 -04:00
2018-02-08 21:28:33 -05:00
// hook up Modulator slots and disposal
connect ( this , & MainWindow : : transmitFrequency , m_modulator , & Modulator : : setFrequency ) ;
connect ( this , & MainWindow : : endTransmitMessage , m_modulator , & Modulator : : stop ) ;
connect ( this , & MainWindow : : tune , m_modulator , & Modulator : : tune ) ;
connect ( this , & MainWindow : : sendMessage , m_modulator , & Modulator : : start ) ;
connect ( & m_audioThread , & QThread : : finished , m_modulator , & QObject : : deleteLater ) ;
// hook up the audio input stream signals, slots and disposal
connect ( this , & MainWindow : : startAudioInputStream , m_soundInput , & SoundInput : : start ) ;
connect ( this , & MainWindow : : suspendAudioInputStream , m_soundInput , & SoundInput : : suspend ) ;
connect ( this , & MainWindow : : resumeAudioInputStream , m_soundInput , & SoundInput : : resume ) ;
connect ( this , & MainWindow : : finished , m_soundInput , & SoundInput : : stop ) ;
connect ( m_soundInput , & SoundInput : : error , this , & MainWindow : : showSoundInError ) ;
2024-09-11 05:58:28 -07:00
connect ( m_soundInput , & SoundInput : : error , & m_config , & Configuration : : invalidate_audio_input_device ) ;
2018-02-08 21:28:33 -05:00
// connect(m_soundInput, &SoundInput::status, this, &MainWindow::showStatusMessage);
connect ( & m_audioThread , & QThread : : finished , m_soundInput , & QObject : : deleteLater ) ;
connect ( this , & MainWindow : : finished , this , & MainWindow : : close ) ;
// hook up the detector signals, slots and disposal
connect ( this , & MainWindow : : FFTSize , m_detector , & Detector : : setBlockSize ) ;
connect ( m_detector , & Detector : : framesWritten , this , & MainWindow : : dataSink ) ;
connect ( & m_audioThread , & QThread : : finished , m_detector , & QObject : : deleteLater ) ;
// setup the waterfall
2024-10-20 14:36:48 -07:00
connect ( m_wideGraph . data ( ) , & WideGraph : : f11f12 , this , & MainWindow : : f11f12 ) ;
connect ( m_wideGraph . data ( ) , & WideGraph : : setXIT , this , & MainWindow : : setXIT ) ;
2018-02-08 21:28:33 -05:00
connect ( this , & MainWindow : : finished , m_wideGraph . data ( ) , & WideGraph : : close ) ;
// setup the log QSO dialog
2018-03-05 14:49:51 -05:00
connect ( m_logDlg . data ( ) , & LogQSO : : acceptQSO , this , & MainWindow : : acceptQSO ) ;
2018-02-08 21:28:33 -05:00
connect ( this , & MainWindow : : finished , m_logDlg . data ( ) , & LogQSO : : close ) ;
2024-11-09 15:10:42 -08:00
// Network message handling
2024-11-04 13:36:42 -08:00
connect ( m_messageClient , & MessageClient : : message , this , & MainWindow : : udpNetworkMessage ) ;
2018-08-07 11:40:02 -04:00
2019-11-19 10:39:27 -05:00
// decoder queue handler
2019-11-20 00:11:30 -05:00
//connect (&m_decodeThread, &QThread::finished, m_notification, &QObject::deleteLater);
//connect(this, &MainWindow::decodedLineReady, this, &MainWindow::processDecodedLine);
2025-01-22 17:42:38 -08:00
connect ( & m_decoder , & JS8 : : Decoder : : decodeEvent , this , & MainWindow : : processDecodeEvent ) ;
2019-11-22 15:00:06 -05:00
2020-01-13 11:11:23 -05:00
QActionGroup * depthGroup = new QActionGroup ( this ) ;
ui - > actionQuickDecode - > setActionGroup ( depthGroup ) ;
ui - > actionMediumDecode - > setActionGroup ( depthGroup ) ;
ui - > actionDeepDecode - > setActionGroup ( depthGroup ) ;
ui - > actionDeepestDecode - > setActionGroup ( depthGroup ) ;
2018-02-08 21:28:33 -05:00
2024-10-10 08:21:36 -07:00
m_dateTimeQSOOn = QDateTime { } ;
2018-10-09 17:33:30 -04:00
2018-02-08 21:28:33 -05:00
// initialize decoded text font and hook up font change signals
// defer initialization until after construction otherwise menu
// fonts do not get set
2024-10-21 15:35:49 -07:00
QTimer : : singleShot ( 0 , this , & MainWindow : : initialize_fonts ) ;
2018-09-07 10:12:40 -04:00
connect ( & m_config , & Configuration : : gui_text_font_changed , [ this ] ( QFont const & font ) {
2018-02-08 21:28:33 -05:00
set_application_font ( font ) ;
2018-09-07 10:12:40 -04:00
} ) ;
2018-09-15 17:33:09 -04:00
connect ( & m_config , & Configuration : : table_font_changed , [ this ] ( QFont const & ) {
ui - > tableWidgetRXAll - > setFont ( m_config . table_font ( ) ) ;
ui - > tableWidgetCalls - > setFont ( m_config . table_font ( ) ) ;
} ) ;
2018-09-07 10:12:40 -04:00
connect ( & m_config , & Configuration : : rx_text_font_changed , [ this ] ( QFont const & ) {
setTextEditFont ( ui - > textEditRX , m_config . rx_text_font ( ) ) ;
} ) ;
2018-09-12 20:51:46 -04:00
connect ( & m_config , & Configuration : : compose_text_font_changed , [ this ] ( QFont const & ) {
setTextEditFont ( ui - > extFreeTextMsgEdit , m_config . compose_text_font ( ) ) ;
2018-09-07 10:12:40 -04:00
} ) ;
2018-08-16 11:02:23 -04:00
connect ( & m_config , & Configuration : : colors_changed , [ this ] ( ) {
2018-09-15 17:33:09 -04:00
setTextEditStyle ( ui - > textEditRX , m_config . color_rx_foreground ( ) , m_config . color_rx_background ( ) , m_config . rx_text_font ( ) ) ;
setTextEditStyle ( ui - > extFreeTextMsgEdit , m_config . color_compose_foreground ( ) , m_config . color_compose_background ( ) , m_config . compose_text_font ( ) ) ;
2019-09-25 20:26:41 -04:00
ui - > extFreeTextMsgEdit - > setFont ( m_config . compose_text_font ( ) , m_config . color_compose_foreground ( ) , m_config . color_compose_background ( ) ) ;
2018-09-07 10:12:40 -04:00
// rehighlight
auto d = ui - > textEditRX - > document ( ) ;
if ( d ) {
for ( int i = 0 ; i < d - > lineCount ( ) ; i + + ) {
auto b = d - > findBlockByLineNumber ( i ) ;
2024-10-30 11:47:36 -07:00
switch ( b . userState ( ) )
{
case State : : RX :
2018-09-07 10:12:40 -04:00
highlightBlock ( b , m_config . rx_text_font ( ) , m_config . color_rx_foreground ( ) , QColor ( Qt : : transparent ) ) ;
break ;
2024-10-30 11:47:36 -07:00
case State : : TX :
2018-09-07 10:12:40 -04:00
highlightBlock ( b , m_config . tx_text_font ( ) , m_config . color_tx_foreground ( ) , QColor ( Qt : : transparent ) ) ;
break ;
}
}
}
2018-08-16 11:02:23 -04:00
} ) ;
2018-02-08 21:28:33 -05:00
setWindowTitle ( program_title ( ) ) ;
// Hook up working frequencies.
2024-11-12 07:50:08 -08:00
2024-11-12 08:58:57 -08:00
ui - > currentFreq - > setCursor ( QCursor ( Qt : : PointingHandCursor ) ) ;
ui - > currentFreq - > display ( " 14.078 000 " ) ;
ui - > currentFreq - > installEventFilter ( new EventFilter : : MouseButtonPress ( [ this ] ( QMouseEvent * event )
2024-11-12 07:50:08 -08:00
{
QMenu * menu = new QMenu ( ui - > currentFreq ) ;
buildFrequencyMenu ( menu ) ;
menu - > popup ( event - > globalPosition ( ) . toPoint ( ) ) ;
2024-11-12 08:58:57 -08:00
return true ;
} , this ) ) ;
2024-11-12 07:50:08 -08:00
2024-11-12 08:58:57 -08:00
ui - > labDialFreqOffset - > setCursor ( QCursor ( Qt : : PointingHandCursor ) ) ;
ui - > labDialFreqOffset - > installEventFilter ( new EventFilter : : MouseButtonPress ( [ this ] ( QMouseEvent * )
2024-11-12 07:50:08 -08:00
{
on_actionSetOffset_triggered ( ) ;
2024-11-12 08:58:57 -08:00
return true ;
} , this ) ) ;
2019-01-21 22:27:43 -05:00
2024-11-12 07:50:08 -08:00
// Hook up callsign label click to open preferences
2024-11-12 08:58:57 -08:00
ui - > labCallsign - > setCursor ( QCursor ( Qt : : PointingHandCursor ) ) ;
ui - > labCallsign - > installEventFilter ( new EventFilter : : MouseButtonPress ( [ this ] ( QMouseEvent * )
2024-11-12 07:50:08 -08:00
{
openSettings ( 0 ) ;
2024-11-12 08:58:57 -08:00
return true ;
} , this ) ) ;
2019-03-23 22:16:01 -04:00
2018-02-08 21:28:33 -05:00
// hook up configuration signals
connect ( & m_config , & Configuration : : transceiver_update , this , & MainWindow : : handle_transceiver_update ) ;
connect ( & m_config , & Configuration : : transceiver_failure , this , & MainWindow : : handle_transceiver_failure ) ;
2024-11-03 08:11:34 -08:00
connect ( & m_config , & Configuration : : udp_server_name_changed , m_messageClient , & MessageClient : : set_server_name ) ;
2018-02-08 21:28:33 -05:00
connect ( & m_config , & Configuration : : udp_server_port_changed , m_messageClient , & MessageClient : : set_server_port ) ;
2018-08-12 10:35:19 -04:00
connect ( & m_config , & Configuration : : band_schedule_changed , this , [ this ] ( ) {
this - > m_bandHopped = true ;
} ) ;
2024-09-11 08:23:39 -07:00
connect ( & m_config , & Configuration : : enumerating_audio_devices , [ this ] ( )
{
showStatusMessage ( tr ( " Enumerating audio devices " ) ) ;
} ) ;
2018-02-08 21:28:33 -05:00
// set up configurations menu
connect ( m_multi_settings , & MultiSettings : : configurationNameChanged , [ this ] ( QString const & name ) {
if ( " Default " ! = name ) {
config_label . setText ( name ) ;
config_label . show ( ) ;
}
else {
config_label . hide ( ) ;
}
} ) ;
m_multi_settings - > create_menu_actions ( this , ui - > menuConfig ) ;
m_configurations_button = m_rigErrorMessageBox . addButton ( tr ( " Configurations... " )
, QMessageBox : : ActionRole ) ;
2024-11-08 12:55:07 -08:00
connect ( ui - > extFreeTextMsgEdit , & QTextEdit : : textChanged , [ this ] ( )
{
currentTextChanged ( ) ;
} ) ;
2018-02-08 21:28:33 -05:00
2024-11-09 19:16:07 -08:00
m_guiTimer . setTimerType ( Qt : : PreciseTimer ) ;
2018-10-15 00:59:38 -04:00
m_guiTimer . setSingleShot ( true ) ;
2018-02-08 21:28:33 -05:00
connect ( & m_guiTimer , & QTimer : : timeout , this , & MainWindow : : guiUpdate ) ;
m_guiTimer . start ( 100 ) ; //### Don't change the 100 ms! ###
ptt0Timer . setSingleShot ( true ) ;
connect ( & ptt0Timer , & QTimer : : timeout , this , & MainWindow : : stopTx2 ) ;
ptt1Timer . setSingleShot ( true ) ;
connect ( & ptt1Timer , & QTimer : : timeout , this , & MainWindow : : startTx2 ) ;
logQSOTimer . setSingleShot ( true ) ;
connect ( & logQSOTimer , & QTimer : : timeout , this , & MainWindow : : on_logQSOButton_clicked ) ;
tuneButtonTimer . setSingleShot ( true ) ;
2024-09-11 13:39:49 -07:00
connect ( & tuneButtonTimer , & QTimer : : timeout , this , & MainWindow : : end_tuning ) ;
2018-02-08 21:28:33 -05:00
tuneATU_Timer . setSingleShot ( true ) ;
connect ( & tuneATU_Timer , & QTimer : : timeout , this , & MainWindow : : stopTuneATU ) ;
TxAgainTimer . setSingleShot ( true ) ;
2024-10-21 15:19:00 -07:00
connect ( & TxAgainTimer , & QTimer : : timeout , this , & MainWindow : : TxAgain ) ;
2018-02-08 21:28:33 -05:00
2018-11-28 22:53:18 -05:00
repeatTimer . setSingleShot ( false ) ;
repeatTimer . setInterval ( 1000 ) ;
connect ( & repeatTimer , & QTimer : : timeout , this , & MainWindow : : checkRepeat ) ;
2018-11-27 23:04:11 -05:00
2024-10-20 14:23:10 -07:00
connect ( m_wideGraph . data ( ) , & WideGraph : : changeFreq , this , & MainWindow : : changeFreq ) ;
connect ( m_wideGraph . data ( ) , & WideGraph : : qsy , this , & MainWindow : : qsy ) ;
connect ( m_wideGraph . data ( ) , & WideGraph : : drifted , this , & MainWindow : : drifted ) ;
2020-05-13 21:15:21 -04:00
2018-02-08 21:28:33 -05:00
decodeBusy ( false ) ;
m_msg [ 0 ] [ 0 ] = 0 ;
2019-06-12 12:03:50 -04:00
displayDialFrequency ( ) ;
2018-02-08 21:28:33 -05:00
readSettings ( ) ; //Restore user's setup params
2019-10-08 21:26:17 -04:00
2020-03-31 15:22:48 -04:00
m_networkThread . start ( m_networkThreadPriority ) ;
2018-02-08 21:28:33 -05:00
m_audioThread . start ( m_audioThreadPriority ) ;
2019-10-08 21:26:17 -04:00
m_notificationAudioThread . start ( m_notificationAudioThreadPriority ) ;
2019-11-20 00:11:30 -05:00
m_decoder . start ( m_decoderThreadPriority ) ;
2018-02-08 21:28:33 -05:00
QString fname { QDir : : toNativeSeparators ( m_config . writeable_data_dir ( ) . absoluteFilePath ( " wsjtx_wisdom.dat " ) ) } ;
QByteArray cfname = fname . toLocal8Bit ( ) ;
fftwf_import_wisdom_from_filename ( cfname ) ;
2024-11-29 11:45:00 -08:00
Q_EMIT startAudioInputStream ( m_config . audio_input_device ( ) , m_framesAudioInputBuffered , m_detector , m_config . audio_input_channel ( ) ) ;
2018-02-08 21:28:33 -05:00
Q_EMIT initializeAudioOutputStream ( m_config . audio_output_device ( ) , AudioDevice : : Mono = = m_config . audio_output_channel ( ) ? 1 : 2 , m_msAudioOutputBuffered ) ;
2024-09-14 09:25:49 -07:00
Q_EMIT initializeNotificationAudioOutputStream ( m_config . notification_audio_output_device ( ) , m_msAudioOutputBuffered ) ;
2024-10-20 13:54:14 -07:00
Q_EMIT transmitFrequency ( freq ( ) - m_XIT ) ;
2018-02-08 21:28:33 -05:00
enable_DXCC_entity ( m_config . DXCC ( ) ) ; // sets text window proportions and (re)inits the logbook
// this must be done before initializing the mode as some modes need
// to turn off split on the rig e.g. WSPR
m_config . transceiver_online ( ) ;
2024-11-08 14:06:18 -08:00
setupJS8 ( ) ;
2024-09-25 21:21:13 -07:00
2024-10-20 13:54:14 -07:00
Q_EMIT transmitFrequency ( freq ( ) - m_XIT ) ;
2019-09-05 14:07:24 -04:00
2018-02-08 21:28:33 -05:00
if ( ( m_ndepth & 7 ) = = 1 ) ui - > actionQuickDecode - > setChecked ( true ) ;
if ( ( m_ndepth & 7 ) = = 2 ) ui - > actionMediumDecode - > setChecked ( true ) ;
2018-10-21 15:37:12 -04:00
if ( ( m_ndepth & 7 ) = = 3 ) ui - > actionDeepDecode - > setChecked ( true ) ;
if ( ( m_ndepth & 7 ) = = 4 ) ui - > actionDeepestDecode - > setChecked ( true ) ;
2018-02-08 21:28:33 -05:00
statusChanged ( ) ;
connect ( & minuteTimer , & QTimer : : timeout , this , & MainWindow : : on_the_minute ) ;
minuteTimer . setSingleShot ( true ) ;
minuteTimer . start ( ms_minute_error ( ) + 60 * 1000 ) ;
2024-10-21 15:35:49 -07:00
QTimer : : singleShot ( 0 , this , & MainWindow : : checkStartupWarnings ) ;
2018-07-06 22:37:25 -04:00
2024-11-12 12:22:54 -08:00
// UI Customizations & Tweaks
2024-11-12 13:00:03 -08:00
ui - > horizontalLayoutBand - > insertSpacing ( 1 , 6 ) ;
ui - > horizontalLayoutBand - > insertWidget ( 2 , m_wideGraph . data ( ) , 1 ) ;
ui - > horizontalLayoutBand - > insertSpacing ( 3 , 8 ) ;
2020-01-13 11:11:23 -05:00
2020-01-13 11:21:46 -05:00
// remove disabled menus from the menu bar
2018-08-06 16:20:46 -04:00
foreach ( auto action , ui - > menuBar - > actions ( ) ) {
2020-01-13 11:11:23 -05:00
if ( action - > isEnabled ( ) ) {
continue ;
}
ui - > menuBar - > removeAction ( action ) ;
2018-08-06 16:20:46 -04:00
}
2020-01-13 11:21:46 -05:00
2019-09-05 14:07:24 -04:00
//auto f = findFreeFreqOffset(1000, 2000, 50);
//setFreqOffsetForRestore(f, false);
2019-08-26 12:08:35 -04:00
ui - > actionModeAutoreply - > setChecked ( m_config . autoreply_on_at_startup ( ) ) ;
2018-08-27 21:19:38 -04:00
ui - > spotButton - > setChecked ( m_config . spot_to_reporting_networks ( ) ) ;
2018-07-03 18:57:49 -04:00
2019-09-05 14:07:24 -04:00
QActionGroup * modeActionGroup = new QActionGroup ( this ) ;
ui - > actionModeJS8Normal - > setActionGroup ( modeActionGroup ) ;
ui - > actionModeJS8Fast - > setActionGroup ( modeActionGroup ) ;
ui - > actionModeJS8Turbo - > setActionGroup ( modeActionGroup ) ;
2019-12-09 14:00:23 -05:00
ui - > actionModeJS8Slow - > setActionGroup ( modeActionGroup ) ;
ui - > actionModeJS8Ultra - > setActionGroup ( modeActionGroup ) ;
2019-09-05 14:07:24 -04:00
2024-11-12 08:58:57 -08:00
ui - > modeButton - > installEventFilter ( new EventFilter : : MouseButtonPress ( [ this ] ( QMouseEvent * event )
2024-11-12 07:50:08 -08:00
{
2024-11-12 08:58:57 -08:00
ui - > menuModeJS8 - > popup ( event - > globalPosition ( ) . toPoint ( ) ) ;
return true ;
} , this ) ) ;
2024-11-12 07:50:08 -08:00
if ( ! JS8_ENABLE_JS8A ) ui - > actionModeJS8Normal - > setVisible ( false ) ;
if ( ! JS8_ENABLE_JS8B ) ui - > actionModeJS8Fast - > setVisible ( false ) ;
if ( ! JS8_ENABLE_JS8C ) ui - > actionModeJS8Turbo - > setVisible ( false ) ;
if ( ! JS8_ENABLE_JS8E ) ui - > actionModeJS8Slow - > setVisible ( false ) ;
if ( ! JS8_ENABLE_JS8I ) ui - > actionModeJS8Ultra - > setVisible ( false ) ;
2019-09-05 14:07:24 -04:00
2019-08-26 12:08:35 -04:00
// prep
2020-04-15 15:23:41 -04:00
prepareMonitorControls ( ) ;
2020-03-28 11:57:38 -04:00
prepareHeartbeatMode ( canCurrentModeSendHeartbeat ( ) & & ui - > actionModeJS8HB - > isChecked ( ) ) ;
2018-12-30 20:18:35 -05:00
2024-11-12 08:58:57 -08:00
ui - > extFreeTextMsgEdit - > installEventFilter ( new EventFilter : : EnterKeyPress ( [ this ] ( QKeyEvent * const event )
2024-11-12 07:50:08 -08:00
{
2024-11-12 08:58:57 -08:00
if ( event - > modifiers ( ) & Qt : : ShiftModifier ) return false ;
if ( ui - > extFreeTextMsgEdit - > isReadOnly ( ) ) return false ;
2018-08-30 17:19:58 -04:00
2024-11-12 08:58:57 -08:00
if ( ui - > extFreeTextMsgEdit - > toPlainText ( ) . trimmed ( ) . isEmpty ( ) ) return true ;
if ( ! ensureCanTransmit ( ) ) return true ;
if ( ! ensureCallsignSet ( true ) ) return true ;
2018-10-03 16:54:25 -04:00
2024-11-12 07:50:08 -08:00
toggleTx ( true ) ;
2024-11-12 08:58:57 -08:00
return true ;
} , this ) ) ;
ui - > textEditRX - > viewport ( ) - > installEventFilter ( new EventFilter : : MouseButtonDblClick ( [ this ] ( QMouseEvent * )
2024-11-08 13:09:29 -08:00
{
QTimer : : singleShot ( 150 , this , [ this ] ( )
{
// When we double click the rx window, we send the selected text to
// the log dialog when the text could be an snr value prefixed with
// a - or +, we extend the selection to include it.
auto textCursor = ui - > textEditRX - > textCursor ( ) ;
auto text = textCursor . selectedText ( ) ;
if ( text . isEmpty ( ) ) return ;
auto const start = textCursor . selectionStart ( ) ;
auto const end = textCursor . selectionEnd ( ) ;
textCursor . clearSelection ( ) ;
textCursor . setPosition ( start ) ;
textCursor . movePosition ( QTextCursor : : PreviousCharacter , QTextCursor : : MoveAnchor ) ;
textCursor . movePosition ( QTextCursor : : NextCharacter , QTextCursor : : KeepAnchor , 1 + end - start ) ;
if ( auto const prev = textCursor . selectedText ( ) ;
prev . startsWith ( " - " ) | |
prev . startsWith ( " + " ) )
{
ui - > textEditRX - > setTextCursor ( textCursor ) ;
text = prev ;
}
m_logDlg - > acceptText ( text ) ;
} ) ;
2024-11-12 08:58:57 -08:00
return false ;
} , this ) ) ;
2019-06-01 22:41:10 -04:00
2018-07-10 15:35:05 -04:00
auto clearActionSep = new QAction ( nullptr ) ;
clearActionSep - > setSeparator ( true ) ;
2018-12-27 00:13:06 -05:00
auto clearActionAll = new QAction ( QString ( " Clear All " ) , nullptr ) ;
2018-12-26 23:08:02 -05:00
connect ( clearActionAll , & QAction : : triggered , this , [ this ] ( ) {
if ( QMessageBox : : Yes ! = QMessageBox : : question ( this , " Clear All Activity " , " Are you sure you would like to clear all activity? " , QMessageBox : : Yes | QMessageBox : : No ) ) {
return ;
}
clearActivity ( ) ;
} ) ;
2018-07-10 15:35:05 -04:00
2018-07-03 18:57:49 -04:00
// setup tablewidget context menus
2018-11-19 22:36:10 -05:00
auto clearAction1 = new QAction ( QString ( " Clear " ) , ui - > textEditRX ) ;
2024-11-08 10:37:24 -08:00
connect ( clearAction1 , & QAction : : triggered , this , [ this ] ( )
{
clearRXActivity ( ) ;
} ) ;
2018-03-19 00:05:12 -04:00
2019-07-11 10:05:20 -04:00
auto saveAction = new QAction ( QString ( " Save As... " ) , ui - > textEditRX ) ;
connect ( saveAction , & QAction : : triggered , this , [ this ] ( ) {
auto writePath = QStandardPaths : : writableLocation ( QStandardPaths : : DocumentsLocation ) ;
auto writeDir = QDir ( writePath ) ;
auto defaultFilename = writeDir . absoluteFilePath ( QString ( " js8call-%1.txt " ) . arg ( DriftingDateTime : : currentDateTimeUtc ( ) . toString ( " yyyyMMdd " ) ) ) ;
QString selectedFilter = " *.txt " ;
auto filename = QFileDialog : : getSaveFileName ( this ,
" Save As... " ,
defaultFilename ,
" Text files (*.txt);; All files (*) " ,
& selectedFilter
) ;
if ( filename . isEmpty ( ) ) {
return ;
}
auto text = ui - > textEditRX - > toPlainText ( ) ;
QFile f ( filename ) ;
if ( f . open ( QIODevice : : Truncate | QIODevice : : WriteOnly | QIODevice : : Text ) ) {
QTextStream stream ( & f ) ;
stream < < text ;
}
} ) ;
2018-09-06 17:44:47 -04:00
ui - > textEditRX - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
2019-07-11 10:05:20 -04:00
connect ( ui - > textEditRX , & QTableWidget : : customContextMenuRequested , this , [ this , clearAction1 , clearActionAll , saveAction ] ( QPoint const & point ) {
2018-09-06 17:44:47 -04:00
QMenu * menu = new QMenu ( ui - > textEditRX ) ;
2018-08-31 09:59:43 -04:00
2018-09-09 10:42:32 -04:00
buildEditMenu ( menu , ui - > textEditRX ) ;
2018-09-06 17:44:47 -04:00
menu - > addSeparator ( ) ;
menu - > addAction ( clearAction1 ) ;
menu - > addAction ( clearActionAll ) ;
2019-07-11 10:05:20 -04:00
menu - > addSeparator ( ) ;
menu - > addAction ( saveAction ) ;
2018-09-06 17:44:47 -04:00
menu - > popup ( ui - > textEditRX - > mapToGlobal ( point ) ) ;
} ) ;
2018-08-31 09:59:43 -04:00
2018-11-19 22:36:10 -05:00
auto clearAction2 = new QAction ( QString ( " Clear " ) , ui - > extFreeTextMsgEdit ) ;
2024-11-08 10:37:24 -08:00
connect ( clearAction2 , & QAction : : triggered , this , [ this ] ( )
{
resetMessage ( ) ;
m_lastTxMessage . clear ( ) ;
} ) ;
2018-07-27 13:53:07 -04:00
auto restoreAction = new QAction ( QString ( " Restore Previous Message " ) , ui - > extFreeTextMsgEdit ) ;
connect ( restoreAction , & QAction : : triggered , this , [ this ] ( ) { this - > restoreMessage ( ) ; } ) ;
ui - > extFreeTextMsgEdit - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
connect ( ui - > extFreeTextMsgEdit , & QTableWidget : : customContextMenuRequested , this , [ this , clearAction2 , clearActionAll , restoreAction ] ( QPoint const & point ) {
QMenu * menu = new QMenu ( ui - > extFreeTextMsgEdit ) ;
2018-10-03 14:56:35 -04:00
auto selectedCall = callsignSelected ( ) ;
bool missingCallsign = selectedCall . isEmpty ( ) ;
2018-12-31 15:14:48 -05:00
buildSuggestionsMenu ( menu , ui - > extFreeTextMsgEdit , point ) ;
2018-09-07 11:20:10 -04:00
restoreAction - > setDisabled ( m_lastTxMessage . isEmpty ( ) ) ;
menu - > addAction ( restoreAction ) ;
2018-09-06 17:44:47 -04:00
2019-02-08 20:45:50 -05:00
auto savedMenu = menu - > addMenu ( " Saved Messages... " ) ;
2018-09-20 10:46:04 -04:00
buildSavedMessagesMenu ( savedMenu ) ;
2018-10-03 14:56:35 -04:00
auto directedMenu = menu - > addMenu ( QString ( " Directed to %1... " ) . arg ( selectedCall ) ) ;
directedMenu - > setDisabled ( missingCallsign ) ;
buildQueryMenu ( directedMenu , selectedCall ) ;
2018-09-09 10:42:32 -04:00
auto relayMenu = menu - > addMenu ( " Relay via... " ) ;
relayMenu - > setDisabled ( ui - > extFreeTextMsgEdit - > toPlainText ( ) . isEmpty ( ) | | m_callActivity . isEmpty ( ) ) ;
buildRelayMenu ( relayMenu ) ;
2018-09-06 17:44:47 -04:00
menu - > addSeparator ( ) ;
2018-09-09 10:42:32 -04:00
buildEditMenu ( menu , ui - > extFreeTextMsgEdit ) ;
2018-07-27 13:53:07 -04:00
menu - > addSeparator ( ) ;
2018-09-07 11:20:10 -04:00
2018-07-27 13:53:07 -04:00
menu - > addAction ( clearAction2 ) ;
menu - > addAction ( clearActionAll ) ;
menu - > popup ( ui - > extFreeTextMsgEdit - > mapToGlobal ( point ) ) ;
2018-12-04 22:20:26 -05:00
displayActivity ( true ) ;
2018-07-27 13:53:07 -04:00
} ) ;
2018-07-10 15:35:05 -04:00
2018-08-31 09:59:43 -04:00
2018-11-19 22:36:10 -05:00
auto clearAction3 = new QAction ( QString ( " Clear " ) , ui - > tableWidgetRXAll ) ;
2024-11-08 10:37:24 -08:00
connect ( clearAction3 , & QAction : : triggered , this , [ this ] ( )
{
clearBandActivity ( ) ;
} ) ;
2018-07-31 23:35:28 -04:00
auto removeActivity = new QAction ( QString ( " Remove Activity " ) , ui - > tableWidgetRXAll ) ;
connect ( removeActivity , & QAction : : triggered , this , [ this ] ( ) {
if ( ui - > tableWidgetRXAll - > selectedItems ( ) . isEmpty ( ) ) {
return ;
}
auto selectedItems = ui - > tableWidgetRXAll - > selectedItems ( ) ;
2019-01-02 21:27:04 -05:00
int selectedOffset = selectedItems . first ( ) - > data ( Qt : : UserRole ) . toInt ( ) ;
2018-07-31 23:35:28 -04:00
m_bandActivity . remove ( selectedOffset ) ;
displayActivity ( true ) ;
} ) ;
2018-08-17 09:35:45 -04:00
auto logAction = new QAction ( QString ( " Log... " ) , ui - > tableWidgetCalls ) ;
connect ( logAction , & QAction : : triggered , this , & MainWindow : : on_logQSOButton_clicked ) ;
2018-07-31 23:35:28 -04:00
2018-12-31 20:30:47 -05:00
ui - > tableWidgetRXAll - > horizontalHeader ( ) - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
connect ( ui - > tableWidgetRXAll - > horizontalHeader ( ) , & QHeaderView : : customContextMenuRequested , this , [ this ] ( QPoint const & point ) {
QMenu * menu = new QMenu ( ui - > tableWidgetRXAll ) ;
2019-03-10 23:48:56 -04:00
QMenu * sortByMenu = menu - > addMenu ( " Sort By... " ) ;
buildBandActivitySortByMenu ( sortByMenu ) ;
QMenu * showColumnsMenu = menu - > addMenu ( " Show Columns... " ) ;
buildShowColumnsMenu ( showColumnsMenu , " band " ) ;
2018-12-31 20:30:47 -05:00
menu - > popup ( ui - > tableWidgetRXAll - > horizontalHeader ( ) - > mapToGlobal ( point ) ) ;
} ) ;
2018-08-31 09:59:43 -04:00
2018-07-31 23:35:28 -04:00
ui - > tableWidgetRXAll - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
2018-08-17 09:35:45 -04:00
connect ( ui - > tableWidgetRXAll , & QTableWidget : : customContextMenuRequested , this , [ this , clearAction3 , clearActionAll , removeActivity , logAction ] ( QPoint const & point ) {
2018-07-31 23:35:28 -04:00
QMenu * menu = new QMenu ( ui - > tableWidgetRXAll ) ;
2018-09-01 18:02:02 -04:00
// clear the selection of the call widget on right click
2019-11-25 20:30:03 -05:00
// but only if the table has rows.
if ( ui - > tableWidgetRXAll - > rowAt ( point . y ( ) ) ! = - 1 ) {
ui - > tableWidgetCalls - > selectionModel ( ) - > clearSelection ( ) ;
}
2018-09-01 18:02:02 -04:00
2018-08-17 09:35:45 -04:00
QString selectedCall = callsignSelected ( ) ;
bool missingCallsign = selectedCall . isEmpty ( ) ;
2018-10-03 14:56:35 -04:00
bool isAllCall = isAllCallIncluded ( selectedCall ) ;
2018-08-17 09:35:45 -04:00
2018-07-31 23:35:28 -04:00
int selectedOffset = - 1 ;
if ( ! ui - > tableWidgetRXAll - > selectedItems ( ) . isEmpty ( ) ) {
auto selectedItems = ui - > tableWidgetRXAll - > selectedItems ( ) ;
2018-12-23 21:06:54 -05:00
selectedOffset = selectedItems . first ( ) - > data ( Qt : : UserRole ) . toInt ( ) ;
2018-07-31 23:35:28 -04:00
}
2018-09-11 18:54:32 -04:00
if ( selectedOffset ! = - 1 ) {
2019-02-08 20:45:50 -05:00
auto qsyAction = menu - > addAction ( QString ( " Jump to %1Hz " ) . arg ( selectedOffset ) ) ;
2018-09-11 18:54:32 -04:00
connect ( qsyAction , & QAction : : triggered , this , [ this , selectedOffset ] ( ) {
2018-09-13 01:51:32 -04:00
setFreqOffsetForRestore ( selectedOffset , false ) ;
2018-09-11 18:54:32 -04:00
} ) ;
2019-11-04 20:21:55 -05:00
2019-12-26 15:11:32 -05:00
if ( m_wideGraph - > filterEnabled ( ) ) {
auto filterQsyAction = menu - > addAction ( QString ( " Center filter at %1Hz " ) . arg ( selectedOffset ) ) ;
connect ( filterQsyAction , & QAction : : triggered , this , [ this , selectedOffset ] ( ) {
m_wideGraph - > setFilterCenter ( selectedOffset ) ;
} ) ;
}
2019-11-04 20:21:55 -05:00
auto items = m_bandActivity . value ( selectedOffset ) ;
if ( ! items . isEmpty ( ) ) {
2019-11-09 09:48:09 -05:00
int submode = items . last ( ) . submode ;
2024-10-08 06:14:21 -07:00
auto speed = JS8 : : Submode : : name ( submode ) ;
2019-11-04 20:21:55 -05:00
if ( submode ! = m_nSubMode ) {
auto qrqAction = menu - > addAction ( QString ( " Jump to %1%2 speed " ) . arg ( speed . left ( 1 ) ) . arg ( speed . mid ( 1 ) . toLower ( ) ) ) ;
connect ( qrqAction , & QAction : : triggered , this , [ this , submode ] ( ) {
setSubmode ( submode ) ;
} ) ;
}
2019-12-13 14:30:47 -05:00
int tdrift = - int ( items . last ( ) . tdrift * 1000 ) ;
2019-12-13 15:27:42 -05:00
auto qtrAction = menu - > addAction ( QString ( " Jump to %1 ms time drift " ) . arg ( tdrift ) ) ;
2019-12-13 14:30:47 -05:00
connect ( qtrAction , & QAction : : triggered , this , [ this , tdrift ] ( ) {
setDrift ( tdrift ) ;
} ) ;
2019-11-04 20:21:55 -05:00
}
2018-09-11 18:54:32 -04:00
menu - > addSeparator ( ) ;
}
2018-08-17 09:35:45 -04:00
menu - > addAction ( logAction ) ;
2018-10-03 14:56:35 -04:00
logAction - > setDisabled ( missingCallsign | | isAllCall ) ;
menu - > addSeparator ( ) ;
2019-02-08 20:45:50 -05:00
auto savedMenu = menu - > addMenu ( " Saved Messages... " ) ;
2018-10-03 14:56:35 -04:00
buildSavedMessagesMenu ( savedMenu ) ;
2018-08-17 09:35:45 -04:00
2018-08-17 11:13:49 -04:00
auto directedMenu = menu - > addMenu ( QString ( " Directed to %1... " ) . arg ( selectedCall ) ) ;
2018-08-17 09:35:45 -04:00
directedMenu - > setDisabled ( missingCallsign ) ;
2018-08-17 11:13:49 -04:00
buildQueryMenu ( directedMenu , selectedCall ) ;
2018-08-17 09:35:45 -04:00
2018-09-09 12:40:02 -04:00
auto relayAction = buildRelayAction ( selectedCall ) ;
relayAction - > setText ( QString ( " Relay via %1... " ) . arg ( selectedCall ) ) ;
relayAction - > setDisabled ( missingCallsign ) ;
menu - > addActions ( { relayAction } ) ;
2018-09-11 18:54:32 -04:00
auto deselectAction = menu - > addAction ( QString ( " Deselect %1 " ) . arg ( selectedCall ) ) ;
deselectAction - > setDisabled ( missingCallsign ) ;
connect ( deselectAction , & QAction : : triggered , this , [ this ] ( ) {
2018-08-31 09:59:43 -04:00
ui - > tableWidgetRXAll - > clearSelection ( ) ;
2018-08-31 21:30:34 -04:00
ui - > tableWidgetCalls - > clearSelection ( ) ;
2018-08-31 09:59:43 -04:00
} ) ;
2018-08-17 09:35:45 -04:00
menu - > addSeparator ( ) ;
2018-07-31 23:35:28 -04:00
removeActivity - > setDisabled ( selectedOffset = = - 1 ) ;
menu - > addAction ( removeActivity ) ;
menu - > addSeparator ( ) ;
menu - > addAction ( clearAction3 ) ;
menu - > addAction ( clearActionAll ) ;
menu - > popup ( ui - > tableWidgetRXAll - > mapToGlobal ( point ) ) ;
2018-12-04 22:20:26 -05:00
displayActivity ( true ) ;
2018-07-31 23:35:28 -04:00
} ) ;
2018-07-03 18:57:49 -04:00
2018-11-19 22:36:10 -05:00
auto clearAction4 = new QAction ( QString ( " Clear " ) , ui - > tableWidgetCalls ) ;
2024-11-08 10:37:24 -08:00
connect ( clearAction4 , & QAction : : triggered , this , [ this ] ( )
{
clearCallActivity ( ) ;
} ) ;
2018-07-24 02:53:01 -04:00
2018-11-19 22:36:28 -05:00
auto addStation = new QAction ( QString ( " Add New Station or Group... " ) , ui - > tableWidgetCalls ) ;
2018-11-12 16:51:51 -05:00
connect ( addStation , & QAction : : triggered , this , [ this ] ( ) {
bool ok = false ;
2019-02-08 20:45:50 -05:00
QString callsign = QInputDialog : : getText ( this , tr ( " Add New Station or Group " ) ,
2018-11-19 22:36:28 -05:00
tr ( " Station or Group Callsign: " ) , QLineEdit : : Normal ,
2018-11-12 16:51:51 -05:00
" " , & ok ) . toUpper ( ) . trimmed ( ) ;
2018-11-19 22:36:28 -05:00
if ( ! ok | | callsign . trimmed ( ) . isEmpty ( ) ) {
2018-11-12 16:51:51 -05:00
return ;
}
2019-02-16 10:40:44 -05:00
// if we're adding allcall, turn off allcall avoidance
if ( callsign = = " @ALLCALL " ) {
m_config . set_avoid_allcall ( false ) ;
}
else if ( callsign . startsWith ( " @ " ) ) {
2018-12-20 20:36:33 -05:00
if ( Varicode : : isCompoundCallsign ( callsign ) ) {
m_config . addGroup ( callsign ) ;
} else {
MessageBox : : critical_message ( this , QString ( " %1 is not a valid group " ) . arg ( callsign ) ) ;
}
2018-11-19 22:36:28 -05:00
} else {
2019-02-01 03:08:47 -05:00
if ( Varicode : : isValidCallsign ( callsign , nullptr ) ) {
CallDetail cd = { } ;
cd . call = callsign ;
m_callActivity [ callsign ] = cd ;
} else {
MessageBox : : critical_message ( this , QString ( " %1 is not a valid callsign or group " ) . arg ( callsign ) ) ;
}
2018-11-19 22:36:28 -05:00
}
2018-11-12 16:51:51 -05:00
displayActivity ( true ) ;
} ) ;
2018-07-27 02:22:48 -04:00
auto removeStation = new QAction ( QString ( " Remove Station " ) , ui - > tableWidgetCalls ) ;
connect ( removeStation , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
2018-11-19 22:36:28 -05:00
if ( selectedCall . isEmpty ( ) ) {
return ;
}
2019-02-16 10:40:44 -05:00
if ( selectedCall = = " @ALLCALL " ) {
m_config . set_avoid_allcall ( true ) ;
}
else if ( selectedCall . startsWith ( " @ " ) ) {
2018-11-19 22:36:28 -05:00
m_config . removeGroup ( selectedCall ) ;
2019-02-16 10:40:44 -05:00
}
else if ( m_callActivity . contains ( selectedCall ) ) {
2018-07-27 02:22:48 -04:00
m_callActivity . remove ( selectedCall ) ;
}
2018-11-19 22:36:28 -05:00
displayActivity ( true ) ;
2018-07-27 02:22:48 -04:00
} ) ;
2024-10-22 20:30:55 -07:00
connect ( ui - > actionShow_Message_Inbox , & QAction : : triggered , this , [ this ] ( ) {
2019-02-02 17:06:01 -05:00
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
2019-02-16 11:47:54 -05:00
selectedCall = " % " ;
2019-02-02 17:06:01 -05:00
}
Inbox inbox ( inboxPath ( ) ) ;
if ( ! inbox . open ( ) ) {
return ;
}
2019-02-07 14:23:46 -05:00
QList < QPair < int , Message > > msgs ;
2019-02-16 23:20:28 -05:00
msgs . append ( inbox . values ( " STORE " , " $.params.TO " , selectedCall , 0 , 1000 ) ) ;
2019-02-07 14:23:46 -05:00
msgs . append ( inbox . values ( " READ " , " $.params.FROM " , selectedCall , 0 , 1000 ) ) ;
2019-02-07 12:19:07 -05:00
foreach ( auto pair , inbox . values ( " UNREAD " , " $.params.FROM " , selectedCall , 0 , 1000 ) ) {
2019-02-07 14:23:46 -05:00
msgs . append ( pair ) ;
2019-02-07 12:19:07 -05:00
// mark as read
2019-02-07 14:23:46 -05:00
auto msg = pair . second ;
2019-02-07 12:19:07 -05:00
msg . setType ( " READ " ) ;
inbox . set ( pair . first , msg ) ;
}
2019-02-02 17:06:01 -05:00
2024-08-31 09:40:28 -07:00
std : : stable_sort ( msgs . begin ( ) , msgs . end ( ) , [ ] ( QPair < int , Message > const & a , QPair < int , Message > const & b ) {
2024-08-31 13:13:39 -07:00
return QVariant : : compare ( a . second . params ( ) . value ( " UTC " ) ,
b . second . params ( ) . value ( " UTC " ) ) = = QPartialOrdering : : Greater ;
2019-02-02 17:06:01 -05:00
} ) ;
auto mw = new MessageWindow ( this ) ;
2019-02-07 12:19:07 -05:00
connect ( mw , & MessageWindow : : finished , this , [ this ] ( int ) {
refreshInboxCounts ( ) ;
displayCallActivity ( ) ;
} ) ;
2019-02-16 23:07:27 -05:00
connect ( mw , & MessageWindow : : deleteMessage , this , [ this ] ( int id ) {
Inbox inbox ( inboxPath ( ) ) ;
if ( ! inbox . open ( ) ) {
return ;
}
inbox . del ( id ) ;
} ) ;
2019-02-07 12:19:07 -05:00
connect ( mw , & MessageWindow : : replyMessage , this , [ this , mw ] ( const QString & text ) {
addMessageText ( text , true , true ) ;
refreshInboxCounts ( ) ;
displayCallActivity ( ) ;
mw - > close ( ) ;
2019-02-02 17:06:01 -05:00
} ) ;
mw - > setCall ( selectedCall ) ;
mw - > populateMessages ( msgs ) ;
mw - > show ( ) ;
} ) ;
2024-10-22 20:30:55 -07:00
auto historyAction = new QAction ( QString ( " Show Message Inbox... " ) , ui - > tableWidgetCalls ) ;
connect ( historyAction , & QAction : : triggered , ui - > actionShow_Message_Inbox , & QAction : : trigger ) ;
2019-02-08 19:41:07 -05:00
auto localMessageAction = new QAction ( QString ( " Store Message... " ) , ui - > tableWidgetCalls ) ;
connect ( localMessageAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
auto m = new MessageReplyDialog ( this ) ;
m - > setWindowTitle ( " Message " ) ;
m - > setLabel ( QString ( " Store this message locally for %1: " ) . arg ( selectedCall ) ) ;
if ( m - > exec ( ) ! = QMessageBox : : Accepted ) {
return ;
}
CommandDetail d = { } ;
2019-02-09 11:37:21 -05:00
d . cmd = " MSG " ;
2019-02-08 19:41:07 -05:00
d . to = selectedCall ;
d . from = m_config . my_callsign ( ) ;
d . relayPath = d . from ;
d . text = m - > textValue ( ) ;
d . utcTimestamp = DriftingDateTime : : currentDateTimeUtc ( ) ;
2019-11-09 09:48:09 -05:00
d . submode = m_nSubMode ;
2019-02-08 19:41:07 -05:00
addCommandToStorage ( " STORE " , d ) ;
} ) ;
2018-12-31 20:30:47 -05:00
ui - > tableWidgetCalls - > horizontalHeader ( ) - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
connect ( ui - > tableWidgetCalls - > horizontalHeader ( ) , & QHeaderView : : customContextMenuRequested , this , [ this ] ( QPoint const & point ) {
QMenu * menu = new QMenu ( ui - > tableWidgetCalls ) ;
2019-03-10 23:48:56 -04:00
QMenu * sortByMenu = menu - > addMenu ( " Sort By... " ) ;
buildCallActivitySortByMenu ( sortByMenu ) ;
QMenu * showColumnsMenu = menu - > addMenu ( " Show Columns... " ) ;
buildShowColumnsMenu ( showColumnsMenu , " call " ) ;
2018-12-31 20:30:47 -05:00
menu - > popup ( ui - > tableWidgetCalls - > horizontalHeader ( ) - > mapToGlobal ( point ) ) ;
} ) ;
2018-07-27 02:22:48 -04:00
2018-07-24 02:53:01 -04:00
ui - > tableWidgetCalls - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
2019-02-08 19:41:07 -05:00
connect ( ui - > tableWidgetCalls , & QTableWidget : : customContextMenuRequested , this , [ this , logAction , historyAction , localMessageAction , clearAction4 , clearActionAll , addStation , removeStation ] ( QPoint const & point ) {
2018-07-24 02:53:01 -04:00
QMenu * menu = new QMenu ( ui - > tableWidgetCalls ) ;
2019-11-25 20:30:03 -05:00
// clear the selection of the call widget on right click
// but only if the table has rows.
if ( ui - > tableWidgetCalls - > rowAt ( point . y ( ) ) ! = - 1 ) {
ui - > tableWidgetRXAll - > selectionModel ( ) - > clearSelection ( ) ;
}
2018-09-01 18:02:02 -04:00
2018-07-27 01:43:25 -04:00
QString selectedCall = callsignSelected ( ) ;
2018-07-31 14:34:36 -04:00
bool isAllCall = isAllCallIncluded ( selectedCall ) ;
2019-02-02 17:06:01 -05:00
bool isGroupCall = isGroupCallIncluded ( selectedCall ) ;
2018-07-27 01:43:25 -04:00
bool missingCallsign = selectedCall . isEmpty ( ) ;
2018-09-11 18:54:32 -04:00
if ( ! missingCallsign & & ! isAllCall ) {
2020-04-04 22:45:51 -04:00
int selectedOffset = m_callActivity [ selectedCall ] . offset ;
2018-09-11 18:54:32 -04:00
if ( selectedOffset ! = - 1 ) {
2019-02-08 20:45:50 -05:00
auto qsyAction = menu - > addAction ( QString ( " Jump to %1Hz " ) . arg ( selectedOffset ) ) ;
2018-09-11 18:54:32 -04:00
connect ( qsyAction , & QAction : : triggered , this , [ this , selectedOffset ] ( ) {
2018-09-13 01:51:32 -04:00
setFreqOffsetForRestore ( selectedOffset , false ) ;
2018-09-11 18:54:32 -04:00
} ) ;
2019-11-04 20:21:55 -05:00
2019-12-26 15:11:32 -05:00
if ( m_wideGraph - > filterEnabled ( ) ) {
auto filterQsyAction = menu - > addAction ( QString ( " Center filter at %1Hz " ) . arg ( selectedOffset ) ) ;
connect ( filterQsyAction , & QAction : : triggered , this , [ this , selectedOffset ] ( ) {
m_wideGraph - > setFilterCenter ( selectedOffset ) ;
} ) ;
}
2019-11-04 20:21:55 -05:00
2019-11-09 09:48:09 -05:00
int submode = m_callActivity [ selectedCall ] . submode ;
2024-10-08 06:14:21 -07:00
auto speed = JS8 : : Submode : : name ( submode ) ;
2019-11-04 20:21:55 -05:00
if ( submode ! = m_nSubMode ) {
auto qrqAction = menu - > addAction ( QString ( " Jump to %1%2 speed " ) . arg ( speed . left ( 1 ) ) . arg ( speed . mid ( 1 ) . toLower ( ) ) ) ;
connect ( qrqAction , & QAction : : triggered , this , [ this , submode ] ( ) {
setSubmode ( submode ) ;
} ) ;
}
2019-12-13 14:30:47 -05:00
int tdrift = - int ( m_callActivity [ selectedCall ] . tdrift * 1000 ) ;
2019-12-13 15:27:42 -05:00
auto qtrAction = menu - > addAction ( QString ( " Jump to %1 ms time drift " ) . arg ( tdrift ) ) ;
2019-12-13 14:30:47 -05:00
connect ( qtrAction , & QAction : : triggered , this , [ this , tdrift ] ( ) {
setDrift ( tdrift ) ;
} ) ;
2018-09-11 18:54:32 -04:00
menu - > addSeparator ( ) ;
}
2018-07-27 01:43:25 -04:00
}
2018-08-10 10:58:04 -04:00
menu - > addAction ( logAction ) ;
logAction - > setDisabled ( missingCallsign | | isAllCall ) ;
2019-02-02 17:06:01 -05:00
menu - > addAction ( historyAction ) ;
2019-02-07 12:19:07 -05:00
historyAction - > setDisabled ( missingCallsign | | isAllCall | | isGroupCall | | ! hasMessageHistory ( selectedCall ) ) ;
2019-02-02 17:06:01 -05:00
2019-02-08 19:41:07 -05:00
menu - > addAction ( localMessageAction ) ;
localMessageAction - > setDisabled ( missingCallsign | | isAllCall | | isGroupCall ) ;
2018-10-03 14:56:35 -04:00
menu - > addSeparator ( ) ;
2019-02-08 20:45:50 -05:00
auto savedMenu = menu - > addMenu ( " Saved Messages... " ) ;
2018-10-03 14:56:35 -04:00
buildSavedMessagesMenu ( savedMenu ) ;
2018-08-17 11:13:49 -04:00
auto directedMenu = menu - > addMenu ( QString ( " Directed to %1... " ) . arg ( selectedCall ) ) ;
2018-07-27 01:43:25 -04:00
directedMenu - > setDisabled ( missingCallsign ) ;
2018-08-17 11:13:49 -04:00
buildQueryMenu ( directedMenu , selectedCall ) ;
2018-07-24 02:53:01 -04:00
2018-09-09 12:40:02 -04:00
auto relayAction = buildRelayAction ( selectedCall ) ;
relayAction - > setText ( QString ( " Relay via %1... " ) . arg ( selectedCall ) ) ;
relayAction - > setDisabled ( missingCallsign | | isAllCall ) ;
menu - > addActions ( { relayAction } ) ;
2018-09-11 18:54:32 -04:00
auto deselect = menu - > addAction ( QString ( " Deselect %1 " ) . arg ( selectedCall ) ) ;
2018-08-31 09:59:43 -04:00
deselect - > setDisabled ( missingCallsign ) ;
connect ( deselect , & QAction : : triggered , this , [ this ] ( ) {
2018-08-31 21:30:34 -04:00
ui - > tableWidgetRXAll - > clearSelection ( ) ;
2018-08-31 09:59:43 -04:00
ui - > tableWidgetCalls - > clearSelection ( ) ;
} ) ;
2018-07-27 02:22:48 -04:00
menu - > addSeparator ( ) ;
2018-11-12 16:51:51 -05:00
menu - > addAction ( addStation ) ;
2019-02-16 10:40:44 -05:00
removeStation - > setDisabled ( missingCallsign ) ;
2018-11-19 22:36:28 -05:00
removeStation - > setText ( selectedCall . startsWith ( " @ " ) ? " Remove Group " : " Remove Station " ) ;
2018-07-27 02:22:48 -04:00
menu - > addAction ( removeStation ) ;
2018-07-24 02:53:01 -04:00
menu - > addSeparator ( ) ;
menu - > addAction ( clearAction4 ) ;
menu - > addAction ( clearActionAll ) ;
menu - > popup ( ui - > tableWidgetCalls - > mapToGlobal ( point ) ) ;
} ) ;
2024-11-08 12:55:07 -08:00
connect ( ui - > tableWidgetRXAll - > selectionModel ( ) , & QItemSelectionModel : : selectionChanged , this , & MainWindow : : tableSelectionChanged ) ;
connect ( ui - > tableWidgetCalls - > selectionModel ( ) , & QItemSelectionModel : : selectionChanged , this , & MainWindow : : tableSelectionChanged ) ;
2018-08-31 21:30:34 -04:00
2018-09-08 14:31:21 -04:00
auto p = ui - > tableWidgetRXAll - > palette ( ) ;
p . setColor ( QPalette : : Inactive , QPalette : : Highlight , p . color ( QPalette : : Active , QPalette : : Highlight ) ) ;
ui - > tableWidgetRXAll - > setPalette ( p ) ;
p = ui - > tableWidgetCalls - > palette ( ) ;
p . setColor ( QPalette : : Inactive , QPalette : : Highlight , p . color ( QPalette : : Active , QPalette : : Highlight ) ) ;
ui - > tableWidgetCalls - > setPalette ( p ) ;
2018-11-27 23:04:11 -05:00
ui - > hbMacroButton - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
connect ( ui - > hbMacroButton , & QPushButton : : customContextMenuRequested , this , [ this ] ( QPoint const & point ) {
QMenu * menu = new QMenu ( ui - > hbMacroButton ) ;
2018-11-29 22:43:15 -05:00
buildHeartbeatMenu ( menu ) ;
2018-11-27 23:04:11 -05:00
menu - > popup ( ui - > hbMacroButton - > mapToGlobal ( point ) ) ;
} ) ;
ui - > cqMacroButton - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
connect ( ui - > cqMacroButton , & QPushButton : : customContextMenuRequested , this , [ this ] ( QPoint const & point ) {
QMenu * menu = new QMenu ( ui - > cqMacroButton ) ;
2018-11-29 22:43:15 -05:00
buildCQMenu ( menu ) ;
2018-11-27 23:04:11 -05:00
menu - > popup ( ui - > cqMacroButton - > mapToGlobal ( point ) ) ;
} ) ;
2018-10-28 10:06:03 -04:00
// Don't block heartbeat's first run...
2019-11-11 22:39:02 -05:00
m_lastTxStartTime = DriftingDateTime : : currentDateTimeUtc ( ) . addSecs ( - 300 ) ;
2019-11-14 16:07:49 -05:00
2019-11-17 01:21:07 -05:00
// But do block the decoder's first run until 50% through next transmit period
m_lastTxStopTime = nextTransmitCycle ( ) . addSecs ( - m_TRperiod / 2 ) ;
2018-07-21 03:32:07 -04:00
2018-11-15 21:57:04 -05:00
int width = 75 ;
/*
QList < QPushButton * > btns ;
foreach ( auto child , ui - > buttonGrid - > children ( ) ) {
if ( ! child - > isWidgetType ( ) ) {
continue ;
}
if ( ! child - > objectName ( ) . contains ( " Button " ) ) {
continue ;
}
auto b = qobject_cast < QPushButton * > ( child ) ;
width = qMax ( width , b - > geometry ( ) . width ( ) ) ;
btns . append ( b ) ;
}
*/
2019-09-05 14:07:24 -04:00
foreach ( auto child , ui - > buttonGrid - > children ( ) ) {
if ( ! child - > isWidgetType ( ) ) {
continue ;
}
if ( ! child - > objectName ( ) . contains ( " Button " ) ) {
continue ;
}
auto b = qobject_cast < QPushButton * > ( child ) ;
b - > setCursor ( QCursor ( Qt : : PointingHandCursor ) ) ;
}
2018-11-15 21:57:04 -05:00
auto buttonLayout = ui - > buttonGrid - > layout ( ) ;
auto gridButtonLayout = qobject_cast < QGridLayout * > ( buttonLayout ) ;
gridButtonLayout - > setColumnMinimumWidth ( 0 , width ) ;
gridButtonLayout - > setColumnMinimumWidth ( 1 , width ) ;
gridButtonLayout - > setColumnMinimumWidth ( 2 , width ) ;
gridButtonLayout - > setColumnStretch ( 0 , 1 ) ;
gridButtonLayout - > setColumnStretch ( 1 , 1 ) ;
gridButtonLayout - > setColumnStretch ( 2 , 1 ) ;
2019-12-27 19:46:36 -05:00
// dial up and down buttons sizes
ui - > dialFreqUpButton - > setFixedSize ( 30 , 24 ) ;
ui - > dialFreqDownButton - > setFixedSize ( 30 , 24 ) ;
2019-07-19 23:08:33 -04:00
// Prepare spotting configuration...
2020-04-03 22:49:28 -04:00
prepareApi ( ) ;
2019-07-19 23:08:33 -04:00
prepareSpotting ( ) ;
2018-09-05 11:33:50 -04:00
2018-07-13 15:45:00 -04:00
displayActivity ( true ) ;
2018-10-02 09:44:46 -04:00
m_txTextDirtyDebounce . setSingleShot ( true ) ;
2018-10-05 18:40:56 -04:00
connect ( & m_txTextDirtyDebounce , & QTimer : : timeout , this , & MainWindow : : refreshTextDisplay ) ;
2018-10-02 09:44:46 -04:00
2019-06-12 23:54:02 -04:00
QTimer : : singleShot ( 500 , this , & MainWindow : : initializeDummyData ) ;
2018-07-12 18:02:54 -04:00
2018-02-08 21:28:33 -05:00
// this must be the last statement of constructor
if ( ! m_valid ) throw std : : runtime_error { " Fatal initialization exception " } ;
}
2024-10-09 06:45:04 -07:00
void
MainWindow : : checkVersion ( bool const alertOnUpToDate )
{
2019-03-31 13:39:10 -04:00
auto m = new QNetworkAccessManager ( this ) ;
2019-03-31 14:36:40 -04:00
connect ( m , & QNetworkAccessManager : : finished , this , [ this , alertOnUpToDate ] ( QNetworkReply * reply ) {
if ( reply - > error ( ) ) {
qDebug ( ) < < " Checking for Updates Error: " < < reply - > errorString ( ) ;
return ;
}
QString content = reply - > readAll ( ) . trimmed ( ) ;
2019-03-31 13:39:10 -04:00
2024-10-09 06:45:04 -07:00
auto const currentVersion = QVersionNumber : : fromString ( version ( ) ) ;
auto const networkVersion = QVersionNumber : : fromString ( content ) ;
2019-03-31 14:36:40 -04:00
qDebug ( ) < < " Checking Version " < < currentVersion < < " with " < < networkVersion ;
if ( currentVersion < networkVersion ) {
SelfDestructMessageBox * m = new SelfDestructMessageBox ( 60 ,
" New Updates Available " ,
QString ( " A new version (%1) of JS8Call is now available. Please see js8call.com for more details. " ) . arg ( content ) ,
QMessageBox : : Information ,
QMessageBox : : Ok ,
QMessageBox : : Ok ,
false ,
this ) ;
m - > show ( ) ;
} else if ( alertOnUpToDate ) {
SelfDestructMessageBox * m = new SelfDestructMessageBox ( 60 ,
" No Updates Available " ,
QString ( " Your version (%1) of JS8Call is up-to-date. " ) . arg ( version ( ) ) ,
QMessageBox : : Information ,
QMessageBox : : Ok ,
QMessageBox : : Ok ,
false ,
this ) ;
m - > show ( ) ;
}
2019-03-31 13:39:10 -04:00
} ) ;
2019-03-31 14:36:40 -04:00
qDebug ( ) < < " Checking for Updates... " ;
2019-03-31 13:39:10 -04:00
QUrl url ( " http://files.js8call.com/version.txt " ) ;
QNetworkRequest r ( url ) ;
m - > get ( r ) ;
}
2018-10-07 11:01:01 -04:00
void MainWindow : : checkStartupWarnings ( )
2018-08-28 16:49:44 -04:00
{
2019-03-31 14:36:40 -04:00
if ( m_config . check_for_updates ( ) ) {
checkVersion ( false ) ;
}
2018-08-23 23:59:15 -04:00
ensureCallsignSet ( false ) ;
2018-03-05 14:49:51 -05:00
}
2018-09-07 11:15:58 -04:00
void MainWindow : : initializeDummyData ( ) {
2018-09-07 11:20:10 -04:00
if ( ! QApplication : : applicationName ( ) . contains ( " dummy " ) ) {
return ;
}
2018-09-09 12:40:02 -04:00
2019-10-03 20:26:55 -04:00
#if 0
Codeword all ;
foreach ( CodewordPair p , JSC : : compress ( " " ) ) {
all . append ( p . first ) ;
}
qDebug ( ) < < all ;
qDebug ( ) < < JSC : : decompress ( all ) < < ( JSC : : decompress ( all ) = = " HELLO WORLD " ) ;
exit ( - 1 ) ;
# endif
2019-09-05 14:07:24 -04:00
#if 0
auto t = new QTimer ( this ) ;
t - > setInterval ( 150 ) ;
connect ( t , & QTimer : : timeout , this , [ this ] ( ) {
if ( ! ui - > extFreeTextMsgEdit - > hasFocus ( ) ) {
return ;
}
auto c = ui - > extFreeTextMsgEdit - > textCursor ( ) ;
int pos = qMin ( c . selectionStart ( ) , c . selectionEnd ( ) ) ;
if ( pos < = 5 & & c . selectionStart ( ) ! = c . selectionEnd ( ) ) {
c . clearSelection ( ) ;
ui - > extFreeTextMsgEdit - > setTextCursor ( c ) ;
}
} ) ;
t - > start ( ) ;
auto kpe = new KeyPressEater ( ) ;
connect ( kpe , & KeyPressEater : : keyPressed , this , [ this ] ( QObject * obj , QKeyEvent * e , bool * pProcessed ) {
auto t = e - > text ( ) ;
auto c = ui - > extFreeTextMsgEdit - > textCursor ( ) ;
int pos = qMin ( c . selectionStart ( ) , c . selectionEnd ( ) ) ;
if ( e - > key ( ) = = Qt : : Key_Escape ) {
* pProcessed = false ;
return ;
}
QTextCursor cc ( c ) ;
cc . setPosition ( 5 ) ;
cc . movePosition ( QTextCursor : : NextWord ) ;
int cpos = qMax ( cc . selectionStart ( ) , cc . selectionEnd ( ) ) ;
if ( e - > key ( ) = = Qt : : Key_Backspace & & e - > modifiers ( ) . testFlag ( Qt : : ControlModifier ) & & pos < cpos ) {
* pProcessed = true ;
return ;
}
if ( ( e - > key ( ) = = Qt : : Key_Backspace & & pos < = 5 ) | |
( e - > key ( ) = = Qt : : Key_Delete & & pos < 5 ) ) {
* pProcessed = true ;
return ;
}
if ( e - > key ( ) = = Qt : : Key_V & & e - > modifiers ( ) . testFlag ( Qt : : ControlModifier ) & & pos < = 5 ) {
* pProcessed = true ;
return ;
}
if ( ! t . isEmpty ( ) & & pos < 5 ) {
* pProcessed = true ;
return ;
}
} ) ;
ui - > extFreeTextMsgEdit - > installEventFilter ( kpe ) ;
connect ( ui - > extFreeTextMsgEdit , & QTextEdit : : copyAvailable , this , [ this ] ( bool available ) {
if ( ! available ) {
return ;
}
auto c = ui - > extFreeTextMsgEdit - > textCursor ( ) ;
qDebug ( ) < < " select " < < c . selectionStart ( ) < < c . selectionEnd ( ) ;
int pos = qMin ( c . selectionStart ( ) , c . selectionEnd ( ) ) ;
if ( pos < = 5 ) {
auto text = c . selectedText ( ) ;
if ( ! text . isEmpty ( ) ) {
c . clearSelection ( ) ;
ui - > extFreeTextMsgEdit - > setTextCursor ( c ) ;
}
}
} ) ;
/*
connect ( ui - > extFreeTextMsgEdit - > document ( ) , & QTextDocument : : cursorPositionChanged , this , [ this ] ( const QTextCursor & ) {
auto c = ui - > extFreeTextMsgEdit - > textCursor ( ) ;
int pos = qMin ( c . selectionStart ( ) , c . selectionEnd ( ) ) ;
if ( pos < = 5 ) {
auto text = c . selectedText ( ) ;
if ( ! text . isEmpty ( ) ) {
c . clearSelection ( ) ;
ui - > extFreeTextMsgEdit - > setTextCursor ( c ) ;
}
}
} ) ;
connect ( ui - > extFreeTextMsgEdit , & QTextEdit : : cursorPositionChanged , this , [ this ] ( ) {
auto c = ui - > extFreeTextMsgEdit - > textCursor ( ) ;
int pos = qMin ( c . selectionStart ( ) , c . selectionEnd ( ) ) ;
if ( pos < = 5 ) {
auto text = c . selectedText ( ) ;
if ( ! text . isEmpty ( ) ) {
c . clearSelection ( ) ;
ui - > extFreeTextMsgEdit - > setTextCursor ( c ) ;
}
}
} ) ;
*/
/*connect(ui->extFreeTextMsgEdit->document(), &QTextDocument::contentsChange, this, [this](int from, int removed, int added){
if ( from < 5 & & removed = = 1 ) {
ui - > extFreeTextMsgEdit - > document ( ) - > blockSignals ( true ) ;
ui - > extFreeTextMsgEdit - > document ( ) - > undo ( ) ;
ui - > extFreeTextMsgEdit - > document ( ) - > clearUndoRedoStacks ( QTextDocument : : RedoStack ) ;
ui - > extFreeTextMsgEdit - > document ( ) - > blockSignals ( false ) ;
}
} ) ; */
ui - > extFreeTextMsgEdit - > setText ( " HELLO BRAVE NEW WORLD " ) ;
auto c = ui - > extFreeTextMsgEdit - > textCursor ( ) ;
c . setPosition ( 0 ) ;
c . setPosition ( 5 , QTextCursor : : KeepAnchor ) ;
auto f = c . charFormat ( ) ;
f . setFontStrikeOut ( true ) ;
c . setCharFormat ( f ) ;
# endif
2019-11-25 20:13:00 -05:00
// this causes a segfault!
2025-01-22 17:42:38 -08:00
// processDecodedLine("223000 -15 -0.3 1681 B 6t++yk+aJbaE 6 \n");
2019-11-25 20:13:00 -05:00
2019-10-23 13:58:43 -04:00
ui - > extFreeTextMsgEdit - > setPlainText ( " HELLOBRAVE NEW WORLD " ) ;
2019-09-25 20:26:41 -04:00
ui - > extFreeTextMsgEdit - > setCharsSent ( 6 ) ;
2019-09-21 15:24:05 -04:00
2019-01-14 09:48:53 -05:00
logHeardGraph ( " KN4CRD " , " OH8STN " ) ;
logHeardGraph ( " KN4CRD " , " K0OG " ) ;
logHeardGraph ( " K0OG " , " KN4CRD " ) ;
2019-01-03 15:43:07 -05:00
2019-01-02 12:27:16 -05:00
auto path = QDir : : toNativeSeparators ( m_config . writeable_data_dir ( ) . absoluteFilePath ( QString ( " test.db3 " ) ) ) ;
auto inbox = Inbox ( path ) ;
if ( inbox . open ( ) ) {
qDebug ( ) < < " test inbox opened " < < inbox . count ( " test " , " $ " , " % " ) < < " messages " ;
2024-09-06 06:40:36 -07:00
// int i = inbox.append(Message("test", "booya1"));
2019-01-02 12:27:16 -05:00
2024-09-06 06:40:36 -07:00
int i = inbox . append ( Message ( " test " , " booya2 " ) ) ;
2019-01-02 12:27:16 -05:00
qDebug ( ) < < " i " < < i ;
qDebug ( ) < < inbox . set ( i , Message ( " test " , " booya3 " ) ) ;
auto m = inbox . value ( i ) ;
qDebug ( ) < < QString ( m . toJson ( ) ) ;
qDebug ( ) < < inbox . del ( i ) ;
foreach ( auto pair , inbox . values ( " test " , " $ " , " % " , 0 , 5 ) ) {
qDebug ( ) < < pair . first < < QString ( pair . second . toJson ( ) ) ;
}
}
2019-09-05 14:07:24 -04:00
auto d = DecodedText ( " SN5-lUuJkby0 " , Varicode : : JS8CallFirst , 1 ) ;
qDebug ( ) < < " KN4CRD: K0OG ===> " < < d . message ( ) ;
2018-11-03 01:14:31 -04:00
2018-10-26 16:57:07 -04:00
// qDebug() << Varicode::isValidCallsign("@GROUP1", nullptr);
// qDebug() << Varicode::packAlphaNumeric50("VE7/KN4CRD");
// qDebug() << Varicode::unpackAlphaNumeric50(Varicode::packAlphaNumeric50("VE7/KN4CRD"));
// qDebug() << Varicode::unpackAlphaNumeric50(Varicode::packAlphaNumeric50("@GROUP/42"));
// qDebug() << Varicode::unpackAlphaNumeric50(Varicode::packAlphaNumeric50("SP1ATOM"));
2018-10-25 22:55:54 -04:00
2019-02-07 12:19:07 -05:00
if ( ! m_config . my_groups ( ) . contains ( " @GROUP42 " ) ) {
m_config . addGroup ( " @GROUP42 " ) ;
}
2019-02-03 23:00:18 -05:00
2018-09-09 12:40:02 -04:00
QList < QString > calls = {
" KN4CRD " ,
2018-10-25 22:55:54 -04:00
" VE7/KN4CRD " ,
2018-09-09 12:40:02 -04:00
" KN4CRD/P " ,
" KC9QNE " ,
" KI6SSI " ,
2018-09-29 12:20:11 -04:00
" K0OG " ,
2018-09-09 12:40:02 -04:00
" LB9YH " ,
" VE7/LB9YH " ,
" M0IAX " ,
" N0JDS " ,
" OH8STN " ,
" VA3OSO " ,
" VK1MIC " ,
" W0FW "
} ;
2018-09-18 17:24:07 -04:00
auto dt = DriftingDateTime : : currentDateTimeUtc ( ) . addSecs ( - 300 ) ;
2018-09-07 11:20:10 -04:00
2018-09-09 14:22:21 -04:00
int i = 0 ;
2018-09-09 12:40:02 -04:00
foreach ( auto call , calls ) {
CallDetail cd = { } ;
cd . call = call ;
2019-10-04 15:25:51 -04:00
cd . through = i = = 2 ? " KN4CRD " : " " ;
2020-04-04 22:45:51 -04:00
cd . dial = 7078000 ;
cd . offset = 500 + 100 * i ;
2018-09-12 09:04:22 -04:00
cd . snr = i = = 3 ? - 100 : i ;
2018-09-29 17:04:05 -04:00
cd . ackTimestamp = i = = 1 ? dt . addSecs ( - 900 ) : QDateTime { } ;
2018-09-09 12:40:02 -04:00
cd . utcTimestamp = dt ;
2018-10-06 23:28:00 -04:00
cd . grid = i = = 5 ? " J042 " : i = = 6 ? " FN42FN42FN " : " " ;
2018-10-29 02:02:58 -04:00
cd . tdrift = 0.1 * i ;
2019-11-10 13:47:17 -05:00
cd . submode = i % 3 ;
2018-09-09 12:40:02 -04:00
logCallActivity ( cd , false ) ;
2018-09-11 23:03:44 -04:00
ActivityDetail ad = { } ;
2019-02-04 19:57:53 -05:00
ad . bits = Varicode : : JS8CallFirst | Varicode : : JS8CallLast ;
2018-09-12 09:04:22 -04:00
ad . snr = i = = 3 ? - 100 : i ;
2020-04-04 22:45:51 -04:00
ad . dial = 7078000 ;
ad . offset = 500 + 100 * i ;
2019-02-04 19:57:53 -05:00
ad . text = QString ( " %1: %2 TEST MESSAGE " ) . arg ( call ) . arg ( m_config . my_callsign ( ) ) ;
2018-09-11 23:03:44 -04:00
ad . utcTimestamp = dt ;
2019-11-09 09:48:09 -05:00
ad . submode = cd . submode ;
2018-09-11 23:03:44 -04:00
m_bandActivity [ 500 + 100 * i ] = { ad } ;
2018-09-15 17:33:09 -04:00
markOffsetDirected ( 500 + 100 * i , false ) ;
2018-09-11 23:03:44 -04:00
i + + ;
2018-09-09 12:40:02 -04:00
}
2018-09-07 11:20:10 -04:00
2019-09-05 14:27:44 -04:00
ActivityDetail adHB1 = { } ;
adHB1 . bits = Varicode : : JS8CallFirst ;
adHB1 . snr = 0 ;
2020-04-04 22:45:51 -04:00
adHB1 . dial = 7078000 ;
adHB1 . offset = 750 ;
2019-09-05 14:27:44 -04:00
adHB1 . text = QString ( " KN4CRD: HB AUTO EM73 " ) ;
adHB1 . utcTimestamp = DriftingDateTime : : currentDateTimeUtc ( ) ;
2019-11-09 09:48:09 -05:00
adHB1 . submode = Varicode : : JS8CallNormal ;
2019-09-05 14:27:44 -04:00
m_bandActivity [ 750 ] . append ( adHB1 ) ;
ActivityDetail adHB2 = { } ;
adHB2 . bits = Varicode : : JS8CallLast ;
adHB2 . snr = 0 ;
2020-04-04 22:45:51 -04:00
adHB2 . dial = 7078000 ;
adHB2 . offset = 750 ;
2019-09-05 14:27:44 -04:00
adHB2 . text = QString ( " MSG ID 1 " ) ;
adHB2 . utcTimestamp = DriftingDateTime : : currentDateTimeUtc ( ) ;
2019-11-09 09:48:09 -05:00
adHB2 . submode = Varicode : : JS8CallNormal ;
2019-09-05 14:27:44 -04:00
m_bandActivity [ 750 ] . append ( adHB2 ) ;
2019-02-03 23:00:18 -05:00
CommandDetail cmd = { } ;
cmd . cmd = " > " ;
cmd . to = m_config . my_callsign ( ) ;
cmd . from = " N0JDS " ;
cmd . relayPath = " N0JDS>OH8STN " ;
cmd . text = " HELLO BRAVE SOUL " ;
cmd . utcTimestamp = dt ;
2019-11-09 09:48:09 -05:00
cmd . submode = Varicode : : JS8CallNormal ;
2019-02-03 23:00:18 -05:00
addCommandToMyInbox ( cmd ) ;
2019-04-21 11:29:31 -04:00
QString eot = m_config . eot ( ) ;
displayTextForFreq ( QString ( " KN4CRD: @ALLCALL? %1 " ) . arg ( eot ) , 42 , DriftingDateTime : : currentDateTimeUtc ( ) . addSecs ( - 315 ) , true , true , true ) ;
displayTextForFreq ( QString ( " J1Y: KN4CRD SNR -05 %1 " ) . arg ( eot ) , 42 , DriftingDateTime : : currentDateTimeUtc ( ) . addSecs ( - 300 ) , false , true , true ) ;
displayTextForFreq ( QString ( " HELLO BRAVE NEW WORLD %1 " ) . arg ( eot ) , 42 , DriftingDateTime : : currentDateTimeUtc ( ) . addSecs ( - 300 ) , false , true , true ) ;
2018-09-11 23:03:44 -04:00
2019-02-21 13:11:30 -05:00
auto now = DriftingDateTime : : currentDateTimeUtc ( ) ;
2019-04-21 11:29:31 -04:00
displayTextForFreq ( QString ( " KN4CRD: JY1 ACK -12 %1 " ) . arg ( eot ) , 780 , now , false , true , true ) ;
displayTextForFreq ( QString ( " KN4CRD: JY1 ACK -12 %1 " ) . arg ( eot ) , 780 , now , false , true , true ) ; // should be hidden (duplicate)
displayTextForFreq ( QString ( " OH8STN: JY1 ACK -12 %1 " ) . arg ( eot ) , 780 , now , false , true , true ) ;
2019-02-21 13:11:30 -05:00
2019-04-21 11:29:31 -04:00
displayTextForFreq ( QString ( " KN4CRD: JY1 ACK -10 %1 " ) . arg ( eot ) , 800 , now , false , true , true ) ;
displayTextForFreq ( QString ( " KN4CRD: JY1 ACK -12 %1 " ) . arg ( eot ) , 780 , now . addSecs ( 120 ) , false , true , true ) ;
2019-02-21 13:11:30 -05:00
2019-04-21 11:29:31 -04:00
displayTextForFreq ( QString ( " HELLO \\ nBRAVE \\ nNEW \\ nWORLD %1 " ) . arg ( eot ) , 1500 , now , false , true , true ) ;
2019-02-25 10:31:11 -05:00
2018-09-07 11:15:58 -04:00
displayActivity ( true ) ;
}
2018-02-08 21:28:33 -05:00
void MainWindow : : initialize_fonts ( )
{
set_application_font ( m_config . text_font ( ) ) ;
2018-09-07 10:12:40 -04:00
setTextEditFont ( ui - > textEditRX , m_config . rx_text_font ( ) ) ;
setTextEditFont ( ui - > extFreeTextMsgEdit , m_config . tx_text_font ( ) ) ;
2018-09-15 17:33:09 -04:00
displayActivity ( true ) ;
2018-02-08 21:28:33 -05:00
}
void MainWindow : : on_the_minute ( )
{
if ( minuteTimer . isSingleShot ( ) )
{
minuteTimer . setSingleShot ( false ) ;
minuteTimer . start ( 60 * 1000 ) ; // run free
}
else
{
auto const & ms_error = ms_minute_error ( ) ;
if ( qAbs ( ms_error ) > 1000 ) // keep drift within +-1s
{
minuteTimer . setSingleShot ( true ) ;
minuteTimer . start ( ms_error + 60 * 1000 ) ;
}
}
2018-10-28 11:24:00 -04:00
if ( m_config . watchdog ( ) )
2018-02-08 21:28:33 -05:00
{
2018-10-28 12:06:37 -04:00
incrementIdleTimer ( ) ;
2018-02-08 21:28:33 -05:00
}
else
{
tx_watchdog ( false ) ;
}
2018-08-09 12:52:48 -04:00
}
2018-08-08 23:19:27 -04:00
2018-08-09 12:52:48 -04:00
void MainWindow : : tryBandHop ( ) {
2018-08-08 23:19:27 -04:00
// see if we need to hop bands...
2018-08-09 12:52:48 -04:00
if ( ! m_config . auto_switch_bands ( ) ) {
return ;
}
2018-08-16 15:19:43 -04:00
// make sure we're not transmitting
if ( isMessageQueuedForTransmit ( ) ) {
return ;
}
2018-08-09 12:52:48 -04:00
// get the current band
2018-08-09 23:07:58 -04:00
auto dialFreq = dialFrequency ( ) ;
2018-08-09 12:52:48 -04:00
auto currentBand = m_config . bands ( ) - > find ( dialFreq ) ;
// get the stations list
2018-08-08 23:19:27 -04:00
auto stations = m_config . stations ( ) - > station_list ( ) ;
2018-08-09 12:52:48 -04:00
// order stations by (switch_at, switch_until) time tuple
2024-08-31 09:40:28 -07:00
std : : stable_sort ( stations . begin ( ) , stations . end ( ) , [ ] ( StationList : : Station const & a , StationList : : Station const & b ) {
2018-08-09 00:06:02 -04:00
return ( a . switch_at_ < b . switch_at_ ) | | ( a . switch_at_ = = b . switch_at_ & & a . switch_until_ < b . switch_until_ ) ;
2018-08-08 23:19:27 -04:00
} ) ;
2018-08-09 12:52:48 -04:00
// we just set the date to a known y/m/d to make the comparisons easier
2018-09-18 17:24:07 -04:00
QDateTime d = DriftingDateTime : : currentDateTimeUtc ( ) ;
2018-08-09 12:52:48 -04:00
d . setDate ( QDate ( 2000 , 1 , 1 ) ) ;
2018-08-12 10:35:19 -04:00
QDateTime startOfDay = QDateTime ( QDate ( 2000 , 1 , 1 ) , QTime ( 0 , 0 ) ) ;
QDateTime endOfDay = QDateTime ( QDate ( 2000 , 1 , 1 ) , QTime ( 23 , 59 ) ) ;
2018-08-09 12:52:48 -04:00
// see if we can find a needed band switch...
2018-08-08 23:19:27 -04:00
foreach ( auto station , stations ) {
2018-08-09 12:52:48 -04:00
// we can switch to this frequency if we're in the time range, inclusive of switch_at, exclusive of switch_until
2018-08-09 15:28:23 -04:00
// and if we are switching to a different frequency than the last hop. this allows us to switch bands at that time,
2018-08-09 12:52:48 -04:00
// but then later we can later switch to a different band if needed without the automatic band switching to take over
2018-08-12 10:35:19 -04:00
bool inTimeRange = (
( station . switch_at_ < = d & & d < = station . switch_until_ ) | | // <- normal range, 12-16 && 6-8, evalued as 12 <= d <= 16 || 6 <= d <= 8
( station . switch_until_ < station . switch_at_ & & ( // <- say for a range of 12->2 & 2->12; 12->2,
( station . switch_at_ < = d & & d < = endOfDay ) | | // should be evaluated as 12 <= d <= 23:59 || 00:00 <= d <= 2
( startOfDay < = d & & d < = station . switch_until_ )
) )
) ;
bool noOverride = (
2024-09-06 06:17:10 -07:00
m_bandHopped | | ( ! m_bandHopped & & station . frequency_ ! = m_bandHoppedFreq )
2018-08-12 10:35:19 -04:00
) ;
bool freqIsDifferent = ( station . frequency_ ! = dialFreq ) ;
2018-08-09 15:28:23 -04:00
bool canSwitch = (
2018-08-12 10:35:19 -04:00
inTimeRange & &
noOverride & &
freqIsDifferent
2018-08-09 15:28:23 -04:00
) ;
2018-08-09 12:52:48 -04:00
// switch, if we can and the band is different than our current band
2018-08-09 15:28:23 -04:00
if ( canSwitch ) {
2018-08-16 15:19:43 -04:00
Frequency frequency = station . frequency_ ;
m_bandHopped = false ;
m_bandHoppedFreq = frequency ;
SelfDestructMessageBox * m = new SelfDestructMessageBox ( 30 ,
" Scheduled Frequency Change " ,
QString ( " A scheduled frequency change has arrived. The rig frequency will be changed to %1 MHz in %2 second(s). " ) . arg ( Radio : : frequency_MHz_string ( station . frequency_ ) ) ,
QMessageBox : : Information ,
QMessageBox : : Ok | QMessageBox : : Cancel ,
QMessageBox : : Ok ,
2019-02-07 14:31:11 -05:00
true ,
2018-08-16 15:19:43 -04:00
this ) ;
connect ( m , & SelfDestructMessageBox : : accepted , this , [ this , frequency ] ( ) {
m_bandHopped = true ;
setRig ( frequency ) ;
} ) ;
2018-08-09 12:52:48 -04:00
2018-08-16 15:19:43 -04:00
m - > show ( ) ;
2018-08-09 15:28:23 -04:00
2018-08-16 15:19:43 -04:00
#if 0
// TODO: jsherer - this is totally a hack because of the signal that gets emitted to clearActivity on band change...
QTimer * t = new QTimer ( this ) ;
t - > setInterval ( 250 ) ;
t - > setSingleShot ( true ) ;
connect ( t , & QTimer : : timeout , this , [ this , station , dialFreq ] ( ) {
auto message = QString ( " Scheduled frequency switch from %1 MHz to %2 MHz " ) ;
message = message . arg ( Radio : : frequency_MHz_string ( dialFreq ) ) ;
message = message . arg ( Radio : : frequency_MHz_string ( station . frequency_ ) ) ;
2018-09-18 17:24:07 -04:00
writeNoticeTextToUI ( DriftingDateTime : : currentDateTimeUtc ( ) , message ) ;
2018-08-16 15:19:43 -04:00
} ) ;
t - > start ( ) ;
# endif
2018-08-08 23:19:27 -04:00
2018-08-16 15:19:43 -04:00
return ;
2018-08-08 23:19:27 -04:00
}
}
2018-02-08 21:28:33 -05:00
}
//--------------------------------------------------- MainWindow destructor
MainWindow : : ~ MainWindow ( )
{
QString fname { QDir : : toNativeSeparators ( m_config . writeable_data_dir ( ) . absoluteFilePath ( " wsjtx_wisdom.dat " ) ) } ;
QByteArray cfname = fname . toLocal8Bit ( ) ;
2019-10-08 21:26:17 -04:00
2018-02-08 21:28:33 -05:00
fftwf_export_wisdom_to_filename ( cfname ) ;
2019-10-08 21:26:17 -04:00
2020-03-31 15:22:48 -04:00
m_networkThread . quit ( ) ;
m_networkThread . wait ( ) ;
2018-02-08 21:28:33 -05:00
m_audioThread . quit ( ) ;
m_audioThread . wait ( ) ;
2019-10-08 21:26:17 -04:00
m_notificationAudioThread . quit ( ) ;
m_notificationAudioThread . wait ( ) ;
2019-11-20 00:11:30 -05:00
m_decoder . quit ( ) ;
2018-02-08 21:28:33 -05:00
remove_child_from_event_filter ( this ) ;
}
//-------------------------------------------------------- writeSettings()
void MainWindow : : writeSettings ( )
{
m_settings - > beginGroup ( " MainWindow " ) ;
m_settings - > setValue ( " geometry " , saveGeometry ( ) ) ;
m_settings - > setValue ( " geometryNoControls " , m_geometryNoControls ) ;
m_settings - > setValue ( " state " , saveState ( ) ) ;
2018-08-10 09:35:40 -04:00
m_settings - > setValue ( " MainSplitter " , ui - > mainSplitter - > saveState ( ) ) ;
m_settings - > setValue ( " TextHorizontalSplitter " , ui - > textHorizontalSplitter - > saveState ( ) ) ;
2018-10-06 04:12:16 -04:00
m_settings - > setValue ( " BandActivityVisible " , ui - > tableWidgetRXAll - > isVisible ( ) ) ;
2019-09-06 13:45:40 -04:00
m_settings - > setValue ( " BandHBActivityVisible " , ui - > actionShow_Band_Heartbeats_and_ACKs - > isChecked ( ) ) ;
2018-08-10 09:35:40 -04:00
m_settings - > setValue ( " TextVerticalSplitter " , ui - > textVerticalSplitter - > saveState ( ) ) ;
2019-12-17 10:39:18 -05:00
m_settings - > setValue ( " TimeDrift " , DriftingDateTime : : drift ( ) ) ;
2018-11-13 12:35:39 -05:00
m_settings - > setValue ( " ShowTooltips " , ui - > actionShow_Tooltips - > isChecked ( ) ) ;
2019-01-09 11:14:46 -05:00
m_settings - > setValue ( " ShowStatusbar " , ui - > statusBar - > isVisible ( ) ) ;
2019-06-12 12:03:50 -04:00
m_settings - > setValue ( " RXActivity " , ui - > textEditRX - > toHtml ( ) ) ;
2018-08-10 09:35:40 -04:00
2018-02-08 21:28:33 -05:00
m_settings - > endGroup ( ) ;
m_settings - > beginGroup ( " Common " ) ;
m_settings - > setValue ( " NDepth " , m_ndepth ) ;
2024-10-20 13:54:14 -07:00
m_settings - > setValue ( " Freq " , freq ( ) ) ;
2019-09-05 14:07:24 -04:00
m_settings - > setValue ( " SubMode " , m_nSubMode ) ;
m_settings - > setValue ( " SubModeHB " , ui - > actionModeJS8HB - > isChecked ( ) ) ;
m_settings - > setValue ( " SubModeHBAck " , ui - > actionHeartbeatAcknowledgements - > isChecked ( ) ) ;
2019-11-04 19:50:57 -05:00
m_settings - > setValue ( " SubModeMultiDecode " , ui - > actionModeMultiDecoder - > isChecked ( ) ) ;
2024-10-26 06:50:58 -07:00
m_settings - > setValue ( " DialFreq " , QVariant : : fromValue ( m_lastMonitoredFrequency ) ) ;
2018-02-08 21:28:33 -05:00
m_settings - > setValue ( " OutAttenuation " , ui - > outAttenuation - > value ( ) ) ;
m_settings - > setValue ( " pwrBandTxMemory " , m_pwrBandTxMemory ) ;
m_settings - > setValue ( " pwrBandTuneMemory " , m_pwrBandTuneMemory ) ;
2018-08-23 11:59:20 -04:00
m_settings - > setValue ( " SortBy " , QVariant ( m_sortCache ) ) ;
2018-09-24 16:46:33 -04:00
m_settings - > setValue ( " ShowColumns " , QVariant ( m_showColumnsCache ) ) ;
2018-11-27 23:04:11 -05:00
m_settings - > setValue ( " HBInterval " , m_hbInterval ) ;
m_settings - > setValue ( " CQInterval " , m_cqInterval ) ;
2018-08-23 11:59:20 -04:00
2018-07-06 02:36:40 -04:00
2019-06-12 12:03:50 -04:00
2018-07-06 02:36:40 -04:00
// TODO: jsherer - need any other customizations?
/*m_settings->setValue("PanelLeftGeometry", ui->tableWidgetRXAll->geometry());
m_settings - > setValue ( " PanelRightGeometry " , ui - > tableWidgetCalls - > geometry ( ) ) ;
m_settings - > setValue ( " PanelTopGeometry " , ui - > extFreeTextMsg - > geometry ( ) ) ;
m_settings - > setValue ( " PanelBottomGeometry " , ui - > extFreeTextMsgEdit - > geometry ( ) ) ;
m_settings - > setValue ( " PanelWaterfallGeometry " , ui - > bandHorizontalWidget - > geometry ( ) ) ; */
//m_settings->setValue("MainSplitter", QVariant::fromValue(ui->mainSplitter->sizes()));
2018-02-08 21:28:33 -05:00
m_settings - > endGroup ( ) ;
2019-06-12 12:03:50 -04:00
auto now = DriftingDateTime : : currentDateTimeUtc ( ) ;
int callsignAging = m_config . callsign_aging ( ) ;
m_settings - > beginGroup ( " CallActivity " ) ;
m_settings - > remove ( " " ) ; // remove all keys in current group
foreach ( auto cd , m_callActivity . values ( ) ) {
if ( cd . call . trimmed ( ) . isEmpty ( ) ) {
continue ;
}
if ( callsignAging & & cd . utcTimestamp . secsTo ( now ) / 60 > = callsignAging ) {
continue ;
}
m_settings - > setValue ( cd . call . trimmed ( ) , QVariantMap {
{ " snr " , QVariant ( cd . snr ) } ,
{ " grid " , QVariant ( cd . grid ) } ,
2020-04-04 22:45:51 -04:00
{ " dial " , QVariant ( cd . dial ) } ,
{ " freq " , QVariant ( cd . offset ) } ,
2019-09-05 14:07:24 -04:00
{ " tdrift " , QVariant ( cd . tdrift ) } ,
2019-06-12 12:03:50 -04:00
# if CACHE_CALL_DATETIME_AS_STRINGS
{ " ackTimestamp " , QVariant ( cd . ackTimestamp . toString ( " yyyy-MM-dd hh:mm:ss " ) ) } ,
{ " utcTimestamp " , QVariant ( cd . utcTimestamp . toString ( " yyyy-MM-dd hh:mm:ss " ) ) } ,
# else
{ " ackTimestamp " , QVariant ( cd . ackTimestamp ) } ,
{ " utcTimestamp " , QVariant ( cd . utcTimestamp ) } ,
# endif
2019-11-09 09:48:09 -05:00
{ " submode " , QVariant ( cd . submode ) } ,
2019-06-12 12:03:50 -04:00
} ) ;
}
m_settings - > endGroup ( ) ;
2018-02-08 21:28:33 -05:00
}
//---------------------------------------------------------- readSettings()
void MainWindow : : readSettings ( )
{
m_settings - > beginGroup ( " MainWindow " ) ;
2018-11-12 16:34:31 -05:00
setMinimumSize ( 800 , 400 ) ;
2018-02-08 21:28:33 -05:00
restoreGeometry ( m_settings - > value ( " geometry " , saveGeometry ( ) ) . toByteArray ( ) ) ;
2018-11-12 16:34:31 -05:00
setMinimumSize ( 800 , 400 ) ;
2018-07-25 20:13:23 -04:00
2018-02-08 21:28:33 -05:00
m_geometryNoControls = m_settings - > value ( " geometryNoControls " , saveGeometry ( ) ) . toByteArray ( ) ;
restoreState ( m_settings - > value ( " state " , saveState ( ) ) . toByteArray ( ) ) ;
2018-08-10 09:35:40 -04:00
2018-08-10 10:58:04 -04:00
auto mainSplitterState = m_settings - > value ( " MainSplitter " ) . toByteArray ( ) ;
if ( ! mainSplitterState . isEmpty ( ) ) {
ui - > mainSplitter - > restoreState ( mainSplitterState ) ;
}
auto horizontalState = m_settings - > value ( " TextHorizontalSplitter " ) . toByteArray ( ) ;
if ( ! horizontalState . isEmpty ( ) ) {
ui - > textHorizontalSplitter - > restoreState ( horizontalState ) ;
2018-09-24 16:46:33 -04:00
auto hsizes = ui - > textHorizontalSplitter - > sizes ( ) ;
2018-10-06 04:12:16 -04:00
2018-09-24 16:46:33 -04:00
ui - > tableWidgetRXAll - > setVisible ( hsizes . at ( 0 ) > 0 ) ;
ui - > tableWidgetCalls - > setVisible ( hsizes . at ( 2 ) > 0 ) ;
2018-08-10 10:58:04 -04:00
}
2018-10-06 04:12:16 -04:00
m_bandActivityWasVisible = m_settings - > value ( " BandActivityVisible " , true ) . toBool ( ) ;
ui - > tableWidgetRXAll - > setVisible ( m_bandActivityWasVisible ) ;
2018-08-10 10:58:04 -04:00
auto verticalState = m_settings - > value ( " TextVerticalSplitter " ) . toByteArray ( ) ;
if ( ! verticalState . isEmpty ( ) ) {
ui - > textVerticalSplitter - > restoreState ( verticalState ) ;
}
2019-12-17 10:39:18 -05:00
setDrift ( m_settings - > value ( " TimeDrift " , 0 ) . toInt ( ) ) ;
2019-12-18 09:50:22 -05:00
ui - > actionShow_Waterfall_Controls - > setChecked ( m_wideGraph - > controlsVisible ( ) ) ;
2020-05-16 15:01:03 -04:00
ui - > actionShow_Waterfall_Time_Drift_Controls - > setChecked ( m_wideGraph - > timeControlsVisible ( ) ) ;
2018-11-13 12:35:39 -05:00
ui - > actionShow_Tooltips - > setChecked ( m_settings - > value ( " ShowTooltips " , true ) . toBool ( ) ) ;
2019-01-09 11:14:46 -05:00
ui - > actionShow_Statusbar - > setChecked ( m_settings - > value ( " ShowStatusbar " , true ) . toBool ( ) ) ;
ui - > statusBar - > setVisible ( ui - > actionShow_Statusbar - > isChecked ( ) ) ;
2019-06-12 23:54:02 -04:00
ui - > textEditRX - > setHtml ( m_config . reset_activity ( ) ? " " : m_settings - > value ( " RXActivity " , " " ) . toString ( ) ) ;
2019-11-12 21:01:14 -05:00
ui - > actionShow_Band_Heartbeats_and_ACKs - > setChecked ( m_settings - > value ( " BandHBActivityVisible " , true ) . toBool ( ) ) ;
2018-02-08 21:28:33 -05:00
m_settings - > endGroup ( ) ;
m_settings - > beginGroup ( " Common " ) ;
2018-09-12 16:08:25 -04:00
2019-09-19 11:51:27 -04:00
// set the frequency offset
2024-10-30 11:47:36 -07:00
setFreqOffsetForRestore ( m_settings - > value ( " Freq " , Default : : FREQUENCY ) . toInt ( ) , false ) ; // XXX
2019-09-19 11:51:27 -04:00
2024-10-31 07:29:55 -07:00
setSubmode ( m_settings - > value ( " SubMode " , Default : : SUBMODE ) . toInt ( ) ) ;
2020-03-28 11:57:38 -04:00
ui - > actionModeJS8HB - > setChecked ( m_settings - > value ( " SubModeHB " , false ) . toBool ( ) ) ;
2019-09-05 14:07:24 -04:00
ui - > actionHeartbeatAcknowledgements - > setChecked ( m_settings - > value ( " SubModeHBAck " , false ) . toBool ( ) ) ;
2019-11-05 22:01:42 -05:00
ui - > actionModeMultiDecoder - > setChecked ( m_settings - > value ( " SubModeMultiDecode " , true ) . toBool ( ) ) ;
2019-11-04 20:21:55 -05:00
2018-02-08 21:28:33 -05:00
m_lastMonitoredFrequency = m_settings - > value ( " DialFreq " ,
2024-10-30 11:47:36 -07:00
QVariant : : fromValue < Frequency > ( Default : : DIAL_FREQUENCY ) ) . value < Frequency > ( ) ;
2024-10-20 13:54:14 -07:00
setFreq ( 0 ) ; // ensure a change is signaled
2024-10-30 11:47:36 -07:00
setFreq ( m_settings - > value ( " Freq " , Default : : FREQUENCY ) . toInt ( ) ) ;
m_ndepth = m_settings - > value ( " NDepth " , Default : : DEPTH ) . toInt ( ) ;
2018-02-08 21:28:33 -05:00
// setup initial value of tx attenuator
m_block_pwr_tooltip = true ;
ui - > outAttenuation - > setValue ( m_settings - > value ( " OutAttenuation " , 0 ) . toInt ( ) ) ;
m_block_pwr_tooltip = false ;
m_pwrBandTxMemory = m_settings - > value ( " pwrBandTxMemory " ) . toHash ( ) ;
m_pwrBandTuneMemory = m_settings - > value ( " pwrBandTuneMemory " ) . toHash ( ) ;
2018-07-06 02:36:40 -04:00
2018-08-23 11:59:20 -04:00
m_sortCache = m_settings - > value ( " SortBy " ) . toMap ( ) ;
2018-09-24 16:46:33 -04:00
m_showColumnsCache = m_settings - > value ( " ShowColumns " ) . toMap ( ) ;
2018-11-27 23:04:11 -05:00
m_hbInterval = m_settings - > value ( " HBInterval " , 0 ) . toInt ( ) ;
m_cqInterval = m_settings - > value ( " CQInterval " , 0 ) . toInt ( ) ;
2018-08-23 11:59:20 -04:00
2018-07-06 02:36:40 -04:00
// TODO: jsherer - any other customizations?
//ui->mainSplitter->setSizes(m_settings->value("MainSplitter", QVariant::fromValue(ui->mainSplitter->sizes())).value<QList<int> >());
//ui->tableWidgetRXAll->restoreGeometry(m_settings->value("PanelLeftGeometry", ui->tableWidgetRXAll->saveGeometry()).toByteArray());
//ui->tableWidgetCalls->restoreGeometry(m_settings->value("PanelRightGeometry", ui->tableWidgetCalls->saveGeometry()).toByteArray());
//ui->extFreeTextMsg->setGeometry( m_settings->value("PanelTopGeometry", ui->extFreeTextMsg->geometry()).toRect());
//ui->extFreeTextMsgEdit->setGeometry( m_settings->value("PanelBottomGeometry", ui->extFreeTextMsgEdit->geometry()).toRect());
//ui->bandHorizontalWidget->setGeometry( m_settings->value("PanelWaterfallGeometry", ui->bandHorizontalWidget->geometry()).toRect());
//qDebug() << m_settings->value("PanelTopGeometry") << ui->extFreeTextMsg;
2018-09-15 17:33:09 -04:00
setTextEditStyle ( ui - > textEditRX , m_config . color_rx_foreground ( ) , m_config . color_rx_background ( ) , m_config . rx_text_font ( ) ) ;
setTextEditStyle ( ui - > extFreeTextMsgEdit , m_config . color_compose_foreground ( ) , m_config . color_compose_background ( ) , m_config . compose_text_font ( ) ) ;
2019-09-25 20:26:41 -04:00
ui - > extFreeTextMsgEdit - > setFont ( m_config . compose_text_font ( ) , m_config . color_compose_foreground ( ) , m_config . color_compose_background ( ) ) ;
2018-02-08 21:28:33 -05:00
m_settings - > endGroup ( ) ;
// use these initialisation settings to tune the audio o/p buffer
// size and audio thread priority
m_settings - > beginGroup ( " Tune " ) ;
m_msAudioOutputBuffered = m_settings - > value ( " Audio/OutputBufferMs " ) . toInt ( ) ;
m_framesAudioInputBuffered = m_settings - > value ( " Audio/InputBufferFrames " , RX_SAMPLE_RATE / 10 ) . toInt ( ) ;
2024-09-10 13:59:43 -07:00
m_audioThreadPriority = static_cast < QThread : : Priority > ( m_settings - > value ( " Audio/ThreadPriority " , QThread : : TimeCriticalPriority ) . toInt ( ) % 8 ) ;
2019-11-20 00:11:30 -05:00
m_notificationAudioThreadPriority = static_cast < QThread : : Priority > ( m_settings - > value ( " Audio/NotificationThreadPriority " , QThread : : LowPriority ) . toInt ( ) % 8 ) ;
m_decoderThreadPriority = static_cast < QThread : : Priority > ( m_settings - > value ( " Audio/DecoderThreadPriority " , QThread : : HighPriority ) . toInt ( ) % 8 ) ;
2020-03-31 15:22:48 -04:00
m_networkThreadPriority = static_cast < QThread : : Priority > ( m_settings - > value ( " Network/NetworkThreadPriority " , QThread : : LowPriority ) . toInt ( ) % 8 ) ;
2018-02-08 21:28:33 -05:00
m_settings - > endGroup ( ) ;
2019-06-12 23:54:02 -04:00
if ( m_config . reset_activity ( ) ) {
// NOOP
} else {
m_settings - > beginGroup ( " CallActivity " ) ;
foreach ( auto call , m_settings - > allKeys ( ) ) {
2019-06-12 12:03:50 -04:00
2019-06-12 23:54:02 -04:00
auto values = m_settings - > value ( call ) . toMap ( ) ;
2019-06-12 12:03:50 -04:00
2019-06-12 23:54:02 -04:00
auto snr = values . value ( " snr " , - 64 ) . toInt ( ) ;
auto grid = values . value ( " grid " , " " ) . toString ( ) ;
2020-04-04 22:45:51 -04:00
auto dial = values . value ( " dial " , 0 ) . toInt ( ) ;
2019-06-12 23:54:02 -04:00
auto freq = values . value ( " freq " , 0 ) . toInt ( ) ;
2019-09-05 14:07:24 -04:00
auto tdrift = values . value ( " tdrift " , 0 ) . toFloat ( ) ;
2019-06-12 12:03:50 -04:00
# if CACHE_CALL_DATETIME_AS_STRINGS
2019-06-12 23:54:02 -04:00
auto ackTimestampStr = values . value ( " ackTimestamp " , " " ) . toString ( ) ;
auto ackTimestamp = QDateTime : : fromString ( ackTimestampStr , " yyyy-MM-dd hh:mm:ss " ) ;
ackTimestamp . setUtcOffset ( 0 ) ;
2019-06-12 12:03:50 -04:00
2019-06-12 23:54:02 -04:00
auto utcTimestampStr = values . value ( " utcTimestamp " , " " ) . toString ( ) ;
auto utcTimestamp = QDateTime : : fromString ( utcTimestampStr , " yyyy-MM-dd hh:mm:ss " ) ;
utcTimestamp . setUtcOffset ( 0 ) ;
2019-06-12 12:03:50 -04:00
# else
2019-06-12 23:54:02 -04:00
auto ackTimestamp = values . value ( " ackTimestamp " ) . toDateTime ( ) ;
auto utcTimestamp = values . value ( " utcTimestamp " ) . toDateTime ( ) ;
2019-06-12 12:03:50 -04:00
# endif
2019-11-09 09:48:09 -05:00
auto submode = values . value ( " submode " , Varicode : : JS8CallNormal ) . toInt ( ) ;
2019-06-12 12:03:50 -04:00
2019-06-12 23:54:02 -04:00
CallDetail cd = { } ;
cd . call = call ;
cd . snr = snr ;
cd . grid = grid ;
2020-04-04 22:45:51 -04:00
cd . dial = dial ;
cd . offset = freq ;
2019-09-05 14:07:24 -04:00
cd . tdrift = tdrift ;
2019-06-12 23:54:02 -04:00
cd . ackTimestamp = ackTimestamp ;
cd . utcTimestamp = utcTimestamp ;
2019-11-09 09:48:09 -05:00
cd . submode = submode ;
2019-06-12 12:03:50 -04:00
2019-06-12 23:54:02 -04:00
logCallActivity ( cd , false ) ;
}
m_settings - > endGroup ( ) ;
2019-06-12 12:03:50 -04:00
}
2019-06-12 23:54:02 -04:00
m_settings_read = true ;
2018-02-08 21:28:33 -05:00
}
void MainWindow : : set_application_font ( QFont const & font )
{
qApp - > setFont ( font ) ;
// set font in the application style sheet as well in case it has
// been modified in the style sheet which has priority
qApp - > setStyleSheet ( qApp - > styleSheet ( ) + " * { " + font_as_stylesheet ( font ) + ' } ' ) ;
for ( auto & widget : qApp - > topLevelWidgets ( ) )
{
widget - > updateGeometry ( ) ;
}
}
2019-11-01 15:07:24 -04:00
2018-02-08 21:28:33 -05:00
//-------------------------------------------------------------- dataSink()
void MainWindow : : dataSink ( qint64 frames )
{
2024-12-23 22:55:14 -08:00
constexpr int NMAX = NTMAX * 12000 ;
constexpr int nfft3 = 16384 ;
constexpr std : : array nch = { 1 , 2 , 4 , 9 , 18 , 36 , 72 } ;
2019-11-05 21:27:41 -05:00
// symspec global vars
static int ja = 0 ;
2019-11-05 15:43:34 -05:00
static int k0 = 999999999 ;
2024-11-14 09:52:37 -08:00
static WF : : SPlot ssum = { } ;
static WF : : SPlot s = { } ;
2018-02-08 21:28:33 -05:00
2019-10-22 01:16:02 -04:00
int k ( frames ) ;
2019-11-05 15:43:34 -05:00
if ( k0 = = 999999999 ) {
2024-10-19 15:10:31 -07:00
m_ihsym = int ( ( float ) frames / ( float ) NSPS ) * 2 ;
2019-11-05 21:27:41 -05:00
ja = k ;
2019-11-05 15:43:34 -05:00
k0 = k ;
}
2020-05-13 22:22:51 -04:00
//qDebug() << "k" << k << "k0" << k0 << "delta" << k-k0;
2020-05-13 21:15:21 -04:00
2019-10-22 01:16:02 -04:00
// Get power, spectrum, and ihsym
2024-12-23 22:55:14 -08:00
int const nsmo = m_wideGraph - > smoothYellow ( ) - 1 ;
2019-10-28 16:06:06 -04:00
2019-11-06 14:11:39 -05:00
// make sure the ssum global is reset every period cycle
static int lastCycle = - 1 ;
2024-10-08 07:22:56 -07:00
int const cycle = JS8 : : Submode : : computeCycleForDecode ( m_nSubMode , k ) ;
2019-11-06 14:11:39 -05:00
if ( cycle ! = lastCycle ) {
2019-11-12 20:12:16 -05:00
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " period loop, resetting ssum " ;
2024-11-14 09:52:37 -08:00
ssum . fill ( 0.0f ) ;
2019-11-06 14:11:39 -05:00
}
lastCycle = cycle ;
// cap ihsym based on the period max
2024-10-19 15:10:31 -07:00
m_ihsym = m_ihsym % ( m_TRperiod * RX_SAMPLE_RATE / NSPS * 2 ) ;
2024-12-23 22:55:14 -08:00
int const jstep = NSPS / 2 ;
if ( k > = 2048 & &
k < = NMAX )
{
if ( k < k0 )
{
// Start a new data block
ja = 0 ;
2024-12-31 02:21:00 -08:00
ssum . fill ( 0.0f ) ;
2024-12-23 22:55:14 -08:00
m_ihsym = 0 ;
if ( ! dec_data . params . ndiskdat )
{
std : : fill ( std : : begin ( dec_data . d2 ) + k ,
std : : end ( dec_data . d2 ) , 0 ) ;
}
}
float gain = pow ( 10.0f , 0.1f * m_inGain ) ;
float sq = 0.0f ;
float pxmax = 0.0f ;
for ( int i = k0 ; i < k ; + + i )
{
float x1 = dec_data . d2 [ i ] ;
pxmax = std : : max ( pxmax , fabs ( x1 ) ) ;
sq + = x1 * x1 ;
}
m_px = sq > 0.0f ? 10.0f * log10 ( sq / ( k - k0 ) ) : 0.0f ;
m_pxmax = pxmax > 0.0f ? 20.0f * log10 ( pxmax ) : 0.0f ;
k0 = k ;
ja + = jstep ;
2024-12-26 13:43:47 -08:00
// Perform real to complex FFT; note that the Fortran code performed
// this operation in the four2a subroutine, which contains a cache
// of plans. However, since the FFTW3 library itself caches, this
// resulted in double caching, so as an experiment we're allowing
// the library to handle caching.
//
// Note that the FFTW library requires all calls but for the plan
// execution, i.e., fftwf_execute(), to be serialized. The library
// doesn't require prescriptive serialization, anything will do so
2025-01-22 17:42:38 -08:00
// long as the result is one thread at a time.
2024-12-26 13:43:47 -08:00
//
// Providing room for an extra complex value, i.e., a pair of floats,
// real and imaginary parts, allows us to use the same buffer for the
// FFT input and output.
//
// While the memory for the FFT can come from anywhere, if we ask the
// library for it, it'll guarantee that it's aligned for use of SIMD
// instructions, which will in turn allow it to use them.
fftwf_complex * fftw_complex ;
float * fftw_real ;
fftwf_plan fftw_plan ;
{
std : : lock_guard < std : : mutex > lock ( fftw_mutex ) ;
2025-01-16 05:52:42 -08:00
fftw_complex = fftwf_alloc_complex ( nfft3 / 2 + 1 ) ;
2024-12-24 07:07:00 -08:00
2024-12-26 13:43:47 -08:00
if ( ! fftw_complex )
{
throw std : : runtime_error ( " Failed to allocate FFT data " ) ;
}
fftw_real = reinterpret_cast < float * > ( fftw_complex ) ;
fftw_plan = fftwf_plan_dft_r2c_1d ( nfft3 ,
fftw_real ,
fftw_complex ,
FFTW_ESTIMATE ) ;
if ( ! fftw_plan )
{
fftwf_free ( fftw_complex ) ;
throw std : : runtime_error ( " Failed to create FFT plan " ) ;
}
}
2019-11-06 14:11:39 -05:00
2024-12-26 13:43:47 -08:00
// Copy data and apply the window, then execute the FFT.
2024-12-24 07:07:00 -08:00
2024-12-23 22:55:14 -08:00
for ( int i = 0 ; i < nfft3 ; + + i )
{
if ( int j = ja + i - nfft3 ;
j > = 0 & &
2024-12-26 13:43:47 -08:00
j < NMAX ) fftw_real [ i ] = 0.1f * dec_data . d2 [ j ] ;
2024-12-23 22:55:14 -08:00
}
+ + m_ihsym ;
2024-12-26 13:43:47 -08:00
fftwf_execute ( fftw_plan ) ;
2024-12-26 09:56:02 -08:00
2024-12-26 13:43:47 -08:00
// Process the resulting spectrum.
2024-12-23 22:55:14 -08:00
m_df3 = 12000.0f / nfft3 ;
auto const iz = std : : min ( NSMAX , static_cast < int > ( 5000.0f / m_df3 ) ) ;
2024-12-26 13:43:47 -08:00
auto const cx = reinterpret_cast < std : : complex < float > * > ( fftw_complex ) ;
2024-12-23 22:55:14 -08:00
auto const fac = std : : pow ( 1.0f / nfft3 , 2.0f ) ;
for ( int i = 0 ; i < iz ; + + i )
{
auto const sx = fac * std : : norm ( cx [ i ] ) ;
2024-12-24 06:28:28 -08:00
ssum [ i ] + = sx ;
s [ i ] = 1000.0f * gain * sx ;
2024-12-23 22:55:14 -08:00
}
2024-12-26 13:43:47 -08:00
// We're now done with the plan and the FFT storage. Even though
// the plan is being 'destroyed' here, the library will cache it
// at its discretion. As above, the calls must be serialized.
fftw_mutex . lock ( ) ;
fftwf_destroy_plan ( fftw_plan ) ;
fftwf_free ( fftw_complex ) ;
fftw_mutex . unlock ( ) ;
// Update average spectra.
2024-12-24 06:43:39 -08:00
for ( int i = 0 ; i < iz ; + + i ) specData . savg [ i ] = ssum [ i ] / m_ihsym ;
2024-12-23 22:55:14 -08:00
if ( m_ihsym % 10 = = 0 )
{
auto const mode4 = nch [ nsmo ] ;
auto const nsmo = 4 * std : : min ( 10 * mode4 , 150 ) ;
2024-12-24 06:43:39 -08:00
flat1 ( specData . savg , iz , nsmo , specData . slin ) ;
2024-12-23 22:55:14 -08:00
if ( mode4 > = 2 )
{
std : : array < float , NSMAX > tmp ;
2024-12-24 06:43:39 -08:00
smo ( specData . slin , tmp . data ( ) , iz , mode4 ) ;
smo ( tmp . data ( ) , specData . slin , iz , mode4 ) ;
2024-12-23 22:55:14 -08:00
}
2024-12-24 06:43:39 -08:00
std : : fill ( std : : begin ( specData . slin ) , std : : begin ( specData . slin ) + 250 , 0.0f ) ;
2024-12-23 22:55:14 -08:00
auto const ia = static_cast < int > ( 500.0 / m_df3 ) ;
auto const ib = static_cast < int > ( 2700.0 / m_df3 ) ;
2024-12-24 06:43:39 -08:00
auto const smin = * std : : min_element ( std : : begin ( specData . slin ) + ia , std : : begin ( specData . slin ) + ib ) ;
auto const smax = * std : : max_element ( std : : begin ( specData . slin ) , std : : begin ( specData . slin ) + iz ) ;
2024-12-23 22:55:14 -08:00
auto const scale = ( smax > smin ) ? 50.0f / ( smax - smin ) : 0.0f ;
2024-12-24 06:43:39 -08:00
for ( auto & val : specData . slin ) val = std : : max ( 0.0f , scale * ( val - smin ) ) ;
2024-12-23 22:55:14 -08:00
}
}
else if ( k < 2048 ) m_ihsym = 0 ;
m_npts8 = k / 8 ;
2019-11-06 14:11:39 -05:00
// make sure ja is equal to k so if we jump ahead in the buffer, everything resolves correctly
2019-11-05 21:27:41 -05:00
ja = k ;
2019-10-28 16:06:06 -04:00
if ( m_ihsym < = 0 ) return ;
2019-10-25 14:15:45 -04:00
2019-10-22 01:16:02 -04:00
if ( ui ) ui - > signal_meter_widget - > setValue ( m_px , m_pxmax ) ; // Update thermometer
2019-11-12 09:34:54 -05:00
2024-09-14 18:34:38 -07:00
if ( m_monitoring ) {
2024-11-14 09:52:37 -08:00
m_wideGraph - > dataSink ( s , m_df3 ) ;
2018-02-08 21:28:33 -05:00
}
2019-10-24 10:52:37 -04:00
2019-11-03 15:29:59 -05:00
m_dateTime = DriftingDateTime : : currentDateTimeUtc ( ) . toString ( " yyyy-MMM-dd hh:mm " ) ;
2019-10-22 01:16:02 -04:00
2019-11-17 01:21:07 -05:00
decode ( k ) ;
2018-02-08 21:28:33 -05:00
}
void MainWindow : : showSoundInError ( const QString & errorMsg )
{
MessageBox : : critical_message ( this , tr ( " Error in Sound Input " ) , errorMsg ) ;
}
void MainWindow : : showSoundOutError ( const QString & errorMsg )
{
MessageBox : : critical_message ( this , tr ( " Error in Sound Output " ) , errorMsg ) ;
}
void MainWindow : : showStatusMessage ( const QString & statusMsg )
2018-11-05 16:00:12 -05:00
{
2024-09-11 07:17:39 -07:00
statusBar ( ) - > showMessage ( statusMsg , 5000 ) ;
2018-11-05 16:00:12 -05:00
}
2018-02-08 21:28:33 -05:00
2019-09-05 14:07:24 -04:00
void MainWindow : : on_menuModeJS8_aboutToShow ( ) {
bool canChangeMode = ! m_transmitting & & m_txFrameCount = = 0 & & m_txFrameQueue . isEmpty ( ) ;
ui - > actionModeJS8Normal - > setEnabled ( canChangeMode ) ;
ui - > actionModeJS8Fast - > setEnabled ( canChangeMode ) ;
ui - > actionModeJS8Turbo - > setEnabled ( canChangeMode ) ;
2019-12-09 14:00:23 -05:00
ui - > actionModeJS8Slow - > setEnabled ( canChangeMode ) ;
ui - > actionModeJS8Ultra - > setEnabled ( canChangeMode ) ;
2020-03-28 15:26:34 -04:00
// dynamically replace the autoreply menu item text
auto autoreplyText = ui - > actionModeAutoreply - > text ( ) ;
if ( m_config . autoreply_confirmation ( ) & & ! autoreplyText . contains ( " with Confirmation " ) ) {
autoreplyText . replace ( " Autoreply " , " Autoreply with Confirmation " ) ;
2020-03-28 15:30:02 -04:00
autoreplyText . replace ( " &AUTO " , " &AUTO+CONF " ) ;
2020-03-28 15:26:34 -04:00
ui - > actionModeAutoreply - > setText ( autoreplyText ) ;
}
else if ( ! m_config . autoreply_confirmation ( ) & & autoreplyText . contains ( " with Confirmation " ) ) {
autoreplyText . replace ( " with Confirmation " , " " ) ;
2020-03-28 15:30:02 -04:00
autoreplyText . replace ( " +CONF " , " " ) ;
2020-03-28 15:26:34 -04:00
ui - > actionModeAutoreply - > setText ( autoreplyText ) ;
}
2019-09-05 14:07:24 -04:00
}
2024-09-11 10:37:24 -07:00
void
MainWindow : : on_menuControl_aboutToShow ( )
{
auto freqMenu = new QMenu ( this - > menuBar ( ) ) ;
2019-01-21 17:45:52 -05:00
buildFrequencyMenu ( freqMenu ) ;
ui - > actionSetFrequency - > setMenu ( freqMenu ) ;
2024-09-11 10:37:24 -07:00
auto heartbeatMenu = new QMenu ( this - > menuBar ( ) ) ;
2018-11-29 22:43:15 -05:00
buildHeartbeatMenu ( heartbeatMenu ) ;
ui - > actionHeartbeat - > setMenu ( heartbeatMenu ) ;
2024-09-11 10:37:24 -07:00
auto cqMenu = new QMenu ( this - > menuBar ( ) ) ;
2018-11-29 22:43:15 -05:00
buildCQMenu ( cqMenu ) ;
ui - > actionCQ - > setMenu ( cqMenu ) ;
2020-04-15 15:33:07 -04:00
ui - > actionEnable_Monitor_RX - > setChecked ( ui - > monitorButton - > isChecked ( ) ) ;
ui - > actionEnable_Transmitter_TX - > setChecked ( ui - > monitorTxButton - > isChecked ( ) ) ;
ui - > actionEnable_Reporting_SPOT - > setChecked ( ui - > spotButton - > isChecked ( ) ) ;
2020-04-26 16:50:13 -04:00
ui - > actionEnable_Tuning_Tone_TUNE - > setChecked ( ui - > tuneButton - > isChecked ( ) ) ;
2018-11-15 22:06:52 -05:00
}
2019-03-31 14:36:40 -04:00
void MainWindow : : on_actionCheck_for_Updates_triggered ( ) {
checkVersion ( true ) ;
}
2020-04-15 15:33:07 -04:00
void MainWindow : : on_actionEnable_Monitor_RX_toggled ( bool checked ) {
ui - > monitorButton - > setChecked ( checked ) ;
}
void MainWindow : : on_actionEnable_Transmitter_TX_toggled ( bool checked ) {
ui - > monitorTxButton - > setChecked ( checked ) ;
}
void MainWindow : : on_actionEnable_Reporting_SPOT_toggled ( bool checked ) {
2018-11-15 22:06:52 -05:00
ui - > spotButton - > setChecked ( checked ) ;
}
2020-04-26 16:50:13 -04:00
void MainWindow : : on_actionEnable_Tuning_Tone_TUNE_toggled ( bool checked ) {
ui - > tuneButton - > setChecked ( checked ) ;
on_tuneButton_clicked ( checked ) ;
}
2018-08-10 09:35:40 -04:00
void MainWindow : : on_menuWindow_aboutToShow ( ) {
2018-11-22 10:37:38 -05:00
ui - > actionShow_Fullscreen - > setChecked ( ( windowState ( ) & Qt : : WindowFullScreen ) = = Qt : : WindowFullScreen ) ;
2019-01-09 11:14:46 -05:00
ui - > actionShow_Statusbar - > setChecked ( ui - > statusBar & & ui - > statusBar - > isVisible ( ) ) ;
2018-08-10 09:35:40 -04:00
auto hsizes = ui - > textHorizontalSplitter - > sizes ( ) ;
ui - > actionShow_Band_Activity - > setChecked ( hsizes . at ( 0 ) > 0 ) ;
ui - > actionShow_Call_Activity - > setChecked ( hsizes . at ( 2 ) > 0 ) ;
auto vsizes = ui - > mainSplitter - > sizes ( ) ;
2018-11-12 16:34:31 -05:00
ui - > actionShow_Frequency_Clock - > setChecked ( vsizes . first ( ) > 0 ) ;
2018-08-10 09:35:40 -04:00
ui - > actionShow_Waterfall - > setChecked ( vsizes . last ( ) > 0 ) ;
2020-05-16 15:01:03 -04:00
ui - > actionShow_Waterfall_Controls - > setChecked ( ui - > actionShow_Waterfall - > isChecked ( ) & & m_wideGraph - > controlsVisible ( ) ) ;
ui - > actionShow_Waterfall_Time_Drift_Controls - > setChecked ( ui - > actionShow_Waterfall - > isChecked ( ) & & m_wideGraph - > timeControlsVisible ( ) ) ;
2018-08-23 12:24:47 -04:00
2018-11-05 16:00:12 -05:00
QMenu * sortBandMenu = new QMenu ( this - > menuBar ( ) ) ; //ui->menuWindow);
2018-08-23 12:24:47 -04:00
buildBandActivitySortByMenu ( sortBandMenu ) ;
ui - > actionSort_Band_Activity - > setMenu ( sortBandMenu ) ;
2018-09-24 16:46:33 -04:00
ui - > actionSort_Band_Activity - > setEnabled ( ui - > actionShow_Band_Activity - > isChecked ( ) ) ;
2018-08-23 12:24:47 -04:00
2018-11-05 16:00:12 -05:00
QMenu * sortCallMenu = new QMenu ( this - > menuBar ( ) ) ; //ui->menuWindow);
2018-08-23 12:24:47 -04:00
buildCallActivitySortByMenu ( sortCallMenu ) ;
ui - > actionSort_Call_Activity - > setMenu ( sortCallMenu ) ;
2018-09-24 16:46:33 -04:00
ui - > actionSort_Call_Activity - > setEnabled ( ui - > actionShow_Call_Activity - > isChecked ( ) ) ;
2018-11-05 16:00:12 -05:00
QMenu * showBandMenu = new QMenu ( this - > menuBar ( ) ) ; //ui->menuWindow);
2018-09-24 16:46:33 -04:00
buildShowColumnsMenu ( showBandMenu , " band " ) ;
ui - > actionShow_Band_Activity_Columns - > setMenu ( showBandMenu ) ;
ui - > actionShow_Band_Activity_Columns - > setEnabled ( ui - > actionShow_Band_Activity - > isChecked ( ) ) ;
2018-11-05 16:00:12 -05:00
QMenu * showCallMenu = new QMenu ( this - > menuBar ( ) ) ; //ui->menuWindow);
2018-09-24 16:46:33 -04:00
buildShowColumnsMenu ( showCallMenu , " call " ) ;
ui - > actionShow_Call_Activity_Columns - > setMenu ( showCallMenu ) ;
ui - > actionShow_Call_Activity_Columns - > setEnabled ( ui - > actionShow_Call_Activity - > isChecked ( ) ) ;
2018-12-10 09:44:03 -05:00
ui - > actionShow_Band_Heartbeats_and_ACKs - > setEnabled ( ui - > actionShow_Band_Activity - > isChecked ( ) ) ;
2018-08-10 09:35:40 -04:00
}
2019-09-19 12:08:33 -04:00
void MainWindow : : on_actionFocus_Message_Receive_Area_triggered ( ) {
ui - > textEditRX - > setFocus ( ) ;
}
2019-05-27 09:18:06 -04:00
void MainWindow : : on_actionFocus_Message_Reply_Area_triggered ( ) {
ui - > extFreeTextMsgEdit - > setFocus ( ) ;
}
void MainWindow : : on_actionFocus_Band_Activity_Table_triggered ( ) {
ui - > tableWidgetRXAll - > setFocus ( ) ;
}
void MainWindow : : on_actionFocus_Call_Activity_Table_triggered ( ) {
ui - > tableWidgetCalls - > setFocus ( ) ;
}
2020-05-16 14:41:22 -04:00
void MainWindow : : on_actionClear_All_Activity_triggered ( ) {
clearActivity ( ) ;
}
void MainWindow : : on_actionClear_Band_Activity_triggered ( ) {
clearBandActivity ( ) ;
}
void MainWindow : : on_actionClear_RX_Activity_triggered ( ) {
clearRXActivity ( ) ;
}
void MainWindow : : on_actionClear_Call_Activity_triggered ( ) {
clearCallActivity ( ) ;
}
2019-01-23 20:31:26 -05:00
void MainWindow : : on_actionSetOffset_triggered ( ) {
2024-10-20 13:54:14 -07:00
bool ok = false ;
auto const currentFreq = freq ( ) ;
2019-02-08 20:45:50 -05:00
QString newFreq = QInputDialog : : getText ( this , tr ( " Set Frequency Offset " ) ,
2019-01-23 20:31:26 -05:00
tr ( " Offset in Hz: " ) , QLineEdit : : Normal ,
QString ( " %1 " ) . arg ( currentFreq ) , & ok ) . toUpper ( ) . trimmed ( ) ;
int offset = newFreq . toInt ( & ok ) ;
if ( ! ok ) {
return ;
}
setFreqOffsetForRestore ( offset , false ) ;
}
2018-11-22 10:37:38 -05:00
void MainWindow : : on_actionShow_Fullscreen_triggered ( bool checked ) {
auto state = windowState ( ) ;
if ( checked ) {
state | = Qt : : WindowFullScreen ;
} else {
state & = ~ Qt : : WindowFullScreen ;
}
setWindowState ( state ) ;
}
2019-01-09 11:14:46 -05:00
void MainWindow : : on_actionShow_Statusbar_triggered ( bool checked ) {
if ( ! ui - > statusBar ) {
return ;
}
ui - > statusBar - > setVisible ( checked ) ;
}
2018-11-12 16:34:31 -05:00
void MainWindow : : on_actionShow_Frequency_Clock_triggered ( bool checked ) {
auto vsizes = ui - > mainSplitter - > sizes ( ) ;
vsizes [ 0 ] = checked ? ui - > logHorizontalWidget - > minimumHeight ( ) : 0 ;
ui - > logHorizontalWidget - > setVisible ( checked ) ;
ui - > mainSplitter - > setSizes ( vsizes ) ;
}
2018-08-10 09:35:40 -04:00
void MainWindow : : on_actionShow_Band_Activity_triggered ( bool checked ) {
auto hsizes = ui - > textHorizontalSplitter - > sizes ( ) ;
2018-12-13 09:48:43 -05:00
if ( m_bandActivityWidth = = 0 ) {
m_bandActivityWidth = ui - > textHorizontalSplitter - > width ( ) / 4 ;
}
if ( m_callActivityWidth = = 0 ) {
m_callActivityWidth = ui - > textHorizontalSplitter - > width ( ) / 4 ;
}
if ( m_textActivityWidth = = 0 ) {
m_textActivityWidth = ui - > textHorizontalSplitter - > width ( ) / 2 ;
}
if ( checked ) {
hsizes [ 0 ] = m_bandActivityWidth ;
hsizes [ 1 ] = m_textActivityWidth ;
if ( hsizes [ 2 ] ) hsizes [ 2 ] = m_callActivityWidth ;
} else {
if ( hsizes [ 0 ] ) m_bandActivityWidth = hsizes [ 0 ] ;
if ( hsizes [ 1 ] ) m_textActivityWidth = hsizes [ 1 ] ;
if ( hsizes [ 2 ] ) m_callActivityWidth = hsizes [ 2 ] ;
hsizes [ 0 ] = 0 ;
}
2018-08-10 09:35:40 -04:00
ui - > textHorizontalSplitter - > setSizes ( hsizes ) ;
2018-09-18 18:07:49 -04:00
ui - > tableWidgetRXAll - > setVisible ( checked ) ;
2018-10-06 04:12:16 -04:00
m_bandActivityWasVisible = checked ;
2018-12-13 09:48:43 -05:00
2018-08-10 09:35:40 -04:00
}
2024-08-31 10:29:30 -07:00
void MainWindow : : on_actionShow_Band_Heartbeats_and_ACKs_triggered ( bool ) {
2018-12-10 09:44:03 -05:00
displayBandActivity ( ) ;
}
2018-08-10 09:35:40 -04:00
void MainWindow : : on_actionShow_Call_Activity_triggered ( bool checked ) {
auto hsizes = ui - > textHorizontalSplitter - > sizes ( ) ;
2018-12-13 09:48:43 -05:00
if ( m_bandActivityWidth = = 0 ) {
m_bandActivityWidth = ui - > textHorizontalSplitter - > width ( ) / 4 ;
}
if ( m_callActivityWidth = = 0 ) {
m_callActivityWidth = ui - > textHorizontalSplitter - > width ( ) / 4 ;
}
if ( m_textActivityWidth = = 0 ) {
m_textActivityWidth = ui - > textHorizontalSplitter - > width ( ) / 2 ;
}
if ( checked ) {
if ( hsizes [ 0 ] ) hsizes [ 0 ] = m_bandActivityWidth ;
hsizes [ 1 ] = m_textActivityWidth ;
hsizes [ 2 ] = m_callActivityWidth ;
} else {
if ( hsizes [ 0 ] ) m_bandActivityWidth = hsizes [ 0 ] ;
if ( hsizes [ 1 ] ) m_textActivityWidth = hsizes [ 1 ] ;
if ( hsizes [ 2 ] ) m_callActivityWidth = hsizes [ 2 ] ;
hsizes [ 2 ] = 0 ;
}
2018-08-10 09:35:40 -04:00
ui - > textHorizontalSplitter - > setSizes ( hsizes ) ;
2018-09-18 18:07:49 -04:00
ui - > tableWidgetCalls - > setVisible ( checked ) ;
2018-08-10 09:35:40 -04:00
}
void MainWindow : : on_actionShow_Waterfall_triggered ( bool checked ) {
auto vsizes = ui - > mainSplitter - > sizes ( ) ;
2018-12-13 09:48:43 -05:00
if ( m_waterfallHeight = = 0 ) {
m_waterfallHeight = ui - > mainSplitter - > height ( ) / 4 ;
}
if ( checked ) {
vsizes [ vsizes . length ( ) - 1 ] = m_waterfallHeight ;
} else {
m_waterfallHeight = vsizes [ vsizes . length ( ) - 1 ] ;
vsizes [ 1 ] + = m_waterfallHeight ;
vsizes [ vsizes . length ( ) - 1 ] = 0 ;
}
2018-08-10 09:35:40 -04:00
ui - > mainSplitter - > setSizes ( vsizes ) ;
2018-09-18 18:07:49 -04:00
ui - > bandHorizontalWidget - > setVisible ( checked ) ;
2018-08-10 09:35:40 -04:00
}
2018-09-01 18:26:10 -04:00
void MainWindow : : on_actionShow_Waterfall_Controls_triggered ( bool checked ) {
m_wideGraph - > setControlsVisible ( checked ) ;
2020-05-16 15:01:03 -04:00
if ( checked & & ! ui - > bandHorizontalWidget - > isVisible ( ) ) {
on_actionShow_Waterfall_triggered ( checked ) ;
}
}
void MainWindow : : on_actionShow_Waterfall_Time_Drift_Controls_triggered ( bool checked ) {
m_wideGraph - > setTimeControlsVisible ( checked ) ;
if ( checked & & ! ui - > bandHorizontalWidget - > isVisible ( ) ) {
on_actionShow_Waterfall_triggered ( checked ) ;
}
2018-09-01 18:26:10 -04:00
}
2018-08-10 09:35:40 -04:00
void MainWindow : : on_actionReset_Window_Sizes_triggered ( ) {
2024-08-31 10:29:30 -07:00
// auto size = this->centralWidget()->size();
2018-08-10 09:35:40 -04:00
ui - > mainSplitter - > setSizes ( {
ui - > logHorizontalWidget - > minimumHeight ( ) ,
ui - > mainSplitter - > height ( ) / 2 ,
ui - > macroHorizonalWidget - > minimumHeight ( ) ,
ui - > mainSplitter - > height ( ) / 4
} ) ;
ui - > textHorizontalSplitter - > setSizes ( {
ui - > textHorizontalSplitter - > width ( ) / 4 ,
ui - > textHorizontalSplitter - > width ( ) / 2 ,
ui - > textHorizontalSplitter - > width ( ) / 4
} ) ;
ui - > textVerticalSplitter - > setSizes ( {
ui - > textVerticalSplitter - > height ( ) / 2 ,
ui - > textVerticalSplitter - > height ( ) / 2
} ) ;
}
2018-09-09 15:47:26 -04:00
void MainWindow : : on_actionSettings_triggered ( ) {
openSettings ( ) ;
}
2018-02-08 21:28:33 -05:00
2018-09-09 15:47:26 -04:00
void MainWindow : : openSettings ( int tab ) {
m_config . select_tab ( tab ) ;
2018-02-08 21:28:33 -05:00
2018-09-09 15:47:26 -04:00
// things that might change that we need know about
auto callsign = m_config . my_callsign ( ) ;
auto my_grid = m_config . my_grid ( ) ;
2024-09-04 14:53:21 -07:00
auto spot_on = m_config . spot_to_reporting_networks ( ) ;
2018-09-09 15:47:26 -04:00
if ( QDialog : : Accepted = = m_config . exec ( ) ) {
if ( m_config . my_callsign ( ) ! = callsign ) {
m_baseCall = Radio : : base_callsign ( m_config . my_callsign ( ) ) ;
}
if ( m_config . my_callsign ( ) ! = callsign | | m_config . my_grid ( ) ! = my_grid ) {
statusUpdate ( ) ;
}
2018-02-08 21:28:33 -05:00
2018-09-09 15:47:26 -04:00
enable_DXCC_entity ( m_config . DXCC ( ) ) ; // sets text window proportions and (re)inits the logbook
2018-02-08 21:28:33 -05:00
2020-04-03 22:49:28 -04:00
prepareApi ( ) ;
2018-09-09 15:47:26 -04:00
prepareSpotting ( ) ;
2018-07-06 16:54:26 -04:00
2024-09-04 14:53:21 -07:00
// this will close the connection to PSKReporter if it has been
// disabled
if ( spot_on & & ! m_config . spot_to_reporting_networks ( ) )
{
2024-11-09 10:25:45 -08:00
Q_EMIT pskReporterSendReport ( true ) ;
2024-09-04 14:53:21 -07:00
}
2024-09-11 05:58:28 -07:00
if ( m_config . restart_audio_input ( ) & & ! m_config . audio_input_device ( ) . isNull ( ) ) {
2024-11-29 11:45:00 -08:00
Q_EMIT startAudioInputStream ( m_config . audio_input_device ( ) ,
m_framesAudioInputBuffered ,
m_detector ,
m_config . audio_input_channel ( ) ) ;
2018-09-09 15:47:26 -04:00
}
2024-09-11 05:58:28 -07:00
if ( m_config . restart_audio_output ( ) & & ! m_config . audio_output_device ( ) . isNull ( ) ) {
2018-09-09 15:47:26 -04:00
Q_EMIT initializeAudioOutputStream ( m_config . audio_output_device ( ) ,
AudioDevice : : Mono = = m_config . audio_output_channel ( ) ? 1 : 2 ,
m_msAudioOutputBuffered ) ;
}
2024-09-11 05:58:28 -07:00
if ( m_config . restart_notification_audio_output ( ) & & ! m_config . notification_audio_output_device ( ) . isNull ( ) ) {
2019-10-15 13:52:30 -04:00
Q_EMIT initializeNotificationAudioOutputStream ( m_config . notification_audio_output_device ( ) ,
m_msAudioOutputBuffered ) ;
2019-10-08 14:33:38 -04:00
}
2018-09-09 15:47:26 -04:00
displayDialFrequency ( ) ;
displayActivity ( true ) ;
2024-10-04 19:25:42 -07:00
setup_status_bar ( ) ;
2024-11-08 14:06:18 -08:00
setupJS8 ( ) ;
2018-09-09 15:47:26 -04:00
m_config . transceiver_online ( ) ;
2019-10-23 10:23:05 -04:00
2024-10-20 13:54:14 -07:00
setXIT ( freq ( ) ) ;
2019-10-23 10:23:05 -04:00
2018-09-09 15:47:26 -04:00
m_opCall = m_config . opCall ( ) ;
2018-02-08 21:28:33 -05:00
}
}
2020-04-03 22:49:28 -04:00
void MainWindow : : prepareApi ( ) {
2020-04-04 14:58:30 -04:00
// the udp api is prepared by default (always listening)
// so, we just need to prepare the tcp api
bool enabled = m_config . tcpEnabled ( ) ;
2020-04-03 22:49:28 -04:00
if ( enabled ) {
2020-04-04 14:58:30 -04:00
emit apiSetMaxConnections ( m_config . tcp_max_connections ( ) ) ;
emit apiSetServer ( m_config . tcp_server_name ( ) , m_config . tcp_server_port ( ) ) ;
2020-04-03 22:49:28 -04:00
emit apiStartServer ( ) ;
} else {
emit apiStopServer ( ) ;
}
}
2018-08-27 21:19:38 -04:00
void MainWindow : : prepareSpotting ( ) {
if ( m_config . spot_to_reporting_networks ( ) ) {
2019-03-24 21:01:03 -04:00
spotSetLocal ( ) ;
2018-08-27 21:19:38 -04:00
pskSetLocal ( ) ;
2020-03-30 23:07:40 -04:00
aprsSetLocal ( ) ;
2020-04-11 11:46:12 -04:00
emit aprsClientSetSkipPercent ( 0.25 ) ;
2020-03-31 15:22:48 -04:00
emit aprsClientSetServer ( m_config . aprs_server_name ( ) , m_config . aprs_server_port ( ) ) ;
emit aprsClientSetPaused ( false ) ;
2018-07-09 16:31:37 -04:00
ui - > spotButton - > setChecked ( true ) ;
} else {
2020-03-31 15:22:48 -04:00
emit aprsClientSetPaused ( true ) ;
2018-07-09 16:31:37 -04:00
ui - > spotButton - > setChecked ( false ) ;
}
}
void MainWindow : : on_spotButton_clicked ( bool checked ) {
// 1. save setting
2018-08-27 21:19:38 -04:00
m_config . set_spot_to_reporting_networks ( checked ) ;
2018-07-09 16:31:37 -04:00
// 2. prepare
2020-04-03 22:49:28 -04:00
prepareApi ( ) ;
2018-08-27 21:19:38 -04:00
prepareSpotting ( ) ;
2018-07-09 16:31:37 -04:00
}
2018-02-08 21:28:33 -05:00
void MainWindow : : on_monitorButton_clicked ( bool checked )
{
if ( ! m_transmitting ) {
auto prior = m_monitoring ;
monitor ( checked ) ;
if ( checked & & ! prior ) {
if ( m_config . monitor_last_used ( ) ) {
// put rig back where it was when last in control
setRig ( m_lastMonitoredFrequency ) ;
2024-10-20 13:54:14 -07:00
setXIT ( freq ( ) ) ;
2018-02-08 21:28:33 -05:00
}
2024-10-20 13:54:14 -07:00
setFreq ( freq ( ) ) ; // ensure FreqCal triggers
2018-02-08 21:28:33 -05:00
}
//Get Configuration in/out of strict split and mode checking
Q_EMIT m_config . sync_transceiver ( true , checked ) ;
} else {
ui - > monitorButton - > setChecked ( false ) ; // disallow
}
}
void MainWindow : : monitor ( bool state )
{
ui - > monitorButton - > setChecked ( state ) ;
2020-04-27 16:35:37 -04:00
// make sure widegraph is running if we are monitoring, otherwise pause it.
m_wideGraph - > setPaused ( ! state ) ;
2018-02-08 21:28:33 -05:00
if ( state ) {
if ( ! m_monitoring ) Q_EMIT resumeAudioInputStream ( ) ;
} else {
Q_EMIT suspendAudioInputStream ( ) ;
}
m_monitoring = state ;
}
void MainWindow : : on_actionAbout_triggered ( ) //Display "About"
{
CAboutDlg { this } . exec ( ) ;
}
2024-08-31 10:29:30 -07:00
void MainWindow : : on_monitorButton_toggled ( bool ) {
2018-11-15 21:57:04 -05:00
resetPushButtonToggleText ( ui - > monitorButton ) ;
}
void MainWindow : : on_monitorTxButton_toggled ( bool checked ) {
resetPushButtonToggleText ( ui - > monitorTxButton ) ;
2020-04-15 15:23:41 -04:00
if ( ! checked ) {
on_stopTxButton_clicked ( ) ;
}
2018-11-15 21:57:04 -05:00
}
2024-08-31 10:29:30 -07:00
void MainWindow : : on_tuneButton_toggled ( bool ) {
2018-11-15 21:57:04 -05:00
resetPushButtonToggleText ( ui - > tuneButton ) ;
}
2024-08-31 10:29:30 -07:00
void MainWindow : : on_spotButton_toggled ( bool ) {
2018-11-15 21:57:04 -05:00
resetPushButtonToggleText ( ui - > spotButton ) ;
}
2018-02-08 21:28:33 -05:00
void MainWindow : : auto_tx_mode ( bool state )
{
2024-10-17 05:31:22 -07:00
m_auto = state ;
statusUpdate ( ) ;
if ( ! state ) on_stopTxButton_clicked ( ) ;
2018-02-08 21:28:33 -05:00
}
void MainWindow : : keyPressEvent ( QKeyEvent * e )
{
2018-03-05 14:49:51 -05:00
switch ( e - > key ( ) ) {
2018-08-03 17:32:56 -04:00
case Qt : : Key_Escape :
2018-08-06 17:11:31 -04:00
on_stopTxButton_clicked ( ) ;
2018-08-31 21:30:34 -04:00
stopTx ( ) ;
2018-08-03 17:32:56 -04:00
return ;
2018-10-03 17:10:43 -04:00
case Qt : : Key_F5 :
on_logQSOButton_clicked ( ) ;
return ;
2018-03-05 14:49:51 -05:00
}
QMainWindow : : keyPressEvent ( e ) ;
2018-02-08 21:28:33 -05:00
}
2024-10-20 13:54:14 -07:00
void
2024-10-20 14:36:48 -07:00
MainWindow : : f11f12 ( int const n )
2018-02-08 21:28:33 -05:00
{
2024-10-20 13:54:14 -07:00
if ( n = = 11 ) setFreq ( freq ( ) - 1 ) ;
if ( n = = 12 ) setFreq ( freq ( ) + 1 ) ;
2018-02-08 21:28:33 -05:00
}
2018-08-08 17:15:49 -04:00
Radio : : Frequency MainWindow : : dialFrequency ( ) {
return Frequency { m_rigState . ptt ( ) & & m_rigState . split ( ) ?
m_rigState . tx_frequency ( ) : m_rigState . frequency ( ) } ;
}
2019-11-04 20:21:55 -05:00
void MainWindow : : setSubmode ( int submode ) {
m_nSubMode = submode ;
ui - > actionModeJS8Normal - > setChecked ( submode = = Varicode : : JS8CallNormal ) ;
ui - > actionModeJS8Fast - > setChecked ( submode = = Varicode : : JS8CallFast ) ;
ui - > actionModeJS8Turbo - > setChecked ( submode = = Varicode : : JS8CallTurbo ) ;
2019-12-09 14:00:23 -05:00
ui - > actionModeJS8Slow - > setChecked ( submode = = Varicode : : JS8CallSlow ) ;
ui - > actionModeJS8Ultra - > setChecked ( submode = = Varicode : : JS8CallUltra ) ;
2024-11-08 14:06:18 -08:00
setupJS8 ( ) ;
2019-11-04 20:21:55 -05:00
}
2019-06-12 23:54:02 -04:00
void MainWindow : : updateCurrentBand ( ) {
QVariant state = ui - > readFreq - > property ( " state " ) ;
if ( ! state . isValid ( ) ) {
return ;
}
auto dial_frequency = dialFrequency ( ) ;
auto const & band_name = m_config . bands ( ) - > find ( dial_frequency ) ;
if ( m_lastBand = = band_name ) {
return ;
}
cacheActivity ( m_lastBand ) ;
// clear activity on startup if asked or on when the previous band is not empty
if ( m_config . reset_activity ( ) | | ! m_lastBand . isEmpty ( ) ) {
clearActivity ( ) ;
}
2024-10-19 09:14:10 -07:00
m_wideGraph - > setBand ( band_name ) ;
2019-06-12 23:54:02 -04:00
qDebug ( ) < < " setting band " < < band_name ;
2020-04-03 22:49:28 -04:00
sendNetworkMessage ( " RIG.FREQ " , " " , {
{ " _ID " , QVariant ( - 1 ) } ,
{ " BAND " , QVariant ( band_name ) } ,
2024-10-20 13:54:14 -07:00
{ " FREQ " , QVariant ( ( quint64 ) dialFrequency ( ) + freq ( ) ) } ,
2020-04-03 22:49:28 -04:00
{ " DIAL " , QVariant ( ( quint64 ) dialFrequency ( ) ) } ,
2024-10-20 13:54:14 -07:00
{ " OFFSET " , QVariant ( ( quint64 ) freq ( ) ) }
2020-04-03 22:49:28 -04:00
} ) ;
2019-06-12 23:54:02 -04:00
m_lastBand = band_name ;
2024-10-25 13:27:38 -07:00
band_changed ( ) ;
2019-06-12 23:54:02 -04:00
restoreActivity ( m_lastBand ) ;
}
2018-09-16 11:30:13 -04:00
void MainWindow : : displayDialFrequency ( ) {
#if 0
qDebug ( ) < < " rx nominal " < < m_freqNominal ;
qDebug ( ) < < " tx nominal " < < m_freqTxNominal ;
2024-10-20 13:54:14 -07:00
qDebug ( ) < < " offset set to " < < freq ( ) < < freq ( ) ;
2018-09-16 11:30:13 -04:00
# endif
2018-02-08 21:28:33 -05:00
2024-10-04 18:49:08 -07:00
auto dial_frequency = dialFrequency ( ) ;
2024-10-20 13:54:14 -07:00
auto audio_frequency = freq ( ) ;
2018-08-28 15:45:23 -04:00
2018-09-16 11:30:13 -04:00
// lookup band
auto const & band_name = m_config . bands ( ) - > find ( dial_frequency ) ;
2018-02-08 21:28:33 -05:00
2019-01-21 17:45:52 -05:00
auto sFreq = Radio : : pretty_frequency_MHz_string ( dial_frequency ) ;
ui - > currentFreq - > setDigitCount ( sFreq . length ( ) ) ;
ui - > currentFreq - > display ( sFreq ) ;
2018-09-16 11:30:13 -04:00
if ( m_splitMode & & m_transmitting ) {
audio_frequency - = m_XIT ;
}
ui - > labDialFreqOffset - > setText ( QString ( " %1 Hz " ) . arg ( audio_frequency ) ) ;
2018-02-08 21:28:33 -05:00
}
void MainWindow : : statusChanged ( )
{
statusUpdate ( ) ;
}
bool MainWindow : : eventFilter ( QObject * object , QEvent * event )
{
switch ( event - > type ( ) )
{
case QEvent : : KeyPress :
// fall through
case QEvent : : MouseButtonPress :
// reset the Tx watchdog
2018-10-28 12:06:37 -04:00
resetIdleTimer ( ) ;
2018-02-08 21:28:33 -05:00
tx_watchdog ( false ) ;
break ;
case QEvent : : ChildAdded :
// ensure our child widgets get added to our event filter
add_child_to_event_filter ( static_cast < QChildEvent * > ( event ) - > child ( ) ) ;
break ;
case QEvent : : ChildRemoved :
// ensure our child widgets get d=removed from our event filter
remove_child_from_event_filter ( static_cast < QChildEvent * > ( event ) - > child ( ) ) ;
break ;
2018-11-13 12:35:39 -05:00
case QEvent : : ToolTip :
if ( ! ui - > actionShow_Tooltips - > isChecked ( ) ) {
return true ;
}
break ;
2018-02-08 21:28:33 -05:00
default : break ;
}
return QObject : : eventFilter ( object , event ) ;
}
void MainWindow : : createStatusBar ( ) //createStatusBar
{
2024-08-31 10:49:10 -07:00
tx_status_label . setAlignment ( Qt : : AlignCenter ) ;
2018-02-08 21:28:33 -05:00
tx_status_label . setMinimumSize ( QSize { 150 , 18 } ) ;
2018-12-20 20:55:55 -05:00
tx_status_label . setStyleSheet ( " QLabel{background-color: #22ff22} " ) ;
2018-02-08 21:28:33 -05:00
tx_status_label . setFrameStyle ( QFrame : : Panel | QFrame : : Sunken ) ;
statusBar ( ) - > addWidget ( & tx_status_label ) ;
2024-08-31 10:49:10 -07:00
config_label . setAlignment ( Qt : : AlignCenter ) ;
2018-02-08 21:28:33 -05:00
config_label . setMinimumSize ( QSize { 80 , 18 } ) ;
config_label . setFrameStyle ( QFrame : : Panel | QFrame : : Sunken ) ;
statusBar ( ) - > addWidget ( & config_label ) ;
config_label . hide ( ) ; // only shown for non-default configuration
2024-08-31 10:49:10 -07:00
mode_label . setAlignment ( Qt : : AlignCenter ) ;
2018-02-08 21:28:33 -05:00
mode_label . setMinimumSize ( QSize { 80 , 18 } ) ;
2024-10-04 19:23:15 -07:00
mode_label . setStyleSheet ( " QLabel{background-color: #6699ff} " ) ;
2018-02-08 21:28:33 -05:00
mode_label . setFrameStyle ( QFrame : : Panel | QFrame : : Sunken ) ;
2024-10-04 19:23:15 -07:00
mode_label . setText ( " JS8 " ) ;
2018-02-08 21:28:33 -05:00
statusBar ( ) - > addWidget ( & mode_label ) ;
2024-08-31 10:49:10 -07:00
last_tx_label . setAlignment ( Qt : : AlignCenter ) ;
2018-02-08 21:28:33 -05:00
last_tx_label . setMinimumSize ( QSize { 150 , 18 } ) ;
last_tx_label . setFrameStyle ( QFrame : : Panel | QFrame : : Sunken ) ;
statusBar ( ) - > addWidget ( & last_tx_label ) ;
2024-09-11 10:57:09 -07:00
statusBar ( ) - > addPermanentWidget ( & progressBar ) ;
2018-02-08 21:28:33 -05:00
progressBar . setMinimumSize ( QSize { 100 , 18 } ) ;
progressBar . setFormat ( " %v/%m " ) ;
2018-10-01 09:57:37 -04:00
statusBar ( ) - > addPermanentWidget ( & wpm_label ) ;
2018-10-01 13:04:50 -04:00
wpm_label . setMinimumSize ( QSize { 120 , 18 } ) ;
2018-10-01 09:57:37 -04:00
wpm_label . setFrameStyle ( QFrame : : Panel | QFrame : : Sunken ) ;
2024-08-31 10:49:10 -07:00
wpm_label . setAlignment ( Qt : : AlignCenter ) ;
2018-02-08 21:28:33 -05:00
}
2024-10-04 19:25:42 -07:00
void
MainWindow : : setup_status_bar ( )
2018-02-08 21:28:33 -05:00
{
2024-10-04 19:25:42 -07:00
last_tx_label . clear ( ) ;
2018-02-08 21:28:33 -05:00
}
void MainWindow : : closeEvent ( QCloseEvent * e )
{
m_valid = false ; // suppresses subprocess errors
m_config . transceiver_offline ( ) ;
writeSettings ( ) ;
m_guiTimer . stop ( ) ;
m_prefixes . reset ( ) ;
m_shortcuts . reset ( ) ;
m_mouseCmnds . reset ( ) ;
Q_EMIT finished ( ) ;
QMainWindow : : closeEvent ( e ) ;
}
2019-12-27 19:46:36 -05:00
void MainWindow : : on_dialFreqUpButton_clicked ( ) {
setRig ( m_freqNominal + 250 ) ;
}
void MainWindow : : on_dialFreqDownButton_clicked ( ) {
setRig ( m_freqNominal - 250 ) ;
}
2018-10-03 17:10:43 -04:00
void MainWindow : : on_actionAdd_Log_Entry_triggered ( ) {
on_logQSOButton_clicked ( ) ;
}
2018-02-08 21:28:33 -05:00
void MainWindow : : on_actionCopyright_Notice_triggered ( )
{
2018-07-13 22:13:36 -04:00
auto const & message = tr ( " If you make fair use of any part of this program under terms of the GNU "
2018-02-08 21:28:33 -05:00
" General Public License, you must display the following copyright "
" notice prominently in your derivative work: \n \n "
" \" The algorithms, source code, look-and-feel of WSJT-X and related "
" programs, and protocol specifications for the modes FSK441, FT8, JT4, "
" JT6M, JT9, JT65, JTMS, QRA64, ISCAT, MSK144 are Copyright (C) "
2018-03-05 14:49:51 -05:00
" 2001-2018 by one or more of the following authors: Joseph Taylor, "
2018-02-08 21:28:33 -05:00
" K1JT; Bill Somerville, G4WJS; Steven Franke, K9AN; Nico Palermo, "
" IV3NWV; Greg Beam, KI7MT; Michael Black, W9MDB; Edson Pereira, PY2SDR; "
2018-07-13 22:13:36 -04:00
" Philip Karn, KA9Q; and other members of the WSJT Development Group. \n \n "
2018-10-04 13:52:52 -04:00
" Further, the source code of JS8Call contains material Copyright (C) "
2019-03-30 09:50:11 -04:00
" 2018-2019 by Jordan Sherer, KN4CRD. \" " ) ;
2018-02-08 21:28:33 -05:00
MessageBox : : warning_message ( this , message ) ;
}
2019-11-12 13:42:21 -05:00
/**
* @ brief MainWindow : : isDecodeReady
* determine if decoding is ready for a given submode
* @ param submode - submode to test
* @ param k - current frame count
* @ param k0 - previous frame count
* @ param pCurrentDecodeStart - input pointer to a static integer with the current decode start position
* @ param pNextDecodeStart - input pointer to a static integer with the next decode start position
* @ param pStart - output pointer to the next start position when decode is ready
* @ param pSz - output pointer to the next size when decode is ready
* @ param pCycle - output pointer to the next cycle when decode is ready
* @ return true if decode is ready for this submode , false otherwise
*/
2024-10-08 08:07:13 -07:00
bool
MainWindow : : isDecodeReady ( int const submode ,
qint32 const k ,
qint32 const k0 ,
qint32 * pCurrentDecodeStart ,
qint32 * pNextDecodeStart ,
qint32 * pStart ,
qint32 * pSz ,
qint32 * pCycle )
{
if ( pCurrentDecodeStart = = nullptr | |
pNextDecodeStart = = nullptr )
{
2019-11-10 22:17:32 -05:00
return false ;
}
2024-10-08 07:26:18 -07:00
qint32 const cycleFrames = JS8 : : Submode : : framesPerCycle ( submode ) ;
2024-10-08 08:07:13 -07:00
qint32 const framesNeeded = JS8 : : Submode : : framesNeeded ( submode ) ;
2024-10-08 07:22:56 -07:00
qint32 const currentCycle = JS8 : : Submode : : computeCycleForDecode ( submode , k ) ;
2024-10-08 08:07:13 -07:00
qint32 const delta = qAbs ( k - k0 ) ;
2019-11-10 22:17:32 -05:00
2019-11-12 13:27:45 -05:00
if ( delta > cycleFrames ) {
2024-10-08 06:14:21 -07:00
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> " < < JS8 : : Submode : : name ( submode ) < < " buffer advance delta " < < delta ;
2019-11-12 13:27:45 -05:00
}
2019-11-14 20:20:05 -05:00
// say, current decode start is 360000 and the next is 540000 (right before we loop)
// frames needed are 150000
// and then we turn off rx until k is 110000 and the cycle frames are 180000 and k0 is a proper 100000
// we need to still reset...
// so, if k is less than the last decode start - cycle frames (in this case 360000-180000, or 180000),
// then we should reset. but, what if k is now 182000??
// k=182000
// k < current (182000 < 360000) true
// k < max(0, current-cycleframes+framesNeeded) (k < 180000+150000) true
// k=6000
// k < current (6000<360000) true
// k < max(0, 360000-180000+150000) true
// k=350000
// k < current (350000<360000) true
// k < max(0, 360000-350000+150000) false
// are we in the space between the end of the last decode and the start of the next decode?
2024-10-08 08:07:13 -07:00
bool const deadAir = ( k < * pCurrentDecodeStart & &
k < qMax ( 0 , * pCurrentDecodeStart - cycleFrames + framesNeeded ) ) ;
2019-11-14 20:20:05 -05:00
2019-11-14 14:29:17 -05:00
// on buffer loop or init, prepare proper next decode start
2019-11-14 20:20:05 -05:00
if (
( deadAir ) | |
( k < k0 ) | |
( delta > cycleFrames ) | |
( * pCurrentDecodeStart = = - 1 ) | |
( * pNextDecodeStart = = - 1 )
) {
2019-11-10 22:17:32 -05:00
* pCurrentDecodeStart = currentCycle * cycleFrames ;
2024-10-08 08:07:13 -07:00
* pNextDecodeStart = * pCurrentDecodeStart + cycleFrames ;
2019-11-10 22:17:32 -05:00
}
2024-10-08 08:07:13 -07:00
bool const ready = * pCurrentDecodeStart + framesNeeded < = k ;
2019-11-10 22:17:32 -05:00
if ( ready ) {
2024-10-08 06:14:21 -07:00
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> " < < JS8 : : Submode : : name ( submode ) < < " from " < < * pCurrentDecodeStart < < " to " < < * pCurrentDecodeStart + framesNeeded < < " k " < < k < < " k0 " < < k0 ;
2019-11-10 22:17:32 -05:00
2019-11-11 21:08:16 -05:00
if ( pCycle ) * pCycle = currentCycle ;
if ( pStart ) * pStart = * pCurrentDecodeStart ;
2024-10-08 08:07:13 -07:00
if ( pSz ) * pSz = qMax ( framesNeeded , k - ( * pCurrentDecodeStart ) ) ;
2019-11-10 22:17:32 -05:00
* pCurrentDecodeStart = * pNextDecodeStart ;
2024-10-08 08:07:13 -07:00
* pNextDecodeStart = * pCurrentDecodeStart + cycleFrames ;
2019-11-10 22:17:32 -05:00
}
return ready ;
}
2019-11-12 13:42:21 -05:00
/**
* @ brief MainWindow : : decode
* try decoding
* @ return true if the decoder was activated , false otherwise
*/
2019-11-17 01:21:07 -05:00
bool MainWindow : : decode ( qint32 k ) {
2019-11-10 22:17:32 -05:00
static int k0 = 9999999 ;
2019-11-12 13:42:21 -05:00
int kZero = k0 ;
k0 = k ;
2019-11-09 09:48:09 -05:00
2019-11-14 12:03:50 -05:00
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " decoder checking if ready... " < < " k " < < k < < " k0 " < < kZero < < " busy? " < < m_decoderBusy < < " lock exists? " < < ( QFile { m_config . temp_dir ( ) . absoluteFilePath ( " .lock " ) } . exists ( ) ) ;
2019-11-09 09:25:03 -05:00
2019-11-14 10:04:29 -05:00
if ( k = = kZero ) {
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> decoder stream has not advanced " ;
return false ;
}
2019-11-13 16:10:26 -05:00
2024-09-14 18:34:38 -07:00
if ( ! m_monitoring ) {
2019-11-14 14:29:17 -05:00
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> decoder stream is not active " ;
return false ;
}
2020-05-11 20:53:03 -04:00
bool ready = false ;
# if JS8_USE_EXPERIMENTAL_DECODE_TIMING
ready = decodeEnqueueReady ( k , kZero ) ;
2019-11-14 20:20:05 -05:00
if ( ready | | ! m_decoderQueue . isEmpty ( ) ) {
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> decoder is ready to be run with " < < m_decoderQueue . count ( ) < < " decode periods " ;
}
2020-05-11 20:53:03 -04:00
# else
ready = decodeEnqueueReadyExperiment ( k , kZero ) ;
if ( ready | | ! m_decoderQueue . isEmpty ( ) ) {
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> decoder is ready to be run with " < < m_decoderQueue . count ( ) < < " decode periods " ;
}
# endif
2019-11-14 20:20:05 -05:00
//
// TODO: what follows can likely be pulled out to an async process
//
2020-04-19 20:35:53 -04:00
// pause decoder if we are currently transmitting
if ( m_transmitting ) {
// we used to use isMessageQueuedForTransmit, and some form of checking for queued messages
// but, that just caused problems with missing decodes, so we only pause if we are actually
// actively transmitting.
2019-11-12 20:12:16 -05:00
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> decoder paused during transmit " ;
2019-11-12 13:42:21 -05:00
return false ;
2019-11-11 09:53:32 -05:00
}
2020-05-14 21:14:01 -04:00
if ( m_decoderBusyStartTime . isValid ( ) & & m_decoderBusyStartTime . msecsTo ( QDateTime : : currentDateTimeUtc ( ) ) < 1000 ) {
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> decoder paused for 1000 ms after last decode start " ;
return false ;
}
2019-12-23 22:40:11 -05:00
int threshold = m_nSubMode = = Varicode : : JS8CallSlow ? 4000 : 2000 ; // two seconds
2019-11-14 16:07:49 -05:00
if ( isInDecodeDelayThreshold ( threshold ) ) {
2019-11-12 20:12:16 -05:00
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> decoder paused for " < < threshold < < " ms after transmit stop " ;
2019-11-12 13:42:21 -05:00
return false ;
2019-11-11 22:39:02 -05:00
}
2019-11-17 02:43:17 -05:00
// critical section (modifying dec_data)
2019-11-11 21:08:16 -05:00
qint32 submode = - 1 ;
2019-11-12 13:42:21 -05:00
if ( ! decodeProcessQueue ( & submode ) ) {
return false ;
2019-11-03 15:29:59 -05:00
}
2018-03-05 14:49:51 -05:00
2019-11-12 13:42:21 -05:00
decodeStart ( ) ;
2019-11-15 15:40:01 -05:00
2019-11-12 13:42:21 -05:00
return true ;
2019-11-11 21:08:16 -05:00
}
2019-11-12 13:42:21 -05:00
/**
* @ brief MainWindow : : decodeEnqueueReady
* compute the available decoder ranges that can be processed and
* place them in the decode queue
* @ param k - the current frame count
* @ param k0 - the previous frame count
* @ return true if decoder ranges were queued , false otherwise
*/
2019-11-11 21:08:16 -05:00
bool MainWindow : : decodeEnqueueReady ( qint32 k , qint32 k0 ) {
// compute the next decode for each submode
// enqueue those decodes that are "ready"
// on an interval, issue a decode
2020-05-10 20:13:20 -04:00
int decodes = 0 ;
2019-11-11 21:08:16 -05:00
2020-05-10 20:13:20 -04:00
bool couldDecodeA = false ;
2019-11-10 22:17:32 -05:00
qint32 startA = - 1 ;
qint32 szA = - 1 ;
2019-11-11 21:08:16 -05:00
qint32 cycleA = - 1 ;
2020-05-07 15:32:28 -04:00
2020-05-10 20:13:20 -04:00
bool couldDecodeB = false ;
qint32 startB = - 1 ;
qint32 szB = - 1 ;
qint32 cycleB = - 1 ;
bool couldDecodeC = false ;
qint32 startC = - 1 ;
qint32 szC = - 1 ;
qint32 cycleC = - 1 ;
bool couldDecodeE = false ;
qint32 startE = - 1 ;
qint32 szE = - 1 ;
qint32 cycleE = - 1 ;
2020-05-11 09:27:04 -04:00
# if JS8_ENABLE_JS8I
2020-05-10 20:13:20 -04:00
bool couldDecodeI = false ;
qint32 startI = - 1 ;
qint32 szI = - 1 ;
qint32 cycleI = - 1 ;
2020-05-11 09:27:04 -04:00
# endif
2020-05-10 20:13:20 -04:00
static qint32 currentDecodeStartA = - 1 ;
static qint32 nextDecodeStartA = - 1 ;
2019-11-14 14:29:17 -05:00
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " ? NORMAL " < < currentDecodeStartA < < nextDecodeStartA ;
2020-05-07 15:32:28 -04:00
couldDecodeA = isDecodeReady ( Varicode : : JS8CallNormal , k , k0 , & currentDecodeStartA , & nextDecodeStartA , & startA , & szA , & cycleA ) ;
2019-11-10 22:17:32 -05:00
static qint32 currentDecodeStartB = - 1 ;
static qint32 nextDecodeStartB = - 1 ;
2019-11-14 14:29:17 -05:00
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " ? FAST " < < currentDecodeStartB < < nextDecodeStartB ;
2020-05-10 20:13:20 -04:00
couldDecodeB = isDecodeReady ( Varicode : : JS8CallFast , k , k0 , & currentDecodeStartB , & nextDecodeStartB , & startB , & szB , & cycleB ) ;
2019-11-10 22:17:32 -05:00
static qint32 currentDecodeStartC = - 1 ;
static qint32 nextDecodeStartC = - 1 ;
2019-11-14 14:29:17 -05:00
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " ? TURBO " < < currentDecodeStartC < < nextDecodeStartC ;
2020-05-10 20:13:20 -04:00
couldDecodeC = isDecodeReady ( Varicode : : JS8CallTurbo , k , k0 , & currentDecodeStartC , & nextDecodeStartC , & startC , & szC , & cycleC ) ;
2019-11-04 14:38:00 -05:00
2019-11-10 22:17:32 -05:00
static qint32 currentDecodeStartE = - 1 ;
static qint32 nextDecodeStartE = - 1 ;
2019-12-09 16:18:50 -05:00
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " ? SLOW " < < currentDecodeStartE < < nextDecodeStartE ;
2020-05-10 20:13:20 -04:00
couldDecodeE = isDecodeReady ( Varicode : : JS8CallSlow , k , k0 , & currentDecodeStartE , & nextDecodeStartE , & startE , & szE , & cycleE ) ;
2019-11-04 14:38:00 -05:00
2019-12-09 14:00:23 -05:00
# if JS8_ENABLE_JS8I
static qint32 currentDecodeStartI = - 1 ;
static qint32 nextDecodeStartI = - 1 ;
2019-12-09 16:18:50 -05:00
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " ? ULTRA " < < currentDecodeStartI < < nextDecodeStartI ;
2020-05-10 20:13:20 -04:00
couldDecodeI = isDecodeReady ( Varicode : : JS8CallUltra , k , k0 , & currentDecodeStartI , & nextDecodeStartI , & startI , & szI , & cycleI ) ;
2019-12-09 14:00:23 -05:00
# endif
2020-05-11 09:27:04 -04:00
if ( couldDecodeA ) {
DecodeParams d ;
d . submode = Varicode : : JS8CallNormal ;
d . start = startA ;
d . sz = szA ;
m_decoderQueue . append ( d ) ;
decodes + + ;
}
if ( couldDecodeB ) {
DecodeParams d ;
d . submode = Varicode : : JS8CallFast ;
d . start = startB ;
d . sz = szB ;
m_decoderQueue . append ( d ) ;
decodes + + ;
}
if ( couldDecodeC ) {
DecodeParams d ;
d . submode = Varicode : : JS8CallTurbo ;
d . start = startC ;
d . sz = szC ;
m_decoderQueue . append ( d ) ;
decodes + + ;
}
if ( couldDecodeE ) {
DecodeParams d ;
d . submode = Varicode : : JS8CallSlow ;
d . start = startE ;
d . sz = szE ;
m_decoderQueue . append ( d ) ;
decodes + + ;
}
# if JS8_ENABLE_JS8I
if ( couldDecodeI ) {
DecodeParams d ;
d . submode = Varicode : : JS8CallUltra ;
d . start = startI ;
d . sz = szI ;
m_decoderQueue . append ( d ) ;
decodes + + ;
}
2020-05-10 20:13:20 -04:00
# endif
2019-12-09 14:00:23 -05:00
2020-05-11 09:27:04 -04:00
return decodes > 0 ;
}
/**
* @ brief MainWindow : : decodeEnqueueReadyExperiment
* compute the available decoder ranges that can be processed and
* place them in the decode queue
*
* experiment with decoding on a much shorter interval than usual
*
* @ param k - the current frame count
* @ param k0 - the previous frame count
* @ return true if decoder ranges were queued , false otherwise
*/
2020-05-11 14:17:55 -04:00
bool MainWindow : : decodeEnqueueReadyExperiment ( qint32 k , qint32 /*k0*/ ) {
2020-05-11 20:53:03 -04:00
// TODO: make this non-static field of MainWindow?
// map of last decode positions for each submode
2020-05-13 21:15:21 -04:00
// static QMap<qint32, qint32> m_lastDecodeStartMap;
2020-05-11 20:53:03 -04:00
// TODO: make this non-static field of MainWindow?
// map of submodes to decode + optional alternate decode positions
static QMap < qint32 , QList < qint32 > > submodes = {
{ Varicode : : JS8CallSlow , { 0 } } ,
{ Varicode : : JS8CallNormal , { 0 } } ,
2020-05-17 14:33:09 -04:00
{ Varicode : : JS8CallFast , { 0 } } , // NORMAL: 0, 10, 20 --- ALT: 15, 25
{ Varicode : : JS8CallTurbo , { 0 } } , // NORMAL: 0, 6, 12, 18 --- ALT: 15, 21, 27
2020-05-17 14:21:38 -04:00
# if JS8_ENABLE_JS8I
2020-05-17 16:41:40 -04:00
{ Varicode : : JS8CallUltra , { 0 } } ,
2020-05-17 14:21:38 -04:00
# endif
2020-05-11 20:53:03 -04:00
} ;
static qint32 maxSamples = NTMAX * RX_SAMPLE_RATE ;
static qint32 oneSecondSamples = RX_SAMPLE_RATE ;
int decodes = 0 ;
// do we have a better way to check this?
bool multi = ui - > actionModeMultiDecoder - > isChecked ( ) ;
2020-05-14 12:05:16 -04:00
// do we need to process alternate positions?
2020-05-17 14:21:38 -04:00
bool skipAlt = true ;
2020-05-14 12:05:16 -04:00
2020-05-11 20:53:03 -04:00
foreach ( auto submode , submodes . keys ( ) ) {
2020-05-17 14:21:38 -04:00
// do we have a better way to check this?
bool everySecond = m_wideGraph - > shouldAutoSyncSubmode ( submode ) ;
// skip if multi is disabled and this mode is not the current submode and we're not autosyncing this mode
if ( ! everySecond & & ! multi & & submode ! = m_nSubMode ) {
2020-05-11 20:53:03 -04:00
continue ;
}
2020-05-13 21:15:21 -04:00
// check all alternate decode positions
2020-05-11 20:53:03 -04:00
foreach ( auto alt , submodes . value ( submode ) ) {
2020-05-17 14:21:38 -04:00
// skip alt decode positions if needed
if ( skipAlt & & alt ! = 0 ) {
2020-05-13 21:15:21 -04:00
continue ;
}
2020-05-17 14:21:38 -04:00
// skip alts if we are decoding every second
if ( everySecond & & alt ! = 0 ) {
2020-05-14 12:05:16 -04:00
continue ;
}
2024-10-08 08:10:11 -07:00
qint32 const cycle = JS8 : : Submode : : computeAltCycleForDecode ( submode , k , alt * oneSecondSamples ) ;
qint32 const cycleFrames = JS8 : : Submode : : framesPerCycle ( submode ) ;
qint32 const cycleFramesNeeded = JS8 : : Submode : : framesForSymbols ( submode ) ; //computeFramesNeededForDecode(submode) - oneSecondSamples;
qint32 cycleFramesReady = k - ( cycle * cycleFrames ) ;
2020-05-17 14:21:38 -04:00
if ( cycleFramesReady < 0 ) {
cycleFramesReady = k + ( maxSamples - ( cycle * cycleFrames ) ) ;
}
2020-05-11 20:53:03 -04:00
2020-05-13 21:15:21 -04:00
if ( ! m_lastDecodeStartMap . contains ( submode ) ) {
m_lastDecodeStartMap [ submode ] = cycle * cycleFrames ;
2020-05-11 20:53:03 -04:00
}
2020-05-13 21:15:21 -04:00
qint32 lastDecodeStart = m_lastDecodeStartMap [ submode ] ;
2020-05-11 20:53:03 -04:00
qint32 incrementedBy = k - lastDecodeStart ;
if ( k < lastDecodeStart ) {
incrementedBy = maxSamples - lastDecodeStart + k ;
}
2024-10-08 06:14:21 -07:00
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < JS8 : : Submode : : name ( submode ) < < " alt " < < alt < < " cycle " < < cycle < < " cycle frames " < < cycleFrames < < " cycle start " < < cycle * cycleFrames < < " cycle end " < < ( cycle + 1 ) * cycleFrames < < " k " < < k < < " frames ready " < < cycleFramesReady < < " incremeted by " < < incrementedBy ;
2020-05-11 20:53:03 -04:00
2020-05-13 21:15:21 -04:00
if ( everySecond & & incrementedBy > = oneSecondSamples ) {
DecodeParams d ;
d . submode = submode ;
d . sz = cycleFrames ;
d . start = k - d . sz ;
if ( d . start < 0 ) {
d . start + = maxSamples ;
}
m_decoderQueue . append ( d ) ;
decodes + + ;
// keep track of last decode position
m_lastDecodeStartMap [ submode ] = k ;
}
2020-05-14 21:14:01 -04:00
else if (
2020-05-17 14:21:38 -04:00
( incrementedBy > = 1.5 * oneSecondSamples & & cycleFramesReady > = cycleFramesNeeded ) | | // within every 3/2 seconds for normal positions
( incrementedBy > = oneSecondSamples & & cycleFramesReady > = cycleFramesNeeded - 1.5 * oneSecondSamples ) | | // within the last 3/2 seconds of a new cycle
( incrementedBy > = oneSecondSamples & & cycleFramesReady < 1.5 * oneSecondSamples ) // within the first 3/2 seconds of a new cycle
2020-05-14 21:14:01 -04:00
) {
2020-05-11 20:53:03 -04:00
DecodeParams d ;
d . submode = submode ;
d . start = cycle * cycleFrames ;
d . sz = cycleFramesReady ;
m_decoderQueue . append ( d ) ;
decodes + + ;
2020-05-13 21:15:21 -04:00
// keep track of last decode position
m_lastDecodeStartMap [ submode ] = k ;
2020-05-11 20:53:03 -04:00
}
}
}
return decodes > 0 ;
}
2019-11-12 13:42:21 -05:00
/**
* @ brief MainWindow : : decodeProcessQueue
* process the decode queue by merging available decode ranges
* into the dec_data shared structure for the decoder to process
* @ param pSubmode - the lowest speed submode in this iteration
* @ return true if the decoder is ready to be run , false otherwise
*/
2019-11-11 21:08:16 -05:00
bool MainWindow : : decodeProcessQueue ( qint32 * pSubmode ) {
2019-11-17 02:43:17 -05:00
// critical section
QMutexLocker mutex ( m_detector - > getMutex ( ) ) ;
2019-11-11 21:08:16 -05:00
if ( m_decoderBusy ) {
2020-05-09 11:36:09 -04:00
int seconds = m_decoderBusyStartTime . secsTo ( QDateTime : : currentDateTimeUtc ( ) ) ;
2019-11-13 16:10:26 -05:00
if ( seconds > 60 ) {
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> decoder should be killed! " < < QString ( " (%1 seconds) " ) . arg ( seconds ) ;
} else if ( seconds > 30 ) {
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> decoder is hanging! " < < QString ( " (%1 seconds) " ) . arg ( seconds ) ;
} else {
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> decoder is busy! " ;
}
2019-11-03 15:29:59 -05:00
return false ;
}
2019-11-11 21:08:16 -05:00
if ( m_decoderQueue . isEmpty ( ) ) {
2019-11-12 20:12:16 -05:00
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> decoder has nothing to process! " ;
2019-11-11 21:08:16 -05:00
return false ;
}
int submode = - 1 ;
int maxDecodes = 1 ;
bool multi = ui - > actionModeMultiDecoder - > isChecked ( ) ;
if ( multi ) {
2020-05-18 10:28:29 -04:00
maxDecodes = JS8_ENABLE_JS8I ? 5 : 4 ;
2019-11-11 21:08:16 -05:00
}
int count = m_decoderQueue . count ( ) ;
if ( count > maxDecodes ) {
2019-11-12 20:12:16 -05:00
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> decoder skipping at least 1 decode cycle " < < " count " < < count < < " max " < < maxDecodes ;
2019-11-11 21:08:16 -05:00
}
2020-05-07 22:41:10 -04:00
// default to no submodes being decoded, then bitwise OR the modes together to decode them all at once
2019-11-11 22:39:02 -05:00
dec_data . params . nsubmodes = 0 ;
2019-11-11 21:08:16 -05:00
while ( ! m_decoderQueue . isEmpty ( ) ) {
auto params = m_decoderQueue . front ( ) ;
m_decoderQueue . removeFirst ( ) ;
// skip if we are not in multi mode and the submode doesn't equal the global submode
if ( ! multi & & params . submode ! = m_nSubMode ) {
continue ;
}
if ( submode = = - 1 | | params . submode < submode ) {
submode = params . submode ;
}
switch ( params . submode ) {
case Varicode : : JS8CallNormal :
dec_data . params . kposA = params . start ;
dec_data . params . kszA = params . sz ;
dec_data . params . nsubmodes | = ( params . submode + 1 ) ;
break ;
case Varicode : : JS8CallFast :
dec_data . params . kposB = params . start ;
dec_data . params . kszB = params . sz ;
dec_data . params . nsubmodes | = ( params . submode < < 1 ) ;
break ;
case Varicode : : JS8CallTurbo :
dec_data . params . kposC = params . start ;
dec_data . params . kszC = params . sz ;
dec_data . params . nsubmodes | = ( params . submode < < 1 ) ;
break ;
2019-12-09 14:00:23 -05:00
case Varicode : : JS8CallSlow :
2019-11-11 21:08:16 -05:00
dec_data . params . kposE = params . start ;
dec_data . params . kszE = params . sz ;
dec_data . params . nsubmodes | = ( params . submode < < 1 ) ;
break ;
2019-12-09 14:00:23 -05:00
# if JS8_ENABLE_JS8I
case Varicode : : JS8CallUltra :
dec_data . params . kposI = params . start ;
dec_data . params . kszI = params . sz ;
dec_data . params . nsubmodes | = ( params . submode < < 1 ) ;
break ;
2019-11-11 22:39:02 -05:00
# endif
2019-11-11 21:08:16 -05:00
}
}
if ( submode = = - 1 ) {
2019-11-12 20:12:16 -05:00
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> decoder has no segments to decode! " ;
2019-11-11 21:08:16 -05:00
return false ;
}
2020-05-17 14:21:38 -04:00
dec_data . params . syncStats = ( m_wideGraph - > shouldDisplayDecodeAttempts ( ) | | m_wideGraph - > isAutoSyncEnabled ( ) ) ;
2024-11-01 10:42:12 -07:00
dec_data . params . npts8 = ( m_ihsym * NSPS ) / 16 ;
dec_data . params . newdat = 1 ;
dec_data . params . nagain = 0 ;
2019-11-12 09:34:54 -05:00
2024-11-01 10:42:12 -07:00
auto const period = JS8 : : Submode : : period ( submode ) ;
auto const t = DriftingDateTime : : currentDateTimeUtc ( ) . addSecs ( 2 - period ) ;
auto const ihr = t . toString ( " hh " ) . toInt ( ) ;
auto const imin = t . toString ( " mm " ) . toInt ( ) ;
auto const isec = t . toString ( " ss " ) . toInt ( ) ;
2024-10-27 13:06:57 -07:00
2024-11-01 10:42:12 -07:00
dec_data . params . nutc = ihr * 10000 +
imin * 100 +
isec - isec % period ;
2019-11-03 15:29:59 -05:00
2024-11-01 10:48:56 -07:00
dec_data . params . nfqso = freq ( ) ;
dec_data . params . ndepth = m_ndepth ;
dec_data . params . nranera = 6 ;
dec_data . params . ndiskdat = 0 ;
dec_data . params . nfa = 0 ;
dec_data . params . nfb = 5000 ;
2019-11-03 15:29:59 -05:00
2024-11-01 10:42:12 -07:00
if ( m_wideGraph - > filterEnabled ( ) )
{
auto const low = max ( 0 , m_wideGraph - > filterMinimum ( ) ) ;
auto const high = min ( m_wideGraph - > filterMaximum ( ) , 5000 ) ;
2019-12-23 14:08:39 -05:00
2024-11-01 10:42:12 -07:00
dec_data . params . nfa = min ( low , high ) ;
dec_data . params . nfb = max ( low , high ) ;
2019-12-16 13:42:44 -05:00
}
2024-11-01 10:48:56 -07:00
if ( dec_data . params . nutc < m_nutc0 ) m_RxLog = 1 ; //Date and Time to ALL.TXT
if ( dec_data . params . newdat = = 1 ) m_nutc0 = dec_data . params . nutc ;
2019-11-03 15:29:59 -05:00
2024-11-01 10:48:56 -07:00
dec_data . params . nmode = 8 ;
dec_data . params . napwid = 50 ;
dec_data . params . nsubmode = - 1 ; // not needed
2019-11-03 15:29:59 -05:00
2024-10-16 15:28:53 -07:00
copyStringData ( m_dateTime , dec_data . params . datetime , sizeof ( dec_data . params . datetime ) ) ;
copyStringData ( m_config . my_callsign ( ) , dec_data . params . mycall , sizeof ( dec_data . params . mycall ) ) ;
2018-02-08 21:28:33 -05:00
2019-11-11 21:08:16 -05:00
// keep track of the minimum submode
if ( pSubmode ) * pSubmode = submode ;
2019-11-03 15:29:59 -05:00
return true ;
}
2019-11-12 13:42:21 -05:00
/**
* @ brief MainWindow : : decodeStart
* copy the dec_data structure to shared memory and
* remove the lock file to start the decoding process
*/
2025-01-22 17:42:38 -08:00
void MainWindow : : decodeStart ( )
{
// critical section
QMutexLocker mutex ( m_detector - > getMutex ( ) ) ;
2019-11-14 10:04:29 -05:00
2025-01-22 17:42:38 -08:00
if ( m_decoderBusy )
{
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> decoder cannot start...busy (busy flag) " ;
return ;
}
2019-11-12 13:27:45 -05:00
2025-01-22 17:42:38 -08:00
// Mark the decoder busy; decodeDone is responsible for marking
// the decode _not_ busy
2019-11-12 20:12:16 -05:00
2025-01-22 17:42:38 -08:00
decodeBusy ( true ) ;
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> decoder starting " ;
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> kin: " < < dec_data . params . kin ;
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> newdat: " < < dec_data . params . newdat ;
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> nsubmodes: " < < dec_data . params . nsubmodes ;
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> A: " < < dec_data . params . kposA < < dec_data . params . kposA + dec_data . params . kszA < < QString ( " (%1) " ) . arg ( dec_data . params . kszA ) ;
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> B: " < < dec_data . params . kposB < < dec_data . params . kposB + dec_data . params . kszB < < QString ( " (%1) " ) . arg ( dec_data . params . kszB ) ;
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> C: " < < dec_data . params . kposC < < dec_data . params . kposC + dec_data . params . kszC < < QString ( " (%1) " ) . arg ( dec_data . params . kszC ) ;
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> E: " < < dec_data . params . kposE < < dec_data . params . kposE + dec_data . params . kszE < < QString ( " (%1) " ) . arg ( dec_data . params . kszE ) ;
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " --> I: " < < dec_data . params . kposI < < dec_data . params . kposI + dec_data . params . kszI < < QString ( " (%1) " ) . arg ( dec_data . params . kszI ) ;
m_decoder . decode ( ) ;
2018-02-08 21:28:33 -05:00
}
2019-11-12 13:42:21 -05:00
/**
* @ brief MainWindow : : decodeBusy
* mark the decoder as currently busy ( to prevent overlapping decodes )
* @ param b - true if busy , false otherwise
*/
2025-01-22 17:42:38 -08:00
void
MainWindow : : decodeBusy ( bool b ) //decodeBusy()
2019-11-11 21:08:16 -05:00
{
2025-01-22 17:42:38 -08:00
m_decoderBusy = b ;
if ( m_decoderBusy )
{
2019-11-11 21:08:16 -05:00
tx_status_label . setText ( " Decoding " ) ;
2025-01-22 17:42:38 -08:00
2020-05-09 11:36:09 -04:00
m_decoderBusyStartTime = QDateTime : : currentDateTimeUtc ( ) ; //DriftingDateTime::currentDateTimeUtc();
2025-01-22 17:42:38 -08:00
m_decoderBusyFreq = dialFrequency ( ) ;
m_decoderBusyBand = m_config . bands ( ) - > find ( m_decoderBusyFreq ) ;
2019-11-11 21:08:16 -05:00
}
}
2019-11-12 13:42:21 -05:00
/**
* @ brief MainWindow : : decodeDone
* clean up after a decode is finished
*/
2025-01-22 17:42:38 -08:00
void
MainWindow : : decodeDone ( )
2019-11-11 21:08:16 -05:00
{
2019-11-17 02:43:17 -05:00
// critical section
QMutexLocker mutex ( m_detector - > getMutex ( ) ) ;
2024-10-06 09:03:47 -07:00
dec_data . params . newdat = false ;
dec_data . params . nagain = false ;
dec_data . params . ndiskdat = false ;
m_RxLog = 0 ;
2020-05-04 20:59:17 -04:00
2020-05-11 15:11:56 -04:00
// cleanup old cached messages (messages > submode period old)
2024-10-27 06:28:31 -07:00
erase_if ( m_messageDupeCache , [ ] ( auto const & it )
{
auto const & cached = it . value ( ) ;
return cached . date . secsTo ( QDateTime : : currentDateTimeUtc ( ) ) > JS8 : : Submode : : period ( cached . submode ) ;
} ) ;
2019-11-17 02:43:17 -05:00
2019-11-17 01:30:37 -05:00
decodeBusy ( false ) ;
2019-11-11 21:08:16 -05:00
}
2018-11-28 22:53:18 -05:00
QDateTime MainWindow : : nextTransmitCycle ( ) {
auto timestamp = DriftingDateTime : : currentDateTimeUtc ( ) ;
// remove milliseconds
auto t = timestamp . time ( ) ;
t . setHMS ( t . hour ( ) , t . minute ( ) , t . second ( ) ) ;
timestamp . setTime ( t ) ;
// round to 15 second increment
int secondsSinceEpoch = ( timestamp . toMSecsSinceEpoch ( ) / 1000 ) ;
2019-11-14 16:07:49 -05:00
int delta = roundUp ( secondsSinceEpoch , m_TRperiod ) + 1 - secondsSinceEpoch ;
2018-11-28 22:53:18 -05:00
timestamp = timestamp . addSecs ( delta ) ;
return timestamp ;
}
void MainWindow : : resetAutomaticIntervalTransmissions ( bool stopCQ , bool stopHB ) {
resetCQTimer ( stopCQ ) ;
resetHeartbeatTimer ( stopHB ) ;
}
void MainWindow : : resetCQTimer ( bool stop ) {
if ( ui - > cqMacroButton - > isChecked ( ) & & m_cqInterval > 0 ) {
ui - > cqMacroButton - > setChecked ( false ) ;
if ( ! stop ) {
ui - > cqMacroButton - > setChecked ( true ) ;
}
}
}
void MainWindow : : resetHeartbeatTimer ( bool stop ) {
// toggle the heartbeat timer if we have a repeating heartbeat
if ( ui - > hbMacroButton - > isChecked ( ) & & m_hbInterval > 0 ) {
ui - > hbMacroButton - > setChecked ( false ) ;
if ( ! stop ) {
ui - > hbMacroButton - > setChecked ( true ) ;
}
}
}
2019-10-05 14:55:15 -04:00
QList < int > generateOffsets ( int minOffset , int maxOffset ) {
QList < int > offsets ;
2019-11-09 09:25:03 -05:00
// TODO: these offsets aren't ordered correctly...
2019-10-05 14:55:15 -04:00
for ( int i = minOffset ; i < = maxOffset ; i + + ) {
offsets . append ( i ) ;
}
return offsets ;
}
2025-01-22 17:42:38 -08:00
void
MainWindow : : processDecodeEvent ( JS8 : : Event : : Variant const & event )
2018-02-08 21:28:33 -05:00
{
2020-05-19 09:52:00 -04:00
static QList < qint32 > driftQueue ;
2025-01-22 17:42:38 -08:00
static qint32 syncStart = - 1 ;
2020-05-13 21:15:21 -04:00
2025-01-22 17:42:38 -08:00
std : : visit ( [ this ] ( auto & & e )
{
using T = std : : decay_t < decltype ( e ) > ;
2020-05-06 20:08:30 -04:00
2025-01-22 17:42:38 -08:00
if constexpr ( std : : is_same_v < T , JS8 : : Event : : DecodeStarted > )
{
if ( m_wideGraph - > shouldDisplayDecodeAttempts ( ) )
{
m_wideGraph - > drawHorizontalLine ( QColor ( Qt : : yellow ) , 0 , 5 ) ;
}
2020-05-07 16:15:38 -04:00
}
2025-01-22 17:42:38 -08:00
else if constexpr ( std : : is_same_v < T , JS8 : : Event : : SyncStart > )
{
syncStart = e . position ;
2020-05-11 09:27:04 -04:00
}
2025-01-23 13:43:11 -08:00
else if constexpr ( std : : is_same_v < T , JS8 : : Event : : SyncState > )
2025-01-22 17:42:38 -08:00
{
if ( m_wideGraph - > shouldDisplayDecodeAttempts ( ) )
{
2025-01-23 14:54:04 -08:00
auto const drawDecodeLine = [ this ,
freq = static_cast < int > ( e . frequency ) ,
mode = e . mode ] ( QColor const & color )
{
m_wideGraph - > drawDecodeLine ( color ,
freq ,
freq + JS8 : : Submode : : bandwidth ( mode ) ) ;
} ;
2020-05-11 09:27:04 -04:00
2025-01-23 15:19:30 -08:00
if ( e . type = = JS8 : : Event : : SyncState : : Type : : DECODED )
2025-01-23 13:43:11 -08:00
{
2025-01-23 14:54:04 -08:00
drawDecodeLine ( Qt : : red ) ;
2025-01-23 13:43:11 -08:00
}
else if ( auto const xdtMs = static_cast < int > ( e . dt * 1000 ) ;
std : : abs ( xdtMs ) < = 2000 )
2025-01-22 17:42:38 -08:00
{
2025-01-23 15:19:30 -08:00
if ( e . sync . candidate < 10 ) drawDecodeLine ( Qt : : darkCyan ) ;
else if ( e . sync . candidate < = 15 ) drawDecodeLine ( Qt : : cyan ) ;
else if ( e . sync . candidate < = 21 ) drawDecodeLine ( Qt : : white ) ;
2020-05-07 16:15:38 -04:00
}
2025-01-22 17:42:38 -08:00
}
2020-05-07 16:15:38 -04:00
}
2025-01-22 17:42:38 -08:00
else if constexpr ( std : : is_same_v < T , JS8 : : Event : : DecodeFinished > )
{
if ( JS8_DEBUG_DECODE ) qDebug ( ) < < " decode duration " < < m_decoderBusyStartTime . msecsTo ( QDateTime : : currentDateTimeUtc ( ) ) < < " ms " ;
2020-05-14 12:05:16 -04:00
2025-01-22 17:42:38 -08:00
// TODO: move this into a function
if ( ! driftQueue . isEmpty ( ) )
{
if ( m_driftMsMMA_N = = 0 )
{
m_driftMsMMA_N = 1 ;
m_driftMsMMA = DriftingDateTime : : drift ( ) ;
}
2020-05-13 21:15:21 -04:00
2025-01-22 17:42:38 -08:00
// let the widegraph know for timing control
m_wideGraph - > notifyDriftedSignalsDecoded ( driftQueue . count ( ) ) ;
2020-05-16 11:57:27 -04:00
2025-01-22 17:42:38 -08:00
while ( ! driftQueue . isEmpty ( ) )
{
2020-05-19 09:52:00 -04:00
qint32 newDrift = driftQueue . first ( ) ;
2020-05-13 21:15:21 -04:00
driftQueue . removeFirst ( ) ;
2020-05-19 09:52:00 -04:00
m_driftMsMMA = ( ( ( m_driftMsMMA_N - 1 ) * m_driftMsMMA ) + newDrift ) / m_driftMsMMA_N ;
2020-05-16 22:08:39 -04:00
if ( m_driftMsMMA_N < 60 ) m_driftMsMMA_N + + ; // cap it to 60 observations
2025-01-22 17:42:38 -08:00
}
2020-05-13 21:15:21 -04:00
2025-01-22 17:42:38 -08:00
// XXX The following lines do nothing; it's a completely dead store. For
// now, just #ifdefing them out, but they were in the 2.2.1-devel code,
// and presumably they were important; need to see what the intent was
// here.
2024-09-06 06:52:57 -07:00
#if 0
2025-01-22 17:42:38 -08:00
qint32 driftLimitMs = JS8 : : Submode : : period ( Varicode : : JS8CallNormal ) * 1000 ;
qint32 newDriftMs = m_driftMsMMA ;
if ( newDriftMs < 0 ) {
newDriftMs = - ( ( - newDriftMs ) % driftLimitMs ) ;
} else {
newDriftMs = ( ( newDriftMs ) % driftLimitMs ) ;
}
2024-09-06 06:52:57 -07:00
# endif
2020-05-16 11:57:27 -04:00
2025-01-22 17:42:38 -08:00
setDrift ( m_driftMsMMA ) ;
//writeNoticeTextToUI(QDateTime::currentDateTimeUtc(), QString("Automatic Drift: %1").arg(driftAvg));
}
2020-05-06 20:08:30 -04:00
2025-01-22 17:42:38 -08:00
m_bDecoded = e . decoded > 0 ;
decodeDone ( ) ;
}
else if constexpr ( std : : is_same_v < T , JS8 : : Event : : Decoded > )
{
// XXX Grimy Q&D for the moment; we should revise the DecodedText
// constructor so we can go direct against it.
qDebug ( ) < < " DECODED "
< < e . utc
< < e . snr
< < e . xdt
< < e . frequency
< < e . data
< < e . type
< < e . quality
< < e . mode ;
QChar m = ' ~ ' ;
switch ( e . mode ) {
case 0 : m = ' A ' ; break ;
case 1 : m = ' B ' ; break ;
case 2 : m = ' C ' ; break ;
case 4 : m = ' E ' ; break ;
case 8 : m = ' I ' ; break ;
}
2019-11-19 10:39:27 -05:00
2025-01-22 17:42:38 -08:00
QString string1 = QString : : fromStdString ( e . data ) ;
2020-05-11 15:11:56 -04:00
2025-01-22 17:42:38 -08:00
// "125600 -23 -0.3 1858 A UqakNUuVKUa0 1 "
// "125600 0 -0.3 1859 A UqakNUuVKUa0 1 "
2018-07-30 17:16:45 -04:00
2025-01-22 17:42:38 -08:00
QString rawText = QString ( " %1 %2 %3 %4 %5 %6 %7 " )
. arg ( e . utc , 6 , 10 , QChar ( ' 0 ' ) ) // Right-aligned with 6 digits, padded with '0'
. arg ( e . snr , 3 , 10 , QChar ( ' ' ) ) // Right-aligned integer with 3 characters, padded with spaces
. arg ( e . xdt , 4 , ' f ' , 1 ) // Right-aligned float with 1 decimal point
. arg ( e . frequency , 4 , ' f ' , 0 ) // Right-aligned float with no decimal points
. arg ( m ) // Character
. arg ( string1 ) // Fixed string (assumed 12 characters)
. arg ( e . type , 3 , 10 , QChar ( ' ' ) ) ; // Right-aligned with 3 digits, padded with spaces
2020-05-11 15:11:56 -04:00
2025-01-22 17:42:38 -08:00
// XXX was if nap.ne.0 in Fortran
2020-05-11 15:11:56 -04:00
2025-01-22 17:42:38 -08:00
if ( e . quality < 0.17f )
{
qDebug ( ) < < " XXX LOW QUALITY DECODE " < < e . quality ;
rawText [ 22 ] = QChar ( ' ? ' ) ;
}
2020-05-11 15:11:56 -04:00
2025-01-22 17:42:38 -08:00
qDebug ( ) < < " RAW " < < rawText ;
2019-10-19 20:30:29 -04:00
2025-01-22 17:42:38 -08:00
DecodedText decodedtext { rawText } ;
2020-05-11 15:11:56 -04:00
2025-01-22 17:42:38 -08:00
// TODO: move this into a function
// frames are valid if they pass our dupe check (haven't seen the same frame in the past 1/2 decode period)
auto frameOffset = decodedtext . frequencyOffset ( ) ;
auto frameDedupeKey = QString ( " %1:%2 " ) . arg ( decodedtext . submode ( ) ) . arg ( decodedtext . frame ( ) ) ;
if ( m_messageDupeCache . contains ( frameDedupeKey ) ) {
auto cached = m_messageDupeCache . value ( frameDedupeKey ) ;
2018-08-06 23:39:43 -04:00
2025-01-22 17:42:38 -08:00
// check to see if the time since last seen is > 1/2 decode period
auto cachedDate = cached . date ;
if ( cachedDate . secsTo ( QDateTime : : currentDateTimeUtc ( ) ) < 0.5 * JS8 : : Submode : : period ( decodedtext . submode ( ) ) ) {
qDebug ( ) < < " duplicate frame at " < < cachedDate < < " using key " < < frameDedupeKey ;
return ;
}
2019-10-19 20:30:29 -04:00
2025-01-22 17:42:38 -08:00
// check to see if the frequency is near our previous frame
auto cachedFreq = cached . freq ;
if ( qAbs ( cachedFreq - frameOffset ) < = JS8 : : Submode : : rxThreshold ( decodedtext . submode ( ) ) ) {
qDebug ( ) < < " duplicate frame from " < < cachedFreq < < " and " < < frameOffset < < " using key " < < frameDedupeKey ;
return ;
}
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
// huzzah!
// if we make it here, the cache is invalid and will be bumped when we cache the new frame below
}
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
// frames are valid if they meet our minimum rx threshold for the submode
bool bValidFrame = decodedtext . snr ( ) > = JS8 : : Submode : : rxSNRThreshold ( decodedtext . submode ( ) ) ;
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
qDebug ( ) < < " valid " < < bValidFrame < < JS8 : : Submode : : name ( decodedtext . submode ( ) ) < < " decoded text " < < decodedtext . message ( ) ;
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
// skip if invalid
if ( ! bValidFrame ) {
return ;
}
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
// TODO: move this into a function
// compute time drift for non-dupe messages
if ( m_wideGraph - > shouldAutoSyncSubmode ( decodedtext . submode ( ) ) ) {
int m = decodedtext . submode ( ) ;
float xdt = decodedtext . dt ( ) ;
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
// if we're here at this point, we _should_ be operating a decode every second
//
// so we need to figure out where:
//
// 1) this current decode started
// 2) when that cycle _should_ have started
// 3) compute the delta
// 4) apply the drift
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
qint32 periodMs = 1000 * JS8 : : Submode : : period ( m ) ;
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
//writeNoticeTextToUI(now, QString("Decode at %1 (kin: %2, lastDecoded: %3)").arg(syncStart).arg(dec_data.params.kin).arg(m_lastDecodeStartMap.value(m)));
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
float expectedStartDelay = JS8 : : Submode : : startDelayMS ( m ) / 1000.0 ;
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
float decodedSignalTime = ( float ) syncStart / ( float ) RX_SAMPLE_RATE ;
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
//writeNoticeTextToUI(now, QString("--> started at %1 seconds into the start of my drifted minute").arg(decodedSignalTime));
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
//writeNoticeTextToUI(now, QString("--> we add a time delta of %1 seconds into the start of the cycle").arg(xdt));
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
// adjust for expected start delay
decodedSignalTime - = expectedStartDelay ;
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
// adjust for time delta
decodedSignalTime + = xdt ;
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
// ensure that we are within a 60 second minute
if ( decodedSignalTime < 0 ) {
decodedSignalTime + = 60.0f ;
} else if ( decodedSignalTime > 60 ) {
decodedSignalTime - = 60.0f ;
}
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
//writeNoticeTextToUI(now, QString("--> so signal adjusted started at %1 seconds into the start of my drifted minute").arg(decodedSignalTime));
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
qint32 decodedSignalTimeMs = 1000 * decodedSignalTime ;
qint32 cycleStartTimeMs = ( decodedSignalTimeMs / periodMs ) * periodMs ;
qint32 driftMs = cycleStartTimeMs - decodedSignalTimeMs ;
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
//writeNoticeTextToUI(now, QString("--> which is a drift adjustment of %1 milliseconds").arg(driftMs));
2020-05-17 14:21:38 -04:00
2025-01-22 17:42:38 -08:00
// if we have a large negative offset (say -14000), use the positive inverse of +1000
if ( driftMs + periodMs < qAbs ( driftMs ) ) {
driftMs + = periodMs ;
}
// if we have a large positive offset (say 14000, use the negative inverse of -1000)
else if ( qAbs ( driftMs - periodMs ) < driftMs ) {
driftMs - = periodMs ;
}
2020-04-15 11:11:49 -04:00
2025-01-22 17:42:38 -08:00
//writeNoticeTextToUI(now, QString("--> which is a corrected drift adjustment of %1 milliseconds").arg(driftMs));
2020-04-15 11:11:49 -04:00
2025-01-22 17:42:38 -08:00
qint32 newDrift = DriftingDateTime : : drift ( ) + driftMs ;
if ( newDrift < 0 ) {
newDrift % = - periodMs ;
} else {
newDrift % = periodMs ;
}
2019-11-20 15:15:58 -05:00
2025-01-22 17:42:38 -08:00
//writeNoticeTextToUI(now, QString("--> which is rounded to a total drift of %1 milliseconds for this period").arg(newDrift));
2018-10-10 09:04:56 -04:00
2025-01-22 17:42:38 -08:00
driftQueue . append ( newDrift ) ;
}
2019-10-19 20:30:29 -04:00
2025-01-22 17:42:38 -08:00
// if the frame is valid, cache it!
m_messageDupeCache [ frameDedupeKey ] = { QDateTime : : currentDateTimeUtc ( ) , decodedtext . submode ( ) , frameOffset } ;
2019-10-19 20:30:29 -04:00
2025-01-22 17:42:38 -08:00
// log valid frames to ALL.txt (and correct their timestamp format)
auto freq = dialFrequency ( ) ;
2019-10-19 20:30:29 -04:00
2025-01-22 17:42:38 -08:00
// if we changed frequencies, use the old frequency that we started the decode with
if ( m_decoderBusyFreq ! = freq ) {
freq = m_decoderBusyFreq ;
2019-10-19 20:30:29 -04:00
}
2018-07-02 15:08:26 -04:00
2025-01-22 17:42:38 -08:00
auto date = DriftingDateTime : : currentDateTimeUtc ( ) . toString ( " yyyy-MM-dd " ) ;
auto time = rawText . left ( 2 ) + " : " + rawText . mid ( 2 , 2 ) + " : " + rawText . mid ( 4 , 2 ) ;
writeAllTxt ( date + " " + time + rawText . mid ( 7 ) + " " + decodedtext . message ( ) , decodedtext . bits ( ) ) ;
2018-08-04 17:12:35 -04:00
2025-01-22 17:42:38 -08:00
ActivityDetail d = { } ;
CallDetail cd = { } ;
CommandDetail cmd = { } ;
CallDetail td = { } ;
2018-07-25 09:06:47 -04:00
2025-01-22 17:42:38 -08:00
// Parse General Activity
# if 1
bool shouldParseGeneralActivity = true ;
if ( shouldParseGeneralActivity & & ! decodedtext . messageWords ( ) . isEmpty ( ) ) {
int offset = decodedtext . frequencyOffset ( ) ;
2018-07-12 14:15:18 -04:00
2025-01-22 17:42:38 -08:00
if ( ! m_bandActivity . contains ( offset ) ) {
int const range = JS8 : : Submode : : rxThreshold ( decodedtext . submode ( ) ) ;
2019-10-19 20:30:29 -04:00
2025-01-22 17:42:38 -08:00
QList < int > offsets = generateOffsets ( offset - range , offset + range ) ;
2019-11-19 10:39:27 -05:00
2025-01-22 17:42:38 -08:00
foreach ( int prevOffset , offsets ) {
if ( ! m_bandActivity . contains ( prevOffset ) ) { continue ; }
m_bandActivity [ offset ] = m_bandActivity [ prevOffset ] ;
m_bandActivity . remove ( prevOffset ) ;
break ;
}
}
2019-11-19 10:39:27 -05:00
2025-01-22 17:42:38 -08:00
//ActivityDetail d = {};
d . isLowConfidence = decodedtext . isLowConfidence ( ) ;
d . isFree = ! decodedtext . isStandardMessage ( ) ;
d . isCompound = decodedtext . isCompound ( ) ;
d . isDirected = decodedtext . isDirectedMessage ( ) ;
d . bits = decodedtext . bits ( ) ;
d . dial = freq ;
d . offset = offset ;
d . text = decodedtext . message ( ) ;
d . utcTimestamp = DriftingDateTime : : currentDateTimeUtc ( ) ;
d . snr = decodedtext . snr ( ) ;
d . isBuffered = false ;
d . submode = decodedtext . submode ( ) ;
d . tdrift = m_wideGraph - > shouldAutoSyncSubmode ( d . submode ) ? DriftingDateTime : : drift ( ) / 1000.0 : decodedtext . dt ( ) ;
// if we have any "first" frame, and a buffer is already established, clear it...
int prevBufferOffset = - 1 ;
if ( ( ( d . bits & Varicode : : JS8CallFirst ) = = Varicode : : JS8CallFirst ) & & hasExistingMessageBuffer ( decodedtext . submode ( ) , d . offset , true , & prevBufferOffset ) ) {
qDebug ( ) < < " first message encountered, clearing existing buffer " < < prevBufferOffset ;
m_messageBuffer . remove ( d . offset ) ;
}
2018-07-13 04:44:41 -04:00
2025-01-22 17:42:38 -08:00
// if we have a data frame, and a message buffer has been established, buffer it...
if ( hasExistingMessageBuffer ( decodedtext . submode ( ) , d . offset , true , & prevBufferOffset ) & & ! decodedtext . isCompound ( ) & & ! decodedtext . isDirectedMessage ( ) ) {
qDebug ( ) < < " buffering data " < < d . dial < < d . offset < < d . text ;
d . isBuffered = true ;
m_messageBuffer [ d . offset ] . msgs . append ( d ) ;
// TODO: incremental display if it's "to" me.
}
m_rxActivityQueue . append ( d ) ;
m_bandActivity [ offset ] . append ( d ) ;
while ( m_bandActivity [ offset ] . count ( ) > 10 ) {
m_bandActivity [ offset ] . removeFirst ( ) ;
}
2019-11-19 10:39:27 -05:00
}
2025-01-22 17:42:38 -08:00
# endif
// Process compound callsign commands (put them in cache)"
# if 1
qDebug ( ) < < " decoded " < < decodedtext . frameType ( ) < < decodedtext . isCompound ( ) < < decodedtext . isDirectedMessage ( ) < < decodedtext . isHeartbeat ( ) ;
bool shouldProcessCompound = true ;
if ( shouldProcessCompound & & decodedtext . isCompound ( ) & & ! decodedtext . isDirectedMessage ( ) ) {
cd . call = decodedtext . compoundCall ( ) ;
cd . grid = decodedtext . extra ( ) ; // compound calls via pings may contain grid...
cd . snr = decodedtext . snr ( ) ;
cd . dial = freq ;
cd . offset = decodedtext . frequencyOffset ( ) ;
cd . utcTimestamp = DriftingDateTime : : currentDateTimeUtc ( ) ;
cd . bits = decodedtext . bits ( ) ;
cd . submode = decodedtext . submode ( ) ;
cd . tdrift = m_wideGraph - > shouldAutoSyncSubmode ( d . submode ) ? DriftingDateTime : : drift ( ) / 1000.0 : decodedtext . dt ( ) ;
// Only respond to HEARTBEATS...remember that CQ messages are "Alt" pings
if ( decodedtext . isHeartbeat ( ) ) {
if ( decodedtext . isAlt ( ) ) {
// this is a cq with a standard or compound call, ala "KN4CRD/P: @ALLCALL CQ CQ CQ"
cd . cqTimestamp = DriftingDateTime : : currentDateTimeUtc ( ) ;
// convert CQ to a directed command and process...
cmd . from = cd . call ;
cmd . to = " @ALLCALL " ;
cmd . cmd = " CQ " ;
cmd . snr = cd . snr ;
cmd . bits = cd . bits ;
cmd . grid = cd . grid ;
cmd . dial = cd . dial ;
cmd . offset = cd . offset ;
cmd . utcTimestamp = cd . utcTimestamp ;
cmd . tdrift = cd . tdrift ;
cmd . submode = cd . submode ;
cmd . text = decodedtext . message ( ) ;
// TODO: check bits so we only auto respond to "finished" cqs
m_rxCommandQueue . append ( cmd ) ;
// since this is no longer processed here we omit logging it here.
// if we change this behavior, we'd change this back to logging here.
// logCallActivity(cd, true);
// notification for cq
tryNotify ( " cq " ) ;
} else {
// convert HEARTBEAT to a directed command and process...
cmd . from = cd . call ;
cmd . to = " @HB " ;
cmd . cmd = " HEARTBEAT " ;
cmd . snr = cd . snr ;
cmd . bits = cd . bits ;
cmd . grid = cd . grid ;
cmd . dial = cd . dial ;
cmd . offset = cd . offset ;
cmd . utcTimestamp = cd . utcTimestamp ;
cmd . tdrift = cd . tdrift ;
cmd . submode = cd . submode ;
// TODO: check bits so we only auto respond to "finished" heartbeats
m_rxCommandQueue . append ( cmd ) ;
// notification for hb
tryNotify ( " hb " ) ;
}
2019-10-19 20:30:29 -04:00
2025-01-22 17:42:38 -08:00
} else {
qDebug ( ) < < " buffering compound call " < < cd . offset < < cd . call < < cd . bits ;
2018-10-10 09:04:56 -04:00
2025-01-22 17:42:38 -08:00
hasExistingMessageBuffer ( cd . submode , cd . offset , true , nullptr ) ;
m_messageBuffer [ cd . offset ] . compound . append ( cd ) ;
}
2019-11-19 10:39:27 -05:00
}
2025-01-22 17:42:38 -08:00
# endif
// Parse commands
// KN4CRD K1JT ?
# if 1
bool shouldProcessDirected = true ;
if ( shouldProcessDirected & & decodedtext . isDirectedMessage ( ) ) {
auto parts = decodedtext . directedMessage ( ) ;
cmd . from = parts . at ( 0 ) ;
cmd . to = parts . at ( 1 ) ;
cmd . cmd = parts . at ( 2 ) ;
cmd . dial = freq ;
cmd . offset = decodedtext . frequencyOffset ( ) ;
cmd . snr = decodedtext . snr ( ) ;
cmd . utcTimestamp = DriftingDateTime : : currentDateTimeUtc ( ) ;
cmd . bits = decodedtext . bits ( ) ;
cmd . extra = parts . length ( ) > 2 ? parts . mid ( 3 ) . join ( " " ) : " " ;
cmd . submode = decodedtext . submode ( ) ;
cmd . tdrift = m_wideGraph - > shouldAutoSyncSubmode ( cmd . submode ) ? DriftingDateTime : : drift ( ) / 1000.0 : decodedtext . dt ( ) ;
// if the command is a buffered command and its not the last frame OR we have from or to in a separate message (compound call)
if ( ( Varicode : : isCommandBuffered ( cmd . cmd ) & & ( cmd . bits & Varicode : : JS8CallLast ) ! = Varicode : : JS8CallLast ) | | cmd . from = = " <....> " | | cmd . to = = " <....> " ) {
qDebug ( ) < < " buffering cmd " < < cmd . dial < < cmd . offset < < cmd . cmd < < cmd . from < < cmd . to ;
// log complete buffered callsigns immediately
if ( cmd . from ! = " <....> " & & cmd . to ! = " <....> " ) {
CallDetail cmdcd = { } ;
cmdcd . call = cmd . from ;
cmdcd . bits = cmd . bits ;
cmdcd . snr = cmd . snr ;
cmdcd . dial = cmd . dial ;
cmdcd . offset = cmd . offset ;
cmdcd . utcTimestamp = cmd . utcTimestamp ;
cmdcd . ackTimestamp = cmd . to = = m_config . my_callsign ( ) ? cmd . utcTimestamp : QDateTime { } ;
cmdcd . tdrift = cmd . tdrift ;
cmdcd . submode = cmd . submode ;
logCallActivity ( cmdcd , false ) ;
logHeardGraph ( cmd . from , cmd . to ) ;
}
2019-10-19 20:30:29 -04:00
2025-01-22 17:42:38 -08:00
// merge any existing buffer to this frequency
hasExistingMessageBuffer ( cmd . submode , cmd . offset , true , nullptr ) ;
2019-10-19 20:30:29 -04:00
2025-01-22 17:42:38 -08:00
if ( cmd . to = = m_config . my_callsign ( ) ) {
d . shouldDisplay = true ;
}
2019-11-19 10:39:27 -05:00
2025-01-22 17:42:38 -08:00
m_messageBuffer [ cmd . offset ] . cmd = cmd ;
m_messageBuffer [ cmd . offset ] . msgs . clear ( ) ;
} else {
m_rxCommandQueue . append ( cmd ) ;
}
2018-07-24 15:19:02 -04:00
2025-01-22 17:42:38 -08:00
// check to see if this is a station we've heard 3rd party
bool shouldCaptureThirdPartyCallsigns = false ;
if ( shouldCaptureThirdPartyCallsigns & & Radio : : base_callsign ( cmd . to ) ! = Radio : : base_callsign ( m_config . my_callsign ( ) ) ) {
QString relayCall = QString ( " %1|%2 " ) . arg ( Radio : : base_callsign ( cmd . from ) ) . arg ( Radio : : base_callsign ( cmd . to ) ) ;
int snr = - 100 ;
if ( parts . length ( ) = = 4 ) {
snr = QString ( parts . at ( 3 ) ) . toInt ( ) ;
}
2018-10-10 09:04:56 -04:00
2025-01-22 17:42:38 -08:00
//CallDetail td = {};
td . through = cmd . from ;
td . call = cmd . to ;
td . grid = " " ;
td . snr = snr ;
td . dial = cmd . dial ;
td . offset = cmd . offset ;
td . utcTimestamp = cmd . utcTimestamp ;
td . tdrift = cmd . tdrift ;
td . submode = cmd . submode ;
logCallActivity ( td , true ) ;
logHeardGraph ( cmd . from , cmd . to ) ;
}
}
# endif
2018-10-10 09:04:56 -04:00
2018-07-24 02:53:01 -04:00
2025-01-22 17:42:38 -08:00
// Parse CQs
#if 0
bool shouldParseCQs = true ;
if ( shouldParseCQs & & decodedtext . isStandardMessage ( ) ) {
QString theircall ;
QString theirgrid ;
decodedtext . deCallAndGrid ( theircall , theirgrid ) ;
QStringList calls = Varicode : : parseCallsigns ( theircall ) ;
if ( ! calls . isEmpty ( ) & & ! calls . first ( ) . isEmpty ( ) ) {
theircall = calls . first ( ) ;
CallDetail d = { } ;
d . bits = decodedtext . bits ( ) ;
d . call = theircall ;
d . grid = theirgrid ;
d . snr = decodedtext . snr ( ) ;
d . freq = decodedtext . frequencyOffset ( ) ;
d . utcTimestamp = DriftingDateTime : : currentDateTimeUtc ( ) ;
m_callActivity [ d . call ] = d ;
}
}
# endif
// Parse standard message callsigns
// K1JT KN4CRD EM73
// KN4CRD K1JT -21
// K1JT KN4CRD R-12
// DE KN4CRD
// KN4CRD
#if 0
bool shouldParseCallsigns = false ;
if ( shouldParseCallsigns ) {
QStringList callsigns = Varicode : : parseCallsigns ( decodedtext . message ( ) ) ;
if ( ! callsigns . isEmpty ( ) ) {
// one callsign
// de [from]
// cq [from]
// two callsigns
// [from]: [to] ...
// [to] [from] [grid|signal]
QStringList grids = Varicode : : parseGrids ( decodedtext . message ( ) ) ;
// one callsigns are handled above... so we only need to handle two callsigns if it's a standard message
if ( decodedtext . isStandardMessage ( ) ) {
if ( callsigns . length ( ) = = 2 ) {
auto de_callsign = callsigns . last ( ) ;
// TODO: jsherer - put this in a function to record a callsign...
CallDetail d ;
d . call = de_callsign ;
d . grid = ! grids . empty ( ) ? grids . first ( ) : " " ;
d . snr = decodedtext . snr ( ) ;
d . freq = decodedtext . frequencyOffset ( ) ;
d . utcTimestamp = DriftingDateTime : : currentDateTimeUtc ( ) ;
m_callActivity [ Radio : : base_callsign ( de_callsign ) ] = d ;
}
}
}
}
# endif
}
} , event ) ;
2018-02-08 21:28:33 -05:00
}
2024-11-08 14:20:30 -08:00
bool
MainWindow : : hasExistingMessageBufferToMe ( int * const pOffset )
{
for ( auto const [ offset , buffer ] : m_messageBuffer . asKeyValueRange ( ) )
{
// if this is a valid buffer and it's to me...
if ( buffer . cmd . utcTimestamp . isValid ( ) & & ( buffer . cmd . to = = m_config . my_callsign ( ) | |
buffer . cmd . to = = Radio : : base_callsign ( m_config . my_callsign ( ) ) ) )
{
if ( pOffset ) * pOffset = offset ;
return true ;
2019-01-01 10:12:09 -05:00
}
2024-11-08 14:20:30 -08:00
}
2019-01-01 10:12:09 -05:00
2024-11-08 14:20:30 -08:00
return false ;
2019-01-01 10:12:09 -05:00
}
2019-11-09 09:25:03 -05:00
bool MainWindow : : hasExistingMessageBuffer ( int submode , int offset , bool drift , int * pPrevOffset ) {
2018-10-06 02:55:59 -04:00
if ( m_messageBuffer . contains ( offset ) ) {
if ( pPrevOffset ) * pPrevOffset = offset ;
return true ;
}
2024-10-11 07:31:31 -07:00
int const range = JS8 : : Submode : : rxThreshold ( submode ) ;
2019-10-05 14:55:15 -04:00
QList < int > offsets = generateOffsets ( offset - range , offset + range ) ;
2018-10-06 02:55:59 -04:00
foreach ( int prevOffset , offsets ) {
if ( ! m_messageBuffer . contains ( prevOffset ) ) { continue ; }
if ( drift ) {
m_messageBuffer [ offset ] = m_messageBuffer [ prevOffset ] ;
m_messageBuffer . remove ( prevOffset ) ;
}
if ( pPrevOffset ) * pPrevOffset = prevOffset ;
return true ;
}
return false ;
}
2019-10-05 14:55:15 -04:00
bool MainWindow : : hasClosedExistingMessageBuffer ( int offset ) {
2019-10-08 14:54:57 -04:00
#if 0
2019-10-05 14:55:15 -04:00
int range = 10 ;
2019-10-05 15:14:27 -04:00
if ( m_nSubMode = = Varicode : : JS8CallFast ) { range = 16 ; }
if ( m_nSubMode = = Varicode : : JS8CallTurbo ) { range = 32 ; }
2019-10-05 14:55:15 -04:00
return offset - range < = m_lastClosedMessageBufferOffset & & m_lastClosedMessageBufferOffset < = offset + range ;
2019-10-08 14:54:57 -04:00
# elif 0
int range = 10 ;
if ( m_nSubMode = = Varicode : : JS8CallFast ) { range = 16 ; }
if ( m_nSubMode = = Varicode : : JS8CallTurbo ) { range = 32 ; }
return m_lastClosedMessageBufferOffset - range < = offset & & offset < = m_lastClosedMessageBufferOffset + range ;
# else
Q_UNUSED ( offset ) ;
# endif
return false ;
2019-10-05 14:55:15 -04:00
}
2018-07-31 23:26:01 -04:00
void MainWindow : : logCallActivity ( CallDetail d , bool spot ) {
2019-01-20 23:08:57 -05:00
// don't log empty calls
2018-11-04 14:57:44 -05:00
if ( d . call . trimmed ( ) . isEmpty ( ) ) {
return ;
}
2019-01-20 23:08:57 -05:00
// don't log relay calls
if ( d . call . contains ( " > " ) ) {
return ;
}
2018-07-30 17:16:45 -04:00
if ( m_callActivity . contains ( d . call ) ) {
// update (keep grid)
CallDetail old = m_callActivity [ d . call ] ;
2018-08-27 22:04:17 -04:00
if ( d . grid . isEmpty ( ) & & ! old . grid . isEmpty ( ) ) {
2018-07-30 17:16:45 -04:00
d . grid = old . grid ;
}
2018-09-29 17:04:05 -04:00
if ( ! d . ackTimestamp . isValid ( ) & & old . ackTimestamp . isValid ( ) ) {
d . ackTimestamp = old . ackTimestamp ;
}
2019-03-23 21:19:24 -04:00
if ( ! d . cqTimestamp . isValid ( ) & & old . cqTimestamp . isValid ( ) ) {
d . cqTimestamp = old . cqTimestamp ;
}
2018-07-30 17:16:45 -04:00
m_callActivity [ d . call ] = d ;
} else {
// create
m_callActivity [ d . call ] = d ;
2018-07-31 23:26:01 -04:00
2019-10-10 22:23:06 -04:00
// notification of old and new callsigns
if ( m_logBook . hasWorkedBefore ( d . call , " " ) ) {
tryNotify ( " call_old " ) ;
} else {
tryNotify ( " call_new " ) ;
}
2019-10-10 20:40:30 -04:00
}
2018-07-31 23:26:01 -04:00
// enqueue for spotting to psk reporter
if ( spot ) {
m_rxCallQueue . append ( d ) ;
}
2018-07-30 17:16:45 -04:00
}
2019-01-14 09:48:53 -05:00
void MainWindow : : logHeardGraph ( QString from , QString to ) {
2019-01-09 18:27:33 -05:00
auto my_callsign = m_config . my_callsign ( ) ;
// hearing
if ( m_heardGraphOutgoing . contains ( my_callsign ) ) {
m_heardGraphOutgoing [ my_callsign ] . insert ( from ) ;
} else {
m_heardGraphOutgoing [ my_callsign ] . insert ( from ) ;
}
// heard by
if ( m_heardGraphIncoming . contains ( from ) ) {
m_heardGraphIncoming [ from ] . insert ( my_callsign ) ;
} else {
m_heardGraphIncoming [ from ] = { my_callsign } ;
}
2019-01-14 09:48:53 -05:00
if ( to = = " @ALLCALL " ) {
return ;
}
// hearing
if ( m_heardGraphOutgoing . contains ( from ) ) {
m_heardGraphOutgoing [ from ] . insert ( to ) ;
} else {
m_heardGraphOutgoing [ from ] = { to } ;
}
// heard by
2019-01-03 12:39:48 -05:00
if ( m_heardGraphIncoming . contains ( to ) ) {
m_heardGraphIncoming [ to ] . insert ( from ) ;
} else {
m_heardGraphIncoming [ to ] = { from } ;
}
}
2018-07-24 02:53:01 -04:00
QString MainWindow : : lookupCallInCompoundCache ( QString const & call ) {
2018-07-24 17:31:06 -04:00
QString myBaseCall = Radio : : base_callsign ( m_config . my_callsign ( ) ) ;
if ( call = = myBaseCall ) {
return m_config . my_callsign ( ) ;
}
2018-07-24 02:53:01 -04:00
return m_compoundCallCache . value ( call , call ) ;
}
2024-11-09 15:07:20 -08:00
void
MainWindow : : spotReport ( int const submode ,
int const dial ,
int const offset ,
int const snr ,
QString const & callsign ,
QString const & grid )
{
if ( ! m_config . spot_to_reporting_networks ( ) | |
( m_config . spot_blacklist ( ) . contains ( callsign ) | |
m_config . spot_blacklist ( ) . contains ( Radio : : base_callsign ( callsign ) ) ) ) return ;
2019-03-24 21:01:03 -04:00
2024-11-09 15:07:20 -08:00
Q_EMIT spotClientEnqueueSpot ( callsign , grid , submode , dial , offset , snr ) ;
2019-03-24 21:01:03 -04:00
}
2024-11-09 15:07:20 -08:00
void
MainWindow : : spotCmd ( CommandDetail const & cmd )
{
if ( ! m_config . spot_to_reporting_networks ( ) | |
( m_config . spot_blacklist ( ) . contains ( cmd . from ) | |
m_config . spot_blacklist ( ) . contains ( Radio : : base_callsign ( cmd . from ) ) ) ) return ;
2019-03-25 15:05:13 -04:00
2024-11-09 15:07:20 -08:00
QString cmdStr = cmd . cmd ;
if ( ! cmdStr . trimmed ( ) . isEmpty ( ) )
{
cmdStr = Varicode : : lstrip ( cmd . cmd ) ;
}
2019-03-25 15:05:13 -04:00
2024-11-09 15:07:20 -08:00
Q_EMIT spotClientEnqueueCmd ( cmdStr ,
cmd . from ,
cmd . to ,
cmd . relayPath ,
cmd . text ,
cmd . grid ,
cmd . extra ,
cmd . submode ,
cmd . dial ,
cmd . offset ,
cmd . snr ) ;
2019-03-25 15:05:13 -04:00
}
2020-03-30 23:07:40 -04:00
// KN4CRD: @APRSIS CMD :EMAIL-2 :email@domain.com booya{1
2024-09-06 06:27:15 -07:00
void MainWindow : : spotAprsCmd ( CommandDetail const & cmd ) {
2020-03-30 23:07:40 -04:00
if ( ! m_config . spot_to_reporting_networks ( ) ) return ;
2020-03-31 15:22:48 -04:00
if ( ! m_config . spot_to_aprs ( ) ) return ;
2020-04-10 20:27:34 -04:00
if ( m_config . spot_blacklist ( ) . contains ( cmd . from ) | | m_config . spot_blacklist ( ) . contains ( Radio : : base_callsign ( cmd . from ) ) ) return ;
2020-03-30 23:07:40 -04:00
if ( cmd . cmd ! = " CMD " ) return ;
qDebug ( ) < < " APRSISClient Enqueueing Third Party Text " < < cmd . from < < cmd . text ;
2024-09-06 06:30:31 -07:00
auto by_call = APRSISClient : : replaceCallsignSuffixWithSSID ( m_config . my_callsign ( ) , Radio : : base_callsign ( m_config . my_callsign ( ) ) ) ;
auto from_call = APRSISClient : : replaceCallsignSuffixWithSSID ( cmd . from , Radio : : base_callsign ( cmd . from ) ) ;
2020-03-30 23:07:40 -04:00
2020-03-31 15:22:48 -04:00
// we use a queued signal here so we can process these spots in a network thread
// to prevent blocking the gui/decoder while waiting on TCP
emit aprsClientEnqueueThirdParty ( by_call , from_call , cmd . text ) ;
2020-03-30 23:07:40 -04:00
}
2020-04-04 22:45:51 -04:00
void MainWindow : : spotAprsGrid ( int dial , int offset , int snr , QString callsign , QString grid ) {
2020-03-30 23:07:40 -04:00
if ( ! m_config . spot_to_reporting_networks ( ) ) return ;
2020-03-31 15:22:48 -04:00
if ( ! m_config . spot_to_aprs ( ) ) return ;
2020-04-10 20:27:34 -04:00
if ( m_config . spot_blacklist ( ) . contains ( callsign ) | | m_config . spot_blacklist ( ) . contains ( Radio : : base_callsign ( callsign ) ) ) return ;
2020-03-30 23:07:40 -04:00
if ( grid . length ( ) < 4 ) return ;
2020-04-04 22:45:51 -04:00
Frequency frequency = dial + offset ;
2020-03-30 23:07:40 -04:00
auto comment = QString ( " %1MHz %2dB " ) . arg ( Radio : : frequency_MHz_string ( frequency ) ) . arg ( Varicode : : formatSNR ( snr ) ) ;
if ( callsign . contains ( " / " ) ) {
comment = QString ( " %1 %2 " ) . arg ( callsign ) . arg ( comment ) ;
}
2020-04-01 20:42:29 -04:00
auto by_call = APRSISClient : : replaceCallsignSuffixWithSSID ( m_config . my_callsign ( ) , Radio : : base_callsign ( m_config . my_callsign ( ) ) ) ;
auto from_call = APRSISClient : : replaceCallsignSuffixWithSSID ( callsign , Radio : : base_callsign ( callsign ) ) ;
2020-03-30 23:07:40 -04:00
2020-03-31 15:22:48 -04:00
// we use a queued signal here so we can process these spots in a network thread
// to prevent blocking the gui/decoder while waiting on TCP
emit aprsClientEnqueueSpot ( by_call , from_call , grid , comment ) ;
2020-03-30 23:07:40 -04:00
}
2024-11-09 10:25:45 -08:00
void
2024-11-09 10:31:23 -08:00
MainWindow : : pskLogReport ( QString const & mode ,
int const dial ,
int const offset ,
int const snr ,
QString const & callsign ,
QString const & grid )
2024-11-09 10:25:45 -08:00
{
if ( ! m_config . spot_to_reporting_networks ( ) | |
( m_config . spot_blacklist ( ) . contains ( callsign ) | |
m_config . spot_blacklist ( ) . contains ( Radio : : base_callsign ( callsign ) ) ) ) return ;
2018-07-20 23:09:17 -04:00
2024-11-09 10:31:23 -08:00
Q_EMIT pskReporterAddRemoteStation ( callsign ,
grid ,
dial + offset ,
mode ,
snr ) ;
2018-07-20 23:09:17 -04:00
}
2018-02-08 21:28:33 -05:00
//------------------------------------------------------------- //guiUpdate()
void MainWindow : : guiUpdate ( )
{
2024-11-09 20:25:26 -08:00
static char message [ 29 ] ;
static char msgsent [ 29 ] ;
static int msgibits ;
QElapsedTimer timer ;
2018-10-29 17:56:31 -04:00
2024-11-09 20:25:26 -08:00
timer . start ( ) ;
2018-10-15 00:59:38 -04:00
2018-02-08 21:28:33 -05:00
if ( m_TRperiod = = 0 ) m_TRperiod = 60 ;
2024-10-08 08:52:58 -07:00
double tx1 = 0.0 ;
2024-10-27 13:06:57 -07:00
double tx2 = JS8 : : Submode : : txDuration ( m_nSubMode ) ;
2018-09-23 10:22:28 -04:00
2018-02-08 21:28:33 -05:00
if ( tx2 > m_TRperiod ) tx2 = m_TRperiod ;
2018-09-18 17:24:07 -04:00
qint64 ms = DriftingDateTime : : currentMSecsSinceEpoch ( ) % 86400000 ;
2018-02-08 21:28:33 -05:00
int nsec = ms / 1000 ;
double tsec = 0.001 * ms ;
2018-10-17 11:19:27 -04:00
double t2p = fmod ( tsec , m_TRperiod ) ;
2018-02-08 21:28:33 -05:00
2018-09-23 10:22:28 -04:00
// how long is the tx?
m_bTxTime = ( t2p > = tx1 ) and ( t2p < tx2 ) ;
2018-02-08 21:28:33 -05:00
2018-10-29 17:56:31 -04:00
if ( m_tune ) m_bTxTime = true ; // "Tune" and tones take precedence
2018-02-08 21:28:33 -05:00
if ( m_transmitting or m_auto or m_tune ) {
2018-09-18 17:24:07 -04:00
m_dateTimeLastTX = DriftingDateTime : : currentDateTime ( ) ;
2018-03-05 14:49:51 -05:00
2018-02-08 21:28:33 -05:00
// Don't transmit another mode in the 30 m WSPR sub-band
2024-10-20 13:54:14 -07:00
Frequency onAirFreq = m_freqNominal + freq ( ) ;
2018-09-16 11:30:13 -04:00
//qDebug() << "transmitting on" << onAirFreq;
2024-10-10 06:43:24 -07:00
if ( ( onAirFreq > 10139900 & &
onAirFreq < 10140320 ) )
{
m_bTxTime = false ;
2018-02-08 21:28:33 -05:00
if ( m_auto ) auto_tx_mode ( false ) ;
2024-10-10 06:43:24 -07:00
if ( onAirFreq ! = m_onAirFreq0 )
{
m_onAirFreq0 = onAirFreq ;
2024-10-10 08:46:24 -07:00
QTimer : : singleShot ( 0 , [ this ]
2024-10-10 06:43:24 -07:00
{
MessageBox : : warning_message ( this ,
tr ( " WSPR Guard Band " ) ,
tr ( " Please choose another Tx frequency. "
" The app will not knowingly transmit another "
" mode in the WSPR sub-band on 30m. " ) ) ;
} ) ;
2018-02-08 21:28:33 -05:00
}
}
2024-10-26 10:37:04 -07:00
auto const msgLength = QStringView ( m_nextFreeTextMsg ) . trimmed ( ) . length ( ) ;
auto const fTR = float ( ( ms % ( 1000 * m_TRperiod ) ) ) /
( 1000 * m_TRperiod ) ;
2018-09-23 10:22:28 -04:00
// TODO: stop
2024-10-26 10:37:04 -07:00
if ( msgLength = = 0 & & ! m_tune ) on_stopTxButton_clicked ( ) ;
2018-08-05 11:33:30 -04:00
2018-10-29 17:56:31 -04:00
// 15.0 - 12.6
2024-10-08 08:59:39 -07:00
double const ratio = JS8 : : Submode : : computeRatio ( m_nSubMode , m_TRperiod ) ;
2019-09-05 14:07:24 -04:00
if ( fTR > 1.0 - ratio & & fTR < 1.0 ) {
2018-10-29 17:56:31 -04:00
if ( ! m_deadAirTone ) {
qDebug ( ) < < " should start dead air tone " ;
m_deadAirTone = true ;
}
} else {
if ( m_deadAirTone ) {
qDebug ( ) < < " should stop dead air tone " ;
m_deadAirTone = false ;
}
}
2019-09-05 14:07:24 -04:00
// the late threshold is the dead air time minus the tx delay time
float lateThreshold = ratio - ( m_config . txDelay ( ) / m_TRperiod ) ;
if ( m_nSubMode = = Varicode : : JS8CallFast ) {
// for the faster mode, only allow 3/4 late threshold
lateThreshold * = 0.75 ;
}
else if ( m_nSubMode = = Varicode : : JS8CallTurbo ) {
// for the turbo mode, only allow 1/2 late threshold
lateThreshold * = 0.5 ;
}
2019-12-09 14:00:23 -05:00
else if ( m_nSubMode = = Varicode : : JS8CallUltra ) {
// for the ultra mode, only allow 1/2 late threshold
lateThreshold * = 0.5 ;
}
2024-10-26 07:13:47 -07:00
if ( m_iptt = = 0 & & ( ( m_bTxTime & & fTR < lateThreshold & & msgLength > 0 ) | | m_tune ) )
{
2018-08-05 11:33:30 -04:00
//### Allow late starts
2024-10-26 07:13:47 -07:00
m_iptt = 1 ;
2018-02-08 21:28:33 -05:00
setRig ( ) ;
2024-10-20 13:54:14 -07:00
setXIT ( freq ( ) ) ;
2019-01-01 10:29:35 -05:00
emitPTT ( true ) ;
2018-02-08 21:28:33 -05:00
m_tx_when_ready = true ;
2018-10-29 09:24:52 -04:00
2019-09-05 14:07:24 -04:00
qDebug ( ) < < " start threshold " < < fTR < < lateThreshold < < ms ;
2018-02-08 21:28:33 -05:00
}
2018-09-23 10:22:28 -04:00
// TODO: stop
if ( ! m_bTxTime and ! m_tune ) m_btxok = false ; //Time to stop transmitting
2018-02-08 21:28:33 -05:00
}
// Calculate Tx tones when needed
2024-10-26 07:13:47 -07:00
if ( ( m_iptt = = 1 & & m_iptt0 = = 0 ) | | m_restart ) {
2018-02-08 21:28:33 -05:00
//----------------------------------------------------------------------
2024-10-26 10:37:04 -07:00
copyMessage ( m_nextFreeTextMsg , message ) ;
2018-09-23 10:22:28 -04:00
2024-12-25 22:47:39 -08:00
if ( m_lastMessageSent ! = m_currentMessage | |
m_lastMessageType ! = m_currentMessageType )
{
m_lastMessageSent = m_currentMessage ;
m_lastMessageType = m_currentMessageType ;
}
2018-09-23 10:22:28 -04:00
2018-02-08 21:28:33 -05:00
m_currentMessageType = 0 ;
2018-09-23 10:22:28 -04:00
2024-12-25 22:47:39 -08:00
if ( m_tune )
{
itone [ 0 ] = 0 ;
}
else
{
2025-01-22 20:22:29 -08:00
JS8 : : encode ( m_i3bit ,
JS8 : : Costas : : array ( JS8 : : Submode : : costas ( m_nSubMode ) ) ,
message ,
const_cast < int * > ( reinterpret_cast < volatile int * > ( itone ) ) ) ; // XXX ick...
2019-09-05 14:07:24 -04:00
2024-12-25 22:47:39 -08:00
std : : fill_n ( std : : begin ( msgsent ) , 22 , ' ' ) ;
std : : copy_n ( std : : begin ( message ) , 12 , std : : begin ( msgsent ) ) ;
2020-01-04 15:55:20 -05:00
2019-09-27 22:44:27 -04:00
qDebug ( ) < < " -> msg: " < < message ;
qDebug ( ) < < " -> bit: " < < m_i3bit ;
2018-11-01 22:15:30 -04:00
2024-12-25 22:47:39 -08:00
for ( int i = 0 ; i < 7 ; + + i ) qDebug ( ) < < " -> tone " < < i < < " = " < < itone [ i ] ;
for ( int i = JS8_NUM_SYMBOLS - 7 ; i < JS8_NUM_SYMBOLS ; + + i ) qDebug ( ) < < " -> tone " < < i < < " = " < < itone [ i ] ;
msgibits = m_i3bit ;
msgsent [ 22 ] = 0 ;
m_currentMessage = QString : : fromLatin1 ( msgsent ) . trimmed ( ) ;
2018-11-07 10:41:53 -05:00
m_currentMessageBits = msgibits ;
2019-02-23 09:50:02 -05:00
emitTones ( ) ;
2018-02-08 21:28:33 -05:00
}
if ( m_tune ) {
m_currentMessage = " TUNE " ;
m_currentMessageType = - 1 ;
}
if ( m_restart ) {
write_transmit_entry ( " ALL.TXT " ) ;
}
2018-09-18 17:24:07 -04:00
auto t2 = DriftingDateTime : : currentDateTimeUtc ( ) . toString ( " hhmm " ) ;
2024-08-31 09:40:28 -07:00
auto msg_parts = m_currentMessage . split ( ' ' , Qt : : SkipEmptyParts ) ;
2018-02-08 21:28:33 -05:00
if ( msg_parts . size ( ) > 2 ) {
// clean up short code forms
msg_parts [ 0 ] . remove ( QChar { ' < ' } ) ;
msg_parts [ 1 ] . remove ( QChar { ' > ' } ) ;
}
if ( ( m_currentMessageType < 6 | | 7 = = m_currentMessageType )
& & msg_parts . length ( ) > = 3
& & ( msg_parts [ 1 ] = = m_config . my_callsign ( ) | |
msg_parts [ 1 ] = = m_baseCall ) )
{
int i1 ;
bool ok ;
i1 = msg_parts [ 2 ] . toInt ( & ok ) ;
if ( ok and i1 > = - 50 and i1 < 50 )
{
m_rptSent = msg_parts [ 2 ] ;
} else {
if ( msg_parts [ 2 ] . mid ( 0 , 1 ) = = " R " )
{
i1 = msg_parts [ 2 ] . mid ( 1 ) . toInt ( & ok ) ;
if ( ok and i1 > = - 50 and i1 < 50 )
{
m_rptSent = msg_parts [ 2 ] . mid ( 1 ) ;
}
}
}
}
m_restart = false ;
//----------------------------------------------------------------------
}
2024-10-26 07:13:47 -07:00
if ( m_iptt = = 1 & & m_iptt0 = = 0 )
2018-02-08 21:28:33 -05:00
{
auto const & current_message = QString : : fromLatin1 ( msgsent ) ;
2024-10-27 13:06:57 -07:00
if ( m_config . watchdog ( ) & & current_message ! = m_msgSent0 ) {
2018-10-28 11:24:00 -04:00
// new messages don't reset the idle timer :|
// tx_watchdog (false); // in case we are auto sequencing
2018-02-08 21:28:33 -05:00
m_msgSent0 = current_message ;
}
if ( ! m_tune ) {
write_transmit_entry ( " ALL.TXT " ) ;
}
2018-07-18 09:04:58 -04:00
// TODO: jsherer - perhaps an on_transmitting signal?
2019-11-11 22:39:02 -05:00
m_lastTxStartTime = DriftingDateTime : : currentDateTimeUtc ( ) ;
2018-07-18 09:04:58 -04:00
2018-02-08 21:28:33 -05:00
m_transmitting = true ;
transmitDisplay ( true ) ;
statusUpdate ( ) ;
}
2018-09-23 10:22:28 -04:00
// TODO: stop
2024-10-26 07:13:47 -07:00
if ( ! m_btxok & & m_btxok0 & & m_iptt = = 1 ) stopTx ( ) ;
2018-02-08 21:28:33 -05:00
2018-07-12 14:15:18 -04:00
//Once per second:
2018-02-08 21:28:33 -05:00
if ( nsec ! = m_sec0 ) {
2018-10-15 01:04:57 -04:00
if ( m_monitoring or m_transmitting ) {
2018-02-08 21:28:33 -05:00
progressBar . setMaximum ( m_TRperiod ) ;
int isec = int ( fmod ( tsec , m_TRperiod ) ) ;
progressBar . setValue ( isec ) ;
2018-10-15 01:04:57 -04:00
} else {
2018-02-08 21:28:33 -05:00
progressBar . setValue ( 0 ) ;
}
if ( m_transmitting ) {
2024-08-31 10:38:59 -07:00
tx_status_label . setStyleSheet ( " QLabel{background-color: #ff2222; color:#000} " ) ;
2018-02-08 21:28:33 -05:00
if ( m_tune ) {
tx_status_label . setText ( " Tx: TUNE " ) ;
} else {
2024-08-31 10:38:59 -07:00
auto message = DecodedText ( msgsent , msgibits , m_nSubMode ) . message ( ) ;
tx_status_label . setText ( QString ( " Tx: %1 " ) . arg ( message ) . left ( 40 ) . trimmed ( ) ) ;
2018-02-08 21:28:33 -05:00
}
2019-05-26 20:06:28 -04:00
transmitDisplay ( true ) ;
2018-11-15 21:57:04 -05:00
2018-02-08 21:28:33 -05:00
} else if ( m_monitoring ) {
2018-10-28 11:24:00 -04:00
if ( m_tx_watchdog ) {
2024-08-31 10:39:50 -07:00
tx_status_label . setStyleSheet ( " QLabel{background-color: #000; color:#fff} " ) ;
2019-02-01 19:19:13 -05:00
tx_status_label . setText ( " Idle timeout " ) ;
2018-10-28 11:24:00 -04:00
} else {
2018-12-20 20:55:55 -05:00
tx_status_label . setStyleSheet ( " QLabel{background-color: #22ff22} " ) ;
2019-11-11 10:06:24 -05:00
tx_status_label . setText ( m_decoderBusy ? " Decoding " : " Receiving " ) ;
2018-02-08 21:28:33 -05:00
}
transmitDisplay ( false ) ;
2024-09-14 18:34:38 -07:00
} else if ( ! m_tx_watchdog ) {
2018-02-08 21:28:33 -05:00
tx_status_label . setStyleSheet ( " " ) ;
tx_status_label . setText ( " " ) ;
}
2018-09-23 16:47:07 -04:00
auto drift = DriftingDateTime : : drift ( ) ;
2018-09-18 17:24:07 -04:00
QDateTime t = DriftingDateTime : : currentDateTimeUtc ( ) ;
2018-11-12 16:34:31 -05:00
QStringList parts ;
2020-05-17 14:21:38 -04:00
parts < < ( t . time ( ) . toString ( ) + ( ! drift ? " " : QString ( " (%1%2ms) " ) . arg ( drift > 0 ? " + " : " " ) . arg ( drift ) ) ) ;
2018-11-12 16:34:31 -05:00
parts < < t . date ( ) . toString ( " yyyy MMM dd " ) ;
ui - > labUTC - > setText ( parts . join ( " \n " ) ) ;
2018-07-02 15:08:26 -04:00
2018-11-25 22:12:54 -05:00
#if 0
2018-10-28 09:47:30 -04:00
auto delta = t . secsTo ( m_nextHeartbeat ) ;
2018-10-31 10:50:51 -04:00
QString ping ;
2018-11-25 22:12:54 -05:00
if ( heartbeatTimer . isActive ( ) ) {
if ( delta > 0 ) {
ping = QString ( " %1 s " ) . arg ( delta ) ;
2018-10-31 10:50:51 -04:00
} else {
2018-11-25 22:12:54 -05:00
ping = " queued! " ;
2018-10-31 10:50:51 -04:00
}
} else if ( m_nextHeartPaused ) {
ping = " paused " ;
} else {
2018-11-25 22:12:54 -05:00
ping = " on demand " ;
2018-10-31 10:50:51 -04:00
}
2018-10-28 09:47:30 -04:00
ui - > labHeartbeat - > setText ( QString ( " Next Heartbeat: %1 " ) . arg ( ping ) ) ;
2018-11-25 22:12:54 -05:00
# endif
2018-08-07 23:11:11 -04:00
auto callLabel = m_config . my_callsign ( ) ;
if ( m_config . use_dynamic_grid ( ) & & ! m_config . my_grid ( ) . isEmpty ( ) ) {
callLabel = QString ( " %1 - %2 " ) . arg ( callLabel ) . arg ( m_config . my_grid ( ) ) ;
}
ui - > labCallsign - > setText ( callLabel ) ;
2018-07-02 15:08:26 -04:00
2024-09-14 18:34:38 -07:00
if ( ! m_monitoring ) {
2018-02-08 21:28:33 -05:00
ui - > signal_meter_widget - > setValue ( 0 , 0 ) ;
}
2018-07-09 20:51:42 -04:00
2018-02-08 21:28:33 -05:00
m_sec0 = nsec ;
2018-07-05 04:56:46 -04:00
2018-07-12 14:15:18 -04:00
// once per period
2018-08-09 12:52:48 -04:00
if ( m_sec0 % m_TRperiod = = 0 ) {
tryBandHop ( ) ;
}
2018-10-15 02:29:40 -04:00
// at the end of the period
2018-08-06 23:39:43 -04:00
bool forceDirty = false ;
2018-10-15 00:59:38 -04:00
if ( m_sec0 % ( m_TRperiod - 2 ) = = 0 | |
2018-10-16 09:49:53 -04:00
m_sec0 % ( m_TRperiod ) = = 0 | |
2019-10-29 21:55:43 -04:00
m_sec0 % ( m_TRperiod + 2 ) = = 0 ) {
2018-10-15 02:29:40 -04:00
// force rx dirty at the end of the period
2018-08-06 23:39:43 -04:00
forceDirty = true ;
2018-07-12 14:15:18 -04:00
}
2019-10-29 21:55:43 -04:00
if ( ! forceDirty ) {
forceDirty = ! m_rxActivityQueue . isEmpty ( ) ;
}
2018-07-12 14:15:18 -04:00
2018-07-31 14:34:36 -04:00
// update the dial frequency once per second..
displayDialFrequency ( ) ;
2018-11-28 22:53:18 -05:00
// update repeat button text once per second..
updateRepeatButtonDisplay ( ) ;
2018-12-22 22:47:48 -05:00
// once per second...but not when we're transmitting, unless it's in the first second...
2018-12-27 22:39:13 -05:00
if ( ! m_transmitting | | ( m_sec0 % ( m_TRperiod ) = = 0 ) ) {
2018-10-15 02:10:48 -04:00
// process all received activity...
processActivity ( forceDirty ) ;
2018-07-05 04:56:46 -04:00
2018-10-15 02:10:48 -04:00
// process outgoing tx queue...
processTxQueue ( ) ;
2018-07-31 17:48:33 -04:00
2018-10-15 02:10:48 -04:00
// once processed, lets update the display...
displayActivity ( forceDirty ) ;
updateButtonDisplay ( ) ;
updateTextDisplay ( ) ;
2018-10-15 00:59:38 -04:00
}
2018-07-06 02:36:40 -04:00
}
2018-07-13 10:32:58 -04:00
// once per 100ms
2018-07-09 20:55:54 -04:00
displayTransmit ( ) ;
2024-10-26 07:13:47 -07:00
m_iptt0 = m_iptt ;
m_btxok0 = m_btxok ;
2018-10-15 00:59:38 -04:00
2024-11-09 20:25:26 -08:00
// Compute the processing time and adjust loop to hit the next 100ms
2018-10-15 00:59:38 -04:00
2024-11-09 20:25:26 -08:00
m_guiTimer . start ( std : : max ( std : : chrono : : milliseconds ( 100 - timer . elapsed ( ) ) ,
std : : chrono : : milliseconds : : zero ( ) ) ) ;
2018-03-05 14:49:51 -05:00
} //End of guiUpdate
2018-02-08 21:28:33 -05:00
2018-07-02 22:54:26 -04:00
void MainWindow : : startTx ( )
{
2018-10-28 11:24:00 -04:00
# if IDLE_BLOCKS_TX
2018-09-11 11:20:17 -04:00
if ( m_tx_watchdog ) {
return ;
}
2018-10-28 11:24:00 -04:00
# endif
2020-04-15 15:23:41 -04:00
2019-09-27 22:44:27 -04:00
auto text = ui - > extFreeTextMsgEdit - > toPlainText ( ) ;
if ( ! ensureCreateMessageReady ( text ) ) {
return ;
}
2018-09-11 11:20:17 -04:00
2018-07-02 22:54:26 -04:00
if ( ! prepareNextMessageFrame ( ) ) {
return ;
}
2024-10-10 08:21:36 -07:00
m_dateTimeQSOOn = QDateTime { } ;
2018-07-02 22:54:26 -04:00
if ( m_transmitting ) m_restart = true ;
2024-10-17 05:31:22 -07:00
if ( ! m_auto ) auto_tx_mode ( true ) ;
2018-07-03 03:29:26 -04:00
// disallow editing of the text while transmitting
2019-09-26 23:19:00 -04:00
// ui->extFreeTextMsgEdit->setReadOnly(true);
2018-07-15 11:00:57 -04:00
update_dynamic_property ( ui - > extFreeTextMsgEdit , " transmitting " , true ) ;
2018-12-12 22:14:45 -05:00
// update the tx button display
updateTxButtonDisplay ( ) ;
2018-07-02 22:54:26 -04:00
}
2018-02-08 21:28:33 -05:00
void MainWindow : : startTx2 ( )
{
2024-10-27 20:33:26 -07:00
if ( m_modulator - > isIdle ( ) )
{
transmit ( ) ;
ui - > signal_meter_widget - > setValue ( 0 , 0 ) ;
2018-02-08 21:28:33 -05:00
}
}
void MainWindow : : stopTx ( )
{
Q_EMIT endTransmitMessage ( ) ;
2019-09-21 15:24:05 -04:00
2019-09-05 14:07:24 -04:00
auto dt = DecodedText ( m_currentMessage . trimmed ( ) , m_currentMessageBits , m_nSubMode ) ;
2018-09-23 16:20:26 -04:00
last_tx_label . setText ( " Last Tx: " + dt . message ( ) ) ; //m_currentMessage.trimmed());
2019-09-26 23:19:00 -04:00
// TODO: uncomment if we want to mark after the frame is sent.
//// // start message marker
//// // - keep track of the total message sent so far, and mark it having been sent
//// m_totalTxMessage.append(dt.message());
//// ui->extFreeTextMsgEdit->setCharsSent(m_totalTxMessage.length());
//// qDebug() << "total sent:\n" << m_totalTxMessage;
//// // end message marker
2019-09-21 15:24:05 -04:00
2024-10-26 07:13:47 -07:00
m_btxok = false ;
m_transmitting = false ;
m_iptt = 0 ;
2019-11-11 22:39:02 -05:00
m_lastTxStopTime = DriftingDateTime : : currentDateTimeUtc ( ) ;
2018-02-08 21:28:33 -05:00
if ( ! m_tx_watchdog ) {
tx_status_label . setStyleSheet ( " " ) ;
tx_status_label . setText ( " " ) ;
}
2018-03-08 17:01:33 -05:00
2018-10-28 11:24:00 -04:00
# if IDLE_BLOCKS_TX
2018-09-16 11:30:13 -04:00
bool shouldContinue = ! m_tx_watchdog & & prepareNextMessageFrame ( ) ;
2018-10-28 11:24:00 -04:00
# else
bool shouldContinue = prepareNextMessageFrame ( ) ;
# endif
2018-10-17 11:19:27 -04:00
if ( ! shouldContinue ) {
2018-07-03 03:29:26 -04:00
// TODO: jsherer - split this up...
2019-09-25 20:26:41 -04:00
ui - > extFreeTextMsgEdit - > clear ( ) ;
2018-07-03 03:29:26 -04:00
ui - > extFreeTextMsgEdit - > setReadOnly ( false ) ;
2018-07-15 11:00:57 -04:00
update_dynamic_property ( ui - > extFreeTextMsgEdit , " transmitting " , false ) ;
2018-07-02 22:54:26 -04:00
on_stopTxButton_clicked ( ) ;
2018-08-08 17:15:49 -04:00
tryRestoreFreqOffset ( ) ;
2018-07-02 22:54:26 -04:00
}
2018-09-23 10:22:28 -04:00
ptt0Timer . start ( 200 ) ; //end-of-transmission sequencer delay stopTx2
2018-02-08 21:28:33 -05:00
monitor ( true ) ;
statusUpdate ( ) ;
}
2019-05-19 11:17:08 -04:00
/**
* stopTx2 is called from stopTx to open the PTT
*/
2019-05-19 10:13:46 -04:00
void MainWindow : : stopTx2 ( ) {
2019-05-19 11:17:08 -04:00
// GM8JCF: m_txFrameCount is set to the number of frames to be transmitted when the send button is pressed
2019-05-19 10:13:46 -04:00
// and remains at that count until the last frame is transmitted.
// So, we keep the PTT ON so long as m_txFrameCount is non-zero
2019-05-26 20:06:28 -04:00
qDebug ( ) < < " stopTx2 frames left " < < m_txFrameCount ;
2019-05-19 11:17:08 -04:00
// If we're holding the PTT and there are more frames to transmit, do not emit the PTT signal
2019-07-11 20:37:40 -04:00
if ( ! m_tune & & m_config . hold_ptt ( ) & & m_txFrameCount > 0 ) {
2019-05-19 11:17:08 -04:00
return ;
2019-05-19 10:13:46 -04:00
}
2019-05-19 11:17:08 -04:00
// Otherwise, emit the PTT signal
emitPTT ( false ) ;
2019-05-19 10:13:46 -04:00
}
2018-02-08 21:28:33 -05:00
void MainWindow : : TxAgain ( )
{
auto_tx_mode ( true ) ;
}
2018-08-28 15:45:23 -04:00
void MainWindow : : cacheActivity ( QString key ) {
2019-03-24 09:59:24 -04:00
m_callActivityBandCache [ key ] = m_callActivity ;
m_bandActivityBandCache [ key ] = m_bandActivity ;
m_rxTextBandCache [ key ] = ui - > textEditRX - > toHtml ( ) ;
m_heardGraphIncomingBandCache [ key ] = m_heardGraphIncoming ;
m_heardGraphOutgoingBandCache [ key ] = m_heardGraphOutgoing ;
2018-08-28 15:45:23 -04:00
}
void MainWindow : : restoreActivity ( QString key ) {
2019-03-24 09:59:24 -04:00
if ( m_callActivityBandCache . contains ( key ) ) {
m_callActivity = m_callActivityBandCache [ key ] ;
2018-08-28 15:45:23 -04:00
}
2019-03-24 09:59:24 -04:00
if ( m_bandActivityBandCache . contains ( key ) ) {
m_bandActivity = m_bandActivityBandCache [ key ] ;
2018-08-28 15:45:23 -04:00
}
2019-03-24 09:59:24 -04:00
if ( m_rxTextBandCache . contains ( key ) ) {
ui - > textEditRX - > setHtml ( m_rxTextBandCache [ key ] ) ;
}
if ( m_heardGraphIncomingBandCache . contains ( key ) ) {
m_heardGraphIncoming = m_heardGraphIncomingBandCache [ key ] ;
}
if ( m_heardGraphOutgoingBandCache . contains ( key ) ) {
m_heardGraphOutgoing = m_heardGraphOutgoingBandCache [ key ] ;
2018-08-28 15:45:23 -04:00
}
displayActivity ( true ) ;
}
2018-07-06 16:54:26 -04:00
void MainWindow : : clearActivity ( ) {
2019-06-12 23:54:02 -04:00
qDebug ( ) < < " clear activity " ;
2020-05-16 14:41:22 -04:00
2018-10-28 09:47:30 -04:00
m_callSeenHeartbeat . clear ( ) ;
2018-07-24 09:23:44 -04:00
m_compoundCallCache . clear ( ) ;
2018-07-06 16:54:26 -04:00
m_rxCallCache . clear ( ) ;
2018-07-31 23:26:01 -04:00
m_rxCallQueue . clear ( ) ;
2018-07-14 09:26:39 -04:00
m_rxRecentCache . clear ( ) ;
2018-07-06 16:54:26 -04:00
m_rxDirectedCache . clear ( ) ;
2018-07-13 10:32:58 -04:00
m_rxCommandQueue . clear ( ) ;
2018-07-27 13:53:07 -04:00
m_lastTxMessage . clear ( ) ;
2018-07-06 18:25:27 -04:00
2019-01-02 12:27:16 -05:00
refreshInboxCounts ( ) ;
2018-10-29 02:02:58 -04:00
resetTimeDeltaAverage ( ) ;
2020-05-16 14:41:22 -04:00
clearBandActivity ( ) ;
clearRXActivity ( ) ;
clearCallActivity ( ) ;
2018-07-13 15:45:00 -04:00
2020-05-16 14:41:22 -04:00
displayActivity ( true ) ;
}
void MainWindow : : clearBandActivity ( ) {
qDebug ( ) < < " clear band activity " ;
m_bandActivity . clear ( ) ;
2024-09-28 06:40:44 -07:00
ui - > tableWidgetRXAll - > setRowCount ( 0 ) ;
2018-07-06 18:25:27 -04:00
2020-05-16 14:41:22 -04:00
resetTimeDeltaAverage ( ) ;
displayBandActivity ( ) ;
}
void MainWindow : : clearRXActivity ( ) {
qDebug ( ) < < " clear rx activity " ;
m_rxFrameBlockNumbers . clear ( ) ;
m_rxActivityQueue . clear ( ) ;
2018-07-06 18:25:27 -04:00
ui - > textEditRX - > clear ( ) ;
2018-07-24 17:31:06 -04:00
// make sure to clear the read only and transmitting flags so there's always a "way out"
2018-07-10 15:35:05 -04:00
ui - > extFreeTextMsgEdit - > clear ( ) ;
2018-07-24 17:31:06 -04:00
ui - > extFreeTextMsgEdit - > setReadOnly ( false ) ;
update_dynamic_property ( ui - > extFreeTextMsgEdit , " transmitting " , false ) ;
2020-05-16 14:41:22 -04:00
}
2018-12-18 21:28:31 -05:00
2020-05-16 14:41:22 -04:00
void MainWindow : : clearCallActivity ( ) {
qDebug ( ) < < " clear call activity " ;
m_callActivity . clear ( ) ;
m_heardGraphIncoming . clear ( ) ;
m_heardGraphOutgoing . clear ( ) ;
2024-09-28 06:40:44 -07:00
ui - > tableWidgetCalls - > setRowCount ( 0 ) ;
2020-05-16 14:41:22 -04:00
createGroupCallsignTableRows ( ui - > tableWidgetCalls , " " ) ;
resetTimeDeltaAverage ( ) ;
displayCallActivity ( ) ;
2018-07-06 16:54:26 -04:00
}
2019-12-29 14:47:21 -05:00
void MainWindow : : createGroupCallsignTableRows ( QTableWidget * table , QString const & selectedCall ) {
2018-10-18 10:51:53 -04:00
int count = 0 ;
auto now = DriftingDateTime : : currentDateTimeUtc ( ) ;
int callsignAging = m_config . callsign_aging ( ) ;
2018-10-06 08:22:43 -04:00
2018-12-18 21:28:31 -05:00
int startCol = 1 ;
2018-12-26 23:38:24 -05:00
foreach ( auto cd , m_callActivity . values ( ) ) {
if ( cd . call . trimmed ( ) . isEmpty ( ) ) {
continue ;
2018-10-18 10:51:53 -04:00
}
2018-12-26 23:38:24 -05:00
if ( callsignAging & & cd . utcTimestamp . secsTo ( now ) / 60 > = callsignAging ) {
continue ;
}
count + + ;
}
table - > horizontalHeaderItem ( startCol ) - > setText ( count = = 0 ? " Callsigns " : QString ( " Callsigns (%1) " ) . arg ( count ) ) ;
if ( ! m_config . avoid_allcall ( ) ) {
table - > insertRow ( table - > rowCount ( ) ) ;
2018-12-18 21:28:31 -05:00
auto emptyItem = new QTableWidgetItem ( " " ) ;
emptyItem - > setData ( Qt : : UserRole , QVariant ( " @ALLCALL " ) ) ;
table - > setItem ( table - > rowCount ( ) - 1 , 0 , emptyItem ) ;
2018-12-26 23:38:24 -05:00
auto item = new QTableWidgetItem ( QString ( " @ALLCALL " ) ) ;
2018-10-27 10:13:34 -04:00
item - > setData ( Qt : : UserRole , QVariant ( " @ALLCALL " ) ) ;
2018-12-18 21:28:31 -05:00
table - > setItem ( table - > rowCount ( ) - 1 , startCol , item ) ;
table - > setSpan ( table - > rowCount ( ) - 1 , startCol , 1 , table - > columnCount ( ) ) ;
2018-10-27 10:13:34 -04:00
if ( selectedCall = = " @ALLCALL " ) {
2018-10-27 10:09:40 -04:00
table - > item ( table - > rowCount ( ) - 1 , 0 ) - > setSelected ( true ) ;
2018-12-18 21:28:31 -05:00
table - > item ( table - > rowCount ( ) - 1 , startCol ) - > setSelected ( true ) ;
2018-10-27 10:09:40 -04:00
}
2018-10-06 08:22:43 -04:00
}
2024-08-31 09:40:28 -07:00
auto groups = m_config . my_groups ( ) . values ( ) ;
std : : sort ( groups . begin ( ) , groups . end ( ) ) ;
2018-10-27 10:09:40 -04:00
foreach ( auto group , groups ) {
table - > insertRow ( table - > rowCount ( ) ) ;
2018-12-18 21:28:31 -05:00
auto emptyItem = new QTableWidgetItem ( " " ) ;
emptyItem - > setData ( Qt : : UserRole , QVariant ( group ) ) ;
2019-12-29 14:47:21 -05:00
emptyItem - > setToolTip ( generateCallDetail ( group ) ) ;
2018-12-18 21:28:31 -05:00
table - > setItem ( table - > rowCount ( ) - 1 , 0 , emptyItem ) ;
2018-10-27 10:09:40 -04:00
auto item = new QTableWidgetItem ( group ) ;
item - > setData ( Qt : : UserRole , QVariant ( group ) ) ;
2019-12-29 14:47:21 -05:00
item - > setToolTip ( generateCallDetail ( group ) ) ;
2018-12-18 21:28:31 -05:00
table - > setItem ( table - > rowCount ( ) - 1 , startCol , item ) ;
table - > setSpan ( table - > rowCount ( ) - 1 , startCol , 1 , table - > columnCount ( ) ) ;
2018-10-27 10:09:40 -04:00
if ( selectedCall = = group ) {
table - > item ( table - > rowCount ( ) - 1 , 0 ) - > setSelected ( true ) ;
2018-12-18 21:28:31 -05:00
table - > item ( table - > rowCount ( ) - 1 , startCol ) - > setSelected ( true ) ;
2018-10-27 10:09:40 -04:00
}
2018-10-06 08:22:43 -04:00
}
}
2018-08-11 18:05:36 -04:00
void MainWindow : : displayTextForFreq ( QString text , int freq , QDateTime date , bool isTx , bool isNewLine , bool isLast ) {
2018-08-12 10:03:14 -04:00
int lowFreq = freq / 10 * 10 ;
int highFreq = lowFreq + 10 ;
int block = - 1 ;
2018-08-30 17:19:58 -04:00
2018-08-12 10:03:14 -04:00
if ( m_rxFrameBlockNumbers . contains ( freq ) ) {
2018-08-30 17:19:58 -04:00
block = m_rxFrameBlockNumbers [ freq ] ;
2018-08-12 10:03:14 -04:00
} else if ( m_rxFrameBlockNumbers . contains ( lowFreq ) ) {
block = m_rxFrameBlockNumbers [ lowFreq ] ;
freq = lowFreq ;
} else if ( m_rxFrameBlockNumbers . contains ( highFreq ) ) {
block = m_rxFrameBlockNumbers [ highFreq ] ;
freq = highFreq ;
}
2019-09-28 14:07:14 -04:00
qDebug ( ) < < " existing block? " < < block < < freq ;
2018-08-11 18:05:36 -04:00
if ( isNewLine ) {
2018-09-11 18:58:33 -04:00
m_rxFrameBlockNumbers . remove ( freq ) ;
2018-08-12 10:03:14 -04:00
m_rxFrameBlockNumbers . remove ( lowFreq ) ;
m_rxFrameBlockNumbers . remove ( highFreq ) ;
2018-08-06 23:39:43 -04:00
block = - 1 ;
2018-07-24 15:19:02 -04:00
}
2018-08-08 17:15:49 -04:00
2019-11-15 15:29:10 -05:00
block = writeMessageTextToUI ( date , text , freq , isTx , block ) ;
2018-08-10 09:35:40 -04:00
2018-08-11 18:05:36 -04:00
// never cache tx or last lines
2019-09-28 14:07:14 -04:00
if ( /*isTx || */ isLast ) {
2018-10-03 15:13:43 -04:00
// reset the cache so we're always progressing forward
m_rxFrameBlockNumbers . clear ( ) ;
2018-08-30 17:19:58 -04:00
} else {
2018-08-10 10:59:17 +03:00
m_rxFrameBlockNumbers . insert ( freq , block ) ;
2018-08-12 10:03:14 -04:00
m_rxFrameBlockNumbers . insert ( lowFreq , block ) ;
m_rxFrameBlockNumbers . insert ( highFreq , block ) ;
2018-08-10 10:59:17 +03:00
}
2018-08-06 23:39:43 -04:00
}
2018-07-24 15:19:02 -04:00
2018-08-16 15:19:43 -04:00
void MainWindow : : writeNoticeTextToUI ( QDateTime date , QString text ) {
auto c = ui - > textEditRX - > textCursor ( ) ;
c . movePosition ( QTextCursor : : End ) ;
if ( c . block ( ) . length ( ) > 1 ) {
c . insertBlock ( ) ;
}
text = text . toHtmlEscaped ( ) ;
c . insertBlock ( ) ;
c . insertHtml ( QString ( " <strong>%1 - %2</strong> " ) . arg ( date . time ( ) . toString ( ) ) . arg ( text ) ) ;
c . movePosition ( QTextCursor : : End ) ;
ui - > textEditRX - > ensureCursorVisible ( ) ;
ui - > textEditRX - > verticalScrollBar ( ) - > setValue ( ui - > textEditRX - > verticalScrollBar ( ) - > maximum ( ) ) ;
}
2019-09-28 14:07:14 -04:00
int MainWindow : : writeMessageTextToUI ( QDateTime date , QString text , int freq , bool isTx , int block ) {
2018-08-06 23:39:43 -04:00
auto c = ui - > textEditRX - > textCursor ( ) ;
2018-09-05 15:14:39 -04:00
// find an existing block (that does not contain an EOT marker)
2018-07-05 04:45:55 -04:00
bool found = false ;
if ( block ! = - 1 ) {
QTextBlock b = c . document ( ) - > findBlockByNumber ( block ) ;
c . setPosition ( b . position ( ) ) ;
2018-09-05 15:14:39 -04:00
c . movePosition ( QTextCursor : : EndOfBlock , QTextCursor : : KeepAnchor ) ;
auto blockText = c . selectedText ( ) ;
c . clearSelection ( ) ;
c . movePosition ( QTextCursor : : EndOfBlock , QTextCursor : : MoveAnchor ) ;
2019-04-21 11:29:31 -04:00
if ( ! blockText . contains ( m_config . eot ( ) ) ) {
2018-09-05 15:14:39 -04:00
found = true ;
}
}
if ( ! found ) {
2018-07-07 16:56:33 -04:00
c . movePosition ( QTextCursor : : End ) ;
2018-08-06 23:39:43 -04:00
if ( c . block ( ) . length ( ) > 1 ) {
c . insertBlock ( ) ;
}
2018-07-05 04:45:55 -04:00
}
2019-01-17 09:56:42 -05:00
// fixup duplicate acks
2019-01-17 09:36:06 -05:00
auto tc = c . document ( ) - > find ( text ) ;
2020-05-03 22:25:51 -04:00
if ( ! tc . isNull ( ) & & tc . selectedText ( ) = = text & & ( text . contains ( " ACK " ) | | text . contains ( " HEARTBEAT SNR " ) ) ) {
2019-02-21 13:11:30 -05:00
tc . select ( QTextCursor : : BlockUnderCursor ) ;
if ( tc . selectedText ( ) . trimmed ( ) . startsWith ( date . time ( ) . toString ( ) ) ) {
qDebug ( ) < < " found " < < tc . selectedText ( ) < < " so not displaying... " ;
return tc . blockNumber ( ) ;
}
2019-01-17 09:36:06 -05:00
}
2019-09-28 14:07:14 -04:00
if ( found ) {
2018-07-19 03:44:02 -04:00
c . clearSelection ( ) ;
c . insertText ( text ) ;
2018-07-05 04:45:55 -04:00
} else {
2018-07-26 14:31:28 -04:00
text = text . toHtmlEscaped ( ) ;
2019-11-15 15:29:10 -05:00
text = text . replace ( " \n " , " <br/> " ) ;
2018-11-05 21:13:16 -05:00
text = text . replace ( " " , " " ) ;
2018-08-27 21:41:22 -04:00
c . insertBlock ( ) ;
2019-09-28 14:07:14 -04:00
c . insertHtml ( QString ( " %1 - (%2) - %3 " ) . arg ( date . time ( ) . toString ( ) ) . arg ( freq ) . arg ( text ) ) ;
2018-07-05 04:45:55 -04:00
}
2019-09-28 14:07:14 -04:00
if ( isTx ) {
2024-10-30 11:47:36 -07:00
c . block ( ) . setUserState ( State : : TX ) ;
2018-09-07 10:12:40 -04:00
highlightBlock ( c . block ( ) , m_config . tx_text_font ( ) , m_config . color_tx_foreground ( ) , QColor ( Qt : : transparent ) ) ;
} else {
2024-10-30 11:47:36 -07:00
c . block ( ) . setUserState ( State : : RX ) ;
2018-09-07 10:12:40 -04:00
highlightBlock ( c . block ( ) , m_config . rx_text_font ( ) , m_config . color_rx_foreground ( ) , QColor ( Qt : : transparent ) ) ;
}
2018-08-06 23:39:43 -04:00
2018-07-06 16:54:26 -04:00
ui - > textEditRX - > ensureCursorVisible ( ) ;
2018-07-21 17:32:35 -04:00
ui - > textEditRX - > verticalScrollBar ( ) - > setValue ( ui - > textEditRX - > verticalScrollBar ( ) - > maximum ( ) ) ;
2018-07-06 16:54:26 -04:00
2018-07-06 22:07:30 -04:00
return c . blockNumber ( ) ;
2018-07-05 04:45:55 -04:00
}
2018-08-15 20:57:30 -04:00
bool MainWindow : : isMessageQueuedForTransmit ( ) {
return m_transmitting | | m_txFrameCount > 0 ;
}
2019-11-14 16:07:49 -05:00
bool MainWindow : : isInDecodeDelayThreshold ( int ms ) {
2019-11-17 01:21:07 -05:00
if ( ! m_lastTxStopTime . isValid ( ) | | m_lastTxStopTime . isNull ( ) ) {
2019-11-11 22:39:02 -05:00
return false ;
}
return m_lastTxStopTime . msecsTo ( DriftingDateTime : : currentDateTimeUtc ( ) ) < ms ;
}
2018-09-09 12:40:02 -04:00
void MainWindow : : prependMessageText ( QString text ) {
// don't add message text if we already have a transmission queued...
if ( isMessageQueuedForTransmit ( ) ) {
return ;
}
auto c = QTextCursor ( ui - > extFreeTextMsgEdit - > textCursor ( ) ) ;
c . movePosition ( QTextCursor : : Start ) ;
c . insertText ( text ) ;
}
2018-08-29 12:08:22 -04:00
void MainWindow : : addMessageText ( QString text , bool clear , bool selectFirstPlaceholder ) {
2018-08-15 20:57:30 -04:00
// don't add message text if we already have a transmission queued...
if ( isMessageQueuedForTransmit ( ) ) {
return ;
}
2018-07-19 02:09:19 -04:00
if ( clear ) {
ui - > extFreeTextMsgEdit - > clear ( ) ;
}
2018-07-05 04:45:55 -04:00
QTextCursor c = ui - > extFreeTextMsgEdit - > textCursor ( ) ;
if ( c . hasSelection ( ) ) {
c . removeSelectedText ( ) ;
}
int pos = c . position ( ) ;
c . movePosition ( QTextCursor : : PreviousCharacter , QTextCursor : : KeepAnchor ) ;
bool isSpace = c . selectedText ( ) . isEmpty ( ) | | c . selectedText ( ) . at ( 0 ) . isSpace ( ) ;
c . clearSelection ( ) ;
c . setPosition ( pos ) ;
if ( ! isSpace ) {
c . insertText ( " " ) ;
}
c . insertText ( text ) ;
2018-08-29 12:08:22 -04:00
if ( selectFirstPlaceholder ) {
2019-02-01 01:46:01 -05:00
auto match = QRegularExpression ( " ( \\ [[^ \\ ]]+ \\ ]) " ) . match ( ui - > extFreeTextMsgEdit - > toPlainText ( ) ) ;
2018-08-29 12:08:22 -04:00
if ( match . hasMatch ( ) ) {
c . setPosition ( match . capturedStart ( ) ) ;
c . setPosition ( match . capturedEnd ( ) , QTextCursor : : KeepAnchor ) ;
ui - > extFreeTextMsgEdit - > setTextCursor ( c ) ;
}
}
2018-07-05 04:45:55 -04:00
ui - > extFreeTextMsgEdit - > setFocus ( ) ;
}
2020-04-04 22:45:51 -04:00
void MainWindow : : confirmThenEnqueueMessage ( int timeout , int priority , QString message , int offset , Callback c ) {
2020-02-17 20:43:26 -05:00
SelfDestructMessageBox * m = new SelfDestructMessageBox ( timeout ,
" Autoreply Confirmation Required " ,
QString ( " A transmission is queued for autoreply: \n \n %1 \n \n Would you like to send this transmission? " ) . arg ( message ) ,
QMessageBox : : Question ,
QMessageBox : : Yes | QMessageBox : : No ,
QMessageBox : : No ,
false ,
this ) ;
2024-08-31 10:29:30 -07:00
connect ( m , & SelfDestructMessageBox : : finished , this , [ this , m , priority , message , offset , c ] ( int ) {
2020-02-17 20:43:26 -05:00
// make sure we delete the message box later...
m - > deleteLater ( ) ;
2020-03-28 22:46:51 -04:00
if ( m - > result ( ) = = QMessageBox : : Yes ) {
2020-04-04 22:45:51 -04:00
enqueueMessage ( priority , message , offset , c ) ;
2020-02-17 20:43:26 -05:00
}
} ) ;
2020-03-28 22:46:51 -04:00
m - > setWindowModality ( Qt : : NonModal ) ;
2020-02-17 20:43:26 -05:00
m - > show ( ) ;
}
2020-04-04 22:45:51 -04:00
void MainWindow : : enqueueMessage ( int priority , QString message , int offset , Callback c ) {
2018-07-31 17:48:33 -04:00
m_txMessageQueue . enqueue (
PrioritizedMessage {
2020-04-04 22:45:51 -04:00
DriftingDateTime : : currentDateTimeUtc ( ) , priority , message , offset , c
2018-07-31 17:48:33 -04:00
}
) ;
}
2018-07-02 22:54:26 -04:00
void MainWindow : : resetMessage ( ) {
resetMessageUI ( ) ;
resetMessageTransmitQueue ( ) ;
}
void MainWindow : : resetMessageUI ( ) {
2024-10-05 08:25:02 -07:00
m_nextFreeTextMsg . clear ( ) ;
2018-07-02 22:54:26 -04:00
ui - > extFreeTextMsgEdit - > clear ( ) ;
2018-07-24 17:45:23 -04:00
ui - > extFreeTextMsgEdit - > setReadOnly ( false ) ;
2020-04-15 15:23:41 -04:00
2018-07-24 17:45:23 -04:00
update_dynamic_property ( ui - > extFreeTextMsgEdit , " transmitting " , false ) ;
2018-07-02 22:54:26 -04:00
if ( ui - > startTxButton - > isChecked ( ) ) {
ui - > startTxButton - > setChecked ( false ) ;
}
}
2018-08-23 23:59:15 -04:00
bool MainWindow : : ensureCallsignSet ( bool alert ) {
2018-08-14 12:09:14 -04:00
if ( m_config . my_callsign ( ) . trimmed ( ) . isEmpty ( ) ) {
2018-08-23 23:59:15 -04:00
if ( alert ) MessageBox : : warning_message ( this , tr ( " Please enter your callsign in the settings. " ) ) ;
2018-09-09 15:47:26 -04:00
openSettings ( ) ;
2018-08-23 23:59:15 -04:00
return false ;
}
if ( m_config . my_grid ( ) . trimmed ( ) . isEmpty ( ) ) {
if ( alert ) MessageBox : : warning_message ( this , tr ( " Please enter your grid locator in the settings. " ) ) ;
2018-09-09 15:47:26 -04:00
openSettings ( ) ;
2018-08-14 12:09:14 -04:00
return false ;
}
return true ;
}
2018-10-27 15:01:27 -04:00
bool MainWindow : : ensureKeyNotStuck ( QString const & text ) {
// be annoying and drop messages with all the same character to reduce spam...
2018-12-03 22:42:19 -05:00
if ( text . length ( ) > 5 & & QString ( text ) . replace ( text . at ( 0 ) , " " ) . trimmed ( ) . isEmpty ( ) ) {
2018-10-27 15:01:27 -04:00
return false ;
}
return true ;
}
2018-12-14 20:02:46 -05:00
bool MainWindow : : ensureNotIdle ( ) {
if ( ! m_config . watchdog ( ) ) {
return true ;
}
if ( m_idleMinutes < m_config . watchdog ( ) ) {
return true ;
}
tx_watchdog ( true ) ; // disable transmit and auto replies
return false ;
}
2020-04-15 15:23:41 -04:00
bool MainWindow : : ensureCanTransmit ( ) {
return ui - > monitorTxButton - > isChecked ( ) ;
}
2019-01-28 09:57:21 -05:00
bool MainWindow : : ensureCreateMessageReady ( const QString & text ) {
2019-09-26 23:19:00 -04:00
if ( text . isEmpty ( ) ) {
return false ;
}
2020-04-15 15:23:41 -04:00
if ( ! ensureCanTransmit ( ) ) {
on_stopTxButton_clicked ( ) ;
return false ;
}
2018-08-14 12:09:14 -04:00
if ( ! ensureCallsignSet ( ) ) {
on_stopTxButton_clicked ( ) ;
2019-01-28 09:57:21 -05:00
return false ;
2018-08-14 12:09:14 -04:00
}
2018-08-31 21:30:34 -04:00
2018-12-14 20:02:46 -05:00
if ( ! ensureNotIdle ( ) ) {
on_stopTxButton_clicked ( ) ;
2019-01-28 09:57:21 -05:00
return false ;
2018-12-14 20:02:46 -05:00
}
2018-10-27 15:01:27 -04:00
if ( ! ensureKeyNotStuck ( text ) ) {
on_stopTxButton_clicked ( ) ;
2018-12-03 22:42:19 -05:00
ui - > monitorButton - > setChecked ( false ) ;
2020-04-15 15:23:41 -04:00
ui - > monitorTxButton - > setChecked ( false ) ;
2018-12-03 22:42:19 -05:00
on_monitorButton_clicked ( false ) ;
2020-04-15 15:23:41 -04:00
on_monitorTxButton_toggled ( false ) ;
2018-12-03 22:42:19 -05:00
foreach ( auto obj , this - > children ( ) ) {
if ( obj - > isWidgetType ( ) ) {
auto wid = qobject_cast < QWidget * > ( obj ) ;
wid - > setEnabled ( false ) ;
}
}
2019-01-28 09:57:21 -05:00
return false ;
2018-10-27 15:01:27 -04:00
}
2019-01-28 09:57:21 -05:00
return true ;
2018-07-03 03:21:01 -04:00
}
2019-11-10 11:54:29 -05:00
QString MainWindow : : createMessage ( QString const & text , bool * pDisableTypeahead ) {
return createMessageTransmitQueue ( replaceMacros ( text , buildMacroValues ( ) , false ) , true , false , pDisableTypeahead ) ;
2019-01-28 09:57:21 -05:00
}
2019-11-10 11:54:29 -05:00
QString MainWindow : : appendMessage ( QString const & text , bool isData , bool * pDisableTypeahead ) {
return createMessageTransmitQueue ( replaceMacros ( text , buildMacroValues ( ) , false ) , false , isData , pDisableTypeahead ) ;
2019-09-26 23:19:00 -04:00
}
2019-11-10 11:54:29 -05:00
QString MainWindow : : createMessageTransmitQueue ( QString const & text , bool reset , bool isData , bool * pDisableTypeahead ) {
2019-01-28 09:57:21 -05:00
if ( reset ) {
resetMessageTransmitQueue ( ) ;
}
2019-11-10 11:54:29 -05:00
auto frames = buildMessageFrames ( text , isData , pDisableTypeahead ) ;
2018-08-31 21:30:34 -04:00
2018-07-30 17:16:45 -04:00
QStringList lines ;
foreach ( auto frame , frames ) {
2019-09-05 14:07:24 -04:00
auto dt = DecodedText ( frame . first , frame . second , m_nSubMode ) ;
2018-07-31 17:48:33 -04:00
lines . append ( dt . message ( ) ) ;
2018-07-30 17:16:45 -04:00
}
2019-09-21 15:24:05 -04:00
m_txFrameQueue . append ( frames ) ;
2019-09-26 23:19:00 -04:00
m_txFrameCount + = frames . length ( ) ;
2019-09-21 15:24:05 -04:00
2019-09-26 23:19:00 -04:00
// TODO: jsherer - move this outside of create message transmit queue
2018-11-28 22:53:18 -05:00
// if we're transmitting a message to be displayed, we should bump the repeat buttons...
2019-09-05 14:07:24 -04:00
# if JS8HB_RESET_HB_TIMER_ON_TX
2018-11-28 22:53:18 -05:00
resetAutomaticIntervalTransmissions ( false , false ) ;
2019-09-05 14:07:24 -04:00
# else
resetCQTimer ( false ) ;
# endif
2018-11-28 22:53:18 -05:00
2019-09-30 10:59:32 -04:00
// return the text
return lines . join ( " " ) ;
2018-07-02 22:54:26 -04:00
}
2018-07-27 13:53:07 -04:00
void MainWindow : : restoreMessage ( ) {
if ( m_lastTxMessage . isEmpty ( ) ) {
return ;
}
2019-10-20 14:25:38 -04:00
addMessageText ( Varicode : : rstrip ( m_lastTxMessage ) , true ) ;
2018-07-27 13:53:07 -04:00
}
2018-07-02 22:54:26 -04:00
void MainWindow : : resetMessageTransmitQueue ( ) {
m_txFrameCount = 0 ;
2019-09-27 22:44:27 -04:00
m_txFrameCountSent = 0 ;
2018-07-02 22:54:26 -04:00
m_txFrameQueue . clear ( ) ;
2018-07-31 18:18:27 -04:00
m_txMessageQueue . clear ( ) ;
2019-09-21 15:24:05 -04:00
// reset the total message sent
m_totalTxMessage . clear ( ) ;
2018-07-02 22:54:26 -04:00
}
2018-10-29 03:26:10 -04:00
QPair < QString , int > MainWindow : : popMessageFrame ( ) {
2018-07-02 22:54:26 -04:00
if ( m_txFrameQueue . isEmpty ( ) ) {
2018-10-29 03:26:10 -04:00
return QPair < QString , int > { } ;
2018-07-02 22:54:26 -04:00
}
return m_txFrameQueue . dequeue ( ) ;
}
2024-11-08 12:55:07 -08:00
void
MainWindow : : currentTextChanged ( )
2018-07-02 22:54:26 -04:00
{
2024-11-08 12:55:07 -08:00
auto const text = ui - > extFreeTextMsgEdit - > toPlainText ( ) ;
2018-10-10 17:43:11 -04:00
2024-11-08 12:55:07 -08:00
// keep track of dirty flags
m_txTextDirty = text ! = m_txTextDirtyLastText ;
m_txTextDirtyLastText = text ;
// immediately update the display
updateButtonDisplay ( ) ;
updateTextDisplay ( ) ;
}
void
MainWindow : : tableSelectionChanged ( QItemSelection const & ,
QItemSelection const & )
{
currentTextChanged ( ) ;
auto const selectedCall = callsignSelected ( ) ;
if ( selectedCall ! = m_prevSelectedCallsign )
{
callsignSelectedChanged ( m_prevSelectedCallsign , selectedCall ) ;
}
2018-07-02 22:54:26 -04:00
}
2019-11-10 11:54:29 -05:00
QList < QPair < QString , int > > MainWindow : : buildMessageFrames ( const QString & text , bool isData , bool * pDisableTypeahead ) {
2018-08-31 21:30:34 -04:00
// prepare selected callsign for directed message
2018-10-26 22:16:48 -04:00
QString selectedCall = callsignSelected ( ) ;
2018-08-31 21:30:34 -04:00
2018-07-24 02:53:01 -04:00
// prepare compound
QString mycall = m_config . my_callsign ( ) ;
2018-10-26 16:57:07 -04:00
QString mygrid = m_config . my_grid ( ) . left ( 4 ) ;
2019-05-27 11:30:30 -04:00
bool forceIdentify = ! m_config . avoid_forced_identify ( ) ;
2019-09-27 20:34:41 -04:00
// TODO: might want to be more explicit?
2019-11-07 14:20:06 -05:00
bool forceData = m_txFrameCountSent > 0 & & isData ;
2019-09-27 20:34:41 -04:00
2019-09-28 14:33:30 -04:00
Varicode : : MessageInfo info ;
2018-10-26 16:57:07 -04:00
auto frames = Varicode : : buildMessageFrames (
mycall ,
mygrid ,
2018-10-26 22:16:48 -04:00
selectedCall ,
2019-05-27 11:30:30 -04:00
text ,
2019-09-05 14:07:24 -04:00
forceIdentify ,
2019-09-27 20:34:41 -04:00
forceData ,
2019-09-28 14:33:30 -04:00
m_nSubMode ,
& info ) ;
2019-11-10 11:54:29 -05:00
if ( pDisableTypeahead ) {
2019-09-28 17:52:26 -04:00
// checksummed commands should not allow typeahead
2019-11-10 11:54:29 -05:00
* pDisableTypeahead = ( ! info . dirCmd . isEmpty ( ) & & Varicode : : isCommandChecksumed ( info . dirCmd ) ) ;
2019-09-28 14:33:30 -04:00
}
2018-07-02 22:54:26 -04:00
2018-10-02 18:03:15 -04:00
#if 0
2018-10-02 09:44:46 -04:00
qDebug ( ) < < " frames: " ;
2018-07-15 15:43:29 -04:00
foreach ( auto frame , frames ) {
2018-10-29 03:26:10 -04:00
auto dt = DecodedText ( frame . frame , frame . bits ) ;
2018-08-04 11:19:07 -04:00
qDebug ( ) < < " -> " < < frame < < dt . message ( ) < < Varicode : : frameTypeString ( dt . frameType ( ) ) ;
2018-07-19 02:09:19 -04:00
}
2018-07-15 15:43:29 -04:00
# endif
2018-07-30 17:16:45 -04:00
return frames ;
2018-03-08 17:01:33 -05:00
}
2018-07-02 22:54:26 -04:00
bool MainWindow : : prepareNextMessageFrame ( )
2018-03-08 17:01:33 -05:00
{
2019-11-07 14:20:06 -05:00
// check to see if the last i3bit was a last bit
bool i3bitLast = ( m_i3bit & Varicode : : JS8CallLast ) = = Varicode : : JS8CallLast ;
// TODO: should this be user configurable?
bool shouldForceDataForTypeahead = ! i3bitLast ;
// reset i3
2018-10-04 13:52:52 -04:00
m_i3bit = Varicode : : JS8Call ;
2018-08-11 18:05:36 -04:00
2019-09-26 23:19:00 -04:00
// typeahead
2019-11-10 11:54:29 -05:00
bool shouldDisableTypeahead = false ;
2019-09-27 22:44:27 -04:00
if ( ui - > extFreeTextMsgEdit - > isDirty ( ) & & ! ui - > extFreeTextMsgEdit - > isEmpty ( ) ) {
2019-10-23 12:18:06 -04:00
// block edit events while computing next frame
QString newText ;
ui - > extFreeTextMsgEdit - > setReadOnly ( true ) ;
{
auto sent = ui - > extFreeTextMsgEdit - > sentText ( ) ;
auto unsent = ui - > extFreeTextMsgEdit - > unsentText ( ) ;
qDebug ( ) < < " text dirty for typeahead \n " < < sent < < " \n " < < unsent ;
m_txFrameQueue . clear ( ) ;
m_txFrameCount = 0 ;
2019-11-07 14:20:06 -05:00
2019-11-10 11:54:29 -05:00
newText = appendMessage ( unsent , shouldForceDataForTypeahead , & shouldDisableTypeahead ) ;
2019-11-07 14:20:06 -05:00
// if this was the last frame, append a newline
if ( i3bitLast ) {
m_totalTxMessage . append ( " \n " ) ;
newText . prepend ( " \n " ) ;
}
2019-10-23 12:18:06 -04:00
qDebug ( ) < < " unsent replaced to " < < " \n " < < newText ;
}
2019-11-10 11:54:29 -05:00
ui - > extFreeTextMsgEdit - > setReadOnly ( shouldDisableTypeahead ) ;
2019-11-07 14:20:06 -05:00
ui - > extFreeTextMsgEdit - > replaceUnsentText ( newText , true ) ;
2019-09-27 22:44:27 -04:00
ui - > extFreeTextMsgEdit - > setClean ( ) ;
2019-09-26 23:19:00 -04:00
}
2018-10-29 03:26:10 -04:00
QPair < QString , int > f = popMessageFrame ( ) ;
auto frame = f . first ;
auto bits = f . second ;
2019-09-27 22:44:27 -04:00
// if not the first frame, ensure first bit is not set
if ( m_txFrameCountSent > 0 ) {
bits & = ~ Varicode : : JS8CallFirst ;
}
// if last frame, ensure the last bit is set
if ( m_txFrameQueue . isEmpty ( ) ) {
bits | = Varicode : : JS8CallLast ;
}
2019-09-21 15:24:05 -04:00
2018-10-06 01:51:15 -04:00
if ( frame . isEmpty ( ) ) {
2024-10-05 08:25:02 -07:00
m_nextFreeTextMsg . clear ( ) ;
2018-10-06 01:51:15 -04:00
updateTxButtonDisplay ( ) ;
2018-07-02 22:54:26 -04:00
return false ;
2019-09-27 22:44:27 -04:00
}
2018-07-18 16:45:27 -04:00
2019-09-27 22:44:27 -04:00
// append this frame to the total message sent so far
auto dt = DecodedText ( frame , bits , m_nSubMode ) ;
m_totalTxMessage . append ( dt . message ( ) ) ;
ui - > extFreeTextMsgEdit - > setCharsSent ( m_totalTxMessage . length ( ) ) ;
m_txFrameCountSent + = 1 ;
2019-09-30 21:15:01 -04:00
m_lastTxMessage = m_totalTxMessage ;
2019-09-27 22:44:27 -04:00
qDebug ( ) < < " total sent: " < < m_txFrameCountSent < < " \n " < < m_totalTxMessage ;
2018-07-15 13:03:16 -04:00
2019-09-27 22:59:45 -04:00
// display the frame...
2024-10-20 13:54:14 -07:00
if ( m_txFrameQueue . isEmpty ( ) )
{
displayTextForFreq ( QString ( " %1 %2 " ) . arg ( dt . message ( ) ) . arg ( m_config . eot ( ) ) ,
freq ( ) ,
DriftingDateTime : : currentDateTimeUtc ( ) ,
true ,
false ,
true ) ;
}
else
{
displayTextForFreq ( dt . message ( ) ,
freq ( ) ,
DriftingDateTime : : currentDateTimeUtc ( ) ,
true ,
m_txFrameCountSent = = 1 ,
false ) ;
2019-09-28 14:07:14 -04:00
}
2019-09-27 22:59:45 -04:00
2024-10-05 08:25:02 -07:00
m_nextFreeTextMsg = frame ;
m_i3bit = bits ;
2018-07-15 13:03:16 -04:00
2019-09-27 22:44:27 -04:00
updateTxButtonDisplay ( ) ;
// TODO: bump heartbeat
return true ;
2018-03-08 17:01:33 -05:00
}
2024-10-21 06:07:07 -07:00
bool
MainWindow : : isFreqOffsetFree ( int const f ,
int const bw )
{
// if this frequency is our current frequency, or it's in our
// directed cache, it's free.
2018-07-31 17:48:33 -04:00
2024-10-21 06:07:07 -07:00
if ( ( freq ( ) = = f ) | | isDirectedOffset ( f , nullptr ) ) return true ;
2018-08-11 10:45:39 -04:00
2024-10-21 06:07:07 -07:00
// Run through the band activity; if there's no activity for a given
// offset, or we last received on it more than 30 seconds ago, then
// it's free. If it's an occupied slot within the bandwidth of where
// we'd like to transmit, then it's not free.
2018-08-06 17:24:42 -04:00
2024-10-21 06:10:22 -07:00
auto const now = DriftingDateTime : : currentDateTimeUtc ( ) ;
2024-10-21 06:36:14 -07:00
for ( auto [ offset , activity ] : m_bandActivity . asKeyValueRange ( ) )
2024-10-21 06:07:07 -07:00
{
if ( activity . isEmpty ( ) | |
2024-10-21 06:10:22 -07:00
activity . last ( ) . utcTimestamp . secsTo ( now ) > = 30 ) continue ;
2018-08-06 17:24:42 -04:00
2024-10-21 06:07:07 -07:00
if ( qAbs ( offset - f ) < bw ) return false ;
}
2018-07-06 16:40:09 -04:00
2024-10-21 06:07:07 -07:00
return true ;
2018-07-06 16:40:09 -04:00
}
2018-07-07 16:56:33 -04:00
int MainWindow : : findFreeFreqOffset ( int fmin , int fmax , int bw ) {
2018-07-06 16:40:09 -04:00
int nslots = ( fmax - fmin ) / bw ;
int f = fmin ;
for ( int i = 0 ; i < nslots ; i + + ) {
2024-08-31 09:40:28 -07:00
f = fmin + bw * ( QRandomGenerator : : global ( ) - > generate ( ) % nslots ) ;
2018-07-07 16:56:33 -04:00
if ( isFreqOffsetFree ( f , bw ) ) {
2018-07-06 16:40:09 -04:00
return f ;
}
}
for ( int i = 0 ; i < nslots ; i + + ) {
2024-08-31 09:40:28 -07:00
f = fmin + ( QRandomGenerator : : global ( ) - > generate ( ) % ( fmax - fmin ) ) ;
2018-07-07 16:56:33 -04:00
if ( isFreqOffsetFree ( f , bw ) ) {
2018-07-06 16:40:09 -04:00
return f ;
}
}
2018-09-06 17:52:29 -04:00
// return fmin if there's no free offset
return fmin ;
2018-07-06 16:40:09 -04:00
}
2018-11-25 22:12:54 -05:00
#if 0
2018-10-28 09:47:30 -04:00
// schedulePing
void MainWindow : : scheduleHeartbeat ( bool first ) {
2018-09-18 17:24:07 -04:00
auto timestamp = DriftingDateTime : : currentDateTimeUtc ( ) ;
2018-07-06 16:19:22 -04:00
2018-10-31 10:50:51 -04:00
// if we have the heartbeat interval disabled, return early, unless this is a "heartbeat now"
if ( ! m_config . heartbeat ( ) & & ! first ) {
heartbeatTimer . stop ( ) ;
return ;
}
2018-07-06 16:19:22 -04:00
// remove milliseconds
auto t = timestamp . time ( ) ;
t . setHMS ( t . hour ( ) , t . minute ( ) , t . second ( ) ) ;
timestamp . setTime ( t ) ;
// round to 15 second increment
int secondsSinceEpoch = ( timestamp . toMSecsSinceEpoch ( ) / 1000 ) ;
2018-10-28 10:06:03 -04:00
int delta = roundUp ( secondsSinceEpoch , 15 ) + 1 + ( first ? 0 : qMax ( 1 , m_config . heartbeat ( ) ) * 60 ) - secondsSinceEpoch ;
2018-07-06 16:19:22 -04:00
timestamp = timestamp . addSecs ( delta ) ;
2018-07-07 16:56:33 -04:00
// 25% of the time, switch intervals
2024-08-31 09:40:28 -07:00
float prob = ( float ) QRandomGenerator : : global ( ) - > generate ( ) / ( RAND_MAX ) ;
2018-07-07 16:56:33 -04:00
if ( prob < 0.25 ) {
timestamp = timestamp . addSecs ( 15 ) ;
}
2018-10-28 09:47:30 -04:00
m_nextHeartbeat = timestamp ;
m_nextHeartbeatQueued = false ;
m_nextHeartPaused = false ;
2018-08-06 14:43:32 -04:00
2018-10-28 09:47:30 -04:00
if ( ! heartbeatTimer . isActive ( ) ) {
heartbeatTimer . setInterval ( 1000 ) ;
heartbeatTimer . start ( ) ;
2018-08-06 14:43:32 -04:00
}
2018-07-03 03:21:01 -04:00
}
2018-10-28 09:47:30 -04:00
// pausePing
void MainWindow : : pauseHeartbeat ( ) {
m_nextHeartPaused = true ;
2018-08-06 14:43:32 -04:00
2018-10-28 09:47:30 -04:00
if ( heartbeatTimer . isActive ( ) ) {
heartbeatTimer . stop ( ) ;
2018-08-06 14:43:32 -04:00
}
2018-07-24 16:46:04 -04:00
}
2018-10-28 11:24:00 -04:00
// unpausePing
void MainWindow : : unpauseHeartbeat ( ) {
scheduleHeartbeat ( false ) ;
}
2018-10-28 09:47:30 -04:00
// checkPing
void MainWindow : : checkHeartbeat ( ) {
2018-11-25 22:12:54 -05:00
if ( m_config . heartbeat ( ) < = 0 ) {
2018-07-03 03:21:01 -04:00
return ;
}
2018-10-28 09:47:30 -04:00
auto secondsUntilHeartbeat = DriftingDateTime : : currentDateTimeUtc ( ) . secsTo ( m_nextHeartbeat ) ;
if ( secondsUntilHeartbeat > 5 & & m_txHeartbeatQueue . isEmpty ( ) ) {
2018-08-06 14:43:32 -04:00
return ;
}
2018-10-28 09:47:30 -04:00
if ( m_nextHeartbeatQueued ) {
2018-07-03 03:21:01 -04:00
return ;
}
2018-09-23 16:41:06 -04:00
if ( m_tx_watchdog ) {
return ;
}
2018-07-03 03:21:01 -04:00
2018-10-28 11:24:00 -04:00
// idle heartbeat watchdog!
if ( m_config . watchdog ( ) & & m_idleMinutes > = m_config . watchdog ( ) ) {
tx_watchdog ( true ) ; // disable transmit
return ;
}
2018-10-28 09:47:30 -04:00
prepareHeartbeat ( ) ;
2018-08-06 14:43:32 -04:00
}
2018-10-28 09:47:30 -04:00
// preparePing
void MainWindow : : prepareHeartbeat ( ) {
2018-07-21 03:32:07 -04:00
QStringList lines ;
2018-08-11 10:31:28 -04:00
QString mycall = m_config . my_callsign ( ) ;
QString mygrid = m_config . my_grid ( ) . left ( 4 ) ;
2018-07-21 03:32:07 -04:00
2018-10-04 13:52:52 -04:00
// JS8Call Style
2018-10-28 09:47:30 -04:00
if ( m_txHeartbeatQueue . isEmpty ( ) ) {
2018-10-28 10:06:03 -04:00
lines . append ( QString ( " %1: HEARTBEAT %2 " ) . arg ( mycall ) . arg ( mygrid ) ) ;
2018-09-21 01:47:21 -04:00
} else {
2018-10-28 09:47:30 -04:00
while ( ! m_txHeartbeatQueue . isEmpty ( ) & & lines . length ( ) < 1 ) {
lines . append ( m_txHeartbeatQueue . dequeue ( ) ) ;
2018-09-21 01:47:21 -04:00
}
2018-08-06 11:01:17 -04:00
}
2018-10-28 09:47:30 -04:00
// Choose a ping frequency
2018-10-28 10:06:03 -04:00
auto f = m_config . heartbeat_anywhere ( ) ? - 1 : findFreeFreqOffset ( 500 , 1000 , 50 ) ;
2018-09-06 17:52:29 -04:00
2018-09-21 01:47:21 -04:00
auto text = lines . join ( QChar ( ' \n ' ) ) ;
if ( text . isEmpty ( ) ) {
return ;
}
2018-10-28 09:47:30 -04:00
// Queue the ping
2018-09-21 01:47:21 -04:00
enqueueMessage ( PriorityLow , text , f , [ this ] ( ) {
2018-10-28 09:47:30 -04:00
m_nextHeartbeatQueued = false ;
2018-08-06 14:43:32 -04:00
} ) ;
2018-07-03 03:21:01 -04:00
2018-10-28 09:47:30 -04:00
m_nextHeartbeatQueued = true ;
2018-07-03 03:21:01 -04:00
}
2018-11-25 22:12:54 -05:00
# endif
2018-07-03 03:21:01 -04:00
2018-11-28 22:53:18 -05:00
void MainWindow : : checkRepeat ( ) {
if ( ui - > hbMacroButton - > isChecked ( ) & & m_hbInterval > 0 & & m_nextHeartbeat . isValid ( ) ) {
if ( DriftingDateTime : : currentDateTimeUtc ( ) . secsTo ( m_nextHeartbeat ) < = 0 ) {
sendHeartbeat ( ) ;
}
}
2018-07-07 22:02:11 -04:00
2018-11-28 22:53:18 -05:00
if ( ui - > cqMacroButton - > isChecked ( ) & & m_cqInterval > 0 & & m_nextCQ . isValid ( ) ) {
if ( DriftingDateTime : : currentDateTimeUtc ( ) . secsTo ( m_nextCQ ) < = 0 ) {
2018-12-01 16:59:17 -05:00
sendCQ ( true ) ;
2018-11-28 22:53:18 -05:00
}
}
2018-11-25 22:12:54 -05:00
}
2018-08-06 14:43:32 -04:00
2018-07-02 22:54:26 -04:00
void MainWindow : : on_startTxButton_toggled ( bool checked )
{
2018-10-06 01:51:15 -04:00
if ( checked ) {
2019-09-27 22:44:27 -04:00
startTx ( ) ;
2018-07-02 22:54:26 -04:00
} else {
resetMessage ( ) ;
2018-08-31 21:30:34 -04:00
on_stopTxButton_clicked ( ) ;
2019-05-26 20:06:28 -04:00
stopTx ( ) ;
2018-07-02 22:54:26 -04:00
}
}
2018-07-24 23:10:47 -04:00
void MainWindow : : toggleTx ( bool start ) {
if ( start & & ui - > startTxButton - > isChecked ( ) ) { return ; }
if ( ! start & & ! ui - > startTxButton - > isChecked ( ) ) { return ; }
ui - > startTxButton - > setChecked ( start ) ;
}
2018-02-08 21:28:33 -05:00
void MainWindow : : on_logQSOButton_clicked ( ) //Log QSO button
{
2019-03-28 16:37:33 -04:00
QString call = callsignSelected ( ) ;
if ( m_callSelectedTime . contains ( call ) ) {
m_dateTimeQSOOn = m_callSelectedTime [ call ] ;
2018-03-05 14:49:51 -05:00
}
2018-02-08 21:28:33 -05:00
if ( ! m_dateTimeQSOOn . isValid ( ) ) {
2018-09-18 17:24:07 -04:00
m_dateTimeQSOOn = DriftingDateTime : : currentDateTimeUtc ( ) ;
2018-02-08 21:28:33 -05:00
}
2018-09-18 17:24:07 -04:00
auto dateTimeQSOOff = DriftingDateTime : : currentDateTimeUtc ( ) ;
2018-02-08 21:28:33 -05:00
if ( dateTimeQSOOff < m_dateTimeQSOOn ) dateTimeQSOOff = m_dateTimeQSOOn ;
2019-03-28 16:37:33 -04:00
2018-10-28 09:47:30 -04:00
if ( call . startsWith ( " @ " ) ) {
2018-08-10 10:58:04 -04:00
call = " " ;
}
QString grid = " " ;
if ( m_callActivity . contains ( call ) ) {
grid = m_callActivity [ call ] . grid ;
}
2019-05-31 21:55:08 -04:00
QString opCall = m_opCall ;
if ( opCall . isEmpty ( ) ) {
opCall = m_config . my_callsign ( ) ;
}
2019-05-31 15:15:45 -04:00
QString comments = ui - > textEditRX - > textCursor ( ) . selectedText ( ) ;
2019-06-05 11:33:21 -04:00
2019-06-05 14:19:31 -04:00
// don't reset the log window if the call hasn't changed.
if ( ! m_logDlg - > currentCall ( ) . isEmpty ( ) & & call . trimmed ( ) = = m_logDlg - > currentCall ( ) ) {
m_logDlg - > show ( ) ;
return ;
}
2024-10-27 13:06:57 -07:00
m_logDlg - > initLogQSO ( call . trimmed ( ) , grid . trimmed ( ) , " JS8 " , m_rptSent , m_rptRcvd ,
2024-10-20 13:54:14 -07:00
m_dateTimeQSOOn , dateTimeQSOOff , m_freqNominal + freq ( ) ,
2018-08-09 15:46:35 -04:00
m_config . my_callsign ( ) , m_config . my_grid ( ) ,
2024-09-20 20:31:55 -07:00
opCall , comments ) ;
2018-02-08 21:28:33 -05:00
}
2018-03-05 14:49:51 -05:00
void MainWindow : : acceptQSO ( QDateTime const & QSO_date_off , QString const & call , QString const & grid
2018-11-30 17:02:14 -05:00
, Frequency dial_freq , QString const & mode , QString const & submode
2018-02-08 21:28:33 -05:00
, QString const & rpt_sent , QString const & rpt_received
2019-02-10 09:37:26 -05:00
, QString const & comments
2018-03-05 14:49:51 -05:00
, QString const & name , QDateTime const & QSO_date_on , QString const & operator_call
2024-11-04 17:48:36 -08:00
, QString const & my_call , QString const & my_grid , QByteArray const & ADIF , QVariantMap const & additionalFields )
2018-02-08 21:28:33 -05:00
{
QString date = QSO_date_on . toString ( " yyyyMMdd " ) ;
2019-06-13 09:35:53 -04:00
m_logBook . addAsWorked ( m_hisCall , m_config . bands ( ) - > find ( m_freqNominal ) , mode , submode , grid , date , name , comments ) ;
2018-02-08 21:28:33 -05:00
2019-06-05 11:33:21 -04:00
// Log to JS8Call API
2020-04-04 22:45:51 -04:00
if ( canSendNetworkMessage ( ) ) {
2019-05-31 15:15:45 -04:00
sendNetworkMessage ( " LOG.QSO " , QString ( ADIF ) , {
2020-04-03 22:49:28 -04:00
{ " _ID " , QVariant ( - 1 ) } ,
2019-05-31 15:15:45 -04:00
{ " UTC.ON " , QVariant ( QSO_date_on . toMSecsSinceEpoch ( ) ) } ,
{ " UTC.OFF " , QVariant ( QSO_date_off . toMSecsSinceEpoch ( ) ) } ,
{ " CALL " , QVariant ( call ) } ,
{ " GRID " , QVariant ( grid ) } ,
{ " FREQ " , QVariant ( dial_freq ) } ,
{ " MODE " , QVariant ( mode ) } ,
{ " SUBMODE " , QVariant ( submode ) } ,
{ " RPT.SENT " , QVariant ( rpt_sent ) } ,
{ " RPT.RECV " , QVariant ( rpt_received ) } ,
{ " NAME " , QVariant ( name ) } ,
{ " COMMENTS " , QVariant ( comments ) } ,
{ " STATION.OP " , QVariant ( operator_call ) } ,
{ " STATION.CALL " , QVariant ( my_call ) } ,
2019-06-05 11:33:21 -04:00
{ " STATION.GRID " , QVariant ( my_grid ) } ,
{ " EXTRA " , additionalFields }
2019-05-31 15:15:45 -04:00
} ) ;
}
2019-06-05 11:33:21 -04:00
// Log to N1MM Logger
if ( m_config . broadcast_to_n1mm ( ) & & m_config . valid_n1mm_info ( ) ) {
const QHostAddress n1mmhost = QHostAddress ( m_config . n1mm_server_name ( ) ) ;
QUdpSocket _sock ;
auto rzult = _sock . writeDatagram ( ADIF + " <eor> " , n1mmhost , quint16 ( m_config . n1mm_server_port ( ) ) ) ;
if ( rzult = = - 1 ) {
2019-11-21 12:04:45 -05:00
bool hidden = m_logDlg - > isHidden ( ) ;
m_logDlg - > setHidden ( true ) ;
2019-06-05 11:33:21 -04:00
MessageBox : : warning_message ( this , tr ( " Error sending log to N1MM " ) ,
tr ( " Write returned \" %1 \" " ) . arg ( rzult ) ) ;
2019-11-21 12:04:45 -05:00
m_logDlg - > setHidden ( hidden ) ;
2019-06-05 11:33:21 -04:00
}
}
// Log to N3FJP Logger
2019-05-31 15:15:45 -04:00
if ( m_config . broadcast_to_n3fjp ( ) & & m_config . valid_n3fjp_info ( ) ) {
QString data = QString (
" <CMD> "
" <ADDDIRECT> "
" <EXCLUDEDUPES>TRUE</EXCLUDEDUPES> "
" <STAYOPEN>FALSE</STAYOPEN> "
" <fldDateStr>%1</fldDateStr> "
" <fldTimeOnStr>%2</fldTimeOnStr> "
" <fldCall>%3</fldCall> "
2019-06-02 13:25:31 -04:00
" <fldGridR>%4</fldGridR> "
2019-05-31 15:15:45 -04:00
" <fldBand>%5</fldBand> "
" <fldFrequency>%6</fldFrequency> "
" <fldMode>JS8</fldMode> "
" <fldOperator>%7</fldOperator> "
" <fldNameR>%8</fldNameR> "
" <fldComments>%9</fldComments> "
2019-06-02 13:25:31 -04:00
" <fldRstS>%10</fldRstS> "
" <fldRstR>%11</fldRstR> "
2019-06-05 11:33:21 -04:00
" %12 "
2019-05-31 15:15:45 -04:00
" </CMD> " ) ;
data = data . arg ( QSO_date_on . toString ( " yyyy/MM/dd " ) ) ;
data = data . arg ( QSO_date_on . toString ( " H:mm " ) ) ;
data = data . arg ( call ) ;
data = data . arg ( grid ) ;
data = data . arg ( m_config . bands ( ) - > find ( dial_freq ) . replace ( " m " , " " ) ) ;
data = data . arg ( Radio : : frequency_MHz_string ( dial_freq ) ) ;
data = data . arg ( operator_call ) ;
data = data . arg ( name ) ;
data = data . arg ( comments ) ;
2019-06-02 13:25:31 -04:00
data = data . arg ( rpt_sent ) ;
data = data . arg ( rpt_received ) ;
2019-05-31 15:15:45 -04:00
2019-06-05 11:33:21 -04:00
int other = 0 ;
QStringList additional ;
if ( ! additionalFields . isEmpty ( ) ) {
foreach ( auto key , additionalFields . keys ( ) ) {
QString n3key ;
if ( N3FJP_ADIF_MAP . contains ( key ) ) {
n3key = N3FJP_ADIF_MAP . value ( key ) ;
} else {
other + + ;
n3key = N3FJP_ADIF_MAP . value ( QString ( " *%1 " ) . arg ( other ) ) ;
}
if ( n3key . isEmpty ( ) ) {
break ;
}
auto value = additionalFields [ key ] . toString ( ) ;
additional . append ( QString ( " <%1>%2</%1> " ) . arg ( n3key ) . arg ( value ) ) ;
}
}
data = data . arg ( additional . join ( " " ) ) ;
2019-05-31 15:15:45 -04:00
auto host = m_config . n3fjp_server_name ( ) ;
auto port = m_config . n3fjp_server_port ( ) ;
2019-06-05 11:49:38 -04:00
if ( m_n3fjpClient - > sendNetworkMessage ( host , port , data . toLocal8Bit ( ) , true , 500 ) ) {
2019-06-05 11:33:21 -04:00
QTimer : : singleShot ( 300 , this , [ this , host , port ] ( ) {
2019-06-05 11:49:38 -04:00
m_n3fjpClient - > sendNetworkMessage ( host , port , " <CMD><CHECKLOG></CMD> " , true , 100 ) ;
m_n3fjpClient - > sendNetworkMessage ( host , port , " \r \n " , true , 100 ) ;
2019-06-05 11:33:21 -04:00
} ) ;
2019-06-05 11:49:38 -04:00
} else {
2019-11-21 12:04:45 -05:00
bool hidden = m_logDlg - > isHidden ( ) ;
m_logDlg - > setHidden ( true ) ;
2019-06-05 11:49:38 -04:00
MessageBox : : warning_message ( this , tr ( " Error sending log to N3FJP " ) ,
tr ( " Write failed for \" %1:%2 \" " ) . arg ( host ) . arg ( port ) ) ;
2019-11-21 12:04:45 -05:00
m_logDlg - > setHidden ( hidden ) ;
2019-06-05 11:33:21 -04:00
}
2019-05-31 15:15:45 -04:00
}
2018-08-28 16:44:21 -04:00
2019-05-31 15:15:45 -04:00
// reload the logbook data
2018-11-03 01:47:54 -04:00
m_logBook . init ( ) ;
2024-09-28 17:32:29 -07:00
clearCallsignSelected ( ) ;
2018-11-20 09:50:53 -05:00
displayCallActivity ( ) ;
2018-11-03 01:47:54 -04:00
2018-02-08 21:28:33 -05:00
m_dateTimeQSOOn = QDateTime { } ;
}
2024-08-31 10:29:30 -07:00
void MainWindow : : on_actionModeJS8HB_toggled ( bool ) {
2019-09-05 14:07:24 -04:00
// prep hb mode
2019-09-16 14:16:17 -04:00
2020-03-28 11:57:38 -04:00
prepareHeartbeatMode ( canCurrentModeSendHeartbeat ( ) & & ui - > actionModeJS8HB - > isChecked ( ) ) ;
2019-09-05 14:07:24 -04:00
displayActivity ( true ) ;
2024-11-08 14:06:18 -08:00
setupJS8 ( ) ;
2019-09-05 14:07:24 -04:00
}
2024-08-31 10:29:30 -07:00
void MainWindow : : on_actionHeartbeatAcknowledgements_toggled ( bool ) {
2019-09-05 14:07:24 -04:00
// prep hb ack mode
2019-09-16 14:16:17 -04:00
2020-03-28 11:57:38 -04:00
prepareHeartbeatMode ( canCurrentModeSendHeartbeat ( ) & & ui - > actionModeJS8HB - > isChecked ( ) ) ;
2019-09-05 14:07:24 -04:00
displayActivity ( true ) ;
2024-11-08 14:06:18 -08:00
setupJS8 ( ) ;
2019-09-05 14:07:24 -04:00
}
2019-11-04 20:32:36 -05:00
void MainWindow : : on_actionModeMultiDecoder_toggled ( bool checked ) {
Q_UNUSED ( checked ) ;
displayActivity ( true ) ;
2024-11-08 14:06:18 -08:00
setupJS8 ( ) ;
2019-11-04 20:32:36 -05:00
}
2019-09-05 14:07:24 -04:00
void MainWindow : : on_actionModeJS8Normal_triggered ( ) {
2024-11-08 14:06:18 -08:00
setupJS8 ( ) ;
2019-09-05 14:07:24 -04:00
}
void MainWindow : : on_actionModeJS8Fast_triggered ( ) {
2024-11-08 14:06:18 -08:00
setupJS8 ( ) ;
2019-09-05 14:07:24 -04:00
}
void MainWindow : : on_actionModeJS8Turbo_triggered ( ) {
2024-11-08 14:06:18 -08:00
setupJS8 ( ) ;
2019-09-05 14:07:24 -04:00
}
2019-12-09 14:00:23 -05:00
void MainWindow : : on_actionModeJS8Slow_triggered ( ) {
2024-11-08 14:06:18 -08:00
setupJS8 ( ) ;
2019-12-09 14:00:23 -05:00
}
void MainWindow : : on_actionModeJS8Ultra_triggered ( ) {
2024-11-08 14:06:18 -08:00
setupJS8 ( ) ;
2019-08-26 12:08:35 -04:00
}
2024-08-31 10:29:30 -07:00
void MainWindow : : on_actionModeAutoreply_toggled ( bool ) {
2019-09-05 14:07:24 -04:00
// update the HB ack option (needs autoreply on)
2020-03-28 11:57:38 -04:00
prepareHeartbeatMode ( canCurrentModeSendHeartbeat ( ) & & ui - > actionModeJS8HB - > isChecked ( ) ) ;
2019-09-05 14:07:24 -04:00
// then update the js8 mode
2024-11-08 14:06:18 -08:00
setupJS8 ( ) ;
2019-08-26 12:08:35 -04:00
}
2024-10-31 07:27:22 -07:00
bool
MainWindow : : canCurrentModeSendHeartbeat ( ) const
{
return ( m_nSubMode = = Varicode : : JS8CallFast | |
m_nSubMode = = Varicode : : JS8CallNormal | |
m_nSubMode = = Varicode : : JS8CallSlow ) ;
2020-03-23 09:52:46 -04:00
}
2020-04-15 15:23:41 -04:00
void MainWindow : : prepareMonitorControls ( ) {
// on_monitorButton_toggled(!m_config.monitor_off_at_startup());
ui - > monitorTxButton - > setChecked ( ! m_config . transmit_off_at_startup ( ) ) ;
}
2019-09-05 14:07:24 -04:00
void MainWindow : : prepareHeartbeatMode ( bool enabled ) {
2020-03-23 09:52:46 -04:00
// heartbeat is only available in a supported HB mode
2019-08-26 12:08:35 -04:00
ui - > hbMacroButton - > setVisible ( enabled ) ;
2019-09-05 14:07:24 -04:00
if ( ! enabled ) {
ui - > hbMacroButton - > setChecked ( false ) ;
}
ui - > actionHeartbeat - > setEnabled ( enabled ) ;
2020-03-28 11:57:38 -04:00
ui - > actionModeJS8HB - > setEnabled ( canCurrentModeSendHeartbeat ( ) ) ;
2020-03-23 09:52:46 -04:00
ui - > actionHeartbeatAcknowledgements - > setEnabled ( enabled & & ui - > actionModeAutoreply - > isChecked ( ) ) ;
2019-09-05 14:07:24 -04:00
2020-04-06 14:09:55 -04:00
#if 0
2020-03-31 20:49:03 -04:00
if ( enabled ) {
m_config . addGroup ( " @HB " ) ;
} else {
m_config . removeGroup ( " @HB " ) ;
}
2020-04-06 14:09:55 -04:00
# endif
2020-03-31 20:49:03 -04:00
2019-09-16 14:16:17 -04:00
#if 0
2019-09-05 14:07:24 -04:00
//ui->actionCQ->setEnabled(!enabled);
//ui->actionFocus_Message_Reply_Area->setEnabled(!enabled);
// default to not displaying the other buttons
// ui->cqMacroButton->setVisible(!enabled);
// ui->replyMacroButton->setVisible(!enabled);
// ui->snrMacroButton->setVisible(!enabled);
// ui->infoMacroButton->setVisible(!enabled);
// ui->macrosMacroButton->setVisible(!enabled);
// ui->queryButton->setVisible(!enabled);
// ui->extFreeTextMsgEdit->setVisible(!enabled);
// if(enabled){
// ui->extFreeTextMsgEdit->clear();
// }
// show heartbeat and acks in hb mode only
// ui->actionShow_Band_Heartbeats_and_ACKs->setChecked(enabled);
// ui->actionShow_Band_Heartbeats_and_ACKs->setVisible(true);
// ui->actionShow_Band_Heartbeats_and_ACKs->setEnabled(false);
2019-09-16 14:16:17 -04:00
# endif
2019-08-26 12:08:35 -04:00
// update the HB button immediately
updateRepeatButtonDisplay ( ) ;
2019-09-05 14:07:24 -04:00
updateButtonDisplay ( ) ;
2019-08-26 12:08:35 -04:00
}
2024-10-31 07:29:26 -07:00
void
2024-11-08 14:06:18 -08:00
MainWindow : : setupJS8 ( )
2018-02-08 21:28:33 -05:00
{
2024-10-31 07:29:26 -07:00
m_nSubMode = Varicode : : JS8CallNormal ;
2024-11-08 14:06:18 -08:00
if ( ui - > actionModeJS8Normal - > isChecked ( ) ) m_nSubMode = Varicode : : JS8CallNormal ;
else if ( ui - > actionModeJS8Fast - > isChecked ( ) ) m_nSubMode = Varicode : : JS8CallFast ;
else if ( ui - > actionModeJS8Turbo - > isChecked ( ) ) m_nSubMode = Varicode : : JS8CallTurbo ;
else if ( ui - > actionModeJS8Slow - > isChecked ( ) ) m_nSubMode = Varicode : : JS8CallSlow ;
else if ( ui - > actionModeJS8Ultra - > isChecked ( ) ) m_nSubMode = Varicode : : JS8CallUltra ;
2019-09-05 14:07:24 -04:00
2020-03-28 11:57:38 -04:00
// Only enable heartbeat for modes that support it
prepareHeartbeatMode ( canCurrentModeSendHeartbeat ( ) & & ui - > actionModeJS8HB - > isChecked ( ) ) ;
2019-09-05 14:07:24 -04:00
2019-10-21 15:58:58 -04:00
updateModeButtonText ( ) ;
2019-09-05 14:07:24 -04:00
m_wideGraph - > setSubMode ( m_nSubMode ) ;
2024-11-08 14:06:18 -08:00
m_wideGraph - > setFilterMinimumBandwidth ( JS8 : : Submode : : bandwidth ( m_nSubMode ) +
JS8 : : Submode : : rxThreshold ( m_nSubMode ) * 2 ) ;
2019-12-16 13:42:44 -05:00
2019-10-24 10:52:37 -04:00
enable_DXCC_entity ( m_config . DXCC ( ) ) ;
2024-11-08 13:14:53 -08:00
m_config . frequencies ( ) - > filter ( m_config . region ( ) , Mode : : JS8 ) ;
2024-10-19 15:10:31 -07:00
m_FFTSize = NSPS / 2 ;
2018-02-08 21:28:33 -05:00
Q_EMIT FFTSize ( m_FFTSize ) ;
2024-10-04 19:25:42 -07:00
setup_status_bar ( ) ;
2024-10-08 07:02:36 -07:00
m_TRperiod = JS8 : : Submode : : period ( m_nSubMode ) ;
2024-10-05 08:07:44 -07:00
m_wideGraph - > show ( ) ;
2020-05-11 20:53:03 -04:00
2019-11-04 14:38:00 -05:00
Q_ASSERT ( NTMAX = = 60 ) ;
2024-10-19 15:10:31 -07:00
m_wideGraph - > setPeriod ( m_TRperiod ) ;
2019-11-06 16:00:16 -05:00
m_detector - > setTRPeriod ( NTMAX ) ; // TODO - not thread safe
2019-09-05 14:07:24 -04:00
updateTextDisplay ( ) ;
refreshTextDisplay ( ) ;
2018-02-08 21:28:33 -05:00
statusChanged ( ) ;
}
2024-10-04 18:49:08 -07:00
void
2024-10-20 13:54:14 -07:00
MainWindow : : setFreq ( int const n )
2018-02-08 21:28:33 -05:00
{
2024-10-20 13:54:14 -07:00
m_freq = n ;
m_wideGraph - > setFreq ( n ) ;
2024-09-25 21:21:13 -07:00
Q_EMIT transmitFrequency ( n - m_XIT ) ;
2018-02-08 21:28:33 -05:00
statusUpdate ( ) ;
}
void MainWindow : : on_actionQuickDecode_toggled ( bool checked )
{
m_ndepth ^ = ( - checked ^ m_ndepth ) & 0x00000001 ;
}
void MainWindow : : on_actionMediumDecode_toggled ( bool checked )
{
m_ndepth ^ = ( - checked ^ m_ndepth ) & 0x00000002 ;
}
2018-10-21 15:37:12 -04:00
void MainWindow : : on_actionDeepDecode_toggled ( bool checked )
2018-02-08 21:28:33 -05:00
{
m_ndepth ^ = ( - checked ^ m_ndepth ) & 0x00000003 ;
}
2018-10-21 15:37:12 -04:00
void MainWindow : : on_actionDeepestDecode_toggled ( bool checked )
{
m_ndepth ^ = ( - checked ^ m_ndepth ) & 0x00000004 ;
}
2018-02-08 21:28:33 -05:00
void MainWindow : : on_actionErase_ALL_TXT_triggered ( ) //Erase ALL.TXT
{
int ret = MessageBox : : query_message ( this , tr ( " Confirm Erase " ) ,
tr ( " Are you sure you want to erase file ALL.TXT? " ) ) ;
if ( ret = = MessageBox : : Yes ) {
QFile f { m_config . writeable_data_dir ( ) . absoluteFilePath ( " ALL.TXT " ) } ;
f . remove ( ) ;
m_RxLog = 1 ;
}
}
2018-10-04 13:52:52 -04:00
void MainWindow : : on_actionErase_js8call_log_adi_triggered ( )
2018-02-08 21:28:33 -05:00
{
int ret = MessageBox : : query_message ( this , tr ( " Confirm Erase " ) ,
2018-10-04 13:52:52 -04:00
tr ( " Are you sure you want to erase file js8call_log.adi? " ) ) ;
2018-02-08 21:28:33 -05:00
if ( ret = = MessageBox : : Yes ) {
2018-10-04 13:52:52 -04:00
QFile f { m_config . writeable_data_dir ( ) . absoluteFilePath ( " js8call_log.adi " ) } ;
2018-02-08 21:28:33 -05:00
f . remove ( ) ;
2018-11-03 01:47:54 -04:00
m_logBook . init ( ) ;
2018-02-08 21:28:33 -05:00
}
}
void MainWindow : : on_actionOpen_log_directory_triggered ( )
{
QDesktopServices : : openUrl ( QUrl : : fromLocalFile ( m_config . writeable_data_dir ( ) . absolutePath ( ) ) ) ;
}
2024-10-25 13:27:38 -07:00
void MainWindow : : band_changed ( )
2018-02-08 21:28:33 -05:00
{
if ( m_config . pwrBandTxMemory ( ) & & ! m_tune ) {
2024-10-25 13:27:38 -07:00
if ( m_pwrBandTxMemory . contains ( m_lastBand ) ) {
ui - > outAttenuation - > setValue ( m_pwrBandTxMemory [ m_lastBand ] . toInt ( ) ) ;
2024-09-11 13:39:49 -07:00
}
else {
2024-10-25 13:27:38 -07:00
m_pwrBandTxMemory [ m_lastBand ] = ui - > outAttenuation - > value ( ) ;
2018-02-08 21:28:33 -05:00
}
}
}
2018-10-30 20:50:31 -04:00
void MainWindow : : enable_DXCC_entity ( bool /*on*/ )
2018-02-08 21:28:33 -05:00
{
2018-10-30 20:50:31 -04:00
m_logBook . init ( ) ; // re-read the log and cty.dat files
2018-02-08 21:28:33 -05:00
updateGeometry ( ) ;
}
2019-01-21 17:45:52 -05:00
void MainWindow : : buildFrequencyMenu ( QMenu * menu ) {
auto custom = menu - > addAction ( " Set a Custom Frequency... " ) ;
connect ( custom , & QAction : : triggered , this , [ this ] ( ) {
bool ok = false ;
auto currentFreq = Radio : : frequency_MHz_string ( dialFrequency ( ) ) ;
2019-02-08 20:45:50 -05:00
QString newFreq = QInputDialog : : getText ( this , tr ( " Set a Custom Frequency " ) ,
2019-01-21 17:45:52 -05:00
tr ( " Frequency in MHz: " ) , QLineEdit : : Normal ,
currentFreq , & ok ) . toUpper ( ) . trimmed ( ) ;
if ( ! ok ) {
return ;
}
setRig ( Radio : : frequency ( newFreq , 6 ) ) ;
} ) ;
menu - > addSeparator ( ) ;
2019-02-16 16:09:44 -05:00
auto frequencies = m_config . frequencies ( ) - > frequency_list ( ) ;
2024-08-31 09:40:28 -07:00
std : : sort ( frequencies . begin ( ) , frequencies . end ( ) , [ ] ( FrequencyList_v2 : : Item & a , FrequencyList_v2 : : Item & b ) {
2019-02-16 16:09:44 -05:00
return a . frequency_ < b . frequency_ ;
} ) ;
foreach ( auto f , frequencies ) {
2019-01-21 17:45:52 -05:00
auto freq = Radio : : pretty_frequency_MHz_string ( f . frequency_ ) ;
auto const & band = m_config . bands ( ) - > find ( f . frequency_ ) ;
2019-01-21 22:27:43 -05:00
auto a = menu - > addAction ( QString ( " %1:%2%2%3 MHz " ) . arg ( band ) . arg ( QString ( " " ) . repeated ( 5 - band . length ( ) ) ) . arg ( freq ) ) ;
2019-01-21 17:45:52 -05:00
connect ( a , & QAction : : triggered , this , [ this , f ] ( ) {
setRig ( f . frequency_ ) ;
} ) ;
}
}
2018-11-29 22:43:15 -05:00
void MainWindow : : buildHeartbeatMenu ( QMenu * menu ) {
2019-02-11 11:04:05 -05:00
if ( m_hbInterval > 0 ) {
auto startStop = menu - > addAction ( ui - > hbMacroButton - > isChecked ( ) ? " Stop Heartbeat Timer " : " Start Heartbeat Timer " ) ;
connect ( startStop , & QAction : : triggered , this , [ this ] ( ) { ui - > hbMacroButton - > toggle ( ) ; } ) ;
menu - > addSeparator ( ) ;
}
2019-03-30 20:03:24 -04:00
buildRepeatMenu ( menu , ui - > hbMacroButton , false , & m_hbInterval ) ;
2018-11-29 22:43:15 -05:00
menu - > addSeparator ( ) ;
auto now = menu - > addAction ( " Send Heartbeat Now " ) ;
connect ( now , & QAction : : triggered , this , & MainWindow : : sendHeartbeat ) ;
}
void MainWindow : : buildCQMenu ( QMenu * menu ) {
2019-02-11 11:04:05 -05:00
if ( m_cqInterval > 0 ) {
auto startStop = menu - > addAction ( ui - > cqMacroButton - > isChecked ( ) ? " Stop CQ Timer " : " Start CQ Timer " ) ;
connect ( startStop , & QAction : : triggered , this , [ this ] ( ) { ui - > cqMacroButton - > toggle ( ) ; } ) ;
menu - > addSeparator ( ) ;
}
2019-03-30 20:03:24 -04:00
buildRepeatMenu ( menu , ui - > cqMacroButton , true , & m_cqInterval ) ;
2018-11-29 22:43:15 -05:00
menu - > addSeparator ( ) ;
2019-02-11 11:04:05 -05:00
auto now = menu - > addAction ( " Send CQ Now " ) ;
2018-12-01 16:59:17 -05:00
connect ( now , & QAction : : triggered , this , [ this ] ( ) { sendCQ ( true ) ; } ) ;
2018-11-29 22:43:15 -05:00
}
2019-03-30 20:03:24 -04:00
void MainWindow : : buildRepeatMenu ( QMenu * menu , QPushButton * button , bool isLowInterval , int * interval ) {
2018-11-27 23:04:11 -05:00
QList < QPair < QString , int > > items = {
2018-11-27 23:25:40 -05:00
{ " On demand / do not repeat " , 0 } ,
{ " Repeat every 1 minute " , 1 } ,
{ " Repeat every 5 minutes " , 5 } ,
{ " Repeat every 10 minutes " , 10 } ,
{ " Repeat every 15 minutes " , 15 } ,
{ " Repeat every 30 minutes " , 30 } ,
{ " Repeat every 60 minutes " , 60 } ,
2019-02-08 22:33:33 -05:00
{ " Repeat every N minutes (Custom Interval) " , - 1 } , // this needs to be last because of isSet bool
2018-11-27 23:04:11 -05:00
} ;
2019-03-30 20:03:24 -04:00
if ( isLowInterval ) {
items . removeAt ( 5 ) ; // remove the thirty minute interval
items . removeAt ( 5 ) ; // remove the sixty minute interval
} else {
items . removeAt ( 1 ) ; // remove the one minute interval
items . removeAt ( 1 ) ; // remove the five minute interval
}
2019-02-08 22:33:33 -05:00
auto customFormat = QString ( " Repeat every %1 minutes (Custom Interval) " ) ;
2018-11-27 23:04:11 -05:00
QActionGroup * group = new QActionGroup ( menu ) ;
2019-02-08 20:53:48 -05:00
bool isSet = false ;
2018-11-27 23:04:11 -05:00
foreach ( auto pair , items ) {
int minutes = pair . second ;
2019-02-08 20:53:48 -05:00
bool isMatch = * interval = = minutes ;
bool isCustom = ( minutes = = - 1 & & isSet = = false ) ;
if ( isMatch ) {
isSet = true ;
2019-02-08 20:45:50 -05:00
}
2019-02-08 22:33:33 -05:00
auto text = pair . first ;
if ( isCustom ) {
text = QString ( customFormat ) . arg ( * interval ) ;
}
auto action = menu - > addAction ( text ) ;
action - > setData ( minutes ) ;
action - > setCheckable ( true ) ;
2019-02-08 20:53:48 -05:00
action - > setChecked ( isMatch | | isCustom ) ;
2018-11-27 23:04:11 -05:00
group - > addAction ( action ) ;
2019-02-08 22:33:33 -05:00
connect ( action , & QAction : : toggled , this , [ this , action , customFormat , minutes , interval , button ] ( bool checked ) {
int min = minutes ;
2018-11-27 23:04:11 -05:00
if ( checked ) {
2019-02-08 20:45:50 -05:00
if ( minutes = = - 1 ) {
bool ok = false ;
2019-02-08 22:33:33 -05:00
min = QInputDialog : : getInt ( this , " Repeat every N minutes " , " Minutes " , 0 , 1 , 1440 , 1 , & ok ) ;
2019-02-08 20:45:50 -05:00
if ( ! ok ) {
return ;
}
2019-02-08 22:33:33 -05:00
action - > setText ( QString ( customFormat ) . arg ( * interval ) ) ;
2019-02-08 20:45:50 -05:00
}
2019-02-08 22:33:33 -05:00
* interval = min ;
if ( min > 0 ) {
2018-11-28 22:53:18 -05:00
// force a re-toggle
button - > setChecked ( false ) ;
}
2019-02-08 22:33:33 -05:00
button - > setChecked ( min > 0 ) ;
2018-11-27 23:04:11 -05:00
}
} ) ;
}
}
void MainWindow : : sendHeartbeat ( ) {
2018-11-25 22:12:54 -05:00
QString mycall = m_config . my_callsign ( ) ;
QString mygrid = m_config . my_grid ( ) . left ( 4 ) ;
2019-01-14 10:15:45 -05:00
QStringList parts ;
2019-01-15 02:06:30 -05:00
parts . append ( QString ( " %1: " ) . arg ( mycall ) ) ;
2020-03-28 15:10:41 -04:00
2020-03-31 20:49:03 -04:00
# if JS8_CUSTOMIZE_HB
2020-03-28 15:10:41 -04:00
auto hb = m_config . hb_message ( ) ;
2020-03-31 20:49:03 -04:00
# else
auto hb = QString { } ;
# endif
2020-03-28 15:10:41 -04:00
if ( hb . isEmpty ( ) ) {
2020-03-31 20:49:03 -04:00
parts . append ( " HEARTBEAT " ) ;
2020-03-28 15:10:41 -04:00
parts . append ( mygrid ) ;
} else {
parts . append ( hb ) ;
}
2019-01-14 10:15:45 -05:00
QString message = parts . join ( " " ) . trimmed ( ) ;
2018-11-25 22:12:54 -05:00
2019-01-16 00:39:15 -05:00
auto f = findFreeFreqOffset ( 500 , 1000 , 50 ) ;
2024-10-20 13:54:14 -07:00
if ( freq ( ) < = 1000 ) {
f = freq ( ) ;
2019-01-16 00:39:15 -05:00
}
else if ( m_config . heartbeat_anywhere ( ) ) {
f = - 1 ;
}
2018-11-29 22:19:17 -05:00
2020-02-17 20:43:26 -05:00
enqueueMessage ( PriorityLow + 1 , message , f , nullptr ) ;
2019-01-28 09:43:47 -05:00
processTxQueue ( ) ;
2018-11-25 22:12:54 -05:00
}
2019-02-01 01:46:01 -05:00
void MainWindow : : sendHeartbeatAck ( QString to , int snr , QString extra ) {
2020-03-30 14:27:36 -04:00
# if JS8_HB_ACK_SNR_CONFIGURABLE
2020-03-29 22:36:53 -04:00
auto message = m_config . heartbeat_ack_snr ( ) ?
QString ( " %1 SNR %2 %3 " ) . arg ( to ) . arg ( Varicode : : formatSNR ( snr ) ) . arg ( extra ) . trimmed ( ) :
QString ( " %1 ACK %2 " ) . arg ( to ) . arg ( extra ) . trimmed ( ) ;
2020-03-30 14:27:36 -04:00
# else
2020-05-03 22:25:51 -04:00
auto message = QString ( " %1 HEARTBEAT SNR %2 %3 " ) . arg ( to ) . arg ( Varicode : : formatSNR ( snr ) ) . arg ( extra ) . trimmed ( ) ;
2020-03-30 14:27:36 -04:00
# endif
2018-12-20 20:16:53 -05:00
auto f = m_config . heartbeat_anywhere ( ) ? - 1 : findFreeFreqOffset ( 500 , 1000 , 50 ) ;
2020-02-17 20:43:26 -05:00
if ( m_config . autoreply_confirmation ( ) ) {
confirmThenEnqueueMessage ( 90 , PriorityLow + 1 , message , f , [ this ] ( ) {
processTxQueue ( ) ;
} ) ;
} else {
enqueueMessage ( PriorityLow + 1 , message , f , nullptr ) ;
processTxQueue ( ) ;
}
2018-12-20 20:16:53 -05:00
}
2018-11-27 23:04:11 -05:00
void MainWindow : : on_hbMacroButton_toggled ( bool checked ) {
if ( checked ) {
2019-12-15 11:47:10 -05:00
// only clear callsign if we do not allow hbs while in qso
if ( m_config . heartbeat_qso_pause ( ) ) {
clearCallsignSelected ( ) ;
}
2019-02-09 16:26:24 -05:00
2018-11-27 23:04:11 -05:00
if ( m_hbInterval ) {
2018-11-28 22:53:18 -05:00
m_nextHeartbeat = nextTransmitCycle ( ) . addSecs ( m_hbInterval * 60 ) ;
2018-11-27 23:04:11 -05:00
2018-11-28 22:53:18 -05:00
if ( ! repeatTimer . isActive ( ) ) {
repeatTimer . start ( ) ;
}
2018-11-27 23:04:11 -05:00
} else {
sendHeartbeat ( ) ;
// make this button emulate a single press button
ui - > hbMacroButton - > setChecked ( false ) ;
}
} else {
2018-11-28 22:53:18 -05:00
m_nextHeartbeat = QDateTime { } ;
2018-11-27 23:04:11 -05:00
}
2018-11-28 22:53:18 -05:00
updateRepeatButtonDisplay ( ) ;
2018-11-27 23:04:11 -05:00
}
void MainWindow : : on_hbMacroButton_clicked ( ) {
}
2018-12-01 16:59:17 -05:00
void MainWindow : : sendCQ ( bool repeat ) {
2018-09-06 15:22:24 -04:00
auto message = m_config . cq_message ( ) ;
if ( message . isEmpty ( ) ) {
QString mygrid = m_config . my_grid ( ) . left ( 4 ) ;
2020-03-28 11:57:38 -04:00
message = QString ( " CQ CQ CQ %1 " ) . arg ( mygrid ) . trimmed ( ) ;
2018-09-06 15:22:24 -04:00
}
2018-10-15 02:41:35 -04:00
clearCallsignSelected ( ) ;
2018-10-17 14:23:17 -04:00
2018-10-27 15:19:49 -04:00
addMessageText ( replaceMacros ( message , buildMacroValues ( ) , true ) ) ;
2018-10-17 09:08:37 -04:00
2018-12-01 16:59:17 -05:00
if ( repeat | | m_config . transmit_directed ( ) ) toggleTx ( true ) ;
2018-07-05 04:45:55 -04:00
}
2018-11-27 23:04:11 -05:00
void MainWindow : : on_cqMacroButton_toggled ( bool checked ) {
if ( checked ) {
2019-02-09 16:26:24 -05:00
clearCallsignSelected ( ) ;
2018-11-27 23:04:11 -05:00
if ( m_cqInterval ) {
2018-11-28 22:53:18 -05:00
m_nextCQ = nextTransmitCycle ( ) . addSecs ( m_cqInterval * 60 ) ;
if ( ! repeatTimer . isActive ( ) ) {
repeatTimer . start ( ) ;
}
2018-11-27 23:04:11 -05:00
} else {
2018-11-28 22:53:18 -05:00
sendCQ ( ) ;
2018-11-27 23:04:11 -05:00
// make this button emulate a single press button
ui - > cqMacroButton - > setChecked ( false ) ;
}
} else {
2018-11-28 22:53:18 -05:00
m_nextCQ = QDateTime { } ;
2018-11-27 23:04:11 -05:00
}
2018-11-28 22:53:18 -05:00
updateRepeatButtonDisplay ( ) ;
2018-11-27 23:04:11 -05:00
}
void MainWindow : : on_cqMacroButton_clicked ( ) {
}
2018-08-28 16:04:49 -04:00
void MainWindow : : on_replyMacroButton_clicked ( ) {
QString call = callsignSelected ( ) ;
if ( call . isEmpty ( ) ) {
2018-07-20 16:11:13 -04:00
return ;
}
2018-10-17 09:08:37 -04:00
2018-10-27 15:19:49 -04:00
auto message = m_config . reply_message ( ) ;
message = replaceMacros ( message , buildMacroValues ( ) , true ) ;
addMessageText ( QString ( " %1 %2 " ) . arg ( call ) . arg ( message ) ) ;
2018-10-17 09:08:37 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
2018-07-05 15:21:47 -04:00
}
2018-10-28 12:37:47 -04:00
void MainWindow : : on_snrMacroButton_clicked ( ) {
QString call = callsignSelected ( ) ;
if ( call . isEmpty ( ) ) {
return ;
}
auto now = DriftingDateTime : : currentDateTimeUtc ( ) ;
int callsignAging = m_config . callsign_aging ( ) ;
if ( ! m_callActivity . contains ( call ) ) {
return ;
}
auto cd = m_callActivity [ call ] ;
if ( callsignAging & & cd . utcTimestamp . secsTo ( now ) / 60 > = callsignAging ) {
return ;
}
auto snr = Varicode : : formatSNR ( cd . snr ) ;
addMessageText ( QString ( " %1 SNR %2 " ) . arg ( call ) . arg ( snr ) ) ;
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
}
2019-02-08 16:57:42 -05:00
void MainWindow : : on_infoMacroButton_clicked ( ) {
QString info = m_config . my_info ( ) ;
if ( info . isEmpty ( ) ) {
2018-07-20 16:11:13 -04:00
return ;
}
2018-10-17 14:23:17 -04:00
2019-02-08 16:57:42 -05:00
addMessageText ( QString ( " INFO %1 " ) . arg ( replaceMacros ( info , buildMacroValues ( ) , true ) ) ) ;
2018-10-17 14:23:17 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
2018-07-07 00:47:17 -04:00
}
2020-03-28 15:10:41 -04:00
void MainWindow : : on_statusMacroButton_clicked ( ) {
QString status = m_config . my_status ( ) ;
if ( status . isEmpty ( ) ) {
return ;
}
addMessageText ( QString ( " STATUS %1 " ) . arg ( replaceMacros ( status , buildMacroValues ( ) , true ) ) ) ;
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
}
2018-09-24 16:46:33 -04:00
void MainWindow : : setShowColumn ( QString tableKey , QString columnKey , bool value ) {
m_showColumnsCache [ tableKey + columnKey ] = QVariant ( value ) ;
displayBandActivity ( ) ;
displayCallActivity ( ) ;
}
2018-11-03 01:59:21 -04:00
bool MainWindow : : showColumn ( QString tableKey , QString columnKey , bool default_ ) {
return m_showColumnsCache . value ( tableKey + columnKey , QVariant ( default_ ) ) . toBool ( ) ;
2018-09-24 16:46:33 -04:00
}
void MainWindow : : buildShowColumnsMenu ( QMenu * menu , QString tableKey ) {
QList < QPair < QString , QString > > columnKeys = {
{ " Frequency Offset " , " offset " } ,
{ " Last heard timestamp " , " timestamp " } ,
{ " SNR " , " snr " } ,
2018-11-03 02:06:20 -04:00
{ " Time Delta " , " tdrift " } ,
2019-11-10 13:47:17 -05:00
{ " Mode Speed " , " submode " } ,
2018-09-24 16:46:33 -04:00
} ;
2018-11-03 01:59:21 -04:00
QMap < QString , bool > defaultOverride = {
2019-11-10 13:47:17 -05:00
{ " submode " , false } ,
2018-11-03 01:59:21 -04:00
{ " tdrift " , false } ,
{ " grid " , false } ,
2024-11-17 05:34:54 -08:00
{ " distance " , false } ,
{ " azimuth " , false }
2018-11-03 01:59:21 -04:00
} ;
2018-09-24 16:46:33 -04:00
if ( tableKey = = " call " ) {
columnKeys . prepend ( { " Callsign " , " callsign " } ) ;
columnKeys . append ( {
{ " Grid Locator " , " grid " } ,
2019-03-03 23:43:39 -05:00
{ " Distance " , " distance " } ,
2024-11-17 05:34:54 -08:00
{ " Azimuth " , " azimuth " } ,
2019-03-10 23:48:56 -04:00
{ " Worked Before " , " log " } ,
{ " Logged Name " , " logName " } ,
{ " Logged Comment " , " logComment " } ,
2018-09-24 16:46:33 -04:00
} ) ;
}
2018-10-28 17:54:10 -04:00
columnKeys . prepend ( { " Show Column Labels " , " labels " } ) ;
bool first = true ;
2018-09-24 16:46:33 -04:00
foreach ( auto p , columnKeys ) {
auto columnLabel = p . first ;
auto columnKey = p . second ;
auto a = menu - > addAction ( columnLabel ) ;
a - > setCheckable ( true ) ;
2018-11-03 01:59:21 -04:00
bool showByDefault = true ;
if ( defaultOverride . contains ( columnKey ) ) {
showByDefault = defaultOverride [ columnKey ] ;
}
a - > setChecked ( showColumn ( tableKey , columnKey , showByDefault ) ) ;
2018-09-24 16:46:33 -04:00
connect ( a , & QAction : : triggered , this , [ this , a , tableKey , columnKey ] ( ) {
setShowColumn ( tableKey , columnKey , a - > isChecked ( ) ) ;
} ) ;
2018-10-28 17:54:10 -04:00
if ( first ) {
menu - > addSeparator ( ) ;
first = false ;
}
2018-09-24 16:46:33 -04:00
}
}
2018-08-23 11:59:20 -04:00
void MainWindow : : setSortBy ( QString key , QString value ) {
m_sortCache [ key ] = QVariant ( value ) ;
displayBandActivity ( ) ;
displayCallActivity ( ) ;
}
2024-10-23 08:14:02 -07:00
QString
MainWindow : : getSortBy ( QString const & key ,
QString const & defaultValue ) const
{
return m_sortCache . value ( key , QVariant ( defaultValue ) ) . toString ( ) ;
}
2024-10-23 09:26:25 -07:00
MainWindow : : SortByReverse
2024-10-23 08:14:02 -07:00
MainWindow : : getSortByReverse ( QString const & key ,
QString const & defaultValue ) const
{
auto const sortBy = getSortBy ( key , defaultValue ) ;
auto const reverse = sortBy . startsWith ( " - " ) ;
return
{
reverse ? sortBy . sliced ( 1 ) : sortBy ,
reverse
} ;
2018-08-23 11:59:20 -04:00
}
2018-08-28 16:35:14 -04:00
void MainWindow : : buildSortByMenu ( QMenu * menu , QString key , QString defaultValue , QList < QPair < QString , QString > > values ) {
2018-08-23 11:59:20 -04:00
auto currentSortBy = getSortBy ( key , defaultValue ) ;
QActionGroup * g = new QActionGroup ( menu ) ;
g - > setExclusive ( true ) ;
2018-08-28 16:35:14 -04:00
foreach ( auto p , values ) {
auto k = p . first ;
auto v = p . second ;
2018-08-23 11:59:20 -04:00
auto a = menu - > addAction ( k ) ;
a - > setCheckable ( true ) ;
a - > setChecked ( v = = currentSortBy ) ;
a - > setActionGroup ( g ) ;
connect ( a , & QAction : : triggered , this , [ this , a , key , v ] ( ) {
if ( a - > isChecked ( ) ) {
setSortBy ( key , v ) ;
}
} ) ;
}
}
2018-08-23 12:24:47 -04:00
void MainWindow : : buildBandActivitySortByMenu ( QMenu * menu ) {
buildSortByMenu ( menu , " bandActivity " , " offset " , {
2018-09-24 16:46:33 -04:00
{ " Frequency offset " , " offset " } ,
2018-08-23 12:24:47 -04:00
{ " Last heard timestamp (oldest first) " , " timestamp " } ,
2018-08-24 00:11:21 -04:00
{ " Last heard timestamp (recent first) " , " -timestamp " } ,
2018-08-23 12:24:47 -04:00
{ " SNR (weakest first) " , " snr " } ,
2019-11-10 13:47:17 -05:00
{ " SNR (strongest first) " , " -snr " } ,
{ " Mode Speed (slowest first) " , " submode " } ,
{ " Mode Speed (fastest first) " , " -submode " }
2018-08-23 12:24:47 -04:00
} ) ;
}
void MainWindow : : buildCallActivitySortByMenu ( QMenu * menu ) {
buildSortByMenu ( menu , " callActivity " , " callsign " , {
{ " Callsign " , " callsign " } ,
2018-09-29 17:30:27 -04:00
{ " Callsigns Replied (recent first) " , " ackTimestamp " } ,
2018-09-24 16:46:33 -04:00
{ " Frequency offset " , " offset " } ,
2018-08-23 12:24:47 -04:00
{ " Distance (closest first) " , " distance " } ,
{ " Distance (farthest first) " , " -distance " } ,
2024-11-17 05:34:54 -08:00
{ " Azimuth " , " azimuth " } ,
2018-08-23 12:24:47 -04:00
{ " Last heard timestamp (oldest first) " , " timestamp " } ,
2018-08-24 00:11:21 -04:00
{ " Last heard timestamp (recent first) " , " -timestamp " } ,
2018-08-23 12:24:47 -04:00
{ " SNR (weakest first) " , " snr " } ,
2019-11-10 13:47:17 -05:00
{ " SNR (strongest first) " , " -snr " } ,
{ " Mode Speed (slowest first) " , " submode " } ,
{ " Mode Speed (fastest first) " , " -submode " }
2018-08-23 12:24:47 -04:00
} ) ;
}
2018-08-17 11:13:49 -04:00
void MainWindow : : buildQueryMenu ( QMenu * menu , QString call ) {
2018-07-31 14:34:36 -04:00
bool isAllCall = isAllCallIncluded ( call ) ;
2018-07-19 10:35:00 -04:00
2018-08-17 11:13:49 -04:00
// for now, we're going to omit displaying the call...delete this if we want the other functionality
call = " " ;
2018-09-21 01:47:21 -04:00
auto grid = m_config . my_grid ( ) ;
2019-02-08 16:57:42 -05:00
bool emptyInfo = m_config . my_info ( ) . isEmpty ( ) ;
2018-10-28 12:37:47 -04:00
bool emptyGrid = m_config . my_grid ( ) . isEmpty ( ) ;
2018-08-29 12:32:09 -04:00
auto callAction = menu - > addAction ( QString ( " Send a directed message to selected callsign " ) ) ;
connect ( callAction , & QAction : : triggered , this , [ this ] ( ) {
2018-07-24 02:53:01 -04:00
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
2018-08-17 11:13:49 -04:00
addMessageText ( QString ( " %1 " ) . arg ( selectedCall ) , true ) ;
2018-07-24 02:53:01 -04:00
} ) ;
2018-08-31 21:30:34 -04:00
menu - > addSeparator ( ) ;
2018-08-29 12:32:09 -04:00
auto sendReplyAction = menu - > addAction ( QString ( " %1 Reply - Send reply message to selected callsign " ) . arg ( call ) . trimmed ( ) ) ;
connect ( sendReplyAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
2018-10-27 15:19:49 -04:00
auto message = m_config . reply_message ( ) ;
message = replaceMacros ( message , buildMacroValues ( ) , true ) ;
addMessageText ( QString ( " %1 %2 " ) . arg ( selectedCall ) . arg ( message ) , true ) ;
2018-08-29 12:32:09 -04:00
} ) ;
2018-07-24 02:53:01 -04:00
2018-08-17 11:13:49 -04:00
auto sendSNRAction = menu - > addAction ( QString ( " %1 SNR - Send a signal report to the selected callsign " ) . arg ( call ) . trimmed ( ) ) ;
2018-08-29 22:17:14 -04:00
sendSNRAction - > setEnabled ( m_callActivity . contains ( callsignSelected ( ) ) ) ;
2018-07-24 02:53:01 -04:00
connect ( sendSNRAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
if ( ! m_callActivity . contains ( selectedCall ) ) {
return ;
}
auto d = m_callActivity [ selectedCall ] ;
addMessageText ( QString ( " %1 SNR %2 " ) . arg ( selectedCall ) . arg ( Varicode : : formatSNR ( d . snr ) ) , true ) ;
2018-08-24 09:38:00 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
2018-07-24 02:53:01 -04:00
} ) ;
2019-02-08 16:57:42 -05:00
auto infoAction = menu - > addAction ( QString ( " %1 INFO - Send my station information " ) . arg ( call ) . trimmed ( ) ) ;
infoAction - > setDisabled ( emptyInfo ) ;
connect ( infoAction , & QAction : : triggered , this , [ this ] ( ) {
2018-10-28 12:37:47 -04:00
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
2019-02-08 16:57:42 -05:00
addMessageText ( QString ( " %1 INFO %2 " ) . arg ( selectedCall ) . arg ( m_config . my_info ( ) ) , true ) ;
2018-10-28 12:37:47 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
} ) ;
auto gridAction = menu - > addAction ( QString ( " %1 GRID %2 - Send my current station Maidenhead grid locator " ) . arg ( call ) . arg ( grid ) . trimmed ( ) ) ;
gridAction - > setDisabled ( emptyGrid ) ;
connect ( gridAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
addMessageText ( QString ( " %1 GRID %2 " ) . arg ( selectedCall ) . arg ( m_config . my_grid ( ) ) , true ) ;
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
} ) ;
2018-08-29 12:08:22 -04:00
menu - > addSeparator ( ) ;
2018-07-13 04:44:41 -04:00
2018-11-03 22:38:27 -04:00
auto snrQueryAction = menu - > addAction ( QString ( " %1 SNR? - What is my signal report? " ) . arg ( call ) . trimmed ( ) ) ;
2018-09-24 21:06:12 -04:00
snrQueryAction - > setDisabled ( isAllCall ) ;
2018-07-25 16:51:47 -04:00
connect ( snrQueryAction , & QAction : : triggered , this , [ this ] ( ) {
2018-07-13 04:44:41 -04:00
2018-07-15 15:43:29 -04:00
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
2018-07-13 04:44:41 -04:00
}
2018-10-28 12:37:47 -04:00
addMessageText ( QString ( " %1 SNR? " ) . arg ( selectedCall ) , true ) ;
2018-08-24 09:38:00 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
2018-07-15 15:43:29 -04:00
} ) ;
2019-02-08 16:57:42 -05:00
auto infoQueryAction = menu - > addAction ( QString ( " %1 INFO? - What is your station information? " ) . arg ( call ) . trimmed ( ) ) ;
infoQueryAction - > setDisabled ( isAllCall ) ;
connect ( infoQueryAction , & QAction : : triggered , this , [ this ] ( ) {
2018-07-15 15:43:29 -04:00
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
2019-02-08 16:57:42 -05:00
addMessageText ( QString ( " %1 INFO? " ) . arg ( selectedCall ) , true ) ;
2018-08-24 09:38:00 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
2018-07-19 02:09:19 -04:00
} ) ;
2018-11-03 22:38:27 -04:00
auto gridQueryAction = menu - > addAction ( QString ( " %1 GRID? - What is your current grid locator? " ) . arg ( call ) . trimmed ( ) ) ;
2018-08-27 21:19:38 -04:00
gridQueryAction - > setDisabled ( isAllCall ) ;
connect ( gridQueryAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
2018-10-28 12:37:47 -04:00
addMessageText ( QString ( " %1 GRID? " ) . arg ( selectedCall ) , true ) ;
2018-08-27 21:19:38 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
} ) ;
2020-03-29 20:00:45 -04:00
auto stationIdleQueryAction = menu - > addAction ( QString ( " %1 STATUS? - What is your station status message? " ) . arg ( call ) . trimmed ( ) ) ;
2018-10-17 09:08:37 -04:00
stationIdleQueryAction - > setDisabled ( isAllCall ) ;
connect ( stationIdleQueryAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
2018-10-28 12:37:47 -04:00
addMessageText ( QString ( " %1 STATUS? " ) . arg ( selectedCall ) , true ) ;
2018-10-17 09:08:37 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
} ) ;
2018-12-17 00:38:34 -05:00
auto heardQueryAction = menu - > addAction ( QString ( " %1 HEARING? - What are the stations are you hearing? (Top 4 ranked by most recently heard) " ) . arg ( call ) . trimmed ( ) ) ;
2018-07-25 16:51:47 -04:00
heardQueryAction - > setDisabled ( isAllCall ) ;
connect ( heardQueryAction , & QAction : : triggered , this , [ this ] ( ) {
2018-07-24 15:19:02 -04:00
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
2018-12-17 00:38:34 -05:00
addMessageText ( QString ( " %1 HEARING? " ) . arg ( selectedCall ) , true ) ;
2018-08-24 09:38:00 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
2018-07-13 04:44:41 -04:00
} ) ;
2018-09-09 12:40:02 -04:00
#if 0
2018-08-29 12:08:22 -04:00
auto retransmitAction = menu - > addAction ( QString ( " %1|[MESSAGE] - Please ACK and retransmit the following message " ) . arg ( call ) . trimmed ( ) ) ;
2018-07-25 22:49:19 -04:00
retransmitAction - > setDisabled ( isAllCall ) ;
connect ( retransmitAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
2018-08-29 12:08:22 -04:00
addMessageText ( QString ( " %1|[MESSAGE] " ) . arg ( selectedCall ) , true , true ) ;
2018-07-25 22:49:19 -04:00
} ) ;
2018-09-09 12:40:02 -04:00
# endif
2018-07-19 02:09:19 -04:00
2019-02-01 01:46:01 -05:00
auto alertAction = menu - > addAction ( QString ( " %1>[MESSAGE] - Please relay this message to its destination " ) . arg ( call ) . trimmed ( ) ) ;
2018-07-26 15:57:19 -04:00
alertAction - > setDisabled ( isAllCall ) ;
connect ( alertAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
2018-09-09 12:40:02 -04:00
addMessageText ( QString ( " %1>[MESSAGE] " ) . arg ( selectedCall ) , true , true ) ;
2018-07-26 15:57:19 -04:00
} ) ;
2019-02-07 14:23:46 -05:00
auto msgAction = menu - > addAction ( QString ( " %1 MSG [MESSAGE] - Please store this message in your inbox " ) . arg ( call ) . trimmed ( ) ) ;
msgAction - > setDisabled ( isAllCall ) ;
connect ( msgAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
addMessageText ( QString ( " %1 MSG [MESSAGE] " ) . arg ( selectedCall ) , true , true ) ;
} ) ;
2019-01-23 20:38:44 -05:00
auto msgToAction = menu - > addAction ( QString ( " %1 MSG TO:[CALLSIGN] [MESSAGE] - Please store this message at your station for later retreival by [CALLSIGN] " ) . arg ( call ) . trimmed ( ) ) ;
msgToAction - > setDisabled ( isAllCall ) ;
connect ( msgToAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
addMessageText ( QString ( " %1 MSG TO:[CALLSIGN] [MESSAGE] " ) . arg ( selectedCall ) , true , true ) ;
} ) ;
2018-12-31 20:03:51 -05:00
auto qsoQueryAction = menu - > addAction ( QString ( " %1 QUERY CALL [CALLSIGN]? - Please acknowledge you can communicate directly with [CALLSIGN] " ) . arg ( call ) . trimmed ( ) ) ;
2018-09-21 01:47:21 -04:00
connect ( qsoQueryAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
2018-12-31 20:03:51 -05:00
addMessageText ( QString ( " %1 QUERY CALL [CALLSIGN]? " ) . arg ( selectedCall ) , true , true ) ;
2018-09-21 01:47:21 -04:00
} ) ;
2019-02-07 22:20:14 -05:00
auto qsoQueryMsgsAction = menu - > addAction ( QString ( " %1 QUERY MSGS - Do you have any messages for me? " ) . arg ( call ) . trimmed ( ) ) ;
connect ( qsoQueryMsgsAction , & QAction : : triggered , this , [ this ] ( ) {
2019-01-23 20:35:29 -05:00
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
addMessageText ( QString ( " %1 QUERY MSGS " ) . arg ( selectedCall ) , true , true ) ;
} ) ;
2019-02-07 22:20:14 -05:00
auto qsoQueryMsgAction = menu - > addAction ( QString ( " %1 QUERY MSG [ID] - Please deliver the complete message identified by ID " ) . arg ( call ) . trimmed ( ) ) ;
connect ( qsoQueryMsgAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
addMessageText ( QString ( " %1 QUERY MSG [ID] " ) . arg ( selectedCall ) , true , true ) ;
} ) ;
2019-09-05 14:07:24 -04:00
menu - > addSeparator ( ) ;
auto agnAction = menu - > addAction ( QString ( " %1 AGN? - Please repeat your last transmission " ) . arg ( call ) . trimmed ( ) ) ;
2018-07-19 10:35:00 -04:00
connect ( agnAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
addMessageText ( QString ( " %1 AGN? " ) . arg ( selectedCall ) , true ) ;
2018-08-24 09:38:00 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
2018-07-19 10:35:00 -04:00
} ) ;
2018-08-17 11:13:49 -04:00
auto qslQueryAction = menu - > addAction ( QString ( " %1 QSL? - Did you receive my last transmission? " ) . arg ( call ) . trimmed ( ) ) ;
2018-08-03 21:01:54 -04:00
connect ( qslQueryAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
addMessageText ( QString ( " %1 QSL? " ) . arg ( selectedCall ) , true ) ;
2018-08-24 09:38:00 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
2018-08-03 21:01:54 -04:00
} ) ;
2018-08-17 11:13:49 -04:00
auto qslAction = menu - > addAction ( QString ( " %1 QSL - I confirm I received your last transmission " ) . arg ( call ) . trimmed ( ) ) ;
2018-08-04 11:19:07 -04:00
connect ( qslAction , & QAction : : triggered , this , [ this ] ( ) {
2018-07-25 16:51:47 -04:00
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
2018-08-04 11:19:07 -04:00
addMessageText ( QString ( " %1 QSL " ) . arg ( selectedCall ) , true ) ;
2018-08-24 09:38:00 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
2018-07-25 16:51:47 -04:00
} ) ;
2018-08-17 11:13:49 -04:00
auto yesAction = menu - > addAction ( QString ( " %1 YES - I confirm your last inquiry " ) . arg ( call ) . trimmed ( ) ) ;
2018-07-19 23:14:11 -04:00
connect ( yesAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
addMessageText ( QString ( " %1 YES " ) . arg ( selectedCall ) , true ) ;
2018-08-24 09:38:00 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
2018-07-19 23:14:11 -04:00
} ) ;
2018-08-17 11:13:49 -04:00
auto noAction = menu - > addAction ( QString ( " %1 NO - I do not confirm your last inquiry " ) . arg ( call ) . trimmed ( ) ) ;
2018-07-19 23:14:11 -04:00
connect ( noAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
addMessageText ( QString ( " %1 NO " ) . arg ( selectedCall ) , true ) ;
2018-08-24 09:38:00 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
2018-07-19 23:14:11 -04:00
} ) ;
2018-08-24 09:21:17 -04:00
auto hwAction = menu - > addAction ( QString ( " %1 HW CPY? - How do you copy? " ) . arg ( call ) . trimmed ( ) ) ;
connect ( hwAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
addMessageText ( QString ( " %1 HW CPY? " ) . arg ( selectedCall ) , true ) ;
2018-08-24 09:38:00 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
2018-08-24 09:21:17 -04:00
} ) ;
auto rrAction = menu - > addAction ( QString ( " %1 RR - Roger. Received. I copy. " ) . arg ( call ) . trimmed ( ) ) ;
connect ( rrAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
addMessageText ( QString ( " %1 RR " ) . arg ( selectedCall ) , true ) ;
2018-08-24 09:38:00 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
2018-08-24 09:21:17 -04:00
} ) ;
auto fbAction = menu - > addAction ( QString ( " %1 FB - Fine Business " ) . arg ( call ) . trimmed ( ) ) ;
connect ( fbAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
addMessageText ( QString ( " %1 FB " ) . arg ( selectedCall ) , true ) ;
2018-08-24 09:38:00 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
2018-08-24 09:21:17 -04:00
} ) ;
2018-10-27 15:01:27 -04:00
auto sevenThreeAction = menu - > addAction ( QString ( " %1 73 - I send my best regards " ) . arg ( call ) . trimmed ( ) ) ;
2018-07-19 10:35:00 -04:00
connect ( sevenThreeAction , & QAction : : triggered , this , [ this ] ( ) {
2018-07-19 02:09:19 -04:00
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
2018-07-19 10:35:00 -04:00
addMessageText ( QString ( " %1 73 " ) . arg ( selectedCall ) , true ) ;
2018-08-24 09:38:00 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
2018-07-19 02:09:19 -04:00
} ) ;
2018-10-27 15:01:27 -04:00
auto skAction = menu - > addAction ( QString ( " %1 SK - End of contact " ) . arg ( call ) . trimmed ( ) ) ;
connect ( skAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
addMessageText ( QString ( " %1 SK " ) . arg ( selectedCall ) , true ) ;
2019-03-04 23:25:17 -05:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
} ) ;
auto ditDitAction = menu - > addAction ( QString ( " %1 DIT DIT - End of contact / Two bits " ) . arg ( call ) . trimmed ( ) ) ;
connect ( ditDitAction , & QAction : : triggered , this , [ this ] ( ) {
QString selectedCall = callsignSelected ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
addMessageText ( QString ( " %1 DIT DIT " ) . arg ( selectedCall ) , true ) ;
2018-10-27 15:01:27 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
} ) ;
2018-07-19 23:14:11 -04:00
}
2018-09-09 10:42:32 -04:00
void MainWindow : : buildRelayMenu ( QMenu * menu ) {
2018-09-18 17:24:07 -04:00
auto now = DriftingDateTime : : currentDateTimeUtc ( ) ;
2018-09-09 10:42:32 -04:00
int callsignAging = m_config . callsign_aging ( ) ;
foreach ( auto cd , m_callActivity . values ( ) ) {
if ( callsignAging & & cd . utcTimestamp . secsTo ( now ) / 60 > = callsignAging ) {
continue ;
}
2018-09-09 12:40:02 -04:00
menu - > addAction ( buildRelayAction ( cd . call ) ) ;
2018-09-09 10:42:32 -04:00
}
}
2018-09-09 12:40:02 -04:00
QAction * MainWindow : : buildRelayAction ( QString call ) {
2018-09-10 10:22:24 -04:00
QAction * a = new QAction ( call , nullptr ) ;
2018-09-09 12:40:02 -04:00
connect ( a , & QAction : : triggered , this , [ this , call ] ( ) {
prependMessageText ( QString ( " %1> " ) . arg ( call ) ) ;
} ) ;
return a ;
}
2018-09-09 10:42:32 -04:00
void MainWindow : : buildEditMenu ( QMenu * menu , QTextEdit * edit ) {
2018-09-06 17:44:47 -04:00
bool hasSelection = ! edit - > textCursor ( ) . selectedText ( ) . isEmpty ( ) ;
auto cut = menu - > addAction ( " Cu&t " ) ;
cut - > setEnabled ( hasSelection & & ! edit - > isReadOnly ( ) ) ;
2024-08-31 10:29:30 -07:00
connect ( edit , & QTextEdit : : copyAvailable , this , [ edit , cut ] ( bool copyAvailable ) {
2018-09-06 17:44:47 -04:00
cut - > setEnabled ( copyAvailable & & ! edit - > isReadOnly ( ) ) ;
} ) ;
2024-08-31 10:29:30 -07:00
connect ( cut , & QAction : : triggered , this , [ edit ] ( ) {
2018-09-06 17:44:47 -04:00
edit - > copy ( ) ;
edit - > textCursor ( ) . removeSelectedText ( ) ;
} ) ;
auto copy = menu - > addAction ( " &Copy " ) ;
copy - > setEnabled ( hasSelection ) ;
2024-08-31 10:29:30 -07:00
connect ( edit , & QTextEdit : : copyAvailable , this , [ copy ] ( bool copyAvailable ) {
2018-09-06 17:44:47 -04:00
copy - > setEnabled ( copyAvailable ) ;
} ) ;
connect ( copy , & QAction : : triggered , edit , & QTextEdit : : copy ) ;
auto paste = menu - > addAction ( " &Paste " ) ;
paste - > setEnabled ( edit - > canPaste ( ) ) ;
connect ( paste , & QAction : : triggered , edit , & QTextEdit : : paste ) ;
}
2018-10-27 14:41:57 -04:00
QMap < QString , QString > MainWindow : : buildMacroValues ( ) {
2020-03-28 15:10:41 -04:00
auto lastActive = DriftingDateTime : : currentDateTimeUtc ( ) . addSecs ( - m_idleMinutes * 60 ) ;
QString myIdle = since ( lastActive ) . toUpper ( ) . replace ( " NOW " , " 0M " ) ;
QString myVersion = version ( ) . replace ( " -devel " , " " ) . replace ( " -rc " , " " ) ;
2018-10-27 14:41:57 -04:00
QMap < QString , QString > values = {
{ " <MYCALL> " , m_config . my_callsign ( ) } ,
{ " <MYGRID4> " , m_config . my_grid ( ) . left ( 4 ) } ,
{ " <MYGRID12> " , m_config . my_grid ( ) . left ( 12 ) } ,
2019-02-08 16:57:42 -05:00
{ " <MYINFO> " , m_config . my_info ( ) } ,
2020-03-28 15:10:41 -04:00
{ " <MYHB> " , m_config . hb_message ( ) } ,
2018-10-27 14:41:57 -04:00
{ " <MYCQ> " , m_config . cq_message ( ) } ,
2018-12-26 20:33:30 -05:00
{ " <MYREPLY> " , m_config . reply_message ( ) } ,
2020-03-28 15:10:41 -04:00
{ " <MYSTATUS> " , m_config . my_status ( ) } ,
{ " <MYVERSION> " , myVersion } ,
{ " <MYIDLE> " , myIdle } ,
2018-10-27 14:41:57 -04:00
} ;
auto selectedCall = callsignSelected ( ) ;
if ( m_callActivity . contains ( selectedCall ) ) {
auto cd = m_callActivity [ selectedCall ] ;
values [ " <CALL> " ] = selectedCall ;
2018-11-05 11:35:37 -05:00
values [ " <TDELTA> " ] = QString ( " %1 ms " ) . arg ( ( int ) ( 1000 * cd . tdrift ) ) ;
2018-10-27 14:41:57 -04:00
if ( cd . snr > - 31 ) {
values [ " <SNR> " ] = Varicode : : formatSNR ( cd . snr ) ;
}
}
2018-12-28 11:44:58 -05:00
// these macros can have recursive macros
2019-02-08 16:57:42 -05:00
values [ " <MYINFO> " ] = replaceMacros ( values [ " <MYINFO> " ] , values , false ) ;
2020-03-28 15:10:41 -04:00
values [ " <MYSTATUS> " ] = replaceMacros ( values [ " <MYSTATUS> " ] , values , false ) ;
2018-12-28 11:44:58 -05:00
values [ " <MYCQ> " ] = replaceMacros ( values [ " <MYCQ> " ] , values , false ) ;
2020-03-28 15:10:41 -04:00
values [ " <MYHB> " ] = replaceMacros ( values [ " <MYHB> " ] , values , false ) ;
2018-12-28 11:44:58 -05:00
values [ " <MYREPLY> " ] = replaceMacros ( values [ " <MYREPLY> " ] , values , false ) ;
2018-10-27 14:41:57 -04:00
return values ;
}
2018-12-30 20:18:35 -05:00
void MainWindow : : buildSuggestionsMenu ( QMenu * menu , QTextEdit * edit , const QPoint & point ) {
2018-12-31 15:25:04 -05:00
if ( ! m_config . spellcheck ( ) ) {
return ;
}
2018-12-30 20:18:35 -05:00
bool found = false ;
auto c = edit - > cursorForPosition ( point ) ;
if ( c . charFormat ( ) . underlineStyle ( ) ! = QTextCharFormat : : WaveUnderline ) {
return ;
}
c . movePosition ( QTextCursor : : StartOfWord ) ;
c . movePosition ( QTextCursor : : EndOfWord , QTextCursor : : KeepAnchor ) ;
2019-09-25 20:26:41 -04:00
auto word = c . selectedText ( ) . toUpper ( ) . trimmed ( ) ;
2018-12-30 20:18:35 -05:00
if ( word . isEmpty ( ) ) {
return ;
}
2018-12-31 15:14:48 -05:00
QStringList suggestions = JSCChecker : : suggestions ( word , 5 , & found ) ;
2018-12-30 20:18:35 -05:00
if ( suggestions . isEmpty ( ) & & ! found ) {
return ;
}
if ( suggestions . isEmpty ( ) ) {
2018-12-31 15:14:48 -05:00
auto a = menu - > addAction ( " No Suggestions " ) ;
2018-12-30 20:18:35 -05:00
a - > setDisabled ( true ) ;
} else {
foreach ( auto suggestion , suggestions ) {
2018-12-31 15:14:48 -05:00
auto a = menu - > addAction ( suggestion ) ;
2018-12-30 20:18:35 -05:00
2024-08-31 10:29:30 -07:00
connect ( a , & QAction : : triggered , this , [ edit , point , suggestion ] ( ) {
2018-12-30 20:18:35 -05:00
auto c = edit - > cursorForPosition ( point ) ;
c . select ( QTextCursor : : WordUnderCursor ) ;
c . insertText ( suggestion ) ;
} ) ;
}
}
menu - > addSeparator ( ) ;
}
2018-09-20 10:46:04 -04:00
void MainWindow : : buildSavedMessagesMenu ( QMenu * menu ) {
2018-10-27 14:41:57 -04:00
auto values = buildMacroValues ( ) ;
2018-09-20 10:46:04 -04:00
foreach ( QString macro , m_config . macros ( ) - > stringList ( ) ) {
2018-10-27 14:41:57 -04:00
QAction * action = menu - > addAction ( replaceMacros ( macro , values , false ) ) ;
2018-10-17 14:23:17 -04:00
connect ( action , & QAction : : triggered , this , [ this , macro ] ( ) {
2018-10-27 14:41:57 -04:00
auto values = buildMacroValues ( ) ;
addMessageText ( replaceMacros ( macro , values , true ) ) ;
2018-10-17 14:23:17 -04:00
if ( m_config . transmit_directed ( ) ) toggleTx ( true ) ;
} ) ;
2018-09-20 10:46:04 -04:00
}
menu - > addSeparator ( ) ;
2018-11-19 22:36:10 -05:00
auto editAction = new QAction ( QString ( " &Edit Saved Messages " ) , menu ) ;
2018-09-20 10:46:04 -04:00
menu - > addAction ( editAction ) ;
connect ( editAction , & QAction : : triggered , this , [ this ] ( ) {
openSettings ( 5 ) ;
} ) ;
auto saveAction = new QAction ( QString ( " &Save Current Message " ) , menu ) ;
saveAction - > setDisabled ( ui - > extFreeTextMsgEdit - > toPlainText ( ) . isEmpty ( ) ) ;
menu - > addAction ( saveAction ) ;
connect ( saveAction , & QAction : : triggered , this , [ this ] ( ) {
auto macros = m_config . macros ( ) ;
if ( macros - > insertRow ( macros - > rowCount ( ) ) ) {
auto index = macros - > index ( macros - > rowCount ( ) - 1 ) ;
macros - > setData ( index , ui - > extFreeTextMsgEdit - > toPlainText ( ) ) ;
writeSettings ( ) ;
}
} ) ;
}
2018-07-19 23:14:11 -04:00
void MainWindow : : on_queryButton_pressed ( ) {
QMenu * menu = ui - > queryButton - > menu ( ) ;
if ( ! menu ) {
menu = new QMenu ( ui - > queryButton ) ;
}
menu - > clear ( ) ;
2018-08-17 11:13:49 -04:00
buildQueryMenu ( menu , callsignSelected ( ) ) ;
2018-07-13 04:44:41 -04:00
ui - > queryButton - > setMenu ( menu ) ;
ui - > queryButton - > showMenu ( ) ;
}
2018-07-09 21:49:38 -04:00
void MainWindow : : on_macrosMacroButton_pressed ( ) {
2018-07-09 15:17:53 -04:00
QMenu * menu = ui - > macrosMacroButton - > menu ( ) ;
if ( ! menu ) {
menu = new QMenu ( ui - > macrosMacroButton ) ;
}
menu - > clear ( ) ;
2018-09-20 10:46:04 -04:00
buildSavedMessagesMenu ( menu ) ;
2018-07-05 15:21:47 -04:00
2018-07-05 04:45:55 -04:00
ui - > macrosMacroButton - > setMenu ( menu ) ;
ui - > macrosMacroButton - > showMenu ( ) ;
2018-07-03 18:57:49 -04:00
}
2018-09-07 10:33:53 -04:00
void MainWindow : : on_deselectButton_pressed ( ) {
clearCallsignSelected ( ) ;
}
2018-07-05 04:45:55 -04:00
2018-09-11 18:54:32 -04:00
void MainWindow : : on_tableWidgetRXAll_cellClicked ( int /*row*/ , int /*col*/ ) {
2018-07-03 15:34:03 -04:00
ui - > tableWidgetCalls - > selectionModel ( ) - > select (
ui - > tableWidgetCalls - > selectionModel ( ) - > selection ( ) ,
QItemSelectionModel : : Deselect ) ;
2018-09-15 17:33:09 -04:00
displayCallActivity ( ) ;
2018-07-03 15:34:03 -04:00
}
void MainWindow : : on_tableWidgetRXAll_cellDoubleClicked ( int row , int col ) {
on_tableWidgetRXAll_cellClicked ( row , col ) ;
// TODO: jsherer - could also parse the messages for the last callsign?
auto item = ui - > tableWidgetRXAll - > item ( row , 0 ) ;
2018-12-23 21:06:54 -05:00
int offset = item - > text ( ) . replace ( " Hz " , " " ) . toInt ( ) ;
2018-07-20 10:54:00 -04:00
2018-12-21 22:14:14 -05:00
// switch to the offset of this row
setFreqOffsetForRestore ( offset , false ) ;
2019-09-05 14:07:24 -04:00
// TODO: prompt mode switch?
2018-07-20 10:54:00 -04:00
// print the history in the main window...
int activityAging = m_config . activity_aging ( ) ;
2018-09-18 17:24:07 -04:00
QDateTime now = DriftingDateTime : : currentDateTimeUtc ( ) ;
2018-07-20 10:54:00 -04:00
QDateTime firstActivity = now ;
QString activityText ;
2018-08-11 18:05:36 -04:00
bool isLast = false ;
2018-07-20 10:54:00 -04:00
foreach ( auto d , m_bandActivity [ offset ] ) {
if ( activityAging & & d . utcTimestamp . secsTo ( now ) / 60 > = activityAging ) {
continue ;
}
if ( activityText . isEmpty ( ) ) {
firstActivity = d . utcTimestamp ;
}
activityText . append ( d . text ) ;
2018-10-04 13:52:52 -04:00
isLast = ( d . bits & Varicode : : JS8CallLast ) = = Varicode : : JS8CallLast ;
2018-08-11 18:05:36 -04:00
if ( isLast ) {
2019-04-21 11:29:31 -04:00
activityText = QString ( " %1 %2 " ) . arg ( Varicode : : rstrip ( activityText ) ) . arg ( m_config . eot ( ) ) ;
2018-07-03 15:34:03 -04:00
}
}
2018-08-11 18:05:36 -04:00
if ( ! activityText . isEmpty ( ) ) {
displayTextForFreq ( activityText , offset , firstActivity , false , true , isLast ) ;
}
2018-07-03 15:34:03 -04:00
}
2019-01-16 00:08:09 -05:00
QString MainWindow : : generateCallDetail ( QString selectedCall ) {
2019-12-29 14:47:21 -05:00
if ( selectedCall . isEmpty ( ) ) {
2019-01-16 00:08:09 -05:00
return " " ;
}
// heard detail
QString hearing = m_heardGraphOutgoing . value ( selectedCall ) . values ( ) . join ( " , " ) ;
QString heardby = m_heardGraphIncoming . value ( selectedCall ) . values ( ) . join ( " , " ) ;
QStringList detail = {
QString ( " <h1>%1</h1> " ) . arg ( selectedCall . toHtmlEscaped ( ) ) ,
2019-12-29 14:47:21 -05:00
hearing . isEmpty ( ) ? " " : QString ( " <p><strong>HEARING</strong>: %1</p> " ) . arg ( hearing . toHtmlEscaped ( ) ) ,
heardby . isEmpty ( ) ? " " : QString ( " <p><strong>HEARD BY</strong>: %1</p> " ) . arg ( heardby . toHtmlEscaped ( ) ) ,
2019-01-16 00:08:09 -05:00
} ;
return detail . join ( " \n " ) ;
}
2018-08-09 15:46:35 -04:00
void MainWindow : : on_tableWidgetCalls_cellClicked ( int /*row*/ , int /*col*/ ) {
2018-07-03 15:34:03 -04:00
ui - > tableWidgetRXAll - > selectionModel ( ) - > select (
ui - > tableWidgetRXAll - > selectionModel ( ) - > selection ( ) ,
QItemSelectionModel : : Deselect ) ;
2018-09-15 17:33:09 -04:00
displayBandActivity ( ) ;
2018-07-03 15:34:03 -04:00
}
void MainWindow : : on_tableWidgetCalls_cellDoubleClicked ( int row , int col ) {
on_tableWidgetCalls_cellClicked ( row , col ) ;
2018-07-24 15:19:02 -04:00
auto call = callsignSelected ( ) ;
2019-02-02 17:06:01 -05:00
addMessageText ( call ) ;
2018-09-11 18:54:32 -04:00
2019-02-07 12:19:07 -05:00
# if SHOW_MESSAGE_HISTORY_ON_DOUBLECLICK
2019-01-02 12:27:16 -05:00
if ( m_rxInboxCountCache . value ( call , 0 ) > 0 ) {
// TODO:
// CommandDetail d = m_rxCallsignInboxCountCache[call].first();
// m_rxCallsignInboxCountCache[call].removeFirst();
//
// processAlertReplyForCommand(d, d.relayPath, d.cmd);
Inbox i ( inboxPath ( ) ) ;
if ( i . open ( ) ) {
2019-02-02 17:06:01 -05:00
QList < Message > msgs ;
foreach ( auto pair , i . values ( " UNREAD " , " $.params.FROM " , call , 0 , 1000 ) ) {
msgs . append ( pair . second ) ;
}
auto mw = new MessageWindow ( this ) ;
mw - > populateMessages ( msgs ) ;
mw - > show ( ) ;
2019-01-02 12:27:16 -05:00
auto pair = i . firstUnreadFrom ( call ) ;
auto id = pair . first ;
auto msg = pair . second ;
auto params = msg . params ( ) ;
CommandDetail d ;
d . cmd = params . value ( " CMD " ) . toString ( ) ;
d . extra = params . value ( " EXTRA " ) . toString ( ) ;
d . freq = params . value ( " OFFSET " ) . toInt ( ) ;
d . from = params . value ( " FROM " ) . toString ( ) ;
d . grid = params . value ( " GRID " ) . toString ( ) ;
d . relayPath = params . value ( " PATH " ) . toString ( ) ;
d . snr = params . value ( " SNR " ) . toInt ( ) ;
d . tdrift = params . value ( " TDRIFT " ) . toFloat ( ) ;
d . text = params . value ( " TEXT " ) . toString ( ) ;
d . to = params . value ( " TO " ) . toString ( ) ;
d . utcTimestamp = QDateTime : : fromString ( params . value ( " UTC " ) . toString ( ) , " yyyy-MM-dd hh:mm:ss " ) ;
d . utcTimestamp . setUtcOffset ( 0 ) ;
2018-12-18 21:28:31 -05:00
2019-01-02 12:27:16 -05:00
msg . setType ( " READ " ) ;
i . set ( id , msg ) ;
m_rxInboxCountCache [ call ] = max ( 0 , m_rxInboxCountCache . value ( call ) - 1 ) ;
processAlertReplyForCommand ( d , d . relayPath , d . cmd ) ;
}
2018-12-18 21:28:31 -05:00
} else {
addMessageText ( call ) ;
}
2019-02-02 17:06:01 -05:00
# endif
2018-07-05 04:45:55 -04:00
}
2018-02-08 21:28:33 -05:00
void MainWindow : : on_tuneButton_clicked ( bool checked )
{
static bool lastChecked = false ;
if ( lastChecked = = checked ) return ;
lastChecked = checked ;
if ( checked & & m_tune = = false ) { // we're starting tuning so remember Tx and change pwr to Tune value
if ( m_config . pwrBandTuneMemory ( ) ) {
2024-10-25 13:27:38 -07:00
m_pwrBandTxMemory [ m_lastBand ] = ui - > outAttenuation - > value ( ) ; // remember our Tx pwr
2018-02-08 21:28:33 -05:00
m_PwrBandSetOK = false ;
2024-10-25 13:27:38 -07:00
if ( m_pwrBandTuneMemory . contains ( m_lastBand ) ) {
ui - > outAttenuation - > setValue ( m_pwrBandTuneMemory [ m_lastBand ] . toInt ( ) ) ; // set to Tune pwr
2018-02-08 21:28:33 -05:00
}
m_PwrBandSetOK = true ;
}
}
if ( m_tune ) {
tuneButtonTimer . start ( 250 ) ;
} else {
itone [ 0 ] = 0 ;
on_monitorButton_clicked ( true ) ;
m_tune = true ;
}
Q_EMIT tune ( checked ) ;
}
2024-09-11 13:39:49 -07:00
void MainWindow : : end_tuning ( )
{
tuneATU_Timer . stop ( ) ; // stop tune watchdog when stopping Tune manually
on_stopTxButton_clicked ( ) ;
// we're turning off so remember our Tune pwr setting and reset to Tx pwr
if ( m_config . pwrBandTuneMemory ( ) | | m_config . pwrBandTxMemory ( ) ) {
2024-10-25 13:27:38 -07:00
m_pwrBandTuneMemory [ m_lastBand ] = ui - > outAttenuation - > value ( ) ; // remember our Tune pwr
2024-09-11 13:39:49 -07:00
m_PwrBandSetOK = false ;
2024-10-25 13:27:38 -07:00
ui - > outAttenuation - > setValue ( m_pwrBandTxMemory [ m_lastBand ] . toInt ( ) ) ; // set to Tx pwr
2024-09-11 13:39:49 -07:00
m_PwrBandSetOK = true ;
}
}
2018-02-08 21:28:33 -05:00
void MainWindow : : stop_tuning ( )
{
2024-09-11 13:39:49 -07:00
tuneATU_Timer . stop ( ) ; // stop tune watchdog when stopping Tune manually
2018-02-08 21:28:33 -05:00
on_tuneButton_clicked ( false ) ;
ui - > tuneButton - > setChecked ( false ) ;
m_bTxTime = false ;
m_tune = false ;
}
void MainWindow : : stopTuneATU ( )
{
on_tuneButton_clicked ( false ) ;
m_bTxTime = false ;
}
2018-11-15 21:57:04 -05:00
void MainWindow : : resetPushButtonToggleText ( QPushButton * btn ) {
bool checked = btn - > isChecked ( ) ;
auto style = btn - > styleSheet ( ) ;
if ( checked ) {
style = style . replace ( " font-weight:normal; " , " font-weight:bold; " ) ;
} else {
style = style . replace ( " font-weight:bold; " , " font-weight:normal; " ) ;
}
btn - > setStyleSheet ( style ) ;
# if PUSH_BUTTON_CHECKMARK
auto on = " ✓ " ;
auto text = btn - > text ( ) ;
if ( checked ) {
btn - > setText ( on + text . replace ( on , " " ) ) ;
} else {
btn - > setText ( text . replace ( on , " " ) ) ;
}
# endif
# if PUSH_BUTTON_MIN_WIDTH
int width = 0 ;
QList < QPushButton * > btns ;
foreach ( auto child , ui - > buttonGrid - > children ( ) ) {
if ( ! child - > isWidgetType ( ) ) {
continue ;
}
if ( ! child - > objectName ( ) . contains ( " Button " ) ) {
continue ;
}
auto b = qobject_cast < QPushButton * > ( child ) ;
width = qMax ( width , b - > geometry ( ) . width ( ) ) ;
btns . append ( b ) ;
}
foreach ( auto child , btns ) {
child - > setMinimumWidth ( width ) ;
}
# endif
}
2018-02-08 21:28:33 -05:00
void MainWindow : : on_stopTxButton_clicked ( ) //Stop Tx
{
if ( m_tune ) stop_tuning ( ) ;
if ( m_auto and ! m_tuneup ) auto_tx_mode ( false ) ;
m_btxok = false ;
2018-07-02 22:54:26 -04:00
resetMessage ( ) ;
2018-11-30 09:09:54 -05:00
resetAutomaticIntervalTransmissions ( false , false ) ;
2018-02-08 21:28:33 -05:00
}
void MainWindow : : rigOpen ( )
{
update_dynamic_property ( ui - > readFreq , " state " , " warning " ) ;
2018-03-09 13:10:19 -05:00
ui - > readFreq - > setText ( " CAT " ) ;
2018-02-08 21:28:33 -05:00
ui - > readFreq - > setEnabled ( true ) ;
m_config . transceiver_online ( ) ;
Q_EMIT m_config . sync_transceiver ( true , true ) ;
}
void MainWindow : : on_readFreq_clicked ( )
{
if ( m_transmitting ) return ;
if ( m_config . transceiver_online ( ) )
{
Q_EMIT m_config . sync_transceiver ( true , true ) ;
}
}
2024-10-20 14:36:48 -07:00
void MainWindow : : setXIT ( int n )
2018-02-08 21:28:33 -05:00
{
2018-09-11 10:52:07 -04:00
if ( m_transmitting & & ! m_config . tx_qsy_allowed ( ) ) return ;
2018-02-08 21:28:33 -05:00
2024-10-20 14:36:48 -07:00
m_XIT = m_config . split_mode ( ) ? ( n / 500 ) * 500 - 1500 : 0 ;
2019-10-24 10:52:37 -04:00
if ( ( m_monitoring | | m_transmitting )
2024-10-20 14:36:48 -07:00
& & m_config . is_transceiver_online ( )
& & m_config . split_mode ( ) )
2019-10-24 10:52:37 -04:00
{
// All conditions are met, reset the transceiver Tx dial
// frequency
2024-10-21 17:40:39 -07:00
m_freqTxNominal = m_freqNominal + m_XIT ;
2024-10-20 14:36:48 -07:00
Q_EMIT m_config . transceiver_tx_frequency ( m_freqTxNominal ) ;
2019-10-24 10:52:37 -04:00
}
2018-02-08 21:28:33 -05:00
//Now set the audio Tx freq
2024-10-20 14:36:48 -07:00
Q_EMIT transmitFrequency ( freq ( ) - m_XIT ) ;
2018-02-08 21:28:33 -05:00
}
2024-10-21 05:39:35 -07:00
void
MainWindow : : qsy ( int const hzDelta )
{
setRig ( m_freqNominal + hzDelta ) ;
setFreqOffsetForRestore ( m_wideGraph - > centerFreq ( ) , false ) ;
2018-09-11 23:03:44 -04:00
2024-10-21 05:39:35 -07:00
// Adjust band activity frequencies.
2024-10-21 05:47:38 -07:00
BandActivity bandActivity ;
2018-09-11 23:03:44 -04:00
2024-10-21 05:39:35 -07:00
for ( auto [ key , value ] : m_bandActivity . asKeyValueRange ( ) )
{
if ( value . isEmpty ( ) ) continue ;
auto const newKey = key - hzDelta ;
2024-10-21 05:47:38 -07:00
bandActivity [ newKey ] = value ;
bandActivity [ newKey ] . last ( ) . offset - = hzDelta ;
2024-10-21 05:39:35 -07:00
}
2024-10-21 05:47:38 -07:00
m_bandActivity . swap ( bandActivity ) ;
2024-10-21 05:39:35 -07:00
// Adjust call activity frequencies.
for ( auto [ key , value ] : m_callActivity . asKeyValueRange ( ) )
{
value . offset - = hzDelta ;
}
displayActivity ( true ) ;
2018-09-09 17:18:07 -04:00
}
2020-05-13 21:15:21 -04:00
void MainWindow : : drifted ( int /*prev*/ , int /*cur*/ ) {
// here we reset the buffer position without clearing the buffer
// this makes the detected emit the correct k when drifting time
m_detector - > resetBufferPosition ( ) ;
}
2018-08-08 17:15:49 -04:00
void MainWindow : : setFreqOffsetForRestore ( int freq , bool shouldRestore ) {
2024-10-20 14:23:10 -07:00
changeFreq ( freq ) ;
2018-07-23 09:09:55 -04:00
if ( shouldRestore ) {
m_shouldRestoreFreq = true ;
} else {
m_previousFreq = 0 ;
m_shouldRestoreFreq = false ;
}
}
2018-08-08 17:15:49 -04:00
bool MainWindow : : tryRestoreFreqOffset ( ) {
if ( ! m_shouldRestoreFreq | | m_previousFreq = = 0 ) {
return false ;
}
setFreqOffsetForRestore ( m_previousFreq , false ) ;
return true ;
}
2024-10-04 18:49:08 -07:00
void
2024-10-20 14:23:10 -07:00
MainWindow : : changeFreq ( int const newFreq )
2024-10-04 18:49:08 -07:00
{
2024-10-20 13:54:14 -07:00
// Don't allow QSY if we've already queued a transmission,
// unless we have that functionality enabled.
2018-08-15 20:57:30 -04:00
2024-10-20 13:54:14 -07:00
if ( isMessageQueuedForTransmit ( ) & & ! m_config . tx_qsy_allowed ( ) ) return ;
2018-07-18 14:26:45 -04:00
2018-10-03 18:55:57 -04:00
// TODO: jsherer - here's where we'd set minimum frequency again (later?)
2018-08-23 13:10:20 -04:00
2024-10-20 13:54:14 -07:00
m_previousFreq = freq ( ) ;
setFreq ( std : : max ( 0 , newFreq ) ) ;
2018-07-21 17:50:33 -04:00
2018-09-16 11:30:13 -04:00
displayDialFrequency ( ) ;
2018-02-08 21:28:33 -05:00
}
void MainWindow : : handle_transceiver_update ( Transceiver : : TransceiverState const & s )
{
2018-09-16 11:30:13 -04:00
//qDebug () << "MainWindow::handle_transceiver_update:" << s;
2018-02-08 21:28:33 -05:00
Transceiver : : TransceiverState old_state { m_rigState } ;
2019-05-19 10:13:46 -04:00
2019-05-19 11:17:08 -04:00
// GM8JCF: in stopTx2 we maintain PTT if there are still untransmitted JS8 frames and we are holding the PTT
// KN4CRD: if we're not holding the PTT we need to check to ensure it's safe to transmit
if ( m_config . hold_ptt ( ) | | ( s . ptt ( ) & & ! m_rigState . ptt ( ) ) ) // safe to start audio (caveat - DX Lab Suite Commander)
{
2024-10-26 07:13:47 -07:00
if ( m_tx_when_ready & & m_iptt ) // waiting to Tx and still needed
2019-05-19 11:17:08 -04:00
{
2018-02-08 21:28:33 -05:00
ptt1Timer . start ( 1000 * m_config . txDelay ( ) ) ; //Start-of-transmission sequencer delay
2019-05-19 11:17:08 -04:00
}
2018-02-08 21:28:33 -05:00
m_tx_when_ready = false ;
2019-05-19 11:17:08 -04:00
}
2018-02-08 21:28:33 -05:00
m_rigState = s ;
2019-06-12 23:54:02 -04:00
2018-02-08 21:28:33 -05:00
auto old_freqNominal = m_freqNominal ;
if ( ! old_freqNominal )
{
// always take initial rig frequency to avoid start up problems
// with bogus Tx frequencies
m_freqNominal = s . frequency ( ) ;
}
2019-06-12 23:54:02 -04:00
2018-02-08 21:28:33 -05:00
if ( old_state . online ( ) = = false & & s . online ( ) = = true )
{
// initializing
on_monitorButton_clicked ( ! m_config . monitor_off_at_startup ( ) ) ;
2020-04-15 15:23:41 -04:00
on_monitorTxButton_toggled ( ! m_config . transmit_off_at_startup ( ) ) ;
2018-02-08 21:28:33 -05:00
}
2019-06-12 23:54:02 -04:00
2018-02-08 21:28:33 -05:00
if ( s . frequency ( ) ! = old_state . frequency ( ) | | s . split ( ) ! = m_splitMode )
{
m_splitMode = s . split ( ) ;
if ( ! s . ptt ( ) )
{
2020-05-31 15:34:57 -04:00
m_freqNominal = s . frequency ( ) ;
2018-02-08 21:28:33 -05:00
if ( old_freqNominal ! = m_freqNominal )
{
m_freqTxNominal = m_freqNominal ;
}
if ( m_monitoring )
{
m_lastMonitoredFrequency = m_freqNominal ;
}
2024-10-04 12:13:36 -07:00
if ( m_lastDialFreq ! = m_freqNominal ) {
2018-08-09 15:28:23 -04:00
2018-02-08 21:28:33 -05:00
m_lastDialFreq = m_freqNominal ;
2018-09-18 17:24:07 -04:00
m_secBandChanged = DriftingDateTime : : currentMSecsSinceEpoch ( ) / 1000 ;
2018-08-09 15:28:23 -04:00
if ( m_freqNominal ! = m_bandHoppedFreq ) {
m_bandHopped = false ;
}
2024-10-27 13:06:57 -07:00
if ( s . frequency ( ) < 30000000u ) {
2020-01-13 11:41:22 -05:00
write_frequency_entry ( " ALL.TXT " ) ;
2018-02-08 21:28:33 -05:00
}
2018-08-27 21:19:38 -04:00
if ( m_config . spot_to_reporting_networks ( ) ) {
2019-03-24 21:01:03 -04:00
spotSetLocal ( ) ;
2018-08-27 21:19:38 -04:00
pskSetLocal ( ) ;
2020-03-30 23:07:40 -04:00
aprsSetLocal ( ) ;
2018-02-08 21:28:33 -05:00
}
statusChanged ( ) ;
2024-10-24 21:25:49 -07:00
m_wideGraph - > setDialFreq ( m_freqNominal / 1.e6 f ) ;
2018-02-08 21:28:33 -05:00
}
} else {
2020-05-31 15:34:57 -04:00
m_freqTxNominal = s . split ( ) ? s . tx_frequency ( ) : s . frequency ( ) ;
2018-02-08 21:28:33 -05:00
}
}
2019-12-17 12:43:19 -05:00
2018-02-08 21:28:33 -05:00
// ensure frequency display is correct
2020-05-31 15:34:57 -04:00
// setRig();
2019-06-12 23:54:02 -04:00
updateCurrentBand ( ) ;
2018-02-08 21:28:33 -05:00
displayDialFrequency ( ) ;
update_dynamic_property ( ui - > readFreq , " state " , " ok " ) ;
ui - > readFreq - > setEnabled ( false ) ;
2018-03-09 13:10:19 -05:00
ui - > readFreq - > setText ( s . split ( ) ? " CAT/S " : " CAT " ) ;
2018-02-08 21:28:33 -05:00
}
void MainWindow : : handle_transceiver_failure ( QString const & reason )
{
update_dynamic_property ( ui - > readFreq , " state " , " error " ) ;
ui - > readFreq - > setEnabled ( true ) ;
on_stopTxButton_clicked ( ) ;
rigFailure ( reason ) ;
}
void MainWindow : : rigFailure ( QString const & reason )
{
if ( m_first_error )
{
// one automatic retry
2024-10-21 15:35:49 -07:00
QTimer : : singleShot ( 0 , this , & MainWindow : : rigOpen ) ;
2018-02-08 21:28:33 -05:00
m_first_error = false ;
}
else
{
m_rigErrorMessageBox . setDetailedText ( reason ) ;
// don't call slot functions directly to avoid recursion
m_rigErrorMessageBox . exec ( ) ;
auto const clicked_button = m_rigErrorMessageBox . clickedButton ( ) ;
if ( clicked_button = = m_configurations_button )
{
ui - > menuConfig - > exec ( QCursor : : pos ( ) ) ;
}
else
{
switch ( m_rigErrorMessageBox . standardButton ( clicked_button ) )
{
case MessageBox : : Ok :
m_config . select_tab ( 1 ) ;
2024-10-21 15:35:49 -07:00
QTimer : : singleShot ( 0 , this , & MainWindow : : on_actionSettings_triggered ) ;
2018-02-08 21:28:33 -05:00
break ;
case MessageBox : : Retry :
2024-10-21 15:35:49 -07:00
QTimer : : singleShot ( 0 , this , & MainWindow : : rigOpen ) ;
2018-02-08 21:28:33 -05:00
break ;
case MessageBox : : Cancel :
2024-10-21 15:35:49 -07:00
QTimer : : singleShot ( 0 , this , & MainWindow : : close ) ;
2018-02-08 21:28:33 -05:00
break ;
default : break ; // squashing compile warnings
}
}
m_first_error = true ; // reset
}
}
2024-11-01 21:07:48 -07:00
void
MainWindow : : transmit ( )
2018-02-08 21:28:33 -05:00
{
2024-10-28 07:51:13 -07:00
Q_EMIT sendMessage ( freq ( ) - m_XIT ,
m_nSubMode ,
2024-10-27 20:47:54 -07:00
m_soundOutput ,
2024-10-27 21:16:31 -07:00
m_config . audio_output_channel ( ) ) ;
2018-02-08 21:28:33 -05:00
}
2024-11-01 21:07:48 -07:00
void
2024-11-01 21:11:28 -07:00
MainWindow : : on_outAttenuation_valueChanged ( int const a )
2018-02-08 21:28:33 -05:00
{
2024-10-13 19:42:02 -07:00
if ( m_PwrBandSetOK )
{
2024-11-01 21:07:48 -07:00
if ( ! m_tune & & m_config . pwrBandTxMemory ( ) ) m_pwrBandTxMemory [ m_lastBand ] = a ; // remember our Tx pwr
2024-10-25 13:27:38 -07:00
if ( m_tune & & m_config . pwrBandTuneMemory ( ) ) m_pwrBandTuneMemory [ m_lastBand ] = a ; // remember our Tune pwr
2018-02-08 21:28:33 -05:00
}
2024-10-13 19:42:02 -07:00
2024-11-01 21:07:48 -07:00
// Slider interpreted as dB / 100.
Q_EMIT outAttenuationChanged ( a / 10.0 ) ;
2018-02-08 21:28:33 -05:00
}
2024-11-06 05:43:03 -08:00
void
MainWindow : : spotSetLocal ( )
2019-03-24 21:01:03 -04:00
{
2024-11-09 15:07:20 -08:00
Q_EMIT spotClientSetLocalStation ( m_config . my_callsign ( ) ,
m_config . my_grid ( ) ,
replaceMacros ( m_config . my_info ( ) , buildMacroValues ( ) , true ) ) ;
2019-03-24 21:01:03 -04:00
}
2024-11-09 10:25:45 -08:00
void
MainWindow : : pskSetLocal ( )
2018-02-08 21:28:33 -05:00
{
2024-11-09 10:25:45 -08:00
Q_EMIT pskReporterSetLocalStation ( m_config . my_callsign ( ) ,
m_config . my_grid ( ) ,
replaceMacros ( m_config . my_info ( ) , buildMacroValues ( ) , true ) ) ;
2018-02-08 21:28:33 -05:00
}
2024-11-09 10:25:45 -08:00
void
MainWindow : : aprsSetLocal ( )
2020-03-30 23:07:40 -04:00
{
2024-11-09 10:25:45 -08:00
Q_EMIT aprsClientSetLocalStation ( " APJ8CL " , QString : : number ( APRSISClient : : hashCallsign ( " APJ8CL " ) ) ) ;
2020-03-30 23:07:40 -04:00
}
2018-02-08 21:28:33 -05:00
void MainWindow : : transmitDisplay ( bool transmitting )
{
if ( transmitting = = m_transmitting ) {
if ( transmitting ) {
ui - > signal_meter_widget - > setValue ( 0 , 0 ) ;
if ( m_monitoring ) monitor ( false ) ;
m_btxok = true ;
}
}
2018-07-09 16:31:37 -04:00
2018-12-12 22:14:45 -05:00
updateTxButtonDisplay ( ) ;
2018-02-08 21:28:33 -05:00
}
2024-08-31 10:29:30 -07:00
void MainWindow : : postDecode ( bool is_new , QString const & )
2018-02-08 21:28:33 -05:00
{
2018-08-07 11:40:02 -04:00
#if 0
2018-02-08 21:28:33 -05:00
auto const & decode = message . trimmed ( ) ;
auto const & parts = decode . left ( 22 ) . split ( ' ' , QString : : SkipEmptyParts ) ;
if ( parts . size ( ) > = 5 )
2018-07-10 22:32:35 -04:00
{
2018-02-08 21:28:33 -05:00
auto has_seconds = parts [ 0 ] . size ( ) > 4 ;
m_messageClient - > decode ( is_new
, QTime : : fromString ( parts [ 0 ] , has_seconds ? " hhmmss " : " hhmm " )
, parts [ 1 ] . toInt ( )
, parts [ 2 ] . toFloat ( ) , parts [ 3 ] . toUInt ( ) , parts [ 4 ]
, decode . mid ( has_seconds ? 24 : 22 , 21 )
, QChar { ' ? ' } = = decode . mid ( has_seconds ? 24 + 21 : 22 + 21 , 1 )
, m_diskData ) ;
2018-07-10 22:32:35 -04:00
}
2018-08-07 11:40:02 -04:00
# endif
2018-07-10 22:32:35 -04:00
if ( is_new ) {
m_rxDirty = true ;
}
2018-07-09 20:51:42 -04:00
}
2018-07-02 15:08:26 -04:00
2024-10-19 02:39:21 -07:00
void
MainWindow : : tryNotify ( QString const & key )
{
if ( auto const path = m_config . notification_path ( key ) ;
! path . isEmpty ( ) )
{
2019-10-10 20:40:30 -04:00
emit playNotification ( path ) ;
2024-10-19 02:39:21 -07:00
}
2019-10-10 20:40:30 -04:00
}
2018-07-09 20:55:54 -04:00
void MainWindow : : displayTransmit ( ) {
2018-10-10 17:43:11 -04:00
// Transmit Activity
update_dynamic_property ( ui - > startTxButton , " transmitting " , m_transmitting ) ;
2020-04-15 15:23:41 -04:00
update_dynamic_property ( ui - > monitorTxButton , " transmitting " , m_transmitting ) ;
2018-07-13 04:44:41 -04:00
}
2019-10-21 15:58:58 -04:00
void MainWindow : : updateModeButtonText ( ) {
auto selectedCallsign = callsignSelected ( ) ;
2019-11-04 19:50:57 -05:00
auto multi = ui - > actionModeMultiDecoder - > isChecked ( ) ;
2019-10-21 15:58:58 -04:00
auto autoreply = ui - > actionModeAutoreply - > isChecked ( ) ;
auto heartbeat = ui - > actionModeJS8HB - > isEnabled ( ) & & ui - > actionModeJS8HB - > isChecked ( ) ;
auto ack = autoreply & & ui - > actionHeartbeatAcknowledgements - > isChecked ( ) & & ( ! m_config . heartbeat_qso_pause ( ) | | selectedCallsign . isEmpty ( ) ) ;
2024-10-08 06:14:21 -07:00
auto modeText = JS8 : : Submode : : name ( m_nSubMode ) ;
2019-11-04 19:50:57 -05:00
if ( multi ) {
modeText + = QString ( " +MULTI " ) ;
}
2019-10-21 15:58:58 -04:00
if ( autoreply ) {
2020-03-28 15:10:41 -04:00
if ( m_config . autoreply_confirmation ( ) ) {
modeText + = QString ( " +AUTO+CONF " ) ;
} else {
modeText + = QString ( " +AUTO " ) ;
}
2019-10-21 15:58:58 -04:00
}
if ( heartbeat ) {
if ( ack ) {
modeText + = QString ( " +HB+ACK " ) ;
} else {
modeText + = QString ( " +HB " ) ;
}
}
2020-05-13 21:15:21 -04:00
2019-10-21 15:58:58 -04:00
ui - > modeButton - > setText ( modeText ) ;
}
2018-07-13 04:44:41 -04:00
void MainWindow : : updateButtonDisplay ( ) {
2019-11-11 09:53:32 -05:00
bool isTransmitting = isMessageQueuedForTransmit ( ) ;
2018-10-05 18:40:56 -04:00
2018-10-03 16:54:25 -04:00
auto selectedCallsign = callsignSelected ( true ) ;
2018-09-24 21:23:24 -04:00
bool emptyCallsign = selectedCallsign . isEmpty ( ) ;
2019-02-08 16:57:42 -05:00
bool emptyInfo = m_config . my_info ( ) . isEmpty ( ) ;
2020-03-28 15:10:41 -04:00
bool emptyStatus = m_config . my_status ( ) . isEmpty ( ) ;
2018-07-15 15:58:07 -04:00
2018-11-26 22:40:34 -05:00
ui - > hbMacroButton - > setDisabled ( isTransmitting ) ;
2018-07-15 15:58:07 -04:00
ui - > cqMacroButton - > setDisabled ( isTransmitting ) ;
2018-08-28 16:04:49 -04:00
ui - > replyMacroButton - > setDisabled ( isTransmitting | | emptyCallsign ) ;
2018-10-28 12:37:47 -04:00
ui - > snrMacroButton - > setDisabled ( isTransmitting | | emptyCallsign ) ;
2019-02-08 16:57:42 -05:00
ui - > infoMacroButton - > setDisabled ( isTransmitting | | emptyInfo ) ;
2020-03-28 15:10:41 -04:00
ui - > statusMacroButton - > setDisabled ( isTransmitting | | emptyStatus ) ;
2018-07-15 15:58:07 -04:00
ui - > macrosMacroButton - > setDisabled ( isTransmitting ) ;
2018-07-24 02:53:01 -04:00
ui - > queryButton - > setDisabled ( isTransmitting | | emptyCallsign ) ;
2018-09-07 10:33:53 -04:00
ui - > deselectButton - > setDisabled ( isTransmitting | | emptyCallsign ) ;
2018-09-24 21:23:24 -04:00
ui - > queryButton - > setText ( emptyCallsign ? " Directed " : QString ( " Directed to %1 " ) . arg ( selectedCallsign ) ) ;
2018-11-28 22:53:18 -05:00
// refresh repeat button text too
updateRepeatButtonDisplay ( ) ;
2019-10-21 15:58:58 -04:00
// update mode button text
updateModeButtonText ( ) ;
2018-11-28 22:53:18 -05:00
}
void MainWindow : : updateRepeatButtonDisplay ( ) {
2019-03-27 11:42:43 -04:00
auto selectedCallsign = callsignSelected ( ) ;
2019-10-21 15:58:58 -04:00
auto hbBase = ui - > actionModeAutoreply - > isChecked ( ) & & ui - > actionHeartbeatAcknowledgements - > isChecked ( ) & & ( ! m_config . heartbeat_qso_pause ( ) | | selectedCallsign . isEmpty ( ) ) ? " HB + ACK " : " HB " ;
2018-11-28 22:53:18 -05:00
if ( ui - > hbMacroButton - > isChecked ( ) & & m_hbInterval > 0 & & m_nextHeartbeat . isValid ( ) ) {
auto secs = DriftingDateTime : : currentDateTimeUtc ( ) . secsTo ( m_nextHeartbeat ) ;
if ( secs > 0 ) {
2019-03-24 13:41:35 -04:00
ui - > hbMacroButton - > setText ( QString ( " %1 (%2) " ) . arg ( hbBase ) . arg ( secs ) ) ;
2018-11-28 22:53:18 -05:00
} else {
2019-03-24 13:41:35 -04:00
ui - > hbMacroButton - > setText ( QString ( " %1 (now) " ) . arg ( hbBase ) ) ;
2018-11-28 22:53:18 -05:00
}
} else {
2019-03-24 13:41:35 -04:00
ui - > hbMacroButton - > setText ( hbBase ) ;
2018-11-28 22:53:18 -05:00
}
if ( ui - > cqMacroButton - > isChecked ( ) & & m_cqInterval > 0 & & m_nextCQ . isValid ( ) ) {
auto secs = DriftingDateTime : : currentDateTimeUtc ( ) . secsTo ( m_nextCQ ) ;
if ( secs > 0 ) {
ui - > cqMacroButton - > setText ( QString ( " CQ (%1) " ) . arg ( secs ) ) ;
} else {
ui - > cqMacroButton - > setText ( QString ( " CQ (now) " ) ) ;
}
} else {
ui - > cqMacroButton - > setText ( " CQ " ) ;
}
2018-10-10 17:43:11 -04:00
}
void MainWindow : : updateTextDisplay ( ) {
2020-04-15 15:23:41 -04:00
bool canTransmit = ensureCanTransmit ( ) ;
2019-11-11 09:53:32 -05:00
bool isTransmitting = isMessageQueuedForTransmit ( ) ;
2018-10-10 17:43:11 -04:00
bool emptyText = ui - > extFreeTextMsgEdit - > toPlainText ( ) . isEmpty ( ) ;
2020-04-15 15:23:41 -04:00
ui - > startTxButton - > setDisabled ( ! canTransmit | | isTransmitting | | emptyText ) ;
2018-09-01 20:23:04 -04:00
2018-10-06 01:43:47 -04:00
if ( m_txTextDirty ) {
// debounce frame and word count
2018-10-02 09:44:46 -04:00
if ( m_txTextDirtyDebounce . isActive ( ) ) {
m_txTextDirtyDebounce . stop ( ) ;
}
2018-10-10 17:43:11 -04:00
m_txTextDirtyDebounce . setSingleShot ( true ) ;
2018-10-06 01:51:15 -04:00
m_txTextDirtyDebounce . start ( 100 ) ;
2018-10-02 09:44:46 -04:00
m_txTextDirty = false ;
}
}
2018-10-01 09:57:37 -04:00
2018-11-01 00:37:29 -04:00
# if __APPLE__
2018-11-01 02:19:48 -04:00
# define USE_SYNC_FRAME_COUNT 0
2018-11-01 00:37:29 -04:00
# else
2018-11-01 00:44:14 -04:00
# define USE_SYNC_FRAME_COUNT 0
2018-11-01 00:37:29 -04:00
# endif
2018-10-06 01:43:47 -04:00
2018-10-05 18:40:56 -04:00
void MainWindow : : refreshTextDisplay ( ) {
2018-10-16 17:09:19 -04:00
qDebug ( ) < < " refreshing text display... " ;
2018-10-02 09:44:46 -04:00
auto text = ui - > extFreeTextMsgEdit - > toPlainText ( ) ;
2018-10-01 09:57:37 -04:00
2018-10-02 09:44:46 -04:00
# if USE_SYNC_FRAME_COUNT
2018-10-05 18:40:56 -04:00
auto frames = buildMessageFrames ( text ) ;
QStringList textList ;
qDebug ( ) < < " frames: " ;
2018-11-01 00:41:15 -04:00
foreach ( auto frame , frames ) {
auto dt = DecodedText ( frame . first , frame . second ) ;
2018-10-05 18:40:56 -04:00
qDebug ( ) < < " -> " < < frame < < dt . message ( ) < < Varicode : : frameTypeString ( dt . frameType ( ) ) ;
textList . append ( dt . message ( ) ) ;
}
2018-10-06 01:43:47 -04:00
auto transmitText = textList . join ( " " ) ;
auto count = frames . length ( ) ;
// ugh...i hate these globals
m_txTextDirtyLastSelectedCall = callsignSelected ( true ) ;
m_txTextDirtyLastText = text ;
m_txFrameCountEstimate = count ;
m_txTextDirty = false ;
2018-12-30 20:18:35 -05:00
updateTextWordCheckerDisplay ( ) ;
2018-10-06 01:43:47 -04:00
updateTextStatsDisplay ( transmitText , count ) ;
updateTxButtonDisplay ( ) ;
2018-10-05 18:40:56 -04:00
2018-10-02 09:44:46 -04:00
# else
// prepare selected callsign for directed message
2018-10-26 22:16:48 -04:00
QString selectedCall = callsignSelected ( ) ;
2018-10-02 09:44:46 -04:00
// prepare compound
QString mycall = m_config . my_callsign ( ) ;
2018-10-26 16:57:07 -04:00
QString mygrid = m_config . my_grid ( ) . left ( 4 ) ;
2019-05-27 11:30:30 -04:00
bool forceIdentify = ! m_config . avoid_forced_identify ( ) ;
2019-09-27 20:34:41 -04:00
bool forceData = false ;
2018-10-02 09:44:46 -04:00
BuildMessageFramesThread * t = new BuildMessageFramesThread (
2018-10-26 16:57:07 -04:00
mycall ,
mygrid ,
2018-10-26 22:16:48 -04:00
selectedCall ,
2019-05-27 11:30:30 -04:00
text ,
2019-09-05 14:07:24 -04:00
forceIdentify ,
2019-09-27 20:34:41 -04:00
forceData ,
2019-09-05 14:07:24 -04:00
m_nSubMode
2018-10-02 09:44:46 -04:00
) ;
connect ( t , & BuildMessageFramesThread : : finished , t , & QObject : : deleteLater ) ;
2018-11-01 02:19:48 -04:00
connect ( t , & BuildMessageFramesThread : : resultReady , this , [ this , text ] ( QString transmitText , int frames ) {
2018-10-15 22:06:31 -04:00
// ugh...i hate these globals
m_txTextDirtyLastSelectedCall = callsignSelected ( true ) ;
2024-10-09 22:05:49 -07:00
m_txTextDirtyLastText = text ;
m_txFrameCountEstimate = frames ;
m_txTextDirty = false ;
2018-10-02 18:03:15 -04:00
2018-12-30 20:18:35 -05:00
updateTextWordCheckerDisplay ( ) ;
2018-11-03 22:52:28 -04:00
updateTextStatsDisplay ( transmitText , m_txFrameCountEstimate ) ;
2018-10-15 22:06:31 -04:00
updateTxButtonDisplay ( ) ;
2018-10-02 09:44:46 -04:00
} ) ;
t - > start ( ) ;
# endif
}
2018-12-30 20:18:35 -05:00
void MainWindow : : updateTextWordCheckerDisplay ( ) {
2018-12-31 15:25:04 -05:00
if ( ! m_config . spellcheck ( ) ) {
return ;
}
2018-12-30 20:18:35 -05:00
JSCChecker : : checkRange ( ui - > extFreeTextMsgEdit , 0 , - 1 ) ;
}
2018-10-05 09:01:25 -04:00
void MainWindow : : updateTextStatsDisplay ( QString text , int count ) {
2019-09-05 14:07:24 -04:00
const double fpm = 60.0 / m_TRperiod ;
2018-10-05 09:01:25 -04:00
if ( count > 0 ) {
2024-08-31 09:40:28 -07:00
auto words = text . split ( " " , Qt : : SkipEmptyParts ) . length ( ) ;
2019-08-14 21:11:06 -04:00
auto wpm = QString : : number ( words / ( count / fpm ) , ' f ' , 1 ) ;
auto cpm = QString : : number ( text . length ( ) / ( count / fpm ) , ' f ' , 1 ) ;
2018-10-02 09:44:46 -04:00
wpm_label . setText ( QString ( " %1wpm / %2cpm " ) . arg ( wpm ) . arg ( cpm ) ) ;
wpm_label . setVisible ( true ) ;
} else {
wpm_label . setVisible ( false ) ;
wpm_label . clear ( ) ;
2018-09-01 20:23:04 -04:00
}
2018-07-09 20:55:54 -04:00
}
2018-10-06 01:43:47 -04:00
void MainWindow : : updateTxButtonDisplay ( ) {
2020-04-15 15:23:41 -04:00
// can we transmit at all?
bool canTransmit = ensureCanTransmit ( ) ;
// if we're tuning or have a message queued
2019-11-11 09:53:32 -05:00
if ( m_tune | | isMessageQueuedForTransmit ( ) ) {
2018-10-06 01:43:47 -04:00
int count = m_txFrameCount ;
2024-10-09 22:05:49 -07:00
int left = m_txFrameQueue . count ( ) ;
int sent = count - left ;
2019-05-26 20:06:28 -04:00
QString buttonText ;
if ( m_tune ) {
2024-09-28 16:18:09 -07:00
buttonText = State : : Tuning . toString ( ) ;
2019-05-26 20:06:28 -04:00
} else if ( m_transmitting ) {
2024-09-28 16:18:09 -07:00
buttonText = State : : timed ( State : : Sending ,
( ( left + 1 ) * m_TRperiod ) - ( ( m_sec0 + 1 ) % m_TRperiod ) ) ;
2019-05-26 20:06:28 -04:00
} else {
2024-09-28 16:18:09 -07:00
buttonText = State : : timed ( State : : Ready ,
sent = = 1
? ( ( left + 1 ) * m_TRperiod )
: ( ( ( left + 2 ) * m_TRperiod ) - ( ( m_sec0 + 1 ) % m_TRperiod ) ) ) ;
2019-05-26 20:06:28 -04:00
}
ui - > startTxButton - > setText ( buttonText ) ;
2018-10-06 01:43:47 -04:00
ui - > startTxButton - > setEnabled ( false ) ;
2018-12-21 20:02:04 -05:00
ui - > startTxButton - > setFlat ( true ) ;
2018-10-06 01:43:47 -04:00
} else {
2024-09-28 16:18:09 -07:00
QString const buttonText = m_txFrameCountEstimate > 0
? State : : timed ( State : : Send ,
m_txFrameCountEstimate * m_TRperiod )
: State : : Send . toString ( ) ;
2019-05-26 20:06:28 -04:00
ui - > startTxButton - > setText ( buttonText ) ;
2020-04-15 15:23:41 -04:00
ui - > startTxButton - > setEnabled ( canTransmit & & m_txFrameCountEstimate > 0 ) ;
2018-12-21 20:02:04 -05:00
ui - > startTxButton - > setFlat ( false ) ;
2018-10-06 01:43:47 -04:00
}
}
2024-08-31 10:29:30 -07:00
QString MainWindow : : callsignSelected ( bool ) {
2018-07-15 11:00:57 -04:00
if ( ! ui - > tableWidgetCalls - > selectedItems ( ) . isEmpty ( ) ) {
auto selectedCalls = ui - > tableWidgetCalls - > selectedItems ( ) ;
2018-07-24 15:19:02 -04:00
if ( ! selectedCalls . isEmpty ( ) ) {
auto call = selectedCalls . first ( ) - > data ( Qt : : UserRole ) . toString ( ) ;
2018-07-31 22:54:09 -04:00
if ( ! call . isEmpty ( ) ) {
return call ;
}
2018-07-24 15:19:02 -04:00
}
2018-07-15 11:00:57 -04:00
}
if ( ! ui - > tableWidgetRXAll - > selectedItems ( ) . isEmpty ( ) ) {
int selectedOffset = - 1 ;
auto selectedItems = ui - > tableWidgetRXAll - > selectedItems ( ) ;
2018-12-23 21:06:54 -05:00
selectedOffset = selectedItems . first ( ) - > data ( Qt : : UserRole ) . toInt ( ) ;
2018-08-17 11:13:49 -04:00
2019-11-25 20:30:03 -05:00
int threshold = 0 ;
auto activity = m_bandActivity . value ( selectedOffset ) ;
if ( ! activity . isEmpty ( ) ) {
2024-10-11 07:31:31 -07:00
threshold = JS8 : : Submode : : rxThreshold ( activity . last ( ) . submode ) ;
2019-11-25 20:30:03 -05:00
}
2018-08-17 11:13:49 -04:00
auto keys = m_callActivity . keys ( ) ;
2024-11-08 16:55:41 -08:00
std : : stable_sort ( keys . begin ( ) ,
keys . end ( ) ,
[ this ] ( QString const & lhs ,
QString const & rhs )
{
auto const lhsTS = m_callActivity [ lhs ] . utcTimestamp ;
auto const rhsTS = m_callActivity [ rhs ] . utcTimestamp ;
return lhsTS = = rhsTS ? lhs < rhs
: rhsTS < lhsTS ;
2018-08-17 11:13:49 -04:00
} ) ;
2019-11-25 20:30:03 -05:00
2024-11-08 16:55:41 -08:00
// Return the first callsign at a frequency within the
// threshold limit of the selected offset, if any.
auto const offsetLo = selectedOffset - threshold ;
auto const offsetHi = selectedOffset + threshold ;
for ( auto const & key : keys )
{
if ( auto const & d = m_callActivity [ key ] ;
offsetLo < = d . offset & &
d . offset < = offsetHi )
{
return d . call ;
}
2018-07-15 11:00:57 -04:00
}
}
2018-10-25 22:55:54 -04:00
# if ALLOW_USE_INPUT_TEXT_CALLSIGN
2018-10-03 16:54:25 -04:00
if ( useInputText ) {
auto text = ui - > extFreeTextMsgEdit - > toPlainText ( ) . left ( 11 ) ; // Maximum callsign is 6 + / + 4 = 11 characters
auto calls = Varicode : : parseCallsigns ( text ) ;
if ( ! calls . isEmpty ( ) & & text . startsWith ( calls . first ( ) ) & & calls . first ( ) ! = m_config . my_callsign ( ) ) {
return calls . first ( ) ;
}
2018-07-23 17:19:26 -04:00
}
2018-10-25 22:55:54 -04:00
# endif
2018-07-23 17:19:26 -04:00
2018-07-15 11:00:57 -04:00
return QString ( ) ;
}
2019-03-28 16:37:33 -04:00
void MainWindow : : callsignSelectedChanged ( QString /*old*/ , QString selectedCall ) {
2019-10-21 15:58:58 -04:00
auto placeholderText = QString ( " Type your outgoing messages here. " ) . toUpper ( ) ;
2019-03-28 16:37:33 -04:00
if ( selectedCall . isEmpty ( ) ) {
// try to restore hb
if ( m_hbPaused ) {
ui - > hbMacroButton - > setChecked ( true ) ;
m_hbPaused = false ;
}
} else {
2019-10-21 15:58:58 -04:00
placeholderText = QString ( " Type your outgoing directed message to %1 here. " ) . arg ( selectedCall ) . toUpper ( ) ;
2019-03-28 16:37:33 -04:00
// when we select a callsign, use it as the qso start time
if ( ! m_callSelectedTime . contains ( selectedCall ) ) {
m_callSelectedTime [ selectedCall ] = DriftingDateTime : : currentDateTimeUtc ( ) ;
}
if ( m_config . heartbeat_qso_pause ( ) ) {
// TODO: jsherer - HB issue
// don't hb if we select a callsign... (but we should keep track so if we deselect, we restore our hb)
if ( ui - > hbMacroButton - > isChecked ( ) ) {
ui - > hbMacroButton - > setChecked ( false ) ;
m_hbPaused = true ;
}
// don't cq if we select a callsign... (and it will not be restored otherwise)
if ( ui - > cqMacroButton - > isChecked ( ) ) {
ui - > cqMacroButton - > setChecked ( false ) ;
}
}
}
ui - > extFreeTextMsgEdit - > setPlaceholderText ( placeholderText ) ;
# if SHOW_CALL_DETAIL_BROWSER
auto html = generateCallDetail ( selectedCall ) ;
ui - > callDetailTextBrowser - > setHtml ( html ) ;
ui - > callDetailTextBrowser - > setVisible ( ! selectedCall . isEmpty ( ) & & ( ! hearing . isEmpty ( ) | | ! heardby . isEmpty ( ) ) ) ;
# endif
2019-12-29 20:34:03 -05:00
// immediately update the display
2019-03-28 16:37:33 -04:00
updateButtonDisplay ( ) ;
updateTextDisplay ( ) ;
2019-12-27 20:08:39 -05:00
statusChanged ( ) ;
2019-03-28 16:37:33 -04:00
m_prevSelectedCallsign = selectedCall ;
}
2018-09-06 11:19:38 -04:00
void MainWindow : : clearCallsignSelected ( ) {
2019-03-28 16:37:33 -04:00
// remove the date cache
m_callSelectedTime . remove ( m_prevSelectedCallsign ) ;
// remove the callsign selection
2018-09-06 11:19:38 -04:00
ui - > tableWidgetCalls - > clearSelection ( ) ;
ui - > tableWidgetRXAll - > clearSelection ( ) ;
}
2019-11-09 09:25:03 -05:00
bool MainWindow : : isRecentOffset ( int submode , int offset ) {
2024-10-20 13:54:14 -07:00
if ( abs ( offset - freq ( ) ) < = JS8 : : Submode : : rxThreshold ( submode ) ) {
2018-08-11 18:05:36 -04:00
return true ;
}
2018-07-14 09:26:39 -04:00
return (
m_rxRecentCache . contains ( offset / 10 * 10 ) & &
2018-09-18 17:24:07 -04:00
m_rxRecentCache [ offset / 10 * 10 ] - > secsTo ( DriftingDateTime : : currentDateTimeUtc ( ) ) < 120
2018-07-14 09:26:39 -04:00
) ;
}
2018-08-11 18:05:36 -04:00
void MainWindow : : markOffsetRecent ( int offset ) {
2018-09-18 17:24:07 -04:00
m_rxRecentCache . insert ( offset / 10 * 10 , new QDateTime ( DriftingDateTime : : currentDateTimeUtc ( ) ) , 10 ) ;
m_rxRecentCache . insert ( offset / 10 * 10 + 10 , new QDateTime ( DriftingDateTime : : currentDateTimeUtc ( ) ) , 10 ) ;
2018-08-11 18:05:36 -04:00
}
2018-08-16 11:31:10 -04:00
bool MainWindow : : isDirectedOffset ( int offset , bool * pIsAllCall ) {
bool isDirected = (
2018-07-11 10:30:12 -04:00
m_rxDirectedCache . contains ( offset / 10 * 10 ) & &
2018-09-21 22:41:20 -04:00
m_rxDirectedCache [ offset / 10 * 10 ] - > date . secsTo ( DriftingDateTime : : currentDateTimeUtc ( ) ) < 120
2018-07-11 10:30:12 -04:00
) ;
2018-08-16 11:31:10 -04:00
2024-09-06 06:15:37 -07:00
if ( isDirected & & pIsAllCall ) {
* pIsAllCall = m_rxDirectedCache [ offset / 10 * 10 ] - > isAllcall ;
2018-08-16 11:31:10 -04:00
}
return isDirected ;
2018-07-11 10:30:12 -04:00
}
2018-08-16 11:31:10 -04:00
void MainWindow : : markOffsetDirected ( int offset , bool isAllCall ) {
2018-09-18 17:24:07 -04:00
CachedDirectedType * d1 = new CachedDirectedType { isAllCall , DriftingDateTime : : currentDateTimeUtc ( ) } ;
CachedDirectedType * d2 = new CachedDirectedType { isAllCall , DriftingDateTime : : currentDateTimeUtc ( ) } ;
2018-08-16 11:31:10 -04:00
m_rxDirectedCache . insert ( offset / 10 * 10 , d1 , 10 ) ;
m_rxDirectedCache . insert ( offset / 10 * 10 + 10 , d2 , 10 ) ;
2018-08-06 23:39:43 -04:00
}
2018-09-21 22:41:20 -04:00
void MainWindow : : clearOffsetDirected ( int offset ) {
m_rxDirectedCache . remove ( offset / 10 * 10 ) ;
m_rxDirectedCache . remove ( offset / 10 * 10 + 10 ) ;
}
2018-07-11 10:40:29 -04:00
bool MainWindow : : isMyCallIncluded ( const QString & text ) {
2018-07-23 11:45:41 -04:00
QString myCall = Radio : : base_callsign ( m_config . my_callsign ( ) ) ;
2018-07-11 10:40:29 -04:00
if ( myCall . isEmpty ( ) ) {
return false ;
}
2019-02-16 11:47:54 -05:00
if ( ! text . contains ( myCall ) ) {
return false ;
}
2019-11-12 21:00:32 -05:00
auto calls = Varicode : : parseCallsigns ( text ) ;
return calls . contains ( myCall ) | | calls . contains ( m_config . my_callsign ( ) ) ;
2018-07-11 10:40:29 -04:00
}
2018-07-15 13:03:16 -04:00
bool MainWindow : : isAllCallIncluded ( const QString & text ) {
2020-04-16 22:07:35 -04:00
return text . contains ( " @ALLCALL " ) | | text . contains ( " @HB " ) ;
2018-07-15 13:03:16 -04:00
}
2018-10-06 03:59:54 -04:00
bool MainWindow : : isGroupCallIncluded ( const QString & text ) {
2018-10-27 10:09:40 -04:00
return m_config . my_groups ( ) . contains ( text ) ;
2018-10-06 03:59:54 -04:00
}
2018-07-31 14:34:36 -04:00
void MainWindow : : processActivity ( bool force ) {
if ( ! m_rxDirty & & ! force ) {
return ;
}
2018-07-13 15:45:00 -04:00
2018-07-31 14:34:36 -04:00
// Recent Rx Activity
processRxActivity ( ) ;
2018-07-06 22:21:04 -04:00
2018-12-22 22:47:48 -05:00
// Process Idle Activity
processIdleActivity ( ) ;
2018-07-31 14:34:36 -04:00
// Grouped Compound Activity
processCompoundActivity ( ) ;
2018-07-06 22:21:04 -04:00
2018-07-31 14:34:36 -04:00
// Buffered Activity
processBufferedActivity ( ) ;
2018-07-03 11:38:16 -04:00
2018-07-31 14:34:36 -04:00
// Command Activity
processCommandActivity ( ) ;
2018-07-02 15:08:26 -04:00
2018-07-31 14:34:36 -04:00
// Process PSKReporter Spots
processSpots ( ) ;
2018-07-10 22:32:35 -04:00
2018-07-31 14:34:36 -04:00
m_rxDirty = false ;
}
2018-07-10 22:32:35 -04:00
2018-10-29 02:02:58 -04:00
void MainWindow : : resetTimeDeltaAverage ( ) {
2020-05-16 22:08:39 -04:00
m_driftMsMMA = 0 ;
2020-05-19 09:52:00 -04:00
m_driftMsMMA_N = 0 ;
2018-10-29 02:02:58 -04:00
}
2019-12-17 10:39:18 -05:00
void MainWindow : : setDrift ( int n ) {
m_wideGraph - > setDrift ( n ) ;
}
2024-10-21 06:25:50 -07:00
void
MainWindow : : processIdleActivity ( )
{
auto const now = DriftingDateTime : : currentDateTimeUtc ( ) ;
2018-12-22 22:47:48 -05:00
2024-10-21 06:25:50 -07:00
// if we detect an idle offset, insert an ellipsis into the activity queue and band activity
2018-12-22 22:47:48 -05:00
2024-10-21 06:25:50 -07:00
for ( auto [ offset , activity ] : m_bandActivity . asKeyValueRange ( ) )
{
if ( activity . isEmpty ( ) ) continue ;
2018-12-22 22:47:48 -05:00
2024-10-21 06:25:50 -07:00
auto const last = activity . last ( ) ;
2018-12-22 22:47:48 -05:00
2024-10-21 06:25:50 -07:00
if ( ( last . bits & Varicode : : JS8CallLast ) = = Varicode : : JS8CallLast ) continue ;
if ( last . text = = m_config . mfi ( ) ) continue ;
if ( last . utcTimestamp . secsTo ( now ) < JS8 : : Submode : : period ( last . submode ) * 1.50 ) continue ;
2018-12-22 22:47:48 -05:00
2024-10-21 06:25:50 -07:00
ActivityDetail d = { } ;
d . text = m_config . mfi ( ) ;
d . isFree = true ;
d . utcTimestamp = last . utcTimestamp ;
d . snr = last . snr ;
d . tdrift = last . tdrift ;
d . dial = last . dial ;
d . offset = last . offset ;
d . submode = last . submode ;
2018-12-22 22:47:48 -05:00
2024-10-21 06:25:50 -07:00
if ( hasExistingMessageBuffer ( d . submode , offset , false , nullptr ) )
{
m_messageBuffer [ offset ] . msgs . append ( d ) ;
2018-12-22 22:47:48 -05:00
}
2024-10-21 06:25:50 -07:00
m_rxActivityQueue . append ( d ) ;
activity . append ( d ) ;
}
2018-12-22 22:47:48 -05:00
}
2018-07-31 14:34:36 -04:00
void MainWindow : : processRxActivity ( ) {
2018-07-31 18:42:33 -04:00
if ( m_rxActivityQueue . isEmpty ( ) ) {
2018-07-31 14:34:36 -04:00
return ;
}
2018-07-21 16:55:04 -04:00
2024-10-20 13:54:14 -07:00
int freqOffset = freq ( ) ;
2018-10-15 12:10:13 -04:00
2019-01-01 10:12:09 -05:00
qDebug ( ) < < m_messageBuffer . count ( ) < < " message buffers open " ;
2018-07-31 18:42:33 -04:00
while ( ! m_rxActivityQueue . isEmpty ( ) ) {
ActivityDetail d = m_rxActivityQueue . dequeue ( ) ;
2019-12-27 20:08:39 -05:00
if ( canSendNetworkMessage ( ) ) {
sendNetworkMessage ( " RX.ACTIVITY " , d . text , {
2020-04-03 22:49:28 -04:00
{ " _ID " , QVariant ( - 1 ) } ,
2020-04-04 22:45:51 -04:00
{ " FREQ " , QVariant ( d . dial + d . offset ) } ,
{ " DIAL " , QVariant ( d . dial ) } ,
{ " OFFSET " , QVariant ( d . offset ) } ,
2019-12-27 20:08:39 -05:00
{ " SNR " , QVariant ( d . snr ) } ,
{ " SPEED " , QVariant ( d . submode ) } ,
{ " TDRIFT " , QVariant ( d . tdrift ) } ,
{ " UTC " , QVariant ( d . utcTimestamp . toMSecsSinceEpoch ( ) ) }
} ) ;
}
2018-08-11 18:05:36 -04:00
// use the actual frequency and check its delta from our current frequency
// meaning, if our current offset is 1502 and the d.freq is 1492, the delta is <= 10;
2024-10-11 07:31:31 -07:00
bool shouldDisplay = abs ( d . offset - freqOffset ) < = JS8 : : Submode : : rxThreshold ( d . submode ) ;
2018-07-21 16:55:04 -04:00
2020-04-04 22:45:51 -04:00
int prevOffset = d . offset ;
if ( hasExistingMessageBuffer ( d . submode , d . offset , false , & prevOffset ) & & (
2018-10-10 13:35:52 -04:00
( m_messageBuffer [ prevOffset ] . cmd . to = = m_config . my_callsign ( ) ) | |
Removed selcall and active flag
In effort to simplify the behavior of automatic responses as well as make the software easier to use, I have removed the SELCALL button and the ACTIVE flag. Now, the response to STATUS is one that contains actual status (AUTO ON/OFF, VERSION NUMBER, etc). HBs used this in their transmissions, but it was never really accurate because it relied on the user to toggle the switch. Hazardous really. So, I approached this by simplifying the behavior. If AUTO is on, you will reply to direct queries. If AUTO is off, you wont. Simple. If HB is on, you will heartbeat. If it is off, you wont. Simple. If both AUTO and HB is on, you will automatically reply to heartbeats with ACKs. If not, you wont. Simple. You can remove yourself from the ALLCALL group. This is the same behavior as the previous SELCALL function and now that we have simplified it I can build an actual SELCALL function (to selectively allow stations to call you) instead of a 1/2 SELCALL that it used to be. Bingo.
2018-12-26 14:05:44 -05:00
// (isAllCallIncluded(m_messageBuffer[prevOffset].cmd.to)) || // uncomment this if we want to incrementally print allcalls
2018-10-27 10:09:40 -04:00
( isGroupCallIncluded ( m_messageBuffer [ prevOffset ] . cmd . to ) )
2018-10-10 13:35:52 -04:00
)
) {
2018-10-10 09:04:56 -04:00
d . isBuffered = true ;
shouldDisplay = true ;
2018-10-11 01:01:38 -04:00
if ( ! m_messageBuffer [ prevOffset ] . compound . isEmpty ( ) ) {
//qDebug() << "should display compound too because at this point it hasn't been displayed" << m_messageBuffer[prevOffset].compound.last().call;
auto lastCompound = m_messageBuffer [ prevOffset ] . compound . last ( ) ;
// fixup compound call incremental text
d . text = QString ( " %1: %2 " ) . arg ( lastCompound . call ) . arg ( d . text ) ;
d . utcTimestamp = qMin ( d . utcTimestamp , lastCompound . utcTimestamp ) ;
}
2020-04-04 22:45:51 -04:00
} else if ( hasClosedExistingMessageBuffer ( d . offset ) ) {
2019-10-05 14:55:15 -04:00
// incremental typeahead should just be displayed...
// TODO: should the buffer be reopened?
shouldDisplay = true ;
2018-10-11 01:01:38 -04:00
2019-05-27 18:14:34 -04:00
} else if ( d . isDirected & & d . text . contains ( " <....> " ) ) {
2018-10-10 09:04:56 -04:00
// if this is a _partial_ directed message, skip until the complete call comes through.
2019-05-27 18:14:34 -04:00
continue ;
2019-10-05 14:55:15 -04:00
2020-03-28 15:17:26 -04:00
} else if ( d . isDirected & & ( d . text . contains ( " : HB " ) | | d . text . contains ( " : @ALLCALL HB " ) ) ) { // TODO: HEARTBEAT
2019-05-27 18:14:34 -04:00
// if this is a heartbeat, process elsewhere...
continue ;
}
2018-10-10 09:04:56 -04:00
2019-05-27 18:14:34 -04:00
// if this is the first data frame of a standard message, parse the first word callsigns and spot them :)
if ( ( d . bits & Varicode : : JS8CallFirst ) = = Varicode : : JS8CallFirst & & ! d . isDirected & & ! d . isCompound ) {
auto calls = Varicode : : parseCallsigns ( d . text ) ;
if ( ! calls . isEmpty ( ) ) {
auto theirCall = calls . first ( ) ;
if ( d . text . startsWith ( theirCall ) & & d . text . mid ( theirCall . length ( ) , 1 ) = = " : " ) {
CallDetail cd = { } ;
cd . call = theirCall ;
2020-04-04 22:45:51 -04:00
cd . dial = d . dial ;
cd . offset = d . offset ;
2019-05-27 18:14:34 -04:00
cd . snr = d . snr ;
cd . bits = d . bits ;
cd . tdrift = d . tdrift ;
cd . utcTimestamp = d . utcTimestamp ;
2019-11-09 09:48:09 -05:00
cd . submode = d . submode ;
2019-05-27 18:14:34 -04:00
logCallActivity ( cd , true ) ;
}
2018-10-10 09:04:56 -04:00
}
}
2019-05-27 18:14:34 -04:00
2018-10-09 20:09:53 -04:00
// TODO: incremental printing of directed messages
// Display if:
// 1) this is a directed message header "to" us and should be buffered...
// 2) or, this is a buffered message frame for a buffer with us as the recipient.
2018-07-31 18:42:33 -04:00
if ( ! shouldDisplay ) {
continue ;
}
2018-10-04 13:52:52 -04:00
bool isFirst = ( d . bits & Varicode : : JS8CallFirst ) = = Varicode : : JS8CallFirst ;
bool isLast = ( d . bits & Varicode : : JS8CallLast ) = = Varicode : : JS8CallLast ;
2018-08-11 18:05:36 -04:00
// if we're the last message, let's display our EOT character
2018-07-31 14:34:36 -04:00
if ( isLast ) {
2019-04-22 23:40:55 -04:00
d . text = QString ( " %1 %2 " ) . arg ( Varicode : : rstrip ( d . text ) ) . arg ( m_config . eot ( ) ) ;
2018-07-31 14:34:36 -04:00
}
2018-07-03 11:38:16 -04:00
2018-07-31 19:09:13 -04:00
// log it to the display!
2020-04-04 22:45:51 -04:00
displayTextForFreq ( d . text , d . offset , d . utcTimestamp , false , isFirst , isLast ) ;
2018-08-06 23:39:43 -04:00
2018-11-28 22:53:18 -05:00
// if we've received a message to be displayed, we should bump the repeat buttons...
resetAutomaticIntervalTransmissions ( true , false ) ;
2018-09-21 22:41:20 -04:00
if ( isLast ) {
2020-04-04 22:45:51 -04:00
clearOffsetDirected ( d . offset ) ;
2018-09-21 22:41:20 -04:00
}
2018-08-07 09:19:18 -04:00
if ( isLast & & ! d . isBuffered ) {
// buffered commands need the rxFrameBlockNumbers cache so it can fixup its display
// all other "last" data frames can clear the rxFrameBlockNumbers cache so the next message will be on a new line.
2020-04-04 22:45:51 -04:00
m_rxFrameBlockNumbers . remove ( d . offset ) ;
2018-08-07 09:19:18 -04:00
}
2018-07-31 14:34:36 -04:00
}
2018-12-22 22:47:48 -05:00
#if 0
// TODO: this works but should also print in the rx window.
foreach ( auto offset , m_bandActivity . keys ( ) ) {
if ( seen . contains ( offset ) ) {
continue ;
}
if ( m_bandActivity [ offset ] . isEmpty ( ) ) {
continue ;
}
auto last = m_bandActivity [ offset ] . last ( ) ;
if ( ( last . bits & Varicode : : JS8CallLast ) = = Varicode : : JS8CallLast ) {
continue ;
}
auto now = DriftingDateTime : : currentDateTimeUtc ( ) ;
if ( last . utcTimestamp . secsTo ( now ) < m_TRperiod ) {
continue ;
}
ActivityDetail d = { } ;
d . text = " . . . " ;
d . isFree = true ;
d . utcTimestamp = now ;
d . snr = - 99 ;
m_bandActivity [ offset ] . append ( d ) ;
}
# endif
2018-07-31 14:34:36 -04:00
}
2018-07-03 18:57:49 -04:00
2018-07-31 14:34:36 -04:00
void MainWindow : : processCompoundActivity ( ) {
if ( m_messageBuffer . isEmpty ( ) ) {
return ;
}
2018-07-03 18:57:49 -04:00
2018-07-31 14:34:36 -04:00
// group compound callsign and directed commands together.
foreach ( auto freq , m_messageBuffer . keys ( ) ) {
2018-07-02 15:08:26 -04:00
2024-11-08 17:21:41 -08:00
auto & buffer = m_messageBuffer [ freq ] ;
2018-07-13 15:45:00 -04:00
2018-07-31 14:34:36 -04:00
qDebug ( ) < < " -> grouping buffer for freq " < < freq ;
2018-07-13 15:45:00 -04:00
2018-07-31 14:34:36 -04:00
if ( buffer . compound . isEmpty ( ) ) {
qDebug ( ) < < " -> buffer.compound is empty...skip " ;
continue ;
}
2018-07-06 18:25:27 -04:00
2018-07-31 14:34:36 -04:00
// if we don't have an initialized command, skip...
2018-08-11 18:05:36 -04:00
int bits = buffer . cmd . bits ;
bool validBits = (
2018-10-04 13:52:52 -04:00
bits = = Varicode : : JS8Call | |
( ( bits & Varicode : : JS8CallFirst ) = = Varicode : : JS8CallFirst ) | |
( ( bits & Varicode : : JS8CallLast ) = = Varicode : : JS8CallLast ) | |
2019-09-05 14:07:24 -04:00
( ( bits & Varicode : : JS8CallData ) = = Varicode : : JS8CallData )
2018-08-11 18:05:36 -04:00
) ;
if ( ! validBits ) {
2018-07-31 14:34:36 -04:00
qDebug ( ) < < " -> buffer.cmd bits is invalid...skip " ;
continue ;
}
2018-07-03 02:02:19 -04:00
2018-07-31 14:34:36 -04:00
// if we need two compound calls, but less than two have arrived...skip
if ( buffer . cmd . from = = " <....> " & & buffer . cmd . to = = " <....> " & & buffer . compound . length ( ) < 2 ) {
qDebug ( ) < < " -> buffer needs two compound, but has less...skip " ;
continue ;
}
2018-07-03 02:02:19 -04:00
2018-07-31 14:34:36 -04:00
// if we need one compound call, but non have arrived...skip
if ( ( buffer . cmd . from = = " <....> " | | buffer . cmd . to = = " <....> " ) & & buffer . compound . length ( ) < 1 ) {
qDebug ( ) < < " -> buffer needs one compound, but has less...skip " ;
continue ;
}
2018-07-20 10:13:12 -04:00
2018-07-31 14:34:36 -04:00
if ( buffer . cmd . from = = " <....> " ) {
auto d = buffer . compound . dequeue ( ) ;
buffer . cmd . from = d . call ;
2018-07-31 23:26:01 -04:00
buffer . cmd . grid = d . grid ;
2018-08-01 22:36:50 -04:00
buffer . cmd . isCompound = true ;
2018-08-31 22:29:13 -04:00
buffer . cmd . utcTimestamp = qMin ( buffer . cmd . utcTimestamp , d . utcTimestamp ) ;
2018-07-20 10:13:12 -04:00
2018-10-04 13:52:52 -04:00
if ( ( d . bits & Varicode : : JS8CallLast ) = = Varicode : : JS8CallLast ) {
2018-07-31 14:34:36 -04:00
buffer . cmd . bits = d . bits ;
}
}
2018-07-24 15:19:02 -04:00
2018-07-31 14:34:36 -04:00
if ( buffer . cmd . to = = " <....> " ) {
auto d = buffer . compound . dequeue ( ) ;
buffer . cmd . to = d . call ;
2018-08-01 22:36:50 -04:00
buffer . cmd . isCompound = true ;
2018-08-31 22:29:13 -04:00
buffer . cmd . utcTimestamp = qMin ( buffer . cmd . utcTimestamp , d . utcTimestamp ) ;
2018-07-07 22:02:11 -04:00
2018-10-04 13:52:52 -04:00
if ( ( d . bits & Varicode : : JS8CallLast ) = = Varicode : : JS8CallLast ) {
2018-07-31 14:34:36 -04:00
buffer . cmd . bits = d . bits ;
}
}
2018-07-05 04:45:55 -04:00
2018-10-04 13:52:52 -04:00
if ( ( buffer . cmd . bits & Varicode : : JS8CallLast ) ! = Varicode : : JS8CallLast ) {
2018-07-31 14:34:36 -04:00
qDebug ( ) < < " -> still not last message...skip " ;
continue ;
}
2018-07-02 15:08:26 -04:00
2018-08-31 22:29:13 -04:00
// fixup the datetime with the "minimum" dt seen
// this will allow us to delete the activity lines
// when the compound buffered command comes in.
auto dt = buffer . cmd . utcTimestamp ;
foreach ( auto c , buffer . compound ) {
dt = qMin ( dt , c . utcTimestamp ) ;
}
foreach ( auto m , buffer . msgs ) {
dt = qMin ( dt , m . utcTimestamp ) ;
}
buffer . cmd . utcTimestamp = dt ;
2018-07-31 14:34:36 -04:00
qDebug ( ) < < " buffered compound command ready " < < buffer . cmd . from < < buffer . cmd . to < < buffer . cmd . cmd ;
2018-07-24 15:19:02 -04:00
2018-07-31 14:34:36 -04:00
m_rxCommandQueue . append ( buffer . cmd ) ;
m_messageBuffer . remove ( freq ) ;
2019-10-05 14:55:15 -04:00
// TODO: only if to me?
m_lastClosedMessageBufferOffset = freq ;
2018-07-31 14:34:36 -04:00
}
}
2018-07-05 04:45:55 -04:00
2024-11-08 14:23:44 -08:00
void
MainWindow : : processBufferedActivity ( )
{
if ( m_messageBuffer . isEmpty ( ) ) return ;
2018-07-18 16:45:27 -04:00
2024-11-08 15:09:53 -08:00
foreach ( auto freq , m_messageBuffer . keys ( ) )
2024-11-08 14:23:44 -08:00
{
2024-11-08 15:09:53 -08:00
auto buffer = m_messageBuffer [ freq ] ;
2018-09-11 17:41:21 -04:00
// check to make sure we empty old buffers by getting the latest timestamp
// and checking to see if it's older than one minute.
2018-09-18 17:24:07 -04:00
auto dt = DriftingDateTime : : currentDateTimeUtc ( ) . addDays ( - 1 ) ;
2018-09-11 17:41:21 -04:00
if ( buffer . cmd . utcTimestamp . isValid ( ) ) {
dt = qMax ( dt , buffer . cmd . utcTimestamp ) ;
}
if ( ! buffer . compound . isEmpty ( ) ) {
dt = qMax ( dt , buffer . compound . last ( ) . utcTimestamp ) ;
}
if ( ! buffer . msgs . isEmpty ( ) ) {
dt = qMax ( dt , buffer . msgs . last ( ) . utcTimestamp ) ;
}
2019-10-05 14:55:15 -04:00
// if the buffer has messages older than 1 minute, and we still haven't closed it, let's mark it as the last frame
2019-04-08 22:33:41 -04:00
if ( dt . secsTo ( DriftingDateTime : : currentDateTimeUtc ( ) ) > 60 & & ! buffer . msgs . isEmpty ( ) ) {
buffer . msgs . last ( ) . bits | = Varicode : : JS8CallLast ;
}
2019-10-05 14:55:15 -04:00
// but, if the buffer is older than 1.5 minutes, and we still haven't closed it, just remove it and skip
2019-04-08 22:33:41 -04:00
if ( dt . secsTo ( DriftingDateTime : : currentDateTimeUtc ( ) ) > 90 ) {
2024-11-08 17:21:41 -08:00
m_messageBuffer . remove ( freq ) ;
2018-09-11 17:41:21 -04:00
continue ;
}
2019-10-05 14:55:15 -04:00
// if the buffer has no messages, skip
2018-07-31 14:34:36 -04:00
if ( buffer . msgs . isEmpty ( ) ) {
continue ;
}
2018-07-19 10:35:00 -04:00
2019-10-05 14:55:15 -04:00
// if the buffered message hasn't seen the last message, skip
2018-10-04 13:52:52 -04:00
if ( ( buffer . msgs . last ( ) . bits & Varicode : : JS8CallLast ) ! = Varicode : : JS8CallLast ) {
2018-07-31 14:34:36 -04:00
continue ;
}
2018-07-13 10:32:58 -04:00
2018-07-31 14:34:36 -04:00
QString message ;
foreach ( auto part , buffer . msgs ) {
message . append ( part . text ) ;
}
2018-10-02 09:44:46 -04:00
message = Varicode : : rstrip ( message ) ;
2018-07-30 17:16:45 -04:00
2018-08-02 02:03:04 -04:00
QString checksum ;
bool valid = false ;
2018-08-11 18:05:36 -04:00
if ( Varicode : : isCommandBuffered ( buffer . cmd . cmd ) ) {
2018-08-27 22:04:17 -04:00
int checksumSize = Varicode : : isCommandChecksumed ( buffer . cmd . cmd ) ;
if ( checksumSize = = 32 ) {
2018-10-17 14:36:15 -04:00
message = Varicode : : lstrip ( message ) ;
2018-08-11 18:05:36 -04:00
checksum = message . right ( 6 ) ;
message = message . left ( message . length ( ) - 7 ) ;
valid = Varicode : : checksum32Valid ( checksum , message ) ;
2018-08-27 22:04:17 -04:00
} else if ( checksumSize = = 16 ) {
2018-10-17 14:36:15 -04:00
message = Varicode : : lstrip ( message ) ;
2018-08-11 18:05:36 -04:00
checksum = message . right ( 3 ) ;
message = message . left ( message . length ( ) - 4 ) ;
valid = Varicode : : checksum16Valid ( checksum , message ) ;
2018-08-27 22:04:17 -04:00
} else if ( checksumSize = = 0 ) {
valid = true ;
2018-08-11 18:05:36 -04:00
}
2018-08-02 02:03:04 -04:00
} else {
2018-08-11 18:05:36 -04:00
valid = true ;
2018-08-02 02:03:04 -04:00
}
2018-07-30 17:16:45 -04:00
2018-08-11 18:05:36 -04:00
2018-08-02 02:03:04 -04:00
if ( valid ) {
2018-10-04 13:52:52 -04:00
buffer . cmd . bits | = Varicode : : JS8CallLast ;
2018-07-31 14:34:36 -04:00
buffer . cmd . text = message ;
2018-08-01 22:36:50 -04:00
buffer . cmd . isBuffered = true ;
2018-07-31 14:34:36 -04:00
m_rxCommandQueue . append ( buffer . cmd ) ;
} else {
qDebug ( ) < < " Buffered message failed checksum...discarding " ;
qDebug ( ) < < " Checksum: " < < checksum ;
qDebug ( ) < < " Message: " < < message ;
}
2018-07-30 17:16:45 -04:00
2018-07-31 14:34:36 -04:00
// regardless of valid or not, remove the "complete" buffered message from the buffer cache
m_messageBuffer . remove ( freq ) ;
2019-10-05 14:55:15 -04:00
m_lastClosedMessageBufferOffset = freq ;
2018-07-30 17:16:45 -04:00
}
2018-07-31 14:34:36 -04:00
}
2018-07-30 17:16:45 -04:00
2018-07-31 14:34:36 -04:00
void MainWindow : : processCommandActivity ( ) {
#if 0
if ( ! m_txFrameQueue . isEmpty ( ) ) {
return ;
2018-07-30 17:16:45 -04:00
}
2018-07-31 14:34:36 -04:00
# endif
2018-07-30 17:16:45 -04:00
2018-07-31 14:34:36 -04:00
if ( m_rxCommandQueue . isEmpty ( ) ) {
return ;
2018-07-30 17:16:45 -04:00
}
2018-07-31 14:34:36 -04:00
#if 0
bool processed = false ;
int f = currentFreq ( ) ;
# endif
2018-09-18 17:24:07 -04:00
auto now = DriftingDateTime : : currentDateTimeUtc ( ) ;
2018-07-31 14:34:36 -04:00
while ( ! m_rxCommandQueue . isEmpty ( ) ) {
auto d = m_rxCommandQueue . dequeue ( ) ;
2019-03-27 11:42:43 -04:00
auto selectedCallsign = callsignSelected ( ) ;
2018-07-31 14:34:36 -04:00
bool isAllCall = isAllCallIncluded ( d . to ) ;
2018-10-27 10:09:40 -04:00
bool isGroupCall = isGroupCallIncluded ( d . to ) ;
2018-07-31 14:34:36 -04:00
2020-04-04 22:45:51 -04:00
qDebug ( ) < < " try processing command " < < d . from < < d . to < < d . cmd < < d . dial < < d . offset < < d . grid < < d . extra < < isAllCall < < isGroupCall ;
2018-07-30 17:16:45 -04:00
2018-07-31 14:34:36 -04:00
// if we need a compound callsign but never got one...skip
if ( d . from = = " <....> " | | d . to = = " <....> " ) {
continue ;
}
2018-07-30 17:16:45 -04:00
2018-07-31 14:34:36 -04:00
// we're only processing a subset of queries at this point
if ( ! Varicode : : isCommandAllowed ( d . cmd ) ) {
continue ;
2018-07-30 17:16:45 -04:00
}
2018-09-29 17:04:05 -04:00
// is this to me?
bool toMe = d . to = = m_config . my_callsign ( ) . trimmed ( ) | | d . to = = Radio : : base_callsign ( m_config . my_callsign ( ) ) . trimmed ( ) ;
2018-08-12 09:23:05 -04:00
// log call activity...
2018-08-16 17:46:53 -04:00
CallDetail cd = { } ;
2018-08-12 09:23:05 -04:00
cd . call = d . from ;
cd . grid = d . grid ;
cd . snr = d . snr ;
2020-04-04 22:45:51 -04:00
cd . dial = d . dial ;
cd . offset = d . offset ;
2018-08-12 09:23:05 -04:00
cd . bits = d . bits ;
2018-11-25 22:12:54 -05:00
cd . ackTimestamp = d . text . contains ( " : ACK " ) | | toMe ? d . utcTimestamp : QDateTime { } ;
2018-08-12 09:23:05 -04:00
cd . utcTimestamp = d . utcTimestamp ;
2018-10-29 02:02:58 -04:00
cd . tdrift = d . tdrift ;
2019-11-09 09:48:09 -05:00
cd . submode = d . submode ;
2018-08-12 09:23:05 -04:00
logCallActivity ( cd , true ) ;
2019-01-14 09:48:53 -05:00
logHeardGraph ( d . from , d . to ) ;
2018-08-12 09:23:05 -04:00
2019-02-18 14:20:25 -05:00
// PROCESS BUFFERED HEARING FOR EVERYONE
if ( d . cmd = = " HEARING " ) {
// 1. parse callsigns
// 2. log it to the heard graph
auto calls = Varicode : : parseCallsigns ( d . text ) ;
foreach ( auto call , calls ) {
logHeardGraph ( d . from , call ) ;
}
2019-09-06 11:21:42 -04:00
}
2019-02-18 14:20:25 -05:00
2019-09-06 11:21:42 -04:00
// PROCESS BUFFERED GRID FOR EVERYONE
if ( d . cmd = = " GRID " ) {
// 1. parse grids
// 2. log it to our call activity
auto grids = Varicode : : parseGrids ( d . text ) ;
foreach ( auto grid , grids ) {
CallDetail cd = { } ;
cd . bits = d . bits ;
cd . call = d . from ;
2020-04-04 22:45:51 -04:00
cd . dial = d . dial ;
cd . offset = d . offset ;
2019-09-06 11:21:42 -04:00
cd . grid = grid ;
cd . snr = d . snr ;
cd . utcTimestamp = d . utcTimestamp ;
cd . tdrift = d . tdrift ;
2019-11-09 09:48:09 -05:00
cd . submode = d . submode ;
2019-09-06 11:21:42 -04:00
2020-03-30 23:07:40 -04:00
// PROCESS GRID SPOTS TO APRSIS FOR EVERYONE
if ( d . to = = " @APRSIS " ) {
2020-04-04 22:45:51 -04:00
spotAprsGrid ( cd . dial , cd . offset , cd . snr , cd . call , cd . grid ) ;
2020-03-30 23:07:40 -04:00
}
2019-09-06 11:21:42 -04:00
logCallActivity ( cd , true ) ;
}
2019-02-18 14:20:25 -05:00
}
2020-04-19 11:29:11 -04:00
// PROCESS @JS8NET, @APRSIS, AND OTHER GROUP SPOTS FOR EVERYONE
if ( d . to . startsWith ( " @ " ) ) {
2019-03-25 15:05:13 -04:00
spotCmd ( d ) ;
}
2020-03-30 23:07:40 -04:00
// PROCESS @APRSIS CMD SPOTS FOR EVERYONE
if ( d . to = = " @APRSIS " ) {
spotAprsCmd ( d ) ;
}
2019-04-05 20:44:23 -04:00
// PREPARE CMD TEXT STRING
QStringList textList = {
QString ( " %1: %2%3 " ) . arg ( d . from ) . arg ( d . to ) . arg ( d . cmd )
} ;
if ( ! d . extra . isEmpty ( ) ) {
textList . append ( d . extra ) ;
}
if ( ! d . text . isEmpty ( ) ) {
textList . append ( d . text ) ;
}
QString text = textList . join ( " " ) ;
bool isLast = ( d . bits & Varicode : : JS8CallLast ) = = Varicode : : JS8CallLast ;
if ( isLast ) {
2019-04-21 11:29:31 -04:00
// append the eot character to the text
text = QString ( " %1 %2 " ) . arg ( Varicode : : rstrip ( text ) ) . arg ( m_config . eot ( ) ) ;
2019-04-05 20:44:23 -04:00
}
// log the text to directed txt log
writeMsgTxt ( text , d . snr ) ;
2020-04-04 22:49:26 -04:00
// write all directed messages to api
if ( canSendNetworkMessage ( ) ) {
sendNetworkMessage ( " RX.DIRECTED " , text , {
{ " _ID " , QVariant ( - 1 ) } ,
{ " FROM " , QVariant ( d . from ) } ,
{ " TO " , QVariant ( d . to ) } ,
{ " CMD " , QVariant ( d . cmd ) } ,
{ " GRID " , QVariant ( d . grid ) } ,
{ " EXTRA " , QVariant ( d . extra ) } ,
{ " TEXT " , QVariant ( text ) } ,
{ " FREQ " , QVariant ( d . dial + d . offset ) } ,
{ " DIAL " , QVariant ( d . dial ) } ,
{ " OFFSET " , QVariant ( d . offset ) } ,
{ " SNR " , QVariant ( d . snr ) } ,
{ " SPEED " , QVariant ( d . submode ) } ,
{ " TDRIFT " , QVariant ( d . tdrift ) } ,
{ " UTC " , QVariant ( d . utcTimestamp . toMSecsSinceEpoch ( ) ) }
} ) ;
}
Removed selcall and active flag
In effort to simplify the behavior of automatic responses as well as make the software easier to use, I have removed the SELCALL button and the ACTIVE flag. Now, the response to STATUS is one that contains actual status (AUTO ON/OFF, VERSION NUMBER, etc). HBs used this in their transmissions, but it was never really accurate because it relied on the user to toggle the switch. Hazardous really. So, I approached this by simplifying the behavior. If AUTO is on, you will reply to direct queries. If AUTO is off, you wont. Simple. If HB is on, you will heartbeat. If it is off, you wont. Simple. If both AUTO and HB is on, you will automatically reply to heartbeats with ACKs. If not, you wont. Simple. You can remove yourself from the ALLCALL group. This is the same behavior as the previous SELCALL function and now that we have simplified it I can build an actual SELCALL function (to selectively allow stations to call you) instead of a 1/2 SELCALL that it used to be. Bingo.
2018-12-26 14:05:44 -05:00
// we're only responding to allcalls if we are participating in the allcall group
// but, don't avoid for heartbeats...those are technically allcalls but are processed differently
2020-04-05 11:49:34 -04:00
if ( isAllCall & & m_config . avoid_allcall ( ) & & d . cmd ! = " CQ " & & d . cmd ! = " HB " & & d . cmd ! = " HEARTBEAT " ) {
2018-07-31 14:34:36 -04:00
continue ;
2019-01-14 10:43:14 -05:00
}
2019-10-10 20:40:30 -04:00
// we're only responding to allcall, groupcalls, and our callsign at this point, so we'll end after logging the callsigns we've heard
if ( ! isAllCall & & ! toMe & & ! isGroupCall ) {
continue ;
}
2018-08-16 17:46:53 -04:00
ActivityDetail ad = { } ;
2018-08-06 23:39:43 -04:00
ad . isLowConfidence = false ;
ad . isFree = true ;
ad . isDirected = true ;
ad . bits = d . bits ;
2020-04-04 22:45:51 -04:00
ad . dial = d . dial ;
ad . offset = d . offset ;
2018-08-06 23:39:43 -04:00
ad . snr = d . snr ;
2019-04-05 20:44:23 -04:00
ad . text = text ;
2018-08-06 23:39:43 -04:00
ad . utcTimestamp = d . utcTimestamp ;
2018-08-11 18:05:36 -04:00
// we'd be double printing here if were on frequency, so let's be "smart" about this...
bool shouldDisplay = true ;
2018-09-29 17:04:05 -04:00
2018-10-28 09:47:30 -04:00
// don't display ping allcalls
2020-04-16 22:07:35 -04:00
if ( isAllCall & & ( d . cmd ! = " " | | ad . text . contains ( " @HB HEARTBEAT " ) ) ) {
2018-09-29 17:04:05 -04:00
shouldDisplay = false ;
}
2018-08-11 18:05:36 -04:00
if ( shouldDisplay ) {
2018-08-30 17:19:58 -04:00
auto c = ui - > textEditRX - > textCursor ( ) ;
c . movePosition ( QTextCursor : : End ) ;
ui - > textEditRX - > setTextCursor ( c ) ;
2020-03-29 22:18:44 -04:00
// ACKs and SNRs are the most likely source of items to be overwritten (multiple responses at once)...
2018-10-15 12:43:49 -04:00
// so don't overwrite those (i.e., print each on a new line)
2020-03-29 22:18:44 -04:00
bool shouldOverwrite = ( ! d . cmd . contains ( " ACK " ) & & ! d . cmd . contains ( " SNR " ) ) ; /* && isRecentOffset(d.freq);*/
2018-10-15 12:43:49 -04:00
if ( shouldOverwrite & & ui - > textEditRX - > find ( d . utcTimestamp . time ( ) . toString ( ) , QTextDocument : : FindBackward ) ) {
2018-08-11 18:05:36 -04:00
// ... maybe we could delete the last line that had this message on this frequency...
2018-08-30 17:19:58 -04:00
c = ui - > textEditRX - > textCursor ( ) ;
2018-08-11 18:05:36 -04:00
c . movePosition ( QTextCursor : : StartOfBlock ) ;
c . movePosition ( QTextCursor : : EndOfBlock , QTextCursor : : KeepAnchor ) ;
2019-09-25 20:26:41 -04:00
qDebug ( ) < < " should display directed message, erasing last rx activity line... " < < c . selectedText ( ) . toUpper ( ) ;
2019-02-01 01:46:01 -05:00
c . removeSelectedText ( ) ;
2018-08-30 17:19:58 -04:00
c . deletePreviousChar ( ) ;
c . deletePreviousChar ( ) ;
2019-02-01 01:46:01 -05:00
/*
2018-08-11 18:05:36 -04:00
c . deleteChar ( ) ;
c . deleteChar ( ) ;
2019-02-01 01:46:01 -05:00
*/
2018-08-11 18:05:36 -04:00
}
// log it to the display!
2020-04-04 22:45:51 -04:00
displayTextForFreq ( ad . text , ad . offset , ad . utcTimestamp , false , true , false ) ;
2018-08-17 10:07:17 -04:00
2020-04-04 22:49:26 -04:00
/*
2018-08-17 10:07:17 -04:00
// and send it to the network in case we want to interact with it from an external app...
2019-12-27 20:08:39 -05:00
if ( canSendNetworkMessage ( ) ) {
2020-04-04 22:49:26 -04:00
sendNetworkMessage ( " RX.DIRECTED.ME " , ad . text , {
2020-04-03 22:49:28 -04:00
{ " _ID " , QVariant ( - 1 ) } ,
2019-12-27 20:08:39 -05:00
{ " FROM " , QVariant ( d . from ) } ,
{ " TO " , QVariant ( d . to ) } ,
{ " CMD " , QVariant ( d . cmd ) } ,
{ " GRID " , QVariant ( d . grid ) } ,
{ " EXTRA " , QVariant ( d . extra ) } ,
{ " TEXT " , QVariant ( d . text ) } ,
2020-04-04 22:45:51 -04:00
{ " FREQ " , QVariant ( ad . dial + ad . offset ) } ,
{ " DIAL " , QVariant ( ad . dial ) } ,
{ " OFFSET " , QVariant ( ad . offset ) } ,
2019-12-27 20:08:39 -05:00
{ " SNR " , QVariant ( ad . snr ) } ,
{ " SPEED " , QVariant ( ad . submode ) } ,
{ " TDRIFT " , QVariant ( ad . tdrift ) } ,
{ " UTC " , QVariant ( ad . utcTimestamp . toMSecsSinceEpoch ( ) ) }
} ) ;
}
2020-04-04 22:49:26 -04:00
*/
2018-09-07 15:38:11 -04:00
2018-09-07 17:33:28 -04:00
if ( ! isAllCall ) {
2018-11-28 22:53:18 -05:00
// if we've received a message to be displayed, we should bump the repeat buttons...
resetAutomaticIntervalTransmissions ( true , false ) ;
2019-10-10 20:40:30 -04:00
// notification for directed message
tryNotify ( " directed " ) ;
2018-09-07 15:38:11 -04:00
}
2018-08-11 18:05:36 -04:00
}
2018-08-06 23:39:43 -04:00
2019-01-21 09:21:35 -05:00
// we're only responding to callsigns in our whitelist if we have one defined...
// make sure the whitelist is empty (no restrictions) or the from callsign or its base callsign is on it
auto whitelist = m_config . auto_whitelist ( ) ;
if ( ! whitelist . isEmpty ( ) & & ! ( whitelist . contains ( d . from ) | | whitelist . contains ( Radio : : base_callsign ( d . from ) ) ) ) {
2019-02-01 19:19:13 -05:00
qDebug ( ) < < " skipping command for whitelist " < < d . from ;
2019-01-21 09:21:35 -05:00
continue ;
}
2019-02-01 19:29:52 -05:00
// we'll never reply to a blacklisted callsign or base callsign
auto blacklist = m_config . auto_blacklist ( ) ;
if ( ! blacklist . isEmpty ( ) & & ( blacklist . contains ( d . from ) | | blacklist . contains ( Radio : : base_callsign ( d . from ) ) ) ) {
qDebug ( ) < < " skipping command for blacklist " < < d . from ;
continue ;
}
2018-12-20 21:05:24 -05:00
// if this is an allcall, check to make sure we haven't replied to their allcall recently (in the past ten minutes)
2018-11-27 23:21:16 -05:00
// that way we never get spammed by allcalls at too high of a frequency
2019-03-30 20:03:24 -04:00
if ( isAllCall & & m_txAllcallCommandCache . contains ( d . from ) & & m_txAllcallCommandCache [ d . from ] - > secsTo ( now ) / 60 < 15 ) {
2019-02-01 19:19:13 -05:00
qDebug ( ) < < " skipping command for allcall timeout " < < d . from ;
2018-10-06 03:59:54 -04:00
continue ;
}
2019-02-01 19:19:13 -05:00
// don't actually process any automatic message replies while in idle
if ( m_tx_watchdog ) {
qDebug ( ) < < " skipping command for idle timeout " < < d . from ;
continue ;
}
2019-02-01 03:08:47 -05:00
// HACK: if this is an autoreply cmd and relay path is populated and cmd is not MSG or MSG TO:, then swap out the relay path
2019-02-23 22:49:02 -05:00
if ( Varicode : : isCommandAutoreply ( d . cmd ) & & ! d . relayPath . isEmpty ( ) & & ! d . cmd . startsWith ( " MSG " ) & & ! d . cmd . startsWith ( " QUERY " ) ) {
2019-02-01 03:08:47 -05:00
d . from = d . relayPath ;
}
2018-08-06 23:39:43 -04:00
2018-07-31 23:26:01 -04:00
// construct a reply, if needed
2018-07-31 14:34:36 -04:00
QString reply ;
2018-09-21 01:47:21 -04:00
int priority = PriorityNormal ;
int freq = - 1 ;
2018-07-30 17:16:45 -04:00
2018-07-31 14:34:36 -04:00
// QUERIED SNR
2018-10-28 12:37:47 -04:00
if ( d . cmd = = " SNR? " & & ! isAllCall ) {
2018-07-31 14:34:36 -04:00
reply = QString ( " %1 SNR %2 " ) . arg ( d . from ) . arg ( Varicode : : formatSNR ( d . snr ) ) ;
}
2018-09-08 23:36:12 -04:00
2019-02-08 16:57:42 -05:00
// QUERIED INFO
else if ( d . cmd = = " INFO? " & & ! isAllCall ) {
QString info = m_config . my_info ( ) ;
if ( info . isEmpty ( ) ) {
2018-08-27 21:19:38 -04:00
continue ;
2018-07-31 14:34:36 -04:00
}
2018-07-29 10:39:58 -04:00
2019-02-08 16:57:42 -05:00
reply = QString ( " %1 INFO %2 " ) . arg ( d . from ) . arg ( replaceMacros ( info , buildMacroValues ( ) , true ) ) ;
2018-07-31 14:34:36 -04:00
}
2018-09-08 23:36:12 -04:00
2018-10-17 09:08:37 -04:00
// QUERIED ACTIVE
2018-10-28 12:37:47 -04:00
else if ( d . cmd = = " STATUS? " & & ! isAllCall ) {
2020-03-28 15:10:41 -04:00
QString status = m_config . my_status ( ) ;
if ( status . isEmpty ( ) ) {
continue ;
}
reply = QString ( " %1 STATUS %2 " ) . arg ( d . from ) . arg ( replaceMacros ( status , buildMacroValues ( ) , true ) ) ;
2018-10-17 09:08:37 -04:00
}
2018-08-27 21:19:38 -04:00
// QUERIED GRID
2018-10-28 12:37:47 -04:00
else if ( d . cmd = = " GRID? " & & ! isAllCall ) {
2018-08-27 21:19:38 -04:00
QString grid = m_config . my_grid ( ) ;
if ( grid . isEmpty ( ) ) {
continue ;
}
reply = QString ( " %1 GRID %2 " ) . arg ( d . from ) . arg ( grid ) ;
}
2018-09-08 23:36:12 -04:00
2018-07-31 14:34:36 -04:00
// QUERIED STATIONS HEARD
2024-11-08 16:28:14 -08:00
else if ( d . cmd = = " HEARING? " & & ! isAllCall )
{
2018-07-31 14:34:36 -04:00
auto calls = m_callActivity . keys ( ) ;
2024-11-08 16:28:14 -08:00
std : : stable_sort ( calls . begin ( ) ,
calls . end ( ) ,
[ this ] ( QString const & lhs ,
QString const & rhs )
{
return m_callActivity [ rhs ] . utcTimestamp <
m_callActivity [ lhs ] . utcTimestamp ;
2018-07-31 14:34:36 -04:00
} ) ;
2024-11-08 16:28:14 -08:00
auto const callsignAging = m_config . callsign_aging ( ) ;
auto const maxStations = 4 ;
auto i = 0 ;
2018-07-31 14:34:36 -04:00
QStringList lines ;
2018-08-01 22:36:50 -04:00
2024-11-08 16:55:41 -08:00
for ( auto const & call : calls )
2024-11-08 16:28:14 -08:00
{
if ( i > = maxStations ) break ;
if ( call = = d . from ) continue ;
2018-08-01 11:25:45 -04:00
2024-11-08 16:28:14 -08:00
auto const & cd = m_callActivity [ call ] ;
2018-10-19 09:53:51 -04:00
2024-11-08 16:28:14 -08:00
if ( callsignAging & & cd . utcTimestamp . secsTo ( now ) / 60 > = callsignAging )
{
continue ;
}
2018-08-01 11:25:45 -04:00
2024-11-08 16:28:14 -08:00
lines . append ( cd . call ) ;
i + + ;
2018-07-31 14:34:36 -04:00
}
2018-08-02 00:32:06 -04:00
2018-10-15 02:37:21 -04:00
lines . prepend ( QString ( " %1 HEARING " ) . arg ( d . from ) ) ;
2018-10-19 09:53:51 -04:00
reply = lines . join ( ' ' ) ;
2018-08-02 02:21:40 -04:00
}
2018-09-08 23:36:12 -04:00
// PROCESS RELAY
2019-02-25 22:25:04 -05:00
else if ( d . cmd = = " > " & & ! isAllCall & & ! m_config . relay_off ( ) ) {
2018-09-08 23:36:12 -04:00
// 1. see if there are any more hops to process
// 2. if so, forward
2018-09-09 12:40:02 -04:00
// 3. otherwise, display alert & reply dialog
2018-09-08 23:36:12 -04:00
2018-09-09 12:40:02 -04:00
QString callToPattern = { R " (^(?<callsign> \b (?<prefix>[A-Z0-9]{1,4} \ /)?(?<base>([0-9A-Z])?([0-9A-Z])([0-9])([A-Z])?([A-Z])?([A-Z])?)(?<suffix> \ /[A-Z0-9]{1,4})?(?<type>[> ])) \b ) " } ;
2018-09-08 23:36:12 -04:00
QRegularExpression re ( callToPattern ) ;
2018-09-09 10:42:32 -04:00
auto text = d . text ;
auto match = re . match ( text ) ;
2018-09-08 23:36:12 -04:00
2019-01-09 11:25:00 -05:00
// if the text starts with a callsign, and relay is not disabled, and this is not a group callsign, then relay.
2019-02-25 22:25:04 -05:00
if ( match . hasMatch ( ) & & ! isGroupCall ) {
2018-09-09 10:42:32 -04:00
// replace freetext with relayed free text
if ( match . captured ( " type " ) ! = " > " ) {
text = text . replace ( match . capturedStart ( " type " ) , match . capturedLength ( " type " ) , " > " ) ;
}
2019-10-18 12:45:35 -04:00
reply = QString ( " %1 *DE* %2 " ) . arg ( text ) . arg ( d . from ) ;
2018-09-08 23:36:12 -04:00
// otherwise, as long as we're not an ACK...alert the user and either send an ACK or Message
2018-12-17 01:27:41 -05:00
} else if ( ! d . text . startsWith ( " ACK " ) ) {
2019-02-01 01:46:01 -05:00
// parse out the callsign path
auto calls = parseRelayPathCallsigns ( d . from , d . text ) ;
2018-09-08 23:36:12 -04:00
2018-09-09 10:42:32 -04:00
// put these third party calls in the heard list
foreach ( auto call , calls ) {
CallDetail cd = { } ;
cd . call = call ;
cd . snr = - 64 ;
2020-04-04 22:45:51 -04:00
cd . dial = d . dial ;
cd . offset = d . offset ;
2018-09-09 10:42:32 -04:00
cd . through = d . from ;
2018-09-18 17:24:07 -04:00
cd . utcTimestamp = DriftingDateTime : : currentDateTimeUtc ( ) ;
2018-10-29 02:02:58 -04:00
cd . tdrift = d . tdrift ;
2019-11-09 09:48:09 -05:00
cd . submode = d . submode ;
2018-09-09 10:42:32 -04:00
logCallActivity ( cd , false ) ;
}
2018-09-08 23:36:12 -04:00
2019-01-02 12:27:16 -05:00
d . relayPath = calls . join ( ' > ' ) ;
2018-09-08 23:36:12 -04:00
2019-01-02 12:27:16 -05:00
reply = QString ( " %1 ACK " ) . arg ( d . relayPath ) ;
2018-12-17 01:27:41 -05:00
2019-01-20 23:08:57 -05:00
// check to see if the relay text contains a command that should be replied to instead of an ack.
2019-02-01 03:08:47 -05:00
QStringList relayedCmds = d . text . split ( " " ) ;
2019-01-20 23:08:57 -05:00
if ( ! relayedCmds . isEmpty ( ) ) {
auto first = relayedCmds . first ( ) ;
auto valid = Varicode : : isCommandAllowed ( first ) ;
if ( ! valid ) {
first = " " + first ;
valid = Varicode : : isCommandAllowed ( first ) ;
2019-02-01 03:08:47 -05:00
if ( valid ) {
relayedCmds . removeFirst ( ) ;
}
}
// HACK: "MSG TO:" should be supported but contains a space :(
2019-02-23 22:49:02 -05:00
if ( ! relayedCmds . isEmpty ( ) ) {
if ( first = = " MSG " ) {
auto second = relayedCmds . first ( ) ;
if ( second = = " TO: " ) {
first = " MSG TO: " ;
relayedCmds . removeFirst ( ) ;
} else if ( second . startsWith ( " TO: " ) ) {
first = " MSG TO: " ;
relayedCmds . replace ( 0 , second . mid ( 3 ) ) ;
}
} else if ( first = = " QUERY " ) {
auto second = relayedCmds . first ( ) ;
if ( second = = " MSGS " | | second = = " MSGS? " ) {
first = " QUERY MSGS " ;
relayedCmds . removeFirst ( ) ;
}
else if ( second = = " CALL " ) {
first = " QUERY CALL " ;
relayedCmds . removeFirst ( ) ;
}
2019-02-01 03:08:47 -05:00
}
2019-01-20 23:08:57 -05:00
}
2019-02-23 22:49:02 -05:00
if ( Varicode : : isCommandAllowed ( first ) & & Varicode : : isCommandAutoreply ( first ) ) {
2019-01-20 23:08:57 -05:00
CommandDetail rd = { } ;
rd . bits = d . bits ;
rd . cmd = first ;
2020-04-04 22:45:51 -04:00
rd . dial = d . dial ;
rd . offset = d . offset ;
2019-02-23 22:49:02 -05:00
rd . from = d . from ; // note, MSG and QUERY commands are not set with from as the relay path.
2019-02-01 03:08:47 -05:00
rd . relayPath = d . relayPath ;
rd . text = relayedCmds . join ( " " ) ; //d.text;
2019-01-20 23:08:57 -05:00
rd . to = d . to ;
rd . utcTimestamp = d . utcTimestamp ;
m_rxCommandQueue . insert ( 0 , rd ) ;
continue ;
}
}
2019-02-01 01:46:01 -05:00
# if STORE_RELAY_MSGS_TO_INBOX
2019-01-20 23:08:57 -05:00
// if we make it here, this is a message
2019-01-23 20:31:26 -05:00
addCommandToMyInbox ( d ) ;
2019-02-01 01:46:01 -05:00
# endif
2018-09-08 23:36:12 -04:00
}
}
2019-01-23 20:31:26 -05:00
// PROCESS MESSAGE STORAGE
else if ( d . cmd = = " MSG TO: " & & ! isAllCall & & ! isGroupCall & & ! m_config . relay_off ( ) ) {
// store message
QStringList segs = d . text . split ( " " ) ;
if ( segs . isEmpty ( ) ) {
continue ;
}
auto to = segs . first ( ) ;
2019-02-01 03:08:47 -05:00
segs . removeFirst ( ) ;
auto text = segs . join ( " " ) . trimmed ( ) ;
auto calls = parseRelayPathCallsigns ( d . from , text ) ;
d . relayPath = calls . join ( " > " ) ;
2019-01-23 20:31:26 -05:00
CommandDetail cd = { } ;
cd . bits = d . bits ;
cd . cmd = d . cmd ;
cd . extra = d . extra ;
2020-04-04 22:45:51 -04:00
cd . dial = d . dial ;
cd . offset = d . offset ;
2019-01-23 20:31:26 -05:00
cd . from = d . from ;
cd . grid = d . grid ;
cd . relayPath = d . relayPath ;
cd . snr = d . snr ;
cd . tdrift = d . tdrift ;
cd . text = text ;
2019-02-07 13:43:35 -05:00
cd . to = Radio : : base_callsign ( to ) ;
2019-01-23 20:31:26 -05:00
cd . utcTimestamp = d . utcTimestamp ;
2019-11-09 09:48:09 -05:00
cd . submode = d . submode ;
2019-01-23 20:31:26 -05:00
qDebug ( ) < < " storing message to " < < to < < " : " < < text ;
2019-02-08 19:41:07 -05:00
addCommandToStorage ( " STORE " , cd ) ;
2019-01-23 20:31:26 -05:00
2019-02-01 03:08:47 -05:00
// we haven't replaced the from with the relay path, so we have to use it for the ack if there is one
reply = QString ( " %1 ACK " ) . arg ( calls . length ( ) > 1 ? d . relayPath : d . from ) ;
2019-01-23 20:31:26 -05:00
}
2018-08-27 21:19:38 -04:00
// PROCESS AGN
2018-10-06 09:06:47 -04:00
else if ( d . cmd = = " AGN? " & & ! isAllCall & & ! isGroupCall & & ! m_lastTxMessage . isEmpty ( ) ) {
2019-10-20 14:28:04 -04:00
reply = Varicode : : rstrip ( m_lastTxMessage ) ;
2018-08-29 12:08:22 -04:00
}
2018-09-08 23:36:12 -04:00
2018-11-25 22:12:54 -05:00
// PROCESS ACTIVE HEARTBEAT
2019-09-05 14:07:24 -04:00
// if we have hb mode enabled and auto reply enabled <del>and auto ack enabled and no callsign is selected</del> update: if we're in HB mode, doesn't matter if a callsign is selected.
2020-03-31 20:49:03 -04:00
else if ( ( d . cmd = = " HB " | | d . cmd = = " HEARTBEAT " ) & & canCurrentModeSendHeartbeat ( ) & & ui - > actionModeJS8HB - > isChecked ( ) & & ui - > actionModeAutoreply - > isChecked ( ) & & ui - > actionHeartbeatAcknowledgements - > isChecked ( ) ) {
2019-09-05 14:27:44 -04:00
// check to make sure we aren't pausing HB transmissions (ACKs) while a callsign is selected
if ( m_config . heartbeat_qso_pause ( ) & & ! selectedCallsign . isEmpty ( ) ) {
qDebug ( ) < < " hb paused during qso " ;
continue ;
}
2019-02-01 01:46:01 -05:00
2019-07-15 09:45:25 -04:00
// check to make sure this callsign isn't blacklisted
if ( m_config . hb_blacklist ( ) . contains ( d . from ) | | m_config . hb_blacklist ( ) . contains ( Radio : : base_callsign ( d . from ) ) ) {
2019-09-05 14:07:24 -04:00
qDebug ( ) < < " hb blacklist blocking " < < d . from ;
2019-07-15 09:45:25 -04:00
continue ;
}
2019-02-01 01:46:01 -05:00
// check to see if we have a message for a station who is heartbeating
QString extra ;
auto mid = getNextMessageIdForCallsign ( d . from ) ;
if ( mid ! = - 1 ) {
extra = QString ( " MSG ID %1 " ) . arg ( mid ) ;
}
2020-02-17 20:43:26 -05:00
// TODO: require confirmation?
2019-02-01 01:46:01 -05:00
sendHeartbeatAck ( d . from , d . snr , extra ) ;
2018-09-21 01:47:21 -04:00
if ( isAllCall ) {
2018-10-28 09:47:30 -04:00
// since all pings are technically @ALLCALL, let's bump the allcall cache here...
2018-10-06 02:12:12 -04:00
m_txAllcallCommandCache . insert ( d . from , new QDateTime ( now ) , 5 ) ;
2018-09-21 01:47:21 -04:00
}
2018-12-20 20:16:53 -05:00
2019-02-01 01:46:01 -05:00
continue ;
}
2020-05-03 22:25:51 -04:00
// PROCESS HEARTBEAT SNR
else if ( d . cmd = = " HEARTBEAT SNR " ) {
qDebug ( ) < < " skipping incoming hb snr " < < d . text ;
continue ;
}
2020-04-05 11:49:34 -04:00
// PROCESS CQ
else if ( d . cmd = = " CQ " ) {
qDebug ( ) < < " skipping incoming cq " < < d . text ;
continue ;
}
2019-02-01 01:46:01 -05:00
// PROCESS MSG
2019-02-01 03:08:47 -05:00
else if ( d . cmd = = " MSG " & & ! isAllCall ) {
2019-02-01 01:46:01 -05:00
2019-02-01 03:08:47 -05:00
auto text = d . text ;
2019-02-01 01:46:01 -05:00
2019-02-01 03:08:47 -05:00
qDebug ( ) < < " adding message to inbox " < < text ;
2019-02-01 01:46:01 -05:00
auto calls = parseRelayPathCallsigns ( d . from , text ) ;
d . cmd = " MSG " ;
d . relayPath = calls . join ( " > " ) ;
d . text = text ;
addCommandToMyInbox ( d ) ;
2019-10-10 20:40:30 -04:00
// notification
tryNotify ( " inbox " ) ;
2019-02-01 03:08:47 -05:00
// we haven't replaced the from with the relay path, so we have to use it for the ack if there is one
reply = QString ( " %1 ACK " ) . arg ( calls . length ( ) > 1 ? d . relayPath : d . from ) ;
2019-02-07 14:23:46 -05:00
# define SHOW_ALERT_FOR_MSG 1
# if SHOW_ALERT_FOR_MSG
SelfDestructMessageBox * m = new SelfDestructMessageBox ( 300 ,
" New Message Received " ,
QString ( " A new message was received at %1 UTC from %2 " ) . arg ( d . utcTimestamp . time ( ) . toString ( ) ) . arg ( d . from ) ,
QMessageBox : : Information ,
QMessageBox : : Ok ,
QMessageBox : : Ok ,
2019-02-07 14:31:11 -05:00
false ,
2019-02-07 14:23:46 -05:00
this ) ;
m - > show ( ) ;
# endif
2019-02-01 01:46:01 -05:00
}
// PROCESS ACKS
2019-02-01 03:08:47 -05:00
else if ( d . cmd = = " ACK " & & ! isAllCall ) {
2019-02-01 01:46:01 -05:00
qDebug ( ) < < " skipping incoming ack " < < d . text ;
2019-10-10 20:50:50 -04:00
// notification for ack
tryNotify ( " ack " ) ;
2018-12-20 20:16:53 -05:00
// make sure this is explicit
continue ;
2018-09-21 01:47:21 -04:00
}
2019-01-31 14:04:44 -05:00
// PROCESS BUFFERED CMD
2019-02-01 03:08:47 -05:00
else if ( d . cmd = = " CMD " & & ! isAllCall ) {
2019-01-31 14:04:44 -05:00
qDebug ( ) < < " skipping incoming command " < < d . text ;
// make sure this is explicit
continue ;
}
2018-11-25 22:12:54 -05:00
// PROCESS BUFFERED QUERY
2019-02-01 03:08:47 -05:00
else if ( d . cmd = = " QUERY " & & ! isAllCall ) {
2019-02-23 22:49:02 -05:00
auto who = d . from ; // keep in mind, this is the sender, not the original requestor if relayed
auto replyPath = d . from ;
if ( d . relayPath . contains ( " > " ) ) {
auto path = d . relayPath . split ( " > " ) ;
who = path . last ( ) ;
replyPath = d . relayPath ;
}
2019-01-31 18:53:29 -05:00
2019-01-31 14:04:44 -05:00
QStringList segs = d . text . split ( " " ) ;
if ( segs . isEmpty ( ) ) {
continue ;
}
2018-12-31 20:03:51 -05:00
2019-01-31 14:04:44 -05:00
auto cmd = segs . first ( ) ;
segs . removeFirst ( ) ;
2019-01-31 18:53:29 -05:00
if ( cmd = = " MSG " & & ! segs . isEmpty ( ) ) {
auto inbox = Inbox ( inboxPath ( ) ) ;
if ( ! inbox . open ( ) ) {
continue ;
}
bool ok = false ;
int mid = QString ( segs . first ( ) ) . toInt ( & ok ) ;
if ( ! ok ) {
continue ;
}
auto msg = inbox . value ( mid ) ;
auto params = msg . params ( ) ;
if ( params . isEmpty ( ) ) {
continue ;
}
auto from = params . value ( " FROM " ) . toString ( ) . trimmed ( ) ;
2019-02-01 03:08:47 -05:00
2020-01-13 11:11:23 -05:00
// TODO: group messaging - allow any message to a @GROUP to be retrieved by anybody
2019-02-01 03:08:47 -05:00
auto to = params . value ( " TO " ) . toString ( ) . trimmed ( ) ;
2019-02-07 13:43:35 -05:00
if ( to ! = who & & to ! = Radio : : base_callsign ( who ) ) {
2019-01-31 18:53:29 -05:00
continue ;
}
auto text = params . value ( " TEXT " ) . toString ( ) . trimmed ( ) ;
if ( text . isEmpty ( ) ) {
continue ;
}
2019-01-31 14:04:44 -05:00
2019-02-01 01:46:01 -05:00
// mark as delivered (so subsequent HBs and QUERY MSGS don't receive this message)
msg . setType ( " DELIVERED " ) ;
inbox . set ( mid , msg ) ;
// and reply
2019-02-23 22:49:02 -05:00
reply = QString ( " %1 MSG %2 FROM %3 " ) ;
reply = reply . arg ( replyPath ) ;
2019-01-31 18:53:29 -05:00
reply = reply . arg ( text ) ;
reply = reply . arg ( from ) ;
2019-01-31 14:04:44 -05:00
}
2018-12-31 20:03:51 -05:00
}
2019-01-23 20:31:26 -05:00
// PROCESS BUFFERED QUERY MSGS
2019-08-26 12:08:35 -04:00
else if ( d . cmd = = " QUERY MSGS " & & ui - > actionModeAutoreply - > isChecked ( ) ) {
2019-02-23 22:49:02 -05:00
auto who = d . from ; // keep in mind, this is the sender, not the original requestor if relayed
auto replyPath = d . from ;
if ( d . relayPath . contains ( " > " ) ) {
auto path = d . relayPath . split ( " > " ) ;
who = path . last ( ) ;
replyPath = d . relayPath ;
}
2019-01-23 20:31:26 -05:00
2019-01-31 18:53:29 -05:00
// if this is an allcall or a directed call, check to see if we have a stored message for user.
// we reply yes if the user would be able to retreive a stored message
2019-02-01 01:46:01 -05:00
auto mid = getNextMessageIdForCallsign ( who ) ;
if ( mid ! = - 1 ) {
2019-02-23 22:49:02 -05:00
reply = QString ( " %1 YES MSG ID %2 " ) . arg ( replyPath ) . arg ( mid ) ;
2019-01-23 20:31:26 -05:00
}
2019-01-31 10:53:17 -05:00
2020-01-13 11:11:23 -05:00
// TODO: group messaging - if a isGroupCall, check to see if there's a message id for the group and return it if there's not an individual message
2019-01-31 14:04:44 -05:00
// if this is not an allcall and we have no messages, reply no.
if ( ! isAllCall & & reply . isEmpty ( ) ) {
2019-02-23 22:49:02 -05:00
reply = QString ( " %1 NO " ) . arg ( replyPath ) ;
2019-01-31 14:04:44 -05:00
}
2019-01-23 20:31:26 -05:00
}
2018-12-31 20:03:51 -05:00
// PROCESS BUFFERED QUERY CALL
2019-08-26 12:08:35 -04:00
else if ( d . cmd = = " QUERY CALL " & & ui - > actionModeAutoreply - > isChecked ( ) ) {
2019-02-23 22:49:02 -05:00
auto replyPath = d . from ;
if ( d . relayPath . contains ( " > " ) ) {
replyPath = d . relayPath ;
}
2018-08-29 12:08:22 -04:00
auto who = d . text ;
if ( who . isEmpty ( ) ) {
continue ;
}
auto callsigns = Varicode : : parseCallsigns ( who ) ;
if ( callsigns . isEmpty ( ) ) {
continue ;
}
2018-09-01 17:56:10 -04:00
QStringList replies ;
2018-09-07 11:15:58 -04:00
int callsignAging = m_config . callsign_aging ( ) ;
2018-09-01 17:56:10 -04:00
auto baseCall = callsigns . first ( ) ;
foreach ( auto cd , m_callActivity . values ( ) ) {
2018-09-07 11:15:58 -04:00
if ( callsignAging & & cd . utcTimestamp . secsTo ( now ) / 60 > = callsignAging ) {
continue ;
}
2018-09-01 17:56:10 -04:00
if ( baseCall = = cd . call | | baseCall = = Radio : : base_callsign ( cd . call ) ) {
2019-02-23 22:49:02 -05:00
auto r = QString ( " %1 (%2) " ) . arg ( Varicode : : formatSNR ( cd . snr ) ) . arg ( since ( cd . utcTimestamp ) ) . trimmed ( ) ;
2018-09-01 17:56:10 -04:00
replies . append ( r ) ;
2019-02-23 22:49:02 -05:00
break ;
2018-09-01 17:56:10 -04:00
}
2018-08-29 12:08:22 -04:00
}
2018-12-31 20:03:51 -05:00
if ( ! replies . isEmpty ( ) ) {
2019-02-23 22:49:02 -05:00
replies . prepend ( QString ( " %1 YES " ) . arg ( replyPath ) ) ;
2018-12-31 20:03:51 -05:00
}
2019-02-23 22:49:02 -05:00
reply = replies . join ( " " ) ;
2018-09-21 01:47:21 -04:00
if ( ! reply . isEmpty ( ) ) {
if ( isAllCall ) {
m_txAllcallCommandCache . insert ( d . from , new QDateTime ( now ) , 25 ) ;
}
}
2018-08-27 21:19:38 -04:00
}
2018-09-08 23:36:12 -04:00
2018-09-09 12:40:02 -04:00
#if 0
2018-07-31 14:34:36 -04:00
// PROCESS ALERT
else if ( d . cmd = = " ! " & & ! isAllCall ) {
2018-07-13 10:32:58 -04:00
2018-09-08 23:36:12 -04:00
// create alert dialog
processAlertReplyForCommand ( d , d . from , " " ) ;
2018-08-28 15:13:38 -04:00
// make sure this is explicit
continue ;
2018-07-31 14:34:36 -04:00
}
2018-09-09 12:40:02 -04:00
# endif
2018-07-13 10:32:58 -04:00
2018-11-03 01:31:24 -04:00
// well, if there's no reply, don't do anything...
2018-07-31 14:34:36 -04:00
if ( reply . isEmpty ( ) ) {
continue ;
}
2018-07-24 16:46:04 -04:00
2018-11-30 10:05:47 -05:00
// do not queue @ALLCALL replies if auto-reply is not checked
2019-08-26 12:08:35 -04:00
if ( ! ui - > actionModeAutoreply - > isChecked ( ) & & isAllCall ) {
2018-08-16 16:41:01 -04:00
continue ;
}
2019-09-05 14:07:24 -04:00
#if 0
2019-03-27 11:42:43 -04:00
// TODO: jsherer - HB issue here
2018-11-30 10:05:47 -05:00
// do not queue a reply if it's a HB and HB is not active
2019-09-05 14:07:24 -04:00
// if((!ui->hbMacroButton->isChecked() || m_hbInterval <= 0) && d.cmd.contains("HB")){
// continue;
// }
# endif
2018-11-30 10:05:47 -05:00
2018-11-03 01:31:24 -04:00
// do not queue for reply if there's text in the window
if ( ! ui - > extFreeTextMsgEdit - > toPlainText ( ) . isEmpty ( ) ) {
continue ;
}
2019-01-01 10:12:09 -05:00
// do not queue for reply if there's a buffer open to us
int bufferOffset = 0 ;
if ( hasExistingMessageBufferToMe ( & bufferOffset ) ) {
qDebug ( ) < < " skipping reply due to open buffer " < < bufferOffset < < m_messageBuffer . count ( ) ;
continue ;
}
2018-10-27 10:13:34 -04:00
// add @ALLCALLs to the @ALLCALL cache
2018-09-15 08:22:24 -04:00
if ( isAllCall ) {
m_txAllcallCommandCache . insert ( d . from , new QDateTime ( now ) , 25 ) ;
}
2018-08-11 10:54:21 -04:00
// queue the reply here to be sent when a free interval is available on the frequency that was sent
// unless, this is an allcall, to which we should be responding on a clear frequency offset
// we always want to make sure that the directed cache has been updated at this point so we have the
// most information available to make a frequency selection.
2020-02-17 20:43:26 -05:00
if ( m_config . autoreply_confirmation ( ) ) {
confirmThenEnqueueMessage ( 90 , priority , reply , freq , nullptr ) ;
} else {
enqueueMessage ( priority , reply , freq , nullptr ) ;
}
2018-07-31 14:34:36 -04:00
}
}
2018-07-13 10:32:58 -04:00
2019-01-02 12:27:16 -05:00
QString MainWindow : : inboxPath ( ) {
return QDir : : toNativeSeparators ( m_config . writeable_data_dir ( ) . absoluteFilePath ( " inbox.db3 " ) ) ;
}
void MainWindow : : refreshInboxCounts ( ) {
auto inbox = Inbox ( inboxPath ( ) ) ;
if ( inbox . open ( ) ) {
// reset inbox counts
m_rxInboxCountCache . clear ( ) ;
// compute new counts from db
auto v = inbox . values ( " UNREAD " , " $ " , " % " , 0 , 10000 ) ;
foreach ( auto pair , v ) {
auto params = pair . second . params ( ) ;
auto to = params . value ( " TO " ) . toString ( ) ;
if ( to . isEmpty ( ) | | ( to ! = m_config . my_callsign ( ) & & to ! = Radio : : base_callsign ( m_config . my_callsign ( ) ) ) ) {
continue ;
}
auto from = params . value ( " FROM " ) . toString ( ) ;
if ( from . isEmpty ( ) ) {
continue ;
}
m_rxInboxCountCache [ from ] = m_rxInboxCountCache . value ( from , 0 ) + 1 ;
2024-10-21 13:44:12 -07:00
if ( ! m_callActivity . contains ( from ) )
{
auto const utc = params . value ( " UTC " ) . toString ( ) ;
auto const snr = params . value ( " SNR " ) . toInt ( ) ;
auto const dial = params . value ( " DIAL " ) . toInt ( ) ;
auto const offset = params . value ( " OFFSET " ) . toInt ( ) ;
auto const tdrift = params . value ( " TDRIFT " ) . toInt ( ) ;
auto const submode = params . value ( " SUBMODE " ) . toInt ( ) ;
2019-01-02 12:27:16 -05:00
CallDetail cd ;
2024-10-21 13:44:12 -07:00
cd . call = from ;
cd . snr = snr ;
cd . dial = dial ;
cd . offset = offset ;
cd . tdrift = tdrift ;
2019-01-02 12:27:16 -05:00
cd . utcTimestamp = QDateTime : : fromString ( utc , " yyyy-MM-dd hh:mm:ss " ) ;
2024-10-21 13:44:12 -07:00
cd . utcTimestamp . setTimeZone ( QTimeZone : : utc ( ) ) ;
2019-01-02 12:27:16 -05:00
cd . ackTimestamp = cd . utcTimestamp ;
2024-10-21 13:44:12 -07:00
cd . submode = submode ;
2019-01-02 12:27:16 -05:00
logCallActivity ( cd , false ) ;
}
}
}
}
2019-02-07 12:19:07 -05:00
bool MainWindow : : hasMessageHistory ( QString call ) {
auto inbox = Inbox ( inboxPath ( ) ) ;
if ( ! inbox . open ( ) ) {
return false ;
}
2019-02-16 23:20:28 -05:00
int store = inbox . count ( " STORE " , " $.params.TO " , call ) ;
2019-02-07 12:19:07 -05:00
int unread = inbox . count ( " UNREAD " , " $.params.FROM " , call ) ;
int read = inbox . count ( " READ " , " $.params.FROM " , call ) ;
2019-02-16 23:20:28 -05:00
return ( store + unread + read ) > 0 ;
2019-02-07 12:19:07 -05:00
}
2019-02-01 01:46:01 -05:00
int MainWindow : : addCommandToMyInbox ( CommandDetail d ) {
2019-01-23 20:31:26 -05:00
// local cache for inbox count
2019-01-02 12:27:16 -05:00
m_rxInboxCountCache [ d . from ] = m_rxInboxCountCache . value ( d . from , 0 ) + 1 ;
2019-01-23 20:31:26 -05:00
// add it to my unread inbox
2019-02-08 19:41:07 -05:00
return addCommandToStorage ( " UNREAD " , d ) ;
2019-01-23 20:31:26 -05:00
}
2019-02-08 19:41:07 -05:00
int MainWindow : : addCommandToStorage ( QString type , CommandDetail d ) {
2019-01-02 12:27:16 -05:00
// inbox:
auto inbox = Inbox ( inboxPath ( ) ) ;
2019-02-01 01:46:01 -05:00
if ( ! inbox . open ( ) ) {
return - 1 ;
}
2024-11-04 17:48:36 -08:00
QVariantMap v = {
2019-02-01 01:46:01 -05:00
{ " UTC " , QVariant ( d . utcTimestamp . toString ( " yyyy-MM-dd hh:mm:ss " ) ) } ,
{ " TO " , QVariant ( d . to ) } ,
{ " FROM " , QVariant ( d . from ) } ,
{ " PATH " , QVariant ( d . relayPath ) } ,
{ " TDRIFT " , QVariant ( d . tdrift ) } ,
2020-04-04 22:45:51 -04:00
{ " FREQ " , QVariant ( d . dial + d . offset ) } ,
{ " DIAL " , QVariant ( d . dial ) } ,
{ " OFFSET " , QVariant ( d . offset ) } ,
2019-02-01 01:46:01 -05:00
{ " CMD " , QVariant ( d . cmd ) } ,
{ " SNR " , QVariant ( d . snr ) } ,
2019-11-09 09:48:09 -05:00
{ " SUBMODE " , QVariant ( d . submode ) } ,
2019-02-01 01:46:01 -05:00
} ;
2019-01-02 12:27:16 -05:00
2019-02-01 01:46:01 -05:00
if ( ! d . grid . isEmpty ( ) ) {
v [ " GRID " ] = QVariant ( d . grid ) ;
}
2019-01-02 12:27:16 -05:00
2019-02-01 01:46:01 -05:00
if ( ! d . extra . isEmpty ( ) ) {
v [ " EXTRA " ] = QVariant ( d . extra ) ;
}
2019-01-02 12:27:16 -05:00
2019-02-01 01:46:01 -05:00
if ( ! d . text . isEmpty ( ) ) {
v [ " TEXT " ] = QVariant ( d . text ) ;
}
auto m = Message ( type , " " , v ) ;
return inbox . append ( m ) ;
}
int MainWindow : : getNextMessageIdForCallsign ( QString callsign ) {
auto inbox = Inbox ( inboxPath ( ) ) ;
if ( ! inbox . open ( ) ) {
return - 1 ;
}
2019-02-07 13:43:35 -05:00
auto v1 = inbox . values ( " STORE " , " $.params.TO " , callsign , 0 , 10 ) ;
foreach ( auto pair , v1 ) {
auto params = pair . second . params ( ) ;
auto text = params . value ( " TEXT " ) . toString ( ) . trimmed ( ) ;
if ( ! text . isEmpty ( ) ) {
return pair . first ;
}
}
auto v2 = inbox . values ( " STORE " , " $.params.TO " , Radio : : base_callsign ( callsign ) , 0 , 10 ) ;
foreach ( auto pair , v2 ) {
2019-02-01 01:46:01 -05:00
auto params = pair . second . params ( ) ;
auto text = params . value ( " TEXT " ) . toString ( ) . trimmed ( ) ;
if ( ! text . isEmpty ( ) ) {
return pair . first ;
2019-01-02 12:27:16 -05:00
}
2019-02-01 01:46:01 -05:00
}
return - 1 ;
}
2019-01-02 12:27:16 -05:00
2019-02-01 01:46:01 -05:00
QStringList MainWindow : : parseRelayPathCallsigns ( QString from , QString text ) {
QStringList calls ;
2019-10-18 12:47:09 -04:00
QString callDePattern = { R " ( \ s([*]DE[*]|VIA) \ s(?<callsign> \b (?<prefix>[A-Z0-9]{1,4} \ /)?(?<base>([0-9A-Z])?([0-9A-Z])([0-9])([A-Z])?([A-Z])?([A-Z])?)(?<suffix> \ /[A-Z0-9]{1,4})?) \b ) " } ;
2019-02-01 01:46:01 -05:00
QRegularExpression re ( callDePattern ) ;
auto iter = re . globalMatch ( text ) ;
while ( iter . hasNext ( ) ) {
auto match = iter . next ( ) ;
calls . prepend ( match . captured ( " callsign " ) ) ;
2018-10-03 21:32:00 -04:00
}
2019-02-01 01:46:01 -05:00
calls . prepend ( from ) ;
return calls ;
2018-10-03 21:32:00 -04:00
}
2018-09-08 23:36:12 -04:00
2018-07-31 14:34:36 -04:00
void MainWindow : : processSpots ( ) {
2019-08-26 12:08:35 -04:00
if ( ! m_config . spot_to_reporting_networks ( ) ) {
2018-07-31 23:26:01 -04:00
m_rxCallQueue . clear ( ) ;
return ;
}
if ( m_rxCallQueue . isEmpty ( ) ) {
return ;
}
2018-07-31 14:34:36 -04:00
// Is it ok to post spots to PSKReporter?
2024-09-08 06:29:38 -07:00
int nsec = DriftingDateTime : : currentSecsSinceEpoch ( ) - m_secBandChanged ;
2018-07-31 14:34:36 -04:00
bool okToPost = ( nsec > ( 4 * m_TRperiod ) / 5 ) ;
if ( ! okToPost ) {
return ;
}
2018-07-13 10:32:58 -04:00
2018-07-31 23:26:01 -04:00
while ( ! m_rxCallQueue . isEmpty ( ) ) {
CallDetail d = m_rxCallQueue . dequeue ( ) ;
if ( d . call . isEmpty ( ) ) {
continue ;
}
2019-01-31 14:04:44 -05:00
2020-04-10 20:27:34 -04:00
if ( m_config . spot_blacklist ( ) . contains ( d . call ) | | m_config . spot_blacklist ( ) . contains ( Radio : : base_callsign ( d . call ) ) ) {
continue ;
}
2020-04-04 22:45:51 -04:00
qDebug ( ) < < " spotting call to reporting networks " < < d . call < < d . snr < < d . dial < < d . offset ;
2019-01-31 14:04:44 -05:00
2020-04-04 22:45:51 -04:00
spotReport ( d . submode , d . dial , d . offset , d . snr , d . call , d . grid ) ;
pskLogReport ( " JS8 " , d . dial , d . offset , d . snr , d . call , d . grid ) ;
2019-01-31 14:04:44 -05:00
2019-12-27 20:08:39 -05:00
if ( canSendNetworkMessage ( ) ) {
sendNetworkMessage ( " RX.SPOT " , " " , {
2020-04-03 22:49:28 -04:00
{ " _ID " , QVariant ( - 1 ) } ,
2020-04-04 22:45:51 -04:00
{ " FREQ " , QVariant ( d . dial + d . offset ) } ,
{ " DIAL " , QVariant ( d . dial ) } ,
{ " OFFSET " , QVariant ( d . offset ) } ,
2019-12-27 20:08:39 -05:00
{ " CALL " , QVariant ( d . call ) } ,
{ " SNR " , QVariant ( d . snr ) } ,
{ " GRID " , QVariant ( d . grid ) } ,
} ) ;
}
2018-07-31 23:26:01 -04:00
}
2018-07-31 14:34:36 -04:00
}
2018-07-13 10:32:58 -04:00
2018-07-31 17:48:33 -04:00
void MainWindow : : processTxQueue ( ) {
2018-10-28 11:24:00 -04:00
# if IDLE_BLOCKS_TX
2018-09-23 16:41:06 -04:00
if ( m_tx_watchdog ) {
return ;
}
2018-10-28 11:24:00 -04:00
# endif
2018-09-23 16:41:06 -04:00
2018-07-31 17:48:33 -04:00
if ( m_txMessageQueue . isEmpty ( ) ) {
return ;
}
// grab the next message...
auto head = m_txMessageQueue . head ( ) ;
// decide if it's ok to transmit...
2020-04-04 22:45:51 -04:00
int f = head . offset ;
2018-08-08 17:15:49 -04:00
if ( f = = - 1 ) {
2024-10-20 13:54:14 -07:00
f = freq ( ) ;
2018-08-08 17:15:49 -04:00
}
2018-07-31 17:48:33 -04:00
// we need a valid frequency...
2018-09-11 18:54:32 -04:00
if ( f < = 0 ) {
2018-07-31 17:48:33 -04:00
return ;
}
// tx frame queue needs to be empty...
if ( ! m_txFrameQueue . isEmpty ( ) ) {
return ;
}
// our message box needs to be empty...
if ( ! ui - > extFreeTextMsgEdit - > toPlainText ( ) . isEmpty ( ) ) {
return ;
}
2018-07-31 18:18:27 -04:00
// and if we are a low priority message, we need to have not transmitted in the past 30 seconds...
2019-11-11 22:39:02 -05:00
if ( head . priority < = PriorityLow & & m_lastTxStartTime . secsTo ( DriftingDateTime : : currentDateTimeUtc ( ) ) < = 30 ) {
2018-07-31 17:48:33 -04:00
return ;
}
2018-07-31 18:18:27 -04:00
// if so... dequeue the next message from the queue...
2018-07-31 17:48:33 -04:00
auto message = m_txMessageQueue . dequeue ( ) ;
// add the message to the outgoing message text box
addMessageText ( message . message , true ) ;
2018-10-28 09:47:30 -04:00
// check to see if this is a high priority message, or if we have autoreply enabled, or if this is a ping and the ping button is enabled
2020-03-31 20:52:38 -04:00
if ( message . priority > = PriorityHigh | |
message . message . contains ( " HEARTBEAT " ) | |
message . message . contains ( " HB " ) | |
message . message . contains ( " ACK " ) | |
2019-08-26 12:08:35 -04:00
ui - > actionModeAutoreply - > isChecked ( )
2018-08-06 23:39:43 -04:00
) {
2018-08-06 14:43:32 -04:00
// then try to set the frequency...
2018-08-08 17:15:49 -04:00
setFreqOffsetForRestore ( f , true ) ;
2018-08-06 14:43:32 -04:00
2018-07-31 18:18:27 -04:00
// then prepare to transmit...
toggleTx ( true ) ;
}
2018-07-31 17:48:33 -04:00
if ( message . callback ) {
message . callback ( ) ;
}
}
2018-07-31 14:34:36 -04:00
void MainWindow : : displayActivity ( bool force ) {
2018-07-31 17:48:33 -04:00
if ( ! m_rxDisplayDirty & & ! force ) {
2018-07-31 14:34:36 -04:00
return ;
}
2018-07-19 02:09:19 -04:00
2018-07-31 14:34:36 -04:00
// Band Activity
displayBandActivity ( ) ;
2018-07-27 01:36:55 -04:00
2018-07-31 14:34:36 -04:00
// Call Activity
displayCallActivity ( ) ;
2018-07-31 17:48:33 -04:00
m_rxDisplayDirty = false ;
2018-07-31 14:34:36 -04:00
}
2018-07-19 23:14:11 -04:00
2018-09-29 17:04:05 -04:00
// updateBandActivity
2018-07-31 14:34:36 -04:00
void MainWindow : : displayBandActivity ( ) {
2018-09-18 17:24:07 -04:00
auto now = DriftingDateTime : : currentDateTimeUtc ( ) ;
2018-07-24 15:19:02 -04:00
2018-09-15 17:33:09 -04:00
ui - > tableWidgetRXAll - > setFont ( m_config . table_font ( ) ) ;
2018-07-31 14:34:36 -04:00
// Selected Offset
int selectedOffset = - 1 ;
auto selectedItems = ui - > tableWidgetRXAll - > selectedItems ( ) ;
if ( ! selectedItems . isEmpty ( ) ) {
2018-12-23 21:06:54 -05:00
selectedOffset = selectedItems . first ( ) - > data ( Qt : : UserRole ) . toInt ( ) ;
2018-07-31 14:34:36 -04:00
}
2018-07-25 22:49:19 -04:00
2018-08-15 23:24:16 -04:00
ui - > tableWidgetRXAll - > setUpdatesEnabled ( false ) ;
{
// Scroll Position
2024-09-28 06:40:44 -07:00
auto const currentScrollPos = ui - > tableWidgetRXAll - > verticalScrollBar ( ) - > value ( ) ;
2018-07-26 15:57:19 -04:00
2018-08-15 23:24:16 -04:00
// Clear the table
2024-09-28 06:40:44 -07:00
ui - > tableWidgetRXAll - > setRowCount ( 0 ) ;
2018-07-26 15:57:19 -04:00
2018-08-23 11:59:20 -04:00
// Sort!
2024-10-23 09:26:25 -07:00
auto const sort = getSortByReverse ( " bandActivity " , " offset " ) ;
auto keys = m_bandActivity . keys ( ) ;
2018-09-12 09:04:22 -04:00
2024-10-23 15:15:26 -07:00
// Base comparison, called by the detail comparisons. We may not need
// to proceed to the detail comparison at all here, if at least one of
// the lists is empty. If both are non-empty, delegate to the detail
// comparison, providing it with the last list elements.
2024-10-23 10:50:53 -07:00
auto const compare = [ this ] ( int const lhsKey ,
int const rhsKey ,
auto & & detail )
2024-10-23 09:12:48 -07:00
{
2024-10-23 09:47:19 -07:00
auto const & lhs = m_bandActivity [ lhsKey ] ;
auto const & rhs = m_bandActivity [ rhsKey ] ;
2018-08-23 11:59:20 -04:00
2024-10-23 09:12:48 -07:00
if ( lhs . isEmpty ( ) ) return false ;
if ( rhs . isEmpty ( ) ) return true ;
2018-08-23 11:59:20 -04:00
2024-10-23 10:50:53 -07:00
return detail ( lhs . last ( ) ,
rhs . last ( ) ) ;
2018-08-23 11:59:20 -04:00
} ;
2024-10-23 15:15:26 -07:00
// Time stamp comparison, easy stuff, just a total ordering on the
// UTC time stamp field.
2024-10-23 15:03:41 -07:00
auto const compareTimestamp = [ compare ] ( int const lhsKey ,
int const rhsKey )
2024-10-23 09:12:48 -07:00
{
2024-10-23 10:50:53 -07:00
return compare ( lhsKey ,
rhsKey ,
[ ] ( auto & & lhs ,
auto & & rhs )
{
return lhs . utcTimestamp <
rhs . utcTimestamp ;
} ) ;
} ;
2018-08-23 11:59:20 -04:00
2024-10-23 15:15:26 -07:00
// SNR comparison; we always want insane SNR values to be at the end
// of the list and the list is going to be reversed if reverse is set,
// so we want to set things up so that insane elements are either all
// at the beginning in the case of a reverse, or all at the end in the
// standard case. Reverse takes care of itself; we just need to sort
// out standard.
2024-10-23 15:03:41 -07:00
auto const compareSNR = [ compare ,
reverse = sort . reverse ] ( int const lhsKey ,
int const rhsKey )
2024-10-23 10:50:53 -07:00
{
return compare ( lhsKey ,
rhsKey ,
[ reverse ] ( auto & & lhs ,
auto & & rhs )
{
auto lhsSNR = lhs . snr ;
auto rhsSNR = rhs . snr ;
2018-08-23 11:59:20 -04:00
2024-10-23 10:50:53 -07:00
if ( ! reverse )
{
if ( lhsSNR < - 60 | | lhsSNR > 60 ) lhsSNR = - lhsSNR ;
if ( rhsSNR < - 60 | | rhsSNR > 60 ) rhsSNR = - rhsSNR ;
}
2018-08-23 11:59:20 -04:00
2024-10-23 10:50:53 -07:00
return lhsSNR < rhsSNR ;
} ) ;
2018-09-12 09:04:22 -04:00
} ;
2018-08-23 11:59:20 -04:00
2024-10-23 15:15:26 -07:00
// Submode comparison; slow mode isn't at the start of the enumeration;
// it's in the middle of it. All the other modes are in the expected order.
2024-10-23 15:03:41 -07:00
auto const compareSubmode = [ compare ] ( int const lhsKey ,
int const rhsKey )
2024-10-23 09:12:48 -07:00
{
2024-10-23 10:50:53 -07:00
return compare ( lhsKey ,
rhsKey ,
[ ] ( auto & & lhs ,
auto & & rhs )
{
auto lhsSubmode = lhs . submode ;
auto rhsSubmode = rhs . submode ;
2019-11-10 13:47:17 -05:00
2024-10-23 10:50:53 -07:00
if ( lhsSubmode = = Varicode : : JS8CallSlow ) lhsSubmode = - lhsSubmode ;
if ( rhsSubmode = = Varicode : : JS8CallSlow ) rhsSubmode = - rhsSubmode ;
2019-12-23 08:25:25 -05:00
2024-10-23 10:50:53 -07:00
return lhsSubmode < rhsSubmode ;
} ) ;
2019-11-10 13:47:17 -05:00
} ;
2024-10-23 16:19:55 -07:00
// Always perform an initial sort by offset.
2024-08-31 09:40:28 -07:00
std : : stable_sort ( keys . begin ( ) , keys . end ( ) ) ;
2018-09-09 14:22:21 -04:00
2024-10-23 16:19:55 -07:00
// If something other than offset was requested as the sort by, perform an
// additional stable sort by the field requested.
2024-10-23 09:26:25 -07:00
if ( sort . by = = " timestamp " ) std : : stable_sort ( keys . begin ( ) , keys . end ( ) , compareTimestamp ) ;
else if ( sort . by = = " snr " ) std : : stable_sort ( keys . begin ( ) , keys . end ( ) , compareSNR ) ;
else if ( sort . by = = " submode " ) std : : stable_sort ( keys . begin ( ) , keys . end ( ) , compareSubmode ) ;
2018-08-23 11:59:20 -04:00
2024-10-23 16:19:55 -07:00
// The sort comparators leave things in forward order. If a reverse sort
// was requested, reverse the keys.
2024-10-23 09:26:25 -07:00
if ( sort . reverse ) std : : reverse ( keys . begin ( ) , keys . end ( ) ) ;
2018-08-15 23:24:16 -04:00
// Build the table
foreach ( int offset , keys ) {
2018-10-26 22:09:38 -04:00
bool isOffsetSelected = ( offset = = selectedOffset ) ;
2018-08-15 23:24:16 -04:00
QList < ActivityDetail > items = m_bandActivity [ offset ] ;
if ( items . length ( ) > 0 ) {
2019-02-25 09:57:01 -05:00
QDateTime timestamp ;
2018-08-15 23:24:16 -04:00
QStringList text ;
QString age ;
int snr = 0 ;
2018-10-28 17:54:10 -04:00
float tdrift = 0 ;
2019-11-14 10:06:31 -05:00
int submode = - 1 ;
2019-09-05 14:07:24 -04:00
2018-08-15 23:24:16 -04:00
int activityAging = m_config . activity_aging ( ) ;
2018-10-26 22:09:38 -04:00
2019-02-10 10:59:50 -05:00
// hide items that shouldn't appear
2019-09-05 14:27:44 -04:00
2019-02-10 10:59:50 -05:00
for ( int i = 0 ; i < items . length ( ) ; i + + ) {
auto item = items [ i ] ;
bool shouldDisplay = true ;
// hide aged items
2018-10-26 22:09:38 -04:00
if ( ! isOffsetSelected & & activityAging & & item . utcTimestamp . secsTo ( now ) / 60 > = activityAging ) {
2019-02-10 10:59:50 -05:00
shouldDisplay = false ;
2018-08-15 23:24:16 -04:00
}
2018-11-27 23:21:16 -05:00
2019-02-10 10:59:50 -05:00
// hide heartbeat items
2019-09-05 14:07:24 -04:00
if ( ! ui - > actionShow_Band_Heartbeats_and_ACKs - > isChecked ( ) ) {
2018-11-29 22:34:39 -05:00
// hide heartbeats and acks if we have heartbeating hidden
2020-05-03 22:25:51 -04:00
if ( item . text . contains ( " @HB " ) | | item . text . contains ( " HEARTBEAT " ) ) {
2019-09-05 14:27:44 -04:00
shouldDisplay = false ;
2019-10-01 21:27:13 -04:00
// hide the previous item if this it shouldn't be displayed either...
if ( i > 0 & & items [ i - 1 ] . shouldDisplay & & items [ i - 1 ] . text . endsWith ( " : " ) ) {
items [ i - 1 ] . shouldDisplay = false ;
}
2019-09-05 14:27:44 -04:00
}
2019-10-01 21:27:13 -04:00
// if our previous item should not be displayed (or this is the first frame) and we have a MSG ID, then don't display it either.
2019-09-07 22:15:08 -04:00
if (
( i = = 0 | | ( i > 0 & & ! items [ i - 1 ] . shouldDisplay ) ) & &
( item . text . contains ( " MSG ID " ) )
) {
2019-02-10 10:59:50 -05:00
shouldDisplay = false ;
}
2018-10-09 15:26:13 -04:00
}
2018-11-27 23:21:16 -05:00
2019-02-10 10:59:50 -05:00
// hide empty items
2018-08-15 23:24:16 -04:00
if ( item . text . isEmpty ( ) ) {
2019-02-10 10:59:50 -05:00
shouldDisplay = false ;
2018-08-15 23:24:16 -04:00
}
2019-02-10 10:59:50 -05:00
// set the visibility of the item
items [ i ] . shouldDisplay = shouldDisplay ;
}
// show the items that should appear
foreach ( ActivityDetail item , items ) {
if ( ! item . shouldDisplay ) {
continue ;
2018-08-15 23:24:16 -04:00
}
2019-02-10 10:59:50 -05:00
2018-08-15 23:24:16 -04:00
if ( item . isLowConfidence ) {
item . text = QString ( " [%1] " ) . arg ( item . text ) ;
}
2019-02-10 10:59:50 -05:00
2018-10-04 13:52:52 -04:00
if ( ( item . bits & Varicode : : JS8CallLast ) = = Varicode : : JS8CallLast ) {
2019-04-21 11:29:31 -04:00
// append the eot character to the text
item . text = QString ( " %1 %2 " ) . arg ( Varicode : : rstrip ( item . text ) ) . arg ( m_config . eot ( ) ) ;
2018-08-15 23:24:16 -04:00
}
text . append ( item . text ) ;
snr = item . snr ;
age = since ( item . utcTimestamp ) ;
2019-02-25 09:57:01 -05:00
timestamp = item . utcTimestamp ;
2018-10-28 17:54:10 -04:00
tdrift = item . tdrift ;
2019-11-09 09:48:09 -05:00
submode = item . submode ;
2018-07-31 14:34:36 -04:00
}
2018-08-15 23:24:16 -04:00
2019-01-28 20:31:11 -05:00
auto joined = Varicode : : rstrip ( text . join ( " " ) ) ;
2018-08-15 23:24:16 -04:00
if ( joined . isEmpty ( ) ) {
2018-07-31 14:34:36 -04:00
continue ;
}
2018-08-15 23:24:16 -04:00
ui - > tableWidgetRXAll - > insertRow ( ui - > tableWidgetRXAll - > rowCount ( ) ) ;
2018-08-30 16:13:43 -04:00
int row = ui - > tableWidgetRXAll - > rowCount ( ) - 1 ;
2018-10-28 17:54:10 -04:00
int col = 0 ;
2018-08-15 23:24:16 -04:00
2018-12-23 09:37:40 -05:00
auto offsetItem = new QTableWidgetItem ( QString ( " %1 Hz " ) . arg ( offset ) ) ;
2018-08-15 23:24:16 -04:00
offsetItem - > setData ( Qt : : UserRole , QVariant ( offset ) ) ;
2024-08-31 10:49:10 -07:00
offsetItem - > setTextAlignment ( Qt : : AlignRight | Qt : : AlignVCenter ) ;
2018-10-28 17:54:10 -04:00
ui - > tableWidgetRXAll - > setItem ( row , col + + , offsetItem ) ;
2018-08-15 23:24:16 -04:00
2019-02-25 09:57:01 -05:00
auto ageItem = new QTableWidgetItem ( age ) ;
2024-08-31 13:13:39 -07:00
ageItem - > setTextAlignment ( Qt : : AlignCenter ) ;
2019-02-25 09:57:01 -05:00
ageItem - > setToolTip ( timestamp . toString ( ) ) ;
2018-10-28 17:54:10 -04:00
ui - > tableWidgetRXAll - > setItem ( row , col + + , ageItem ) ;
2018-08-15 23:24:16 -04:00
2019-02-23 22:49:02 -05:00
auto snrText = Varicode : : formatSNR ( snr ) ;
auto snrItem = new QTableWidgetItem ( snrText . isEmpty ( ) ? " " : QString ( " %1 dB " ) . arg ( snrText ) ) ;
2024-08-31 10:49:10 -07:00
snrItem - > setTextAlignment ( Qt : : AlignRight | Qt : : AlignVCenter ) ;
2018-10-28 17:54:10 -04:00
ui - > tableWidgetRXAll - > setItem ( row , col + + , snrItem ) ;
2018-10-29 02:02:58 -04:00
auto tdriftItem = new QTableWidgetItem ( QString ( " %1 ms " ) . arg ( ( int ) ( 1000 * tdrift ) ) ) ;
2018-10-28 17:54:10 -04:00
tdriftItem - > setData ( Qt : : UserRole , QVariant ( tdrift ) ) ;
2024-08-31 10:49:10 -07:00
tdriftItem - > setTextAlignment ( Qt : : AlignRight | Qt : : AlignVCenter ) ;
2018-10-28 17:54:10 -04:00
ui - > tableWidgetRXAll - > setItem ( row , col + + , tdriftItem ) ;
2018-08-15 23:24:16 -04:00
2024-10-08 06:14:21 -07:00
auto name = JS8 : : Submode : : name ( submode ) ;
2019-11-09 09:48:09 -05:00
auto submodeItem = new QTableWidgetItem ( name . left ( 1 ) . replace ( " H " , " N " ) ) ;
submodeItem - > setToolTip ( name ) ;
submodeItem - > setData ( Qt : : UserRole , QVariant ( name ) ) ;
2024-08-31 10:49:10 -07:00
submodeItem - > setTextAlignment ( Qt : : AlignCenter ) ;
2019-11-09 09:48:09 -05:00
ui - > tableWidgetRXAll - > setItem ( row , col + + , submodeItem ) ;
2019-09-05 14:07:24 -04:00
2018-08-15 23:24:16 -04:00
// align right if eliding...
int colWidth = ui - > tableWidgetRXAll - > columnWidth ( 3 ) ;
auto textItem = new QTableWidgetItem ( joined ) ;
2019-01-28 20:31:11 -05:00
auto html = QString ( " <qt/>%1 " ) . arg ( joined . toHtmlEscaped ( ) ) ;
2019-04-21 11:29:31 -04:00
html = html . replace ( m_config . eot ( ) , m_config . eot ( ) + " <br/><br/> " ) ;
2019-01-28 20:31:11 -05:00
html = html . replace ( QRegularExpression ( " ([<]br[/][>])+$ " ) , " " ) ;
textItem - > setToolTip ( html ) ;
2019-01-09 11:31:15 -05:00
2018-08-15 23:24:16 -04:00
QFontMetrics fm ( textItem - > font ( ) ) ;
auto elidedText = fm . elidedText ( joined , Qt : : ElideLeft , colWidth ) ;
auto flag = Qt : : AlignLeft | Qt : : AlignVCenter ;
if ( elidedText ! = joined ) {
flag = Qt : : AlignRight | Qt : : AlignVCenter ;
textItem - > setText ( joined ) ;
2018-08-01 22:36:50 -04:00
}
2018-08-15 23:24:16 -04:00
textItem - > setTextAlignment ( flag ) ;
2019-07-04 01:32:01 -04:00
ui - > tableWidgetRXAll - > setItem ( row , col + + , textItem ) ;
if ( isOffsetSelected ) {
for ( int i = 0 ; i < ui - > tableWidgetRXAll - > columnCount ( ) ; i + + ) {
ui - > tableWidgetRXAll - > item ( row , i ) - > setSelected ( true ) ;
}
2018-07-31 14:34:36 -04:00
}
2018-08-15 23:24:16 -04:00
2018-08-16 11:31:10 -04:00
bool isDirectedAllCall = false ;
2019-07-04 01:32:01 -04:00
if (
2019-02-16 11:47:54 -05:00
( isDirectedOffset ( offset , & isDirectedAllCall ) & & ! isDirectedAllCall ) | | isMyCallIncluded ( text . last ( ) )
2019-07-04 01:32:01 -04:00
) {
for ( int i = 0 ; i < ui - > tableWidgetRXAll - > columnCount ( ) ; i + + ) {
ui - > tableWidgetRXAll - > item ( row , i ) - > setBackground ( QBrush ( m_config . color_MyCall ( ) ) ) ;
}
2018-07-31 14:34:36 -04:00
}
2018-07-26 15:57:19 -04:00
2019-07-09 13:58:28 -04:00
if ( ! text . isEmpty ( ) ) {
2024-10-23 05:16:03 -07:00
auto const list = joined . split ( QRegularExpression ( " [:> ] " ) , Qt : : SkipEmptyParts ) ;
2024-08-31 09:40:28 -07:00
QSet < QString > words ( list . begin ( ) , list . end ( ) ) ;
2019-07-09 13:58:28 -04:00
2019-07-11 20:29:32 -04:00
if ( words . contains ( " CQ " ) ) {
for ( int i = 0 ; i < ui - > tableWidgetRXAll - > columnCount ( ) ; i + + ) {
ui - > tableWidgetRXAll - > item ( row , i ) - > setBackground ( QBrush ( m_config . color_CQ ( ) ) ) ;
}
}
2019-07-09 13:58:28 -04:00
auto matchingSecondaryWords = m_config . secondary_highlight_words ( ) & words ;
if ( ! matchingSecondaryWords . isEmpty ( ) ) {
for ( int i = 0 ; i < ui - > tableWidgetRXAll - > columnCount ( ) ; i + + ) {
ui - > tableWidgetRXAll - > item ( row , i ) - > setBackground ( QBrush ( m_config . color_secondary_highlight ( ) ) ) ;
}
}
auto matchingPrimaryWords = m_config . primary_highlight_words ( ) & words ;
if ( ! matchingPrimaryWords . isEmpty ( ) ) {
for ( int i = 0 ; i < ui - > tableWidgetRXAll - > columnCount ( ) ; i + + ) {
ui - > tableWidgetRXAll - > item ( row , i ) - > setBackground ( QBrush ( m_config . color_primary_highlight ( ) ) ) ;
}
2018-08-30 16:13:43 -04:00
}
2018-08-15 23:24:16 -04:00
}
2018-07-31 14:34:36 -04:00
}
2018-08-15 23:24:16 -04:00
}
2018-07-26 15:57:19 -04:00
2018-09-15 17:33:09 -04:00
// Set table color
2019-12-27 22:50:45 -05:00
auto style = QString ( " QTableWidget { background:%1; selection-background-color:%2; alternate-background-color:%1; color:%3; } "
2019-12-28 22:24:24 -05:00
" QTableWidget::item:selected { background-color: %2; color: %3; } " ) ;
2018-09-15 17:33:09 -04:00
style = style . arg ( m_config . color_table_background ( ) . name ( ) ) ;
style = style . arg ( m_config . color_table_highlight ( ) . name ( ) ) ;
style = style . arg ( m_config . color_table_foreground ( ) . name ( ) ) ;
ui - > tableWidgetRXAll - > setStyleSheet ( style ) ;
2018-12-23 22:31:22 -05:00
// Set the table palette for inactive selected row
auto p = ui - > tableWidgetRXAll - > palette ( ) ;
2019-12-27 22:50:45 -05:00
2019-12-27 22:31:02 -05:00
p . setColor ( QPalette : : Highlight , m_config . color_table_highlight ( ) ) ;
p . setColor ( QPalette : : HighlightedText , m_config . color_table_foreground ( ) ) ;
2018-12-23 22:31:22 -05:00
p . setColor ( QPalette : : Inactive , QPalette : : Highlight , p . color ( QPalette : : Active , QPalette : : Highlight ) ) ;
ui - > tableWidgetRXAll - > setPalette ( p ) ;
2018-09-15 17:33:09 -04:00
// Set item fonts
for ( int row = 0 ; row < ui - > tableWidgetRXAll - > rowCount ( ) ; row + + ) {
for ( int col = 0 ; col < ui - > tableWidgetRXAll - > columnCount ( ) ; col + + ) {
auto item = ui - > tableWidgetRXAll - > item ( row , col ) ;
if ( item ) {
item - > setFont ( m_config . table_font ( ) ) ;
}
}
}
2018-10-28 17:54:10 -04:00
// Column labels
ui - > tableWidgetRXAll - > horizontalHeader ( ) - > setVisible ( showColumn ( " band " , " labels " ) ) ;
2018-09-24 16:46:33 -04:00
// Hide columns
ui - > tableWidgetRXAll - > setColumnHidden ( 0 , ! showColumn ( " band " , " offset " ) ) ;
ui - > tableWidgetRXAll - > setColumnHidden ( 1 , ! showColumn ( " band " , " timestamp " ) ) ;
ui - > tableWidgetRXAll - > setColumnHidden ( 2 , ! showColumn ( " band " , " snr " ) ) ;
2018-11-03 01:59:21 -04:00
ui - > tableWidgetRXAll - > setColumnHidden ( 3 , ! showColumn ( " band " , " tdrift " , false ) ) ;
2019-11-10 13:47:17 -05:00
ui - > tableWidgetRXAll - > setColumnHidden ( 4 , ! showColumn ( " band " , " submode " , false ) ) ;
2018-09-24 16:46:33 -04:00
2018-08-15 23:24:16 -04:00
// Resize the table columns
ui - > tableWidgetRXAll - > resizeColumnToContents ( 0 ) ;
ui - > tableWidgetRXAll - > resizeColumnToContents ( 1 ) ;
ui - > tableWidgetRXAll - > resizeColumnToContents ( 2 ) ;
2018-10-28 17:54:10 -04:00
ui - > tableWidgetRXAll - > resizeColumnToContents ( 3 ) ;
2019-09-05 14:07:24 -04:00
ui - > tableWidgetRXAll - > resizeColumnToContents ( 4 ) ;
2018-07-26 15:57:19 -04:00
2018-08-15 23:24:16 -04:00
// Reset the scroll position
ui - > tableWidgetRXAll - > verticalScrollBar ( ) - > setValue ( currentScrollPos ) ;
2018-07-31 14:34:36 -04:00
}
2018-08-15 23:24:16 -04:00
ui - > tableWidgetRXAll - > setUpdatesEnabled ( true ) ;
2018-07-31 14:34:36 -04:00
}
2018-07-19 02:09:19 -04:00
2018-09-29 17:04:05 -04:00
// updateCallActivity
2018-07-31 14:34:36 -04:00
void MainWindow : : displayCallActivity ( ) {
2018-09-18 17:24:07 -04:00
auto now = DriftingDateTime : : currentDateTimeUtc ( ) ;
2018-07-13 10:32:58 -04:00
2018-12-21 22:20:52 -05:00
ui - > tableWidgetCalls - > setFont ( m_config . table_font ( ) ) ;
2018-07-31 14:34:36 -04:00
// Selected callsign
QString selectedCall = callsignSelected ( ) ;
2018-07-13 10:32:58 -04:00
2018-08-30 16:13:43 -04:00
auto currentScrollPos = ui - > tableWidgetCalls - > verticalScrollBar ( ) - > value ( ) ;
2018-08-15 23:24:16 -04:00
ui - > tableWidgetCalls - > setUpdatesEnabled ( false ) ;
{
// Clear the table
2024-09-28 06:40:44 -07:00
ui - > tableWidgetCalls - > setRowCount ( 0 ) ;
2024-11-17 09:09:17 -08:00
ui - > tableWidgetCalls - > horizontalHeaderItem ( 8 ) - > setText ( m_config . miles ( ) ? " mi " : " km " ) ;
2019-12-29 14:47:21 -05:00
createGroupCallsignTableRows ( ui - > tableWidgetCalls , selectedCall ) ; // isAllCallIncluded(selectedCall)); // || isGroupCallIncluded(selectedCall));
2018-07-13 15:45:00 -04:00
2018-08-15 23:24:16 -04:00
// Build the table
2018-08-23 11:59:20 -04:00
2024-10-23 09:26:25 -07:00
auto const sort = getSortByReverse ( " callActivity " , " callsign " ) ;
auto keys = m_callActivity . keys ( ) ;
2018-09-12 09:04:22 -04:00
2024-10-23 07:35:25 -07:00
auto const compareOffset = [ this ] ( QString const & lhsKey ,
QString const & rhsKey )
{
return m_callActivity [ lhsKey ] . offset <
m_callActivity [ rhsKey ] . offset ;
2018-08-28 16:35:14 -04:00
} ;
2024-11-17 05:34:54 -08:00
auto const compareAzimuth = [ this ,
reverse = sort . reverse ,
2024-11-17 07:37:39 -08:00
my_grid = m_config . my_grid ( ) ] ( QString const & lhsKey ,
QString const & rhsKey )
2024-11-17 05:34:54 -08:00
{
2024-11-17 14:26:06 -08:00
auto const lhs = Geodesic : : vector ( my_grid , m_callActivity [ lhsKey ] . grid ) . azimuth ( ) ;
auto const rhs = Geodesic : : vector ( my_grid , m_callActivity [ rhsKey ] . grid ) . azimuth ( ) ;
2024-11-17 05:34:54 -08:00
// We always want invalid azimuths to be at the end of the list,
// and the list is going to be reversed if reverse is set, so we
// want to set things up so that invalid elements are either all
// at the beginning in the case of a reverse, or all at the end
// in the standard case.
if ( ! lhs ) return reverse & & rhs ;
else if ( ! rhs ) return ! reverse ;
2024-11-17 07:37:39 -08:00
else return lhs < rhs ;
2024-11-17 05:34:54 -08:00
} ;
2024-10-23 07:35:25 -07:00
auto const compareDistance = [ this ,
2024-10-23 09:26:25 -07:00
reverse = sort . reverse ,
2024-11-17 07:37:39 -08:00
my_grid = m_config . my_grid ( ) ] ( QString const & lhsKey ,
QString const & rhsKey )
2024-10-17 14:15:25 -07:00
{
2024-11-17 14:26:06 -08:00
auto const lhs = Geodesic : : vector ( my_grid , m_callActivity [ lhsKey ] . grid ) . distance ( ) ;
auto const rhs = Geodesic : : vector ( my_grid , m_callActivity [ rhsKey ] . grid ) . distance ( ) ;
2024-10-17 14:15:25 -07:00
// We always want invalid distances to be at the end of the list,
2024-10-23 07:35:25 -07:00
// and the list is going to be reversed if reverse is set, so we
// want to set things up so that invalid elements are either all
// at the beginning in the case of a reverse, or all at the end
// in the standard case.
2018-08-23 11:59:20 -04:00
2024-10-17 14:15:25 -07:00
if ( ! lhs ) return reverse & & rhs ;
else if ( ! rhs ) return ! reverse ;
2024-11-17 06:53:08 -08:00
else return lhs < rhs ;
2018-08-23 11:59:20 -04:00
} ;
2024-10-23 07:35:25 -07:00
auto const compareTimestamp = [ this ] ( QString const & lhsKey ,
QString const & rhsKey )
{
return m_callActivity [ lhsKey ] . utcTimestamp <
m_callActivity [ rhsKey ] . utcTimestamp ;
2018-08-23 11:59:20 -04:00
} ;
2024-10-23 07:35:25 -07:00
auto const compareAckTimestamp = [ this ] ( QString const & lhsKey ,
QString const & rhsKey )
{
return m_callActivity [ rhsKey ] . ackTimestamp <
m_callActivity [ lhsKey ] . ackTimestamp ;
2018-09-29 17:30:27 -04:00
} ;
2024-10-23 09:26:25 -07:00
auto const compareSNR = [ this ,
reverse = sort . reverse ] ( QString const & lhsKey ,
QString const & rhsKey )
2024-10-23 07:35:25 -07:00
{
auto lhs = m_callActivity [ lhsKey ] . snr ;
auto rhs = m_callActivity [ rhsKey ] . snr ;
2018-08-23 11:59:20 -04:00
2024-10-23 07:35:25 -07:00
// We always want insane SNR values to be at the end of the list,
// and the list is going to be reversed if reverse is set, so we
// want to set things up so that insane elements are either all
// at the beginning in the case of a reverse, or all at the end
2024-10-23 07:53:08 -07:00
// in the standard case. Reverse takes care of itself; we just
// need to sort out standard.
2018-09-12 09:04:22 -04:00
2024-10-23 07:53:08 -07:00
if ( ! reverse )
{
if ( lhs < - 60 | | lhs > 60 ) lhs = - lhs ;
if ( rhs < - 60 | | rhs > 60 ) rhs = - rhs ;
}
2024-10-23 07:35:25 -07:00
return lhs < rhs ;
2018-08-23 11:59:20 -04:00
} ;
2024-10-23 07:35:25 -07:00
auto const compareSubmode = [ this ] ( QString const & lhsKey ,
QString const & rhsKey )
{
auto lhs = m_callActivity [ lhsKey ] . submode ;
auto rhs = m_callActivity [ rhsKey ] . submode ;
2019-11-10 13:47:17 -05:00
2024-10-23 07:35:25 -07:00
// Slow mode isn't at the start of the enumeration; it's in the
// middle of it. All the other modes are in the expected order.
2019-12-23 08:25:25 -05:00
2024-10-23 07:35:25 -07:00
if ( lhs = = Varicode : : JS8CallSlow ) lhs = - lhs ;
if ( rhs = = Varicode : : JS8CallSlow ) rhs = - rhs ;
2019-12-23 08:25:25 -05:00
2024-10-23 07:35:25 -07:00
return lhs < rhs ;
2019-11-10 13:47:17 -05:00
} ;
2024-10-23 16:19:55 -07:00
// Always perform an initial sort by callsign.
2024-08-31 09:40:28 -07:00
std : : stable_sort ( keys . begin ( ) , keys . end ( ) ) ;
2018-09-09 14:22:21 -04:00
2024-10-23 16:19:55 -07:00
// If something other than callsign was requested as the sort by, perform an
// additional stable sort by the field requested.
2024-10-23 09:26:25 -07:00
if ( sort . by = = " offset " ) std : : stable_sort ( keys . begin ( ) , keys . end ( ) , compareOffset ) ;
else if ( sort . by = = " distance " ) std : : stable_sort ( keys . begin ( ) , keys . end ( ) , compareDistance ) ;
2024-11-17 05:34:54 -08:00
else if ( sort . by = = " azimuth " ) std : : stable_sort ( keys . begin ( ) , keys . end ( ) , compareAzimuth ) ;
2024-10-23 09:26:25 -07:00
else if ( sort . by = = " timestamp " ) std : : stable_sort ( keys . begin ( ) , keys . end ( ) , compareTimestamp ) ;
else if ( sort . by = = " ackTimestamp " ) std : : stable_sort ( keys . begin ( ) , keys . end ( ) , compareAckTimestamp ) ;
else if ( sort . by = = " snr " ) std : : stable_sort ( keys . begin ( ) , keys . end ( ) , compareSNR ) ;
else if ( sort . by = = " submode " ) std : : stable_sort ( keys . begin ( ) , keys . end ( ) , compareSubmode ) ;
2018-08-23 11:59:20 -04:00
2024-10-23 16:19:55 -07:00
// The sort comparators leave things in forward order. If a reverse sort
// was requested, reverse the keys.
2024-10-23 09:26:25 -07:00
if ( sort . reverse ) std : : reverse ( keys . begin ( ) , keys . end ( ) ) ;
2018-08-23 11:59:20 -04:00
2018-12-18 21:28:31 -05:00
// pin messages to the top
2024-10-23 07:35:25 -07:00
std : : stable_sort ( keys . begin ( ) , keys . end ( ) , [ this ] ( QString const & lhsKey ,
QString const & rhsKey )
{
auto const lhs = ( int ) ! ( m_rxInboxCountCache . value ( lhsKey , 0 ) > 0 ) ;
auto const rhs = ( int ) ! ( m_rxInboxCountCache . value ( rhsKey , 0 ) > 0 ) ;
2018-12-18 21:28:31 -05:00
2024-10-23 07:35:25 -07:00
return lhs < rhs ;
2018-12-18 21:28:31 -05:00
} ) ;
bool showIconColumn = false ;
2018-08-15 23:24:16 -04:00
int callsignAging = m_config . callsign_aging ( ) ;
2018-08-23 11:59:20 -04:00
foreach ( QString call , keys ) {
2018-11-04 14:57:44 -05:00
if ( call . trimmed ( ) . isEmpty ( ) ) {
continue ;
}
2018-08-15 23:24:16 -04:00
CallDetail d = m_callActivity [ call ] ;
2018-11-19 22:36:28 -05:00
if ( d . call . trimmed ( ) . isEmpty ( ) ) {
continue ;
}
2018-08-15 23:24:16 -04:00
2018-10-26 22:09:38 -04:00
bool isCallSelected = ( call = = selectedCall ) ;
2019-01-02 12:27:16 -05:00
// icon flags (flag -> star -> empty)
bool hasMessage = m_rxInboxCountCache . value ( d . call , 0 ) > 0 ;
2019-03-23 21:29:26 -04:00
// display telephone icon if called cq in the past 5 minutes
bool hasCQ = d . cqTimestamp . isValid ( ) & & d . cqTimestamp . secsTo ( now ) / 60 < 5 ;
// display star if they've acked a message from us
2019-03-23 21:19:24 -04:00
bool hasACK = d . ackTimestamp . isValid ( ) ;
2019-01-02 12:27:16 -05:00
if ( ! isCallSelected & & ! hasMessage & & callsignAging & & d . utcTimestamp . secsTo ( now ) / 60 > = callsignAging ) {
2018-08-15 23:24:16 -04:00
continue ;
}
2018-07-31 14:34:36 -04:00
2018-08-15 23:24:16 -04:00
ui - > tableWidgetCalls - > insertRow ( ui - > tableWidgetCalls - > rowCount ( ) ) ;
2018-08-30 16:13:43 -04:00
int row = ui - > tableWidgetCalls - > rowCount ( ) - 1 ;
2018-10-28 17:54:10 -04:00
int col = 0 ;
2018-07-31 14:34:36 -04:00
2018-09-09 10:42:32 -04:00
# if SHOW_THROUGH_CALLS
QString displayCall = d . through . isEmpty ( ) ? d . call : QString ( " %1>%2 " ) . arg ( d . through ) . arg ( d . call ) ;
# else
2018-12-18 21:28:31 -05:00
QString displayCall = d . call ;
2018-09-09 10:42:32 -04:00
# endif
2019-10-04 15:25:51 -04:00
bool hasThrough = ! d . through . isEmpty ( ) ;
2018-10-30 20:50:31 -04:00
2019-10-04 15:25:51 -04:00
auto iconItem = new QTableWidgetItem ( hasMessage ? " \u2691 " : hasACK ? " \u2605 " : hasCQ ? " \u260E " : hasThrough ? " \u269F " : " " ) ;
2019-03-23 21:19:24 -04:00
iconItem - > setData ( Qt : : UserRole , QVariant ( d . call ) ) ;
2020-04-26 16:36:42 -04:00
iconItem - > setToolTip (
hasMessage ? " Message Available " :
hasACK ? QString ( " Hearing Your Station (%1) " ) . arg ( since ( d . ackTimestamp ) ) :
hasCQ ? QString ( " Calling CQ (%1) " ) . arg ( since ( d . cqTimestamp ) ) :
hasThrough ? QString ( " Heard Through Relay (%1) " ) . arg ( d . through ) :
" " ) ;
2024-08-31 10:49:10 -07:00
iconItem - > setTextAlignment ( Qt : : AlignCenter ) ;
2018-12-18 21:28:31 -05:00
ui - > tableWidgetCalls - > setItem ( row , col + + , iconItem ) ;
2019-10-04 15:25:51 -04:00
if ( hasMessage | | hasACK | | hasCQ | | hasThrough ) {
2018-12-18 21:28:31 -05:00
showIconColumn = true ;
}
2018-08-15 23:24:16 -04:00
auto displayItem = new QTableWidgetItem ( displayCall ) ;
2018-12-23 21:06:54 -05:00
displayItem - > setData ( Qt : : UserRole , QVariant ( d . call ) ) ;
2019-01-16 00:08:09 -05:00
displayItem - > setToolTip ( generateCallDetail ( displayCall ) ) ;
2018-10-28 17:54:10 -04:00
ui - > tableWidgetCalls - > setItem ( row , col + + , displayItem ) ;
2018-10-30 20:50:31 -04:00
2019-03-23 21:19:24 -04:00
# if ONLY_SHOW_HEARD_CALLSIGNS
if ( d . utcTimestamp . isValid ( ) ) {
# else
if ( true ) {
# endif
2019-02-25 09:57:01 -05:00
auto ageItem = new QTableWidgetItem ( since ( d . utcTimestamp ) ) ;
2024-08-31 10:49:10 -07:00
ageItem - > setTextAlignment ( Qt : : AlignCenter ) ;
2019-02-25 09:57:01 -05:00
ageItem - > setToolTip ( d . utcTimestamp . toString ( ) ) ;
ui - > tableWidgetCalls - > setItem ( row , col + + , ageItem ) ;
2019-02-23 22:49:02 -05:00
auto snrText = Varicode : : formatSNR ( d . snr ) ;
2024-08-31 10:49:10 -07:00
auto snrItem = new QTableWidgetItem ( snrText . isEmpty ( ) ? " " : QString ( " %1 dB " ) . arg ( snrText ) ) ;
snrItem - > setTextAlignment ( Qt : : AlignRight | Qt : : AlignVCenter ) ;
ui - > tableWidgetCalls - > setItem ( row , col + + , snrItem ) ;
2018-12-23 21:06:54 -05:00
2020-04-04 22:45:51 -04:00
auto offsetItem = new QTableWidgetItem ( QString ( " %1 Hz " ) . arg ( d . offset ) ) ;
offsetItem - > setData ( Qt : : UserRole , QVariant ( d . offset ) ) ;
2024-08-31 10:49:10 -07:00
offsetItem - > setTextAlignment ( Qt : : AlignRight | Qt : : AlignVCenter ) ;
2018-12-23 21:06:54 -05:00
ui - > tableWidgetCalls - > setItem ( row , col + + , offsetItem ) ;
2024-08-31 10:49:10 -07:00
auto tdriftItem = new QTableWidgetItem ( QString ( " %1 ms " ) . arg ( ( int ) ( 1000 * d . tdrift ) ) ) ;
tdriftItem - > setTextAlignment ( Qt : : AlignRight | Qt : : AlignVCenter ) ;
ui - > tableWidgetCalls - > setItem ( row , col + + , tdriftItem ) ;
2018-11-12 16:51:51 -05:00
2024-10-08 06:14:21 -07:00
auto name = JS8 : : Submode : : name ( d . submode ) ;
2019-11-09 09:48:09 -05:00
auto modeItem = new QTableWidgetItem ( name . left ( 1 ) . replace ( " H " , " N " ) ) ;
modeItem - > setToolTip ( name ) ;
modeItem - > setData ( Qt : : UserRole , QVariant ( name ) ) ;
2024-08-31 10:49:10 -07:00
modeItem - > setTextAlignment ( Qt : : AlignCenter ) ;
2019-09-05 14:07:24 -04:00
ui - > tableWidgetCalls - > setItem ( row , col + + , modeItem ) ;
2018-11-12 16:51:51 -05:00
auto gridItem = new QTableWidgetItem ( QString ( " %1 " ) . arg ( d . grid . trimmed ( ) . left ( 4 ) ) ) ;
gridItem - > setToolTip ( d . grid . trimmed ( ) ) ;
ui - > tableWidgetCalls - > setItem ( row , col + + , gridItem ) ;
2024-11-17 14:26:06 -08:00
auto const vector = Geodesic : : vector ( m_config . my_grid ( ) , d . grid ) ;
2024-11-17 09:37:27 -08:00
auto const units = ! showColumn ( " call " , " labels " ) ;
2024-11-17 05:34:54 -08:00
2024-11-17 09:37:27 -08:00
auto distanceItem = new QTableWidgetItem ( vector . distance ( ) . toString ( m_config . miles ( ) , units ) ) ;
2018-11-12 16:51:51 -05:00
distanceItem - > setTextAlignment ( Qt : : AlignRight | Qt : : AlignVCenter ) ;
2019-03-10 23:48:56 -04:00
ui - > tableWidgetCalls - > setItem ( row , col + + , distanceItem ) ;
2018-11-12 16:51:51 -05:00
2024-11-17 09:37:27 -08:00
auto azimuthItem = new QTableWidgetItem ( vector . azimuth ( ) . toString ( units ) ) ;
2024-11-19 16:54:13 -08:00
if ( auto const azimuth = vector . azimuth ( ) ) azimuthItem - > setToolTip ( azimuth . compass ( ) . toString ( ) ) ;
2024-11-17 05:34:54 -08:00
azimuthItem - > setTextAlignment ( Qt : : AlignRight | Qt : : AlignVCenter ) ;
ui - > tableWidgetCalls - > setItem ( row , col + + , azimuthItem ) ;
2019-03-03 23:43:39 -05:00
QString flag ;
if ( m_logBook . hasWorkedBefore ( d . call , " " ) ) {
// unicode checkmark
flag = " \u2713 " ;
}
2019-03-10 23:48:56 -04:00
auto workedBeforeItem = new QTableWidgetItem ( flag ) ;
2024-08-31 10:49:10 -07:00
workedBeforeItem - > setTextAlignment ( Qt : : AlignCenter ) ;
2019-03-10 23:48:56 -04:00
ui - > tableWidgetCalls - > setItem ( row , col + + , workedBeforeItem ) ;
2019-06-13 09:35:53 -04:00
QString logDetailGrid ;
2019-03-10 23:48:56 -04:00
QString logDetailDate ;
QString logDetailName ;
QString logDetailComment ;
2019-06-19 09:29:18 -04:00
bool gridItemEmpty = gridItem - > text ( ) . isEmpty ( ) ;
2019-03-10 23:48:56 -04:00
2019-06-19 09:29:18 -04:00
if ( ( gridItemEmpty & & showColumn ( " call " , " grid " ) ) | | showColumn ( " call " , " log " ) | | showColumn ( " call " , " logName " ) | | showColumn ( " call " , " logComment " ) ) {
2019-06-13 09:35:53 -04:00
m_logBook . findCallDetails ( d . call , logDetailGrid , logDetailDate , logDetailName , logDetailComment ) ;
}
2019-06-19 09:29:18 -04:00
if ( gridItemEmpty & & ! logDetailGrid . isEmpty ( ) ) {
2019-06-13 09:35:53 -04:00
gridItem - > setText ( logDetailGrid . trimmed ( ) . left ( 4 ) ) ;
gridItem - > setToolTip ( logDetailGrid . trimmed ( ) ) ;
2024-11-17 05:34:54 -08:00
2024-11-17 14:26:06 -08:00
auto const vector = Geodesic : : vector ( m_config . my_grid ( ) , d . grid ) ;
2024-11-17 09:37:27 -08:00
auto const units = ! showColumn ( " call " , " labels " ) ;
2024-11-17 05:34:54 -08:00
2024-11-17 09:37:27 -08:00
distanceItem - > setText ( vector . distance ( ) . toString ( m_config . miles ( ) , units ) ) ;
azimuthItem - > setText ( vector . azimuth ( ) . toString ( units ) ) ;
2024-11-19 16:54:13 -08:00
if ( auto const azimuth = vector . azimuth ( ) ) azimuthItem - > setToolTip ( azimuth . compass ( ) . toString ( ) ) ;
2019-06-19 09:29:18 -04:00
// update the call activity cache with the loaded grid
if ( m_callActivity . contains ( d . call ) ) {
m_callActivity [ call ] . grid = logDetailGrid . trimmed ( ) ;
}
2019-03-10 23:48:56 -04:00
}
2019-03-23 21:19:24 -04:00
if ( ! logDetailDate . isEmpty ( ) ) {
auto lastLogged = QDate : : fromString ( logDetailDate , " yyyyMMdd " ) ;
workedBeforeItem - > setToolTip ( QString ( " Last Logged: %1 " ) . arg ( lastLogged . toString ( ) ) ) ;
}
2019-03-10 23:48:56 -04:00
auto logNameItem = new QTableWidgetItem ( logDetailName ) ;
2024-08-31 10:49:10 -07:00
logNameItem - > setTextAlignment ( Qt : : AlignCenter ) ;
2019-10-21 11:34:44 -04:00
logNameItem - > setToolTip ( logDetailName ) ;
2019-03-10 23:48:56 -04:00
ui - > tableWidgetCalls - > setItem ( row , col + + , logNameItem ) ;
auto logCommentItem = new QTableWidgetItem ( logDetailComment ) ;
2024-08-31 10:49:10 -07:00
logCommentItem - > setTextAlignment ( Qt : : AlignCenter ) ;
2019-10-21 11:34:44 -04:00
logCommentItem - > setToolTip ( logDetailComment ) ;
2019-03-10 23:48:56 -04:00
ui - > tableWidgetCalls - > setItem ( row , col + + , logCommentItem ) ;
2019-03-03 23:43:39 -05:00
2018-11-12 16:51:51 -05:00
} else {
2018-12-18 21:28:31 -05:00
ui - > tableWidgetCalls - > setItem ( row , col + + , new QTableWidgetItem ( " " ) ) ; // age
ui - > tableWidgetCalls - > setItem ( row , col + + , new QTableWidgetItem ( " " ) ) ; // snr
ui - > tableWidgetCalls - > setItem ( row , col + + , new QTableWidgetItem ( " " ) ) ; // freq
ui - > tableWidgetCalls - > setItem ( row , col + + , new QTableWidgetItem ( " " ) ) ; // tdrift
2019-09-05 14:07:24 -04:00
ui - > tableWidgetCalls - > setItem ( row , col + + , new QTableWidgetItem ( " " ) ) ; // mode
2018-12-18 21:28:31 -05:00
ui - > tableWidgetCalls - > setItem ( row , col + + , new QTableWidgetItem ( " " ) ) ; // grid
ui - > tableWidgetCalls - > setItem ( row , col + + , new QTableWidgetItem ( " " ) ) ; // distance
2024-11-17 05:34:54 -08:00
ui - > tableWidgetCalls - > setItem ( row , col + + , new QTableWidgetItem ( " " ) ) ; // azimuth
2019-03-10 23:48:56 -04:00
ui - > tableWidgetCalls - > setItem ( row , col + + , new QTableWidgetItem ( " " ) ) ; // worked before
ui - > tableWidgetCalls - > setItem ( row , col + + , new QTableWidgetItem ( " " ) ) ; // log name
ui - > tableWidgetCalls - > setItem ( row , col + + , new QTableWidgetItem ( " " ) ) ; // log comment
2018-11-12 16:51:51 -05:00
}
2018-08-15 23:24:16 -04:00
2018-10-26 22:09:38 -04:00
if ( isCallSelected ) {
2018-08-30 16:13:43 -04:00
for ( int i = 0 ; i < ui - > tableWidgetCalls - > columnCount ( ) ; i + + ) {
ui - > tableWidgetCalls - > item ( row , i ) - > setSelected ( true ) ;
}
2018-08-15 23:24:16 -04:00
}
2019-07-04 01:32:01 -04:00
2019-07-11 20:29:32 -04:00
if ( hasCQ ) {
2019-07-11 20:37:40 -04:00
for ( int i = 0 ; i < ui - > tableWidgetCalls - > columnCount ( ) ; i + + ) {
2019-07-11 20:29:32 -04:00
ui - > tableWidgetCalls - > item ( row , i ) - > setBackground ( QBrush ( m_config . color_CQ ( ) ) ) ;
}
}
2019-07-09 13:58:28 -04:00
if ( m_config . secondary_highlight_words ( ) . contains ( call ) ) {
for ( int i = 0 ; i < ui - > tableWidgetCalls - > columnCount ( ) ; i + + ) {
ui - > tableWidgetCalls - > item ( row , i ) - > setBackground ( QBrush ( m_config . color_secondary_highlight ( ) ) ) ;
}
}
if ( m_config . primary_highlight_words ( ) . contains ( call ) ) {
2019-07-04 01:32:01 -04:00
for ( int i = 0 ; i < ui - > tableWidgetCalls - > columnCount ( ) ; i + + ) {
2019-07-09 13:58:28 -04:00
ui - > tableWidgetCalls - > item ( row , i ) - > setBackground ( QBrush ( m_config . color_primary_highlight ( ) ) ) ;
2019-07-04 01:32:01 -04:00
}
}
2018-07-31 14:34:36 -04:00
}
2018-07-13 15:45:00 -04:00
2018-09-15 17:33:09 -04:00
// Set table color
2019-12-27 22:50:45 -05:00
auto style = QString ( " QTableWidget { background:%1; selection-background-color:%2; alternate-background-color:%1; color:%3; } "
2019-12-28 22:24:24 -05:00
" QTableWidget::item:selected { background-color: %2; color: %3; } " ) ;
2018-09-15 17:33:09 -04:00
style = style . arg ( m_config . color_table_background ( ) . name ( ) ) ;
style = style . arg ( m_config . color_table_highlight ( ) . name ( ) ) ;
style = style . arg ( m_config . color_table_foreground ( ) . name ( ) ) ;
ui - > tableWidgetCalls - > setStyleSheet ( style ) ;
2018-12-23 22:31:22 -05:00
// Set the table palette for inactive selected row
auto p = ui - > tableWidgetCalls - > palette ( ) ;
2019-12-27 22:31:02 -05:00
p . setColor ( QPalette : : Highlight , m_config . color_table_highlight ( ) ) ;
p . setColor ( QPalette : : HighlightedText , m_config . color_table_foreground ( ) ) ;
2018-12-23 22:31:22 -05:00
p . setColor ( QPalette : : Inactive , QPalette : : Highlight , p . color ( QPalette : : Active , QPalette : : Highlight ) ) ;
ui - > tableWidgetCalls - > setPalette ( p ) ;
2018-09-15 17:33:09 -04:00
// Set item fonts
for ( int row = 0 ; row < ui - > tableWidgetCalls - > rowCount ( ) ; row + + ) {
2018-12-18 21:28:31 -05:00
auto bold = ui - > tableWidgetCalls - > item ( row , 0 ) - > text ( ) = = " \u2691 " ;
2018-09-15 17:33:09 -04:00
for ( int col = 0 ; col < ui - > tableWidgetCalls - > columnCount ( ) ; col + + ) {
auto item = ui - > tableWidgetCalls - > item ( row , col ) ;
if ( item ) {
2018-12-18 21:28:31 -05:00
auto f = m_config . table_font ( ) ;
2018-12-23 22:24:45 -05:00
if ( bold ) {
f . setBold ( true ) ;
}
2018-12-18 21:28:31 -05:00
item - > setFont ( f ) ;
2018-09-15 17:33:09 -04:00
}
}
}
2018-10-28 17:54:10 -04:00
// Column labels
ui - > tableWidgetCalls - > horizontalHeader ( ) - > setVisible ( showColumn ( " call " , " labels " ) ) ;
2018-09-24 16:46:33 -04:00
// Hide columns
2018-12-18 21:28:31 -05:00
ui - > tableWidgetCalls - > setColumnHidden ( 0 , ! showIconColumn ) ;
ui - > tableWidgetCalls - > setColumnHidden ( 1 , ! showColumn ( " call " , " callsign " ) ) ;
2019-03-03 23:43:39 -05:00
ui - > tableWidgetCalls - > setColumnHidden ( 2 , ! showColumn ( " call " , " timestamp " ) ) ;
ui - > tableWidgetCalls - > setColumnHidden ( 3 , ! showColumn ( " call " , " snr " ) ) ;
ui - > tableWidgetCalls - > setColumnHidden ( 4 , ! showColumn ( " call " , " offset " ) ) ;
ui - > tableWidgetCalls - > setColumnHidden ( 5 , ! showColumn ( " call " , " tdrift " , false ) ) ;
2019-11-10 13:47:17 -05:00
ui - > tableWidgetCalls - > setColumnHidden ( 6 , ! showColumn ( " call " , " submode " , false ) ) ;
2019-09-05 14:07:24 -04:00
ui - > tableWidgetCalls - > setColumnHidden ( 7 , ! showColumn ( " call " , " grid " , false ) ) ;
ui - > tableWidgetCalls - > setColumnHidden ( 8 , ! showColumn ( " call " , " distance " , false ) ) ;
2024-11-17 05:34:54 -08:00
ui - > tableWidgetCalls - > setColumnHidden ( 9 , ! showColumn ( " call " , " azimuth " , false ) ) ;
ui - > tableWidgetCalls - > setColumnHidden ( 10 , ! showColumn ( " call " , " log " ) ) ;
ui - > tableWidgetCalls - > setColumnHidden ( 11 , ! showColumn ( " call " , " logName " ) ) ;
ui - > tableWidgetCalls - > setColumnHidden ( 12 , ! showColumn ( " call " , " logComment " ) ) ;
2018-09-24 16:46:33 -04:00
2018-08-15 23:24:16 -04:00
// Resize the table columns
ui - > tableWidgetCalls - > resizeColumnToContents ( 0 ) ;
ui - > tableWidgetCalls - > resizeColumnToContents ( 1 ) ;
ui - > tableWidgetCalls - > resizeColumnToContents ( 2 ) ;
ui - > tableWidgetCalls - > resizeColumnToContents ( 3 ) ;
2018-08-28 16:35:14 -04:00
ui - > tableWidgetCalls - > resizeColumnToContents ( 4 ) ;
2018-10-28 17:54:10 -04:00
ui - > tableWidgetCalls - > resizeColumnToContents ( 5 ) ;
2018-10-30 20:50:31 -04:00
ui - > tableWidgetCalls - > resizeColumnToContents ( 6 ) ;
2018-12-18 21:28:31 -05:00
ui - > tableWidgetCalls - > resizeColumnToContents ( 7 ) ;
2019-03-10 23:48:56 -04:00
ui - > tableWidgetCalls - > resizeColumnToContents ( 8 ) ;
ui - > tableWidgetCalls - > resizeColumnToContents ( 9 ) ;
2019-09-05 14:07:24 -04:00
ui - > tableWidgetCalls - > resizeColumnToContents ( 10 ) ;
2024-11-17 05:34:54 -08:00
ui - > tableWidgetCalls - > resizeColumnToContents ( 11 ) ;
2018-08-15 23:24:16 -04:00
// Reset the scroll position
ui - > tableWidgetCalls - > verticalScrollBar ( ) - > setValue ( currentScrollPos ) ;
}
ui - > tableWidgetCalls - > setUpdatesEnabled ( true ) ;
2018-02-08 21:28:33 -05:00
}
2019-01-01 10:29:35 -05:00
void MainWindow : : emitPTT ( bool on ) {
2019-05-19 11:17:08 -04:00
qDebug ( ) < < " PTT: " < < on ;
2019-01-01 10:29:35 -05:00
Q_EMIT m_config . transceiver_ptt ( on ) ;
// emit to network
sendNetworkMessage ( " RIG.PTT " , on ? " on " : " off " , {
2020-04-03 22:49:28 -04:00
{ " _ID " , QVariant ( - 1 ) } ,
2019-01-01 10:29:35 -05:00
{ " PTT " , QVariant ( on ) } ,
{ " UTC " , QVariant ( DriftingDateTime : : currentDateTimeUtc ( ) . toMSecsSinceEpoch ( ) ) } ,
} ) ;
}
2019-02-23 09:50:02 -05:00
void MainWindow : : emitTones ( ) {
2020-04-04 22:45:51 -04:00
if ( ! canSendNetworkMessage ( ) ) {
2019-02-23 09:50:02 -05:00
return ;
}
// emit tone numbers to network
QVariantList t ;
2019-08-14 21:11:06 -04:00
for ( int i = 0 ; i < JS8_NUM_SYMBOLS ; i + + ) {
2019-09-05 14:07:24 -04:00
//qDebug() << "tone" << i << "=" << itone[i];
2019-02-23 09:50:02 -05:00
t . append ( QVariant ( ( int ) itone [ i ] ) ) ;
}
sendNetworkMessage ( " TX.FRAME " , " " , {
2020-04-03 22:49:28 -04:00
{ " _ID " , QVariant ( - 1 ) } ,
2019-02-23 09:50:02 -05:00
{ " TONES " , t }
} ) ;
}
2020-04-03 22:49:28 -04:00
void MainWindow : : udpNetworkMessage ( Message const & message )
2018-08-07 23:11:11 -04:00
{
2019-01-03 13:33:08 -05:00
if ( ! m_config . udpEnabled ( ) ) {
return ;
}
2018-08-07 23:11:11 -04:00
if ( ! m_config . accept_udp_requests ( ) ) {
return ;
}
2020-04-03 22:49:28 -04:00
networkMessage ( message ) ;
}
void MainWindow : : tcpNetworkMessage ( Message const & message )
{
2020-04-04 14:58:30 -04:00
if ( ! m_config . tcpEnabled ( ) ) {
return ;
}
if ( ! m_config . accept_tcp_requests ( ) ) {
return ;
}
2020-04-03 22:49:28 -04:00
networkMessage ( message ) ;
}
void MainWindow : : networkMessage ( Message const & message )
{
2018-08-08 17:15:49 -04:00
auto type = message . type ( ) ;
2018-11-25 22:12:54 -05:00
if ( type = = " PING " ) {
2018-08-08 17:15:49 -04:00
return ;
}
2020-04-03 22:49:28 -04:00
auto id = message . id ( ) ;
2019-02-01 12:07:15 -05:00
2020-04-03 22:49:28 -04:00
qDebug ( ) < < " try processing network message " < < type < < id ;
2019-02-01 03:34:20 -05:00
2018-08-09 22:44:25 -04:00
// Inspired by FLDigi
// TODO: MAIN.RX - Turn on RX
// TODO: MAIN.TX - Transmit
2019-01-01 10:29:35 -05:00
// TODO: MAIN.PTT - PTT
2018-08-09 22:44:25 -04:00
// TODO: MAIN.TUNE - Tune
// TODO: MAIN.HALT - Halt
2019-01-01 10:29:35 -05:00
// TODO: MAIN.AUTO - Auto
// TODO: MAIN.SPOT - Spot
// TODO: MAIN.HB - HB
2018-08-08 17:15:49 -04:00
2018-08-09 22:44:25 -04:00
// RIG.GET_FREQ - Get the current Frequency
// RIG.SET_FREQ - Set the current Frequency
if ( type = = " RIG.GET_FREQ " ) {
sendNetworkMessage ( " RIG.FREQ " , " " , {
2019-02-01 03:34:20 -05:00
{ " _ID " , id } ,
2024-10-20 13:54:14 -07:00
{ " FREQ " , QVariant ( ( quint64 ) dialFrequency ( ) + freq ( ) ) } ,
2018-08-08 17:15:49 -04:00
{ " DIAL " , QVariant ( ( quint64 ) dialFrequency ( ) ) } ,
2024-10-20 13:54:14 -07:00
{ " OFFSET " , QVariant ( ( quint64 ) freq ( ) ) }
2018-08-08 17:15:49 -04:00
} ) ;
return ;
}
2018-08-09 22:44:25 -04:00
if ( type = = " RIG.SET_FREQ " ) {
2018-08-08 17:15:49 -04:00
auto params = message . params ( ) ;
if ( params . contains ( " DIAL " ) ) {
bool ok = false ;
auto f = params [ " DIAL " ] . toInt ( & ok ) ;
if ( ok ) {
setRig ( f ) ;
displayDialFrequency ( ) ;
}
}
if ( params . contains ( " OFFSET " ) ) {
bool ok = false ;
auto f = params [ " OFFSET " ] . toInt ( & ok ) ;
if ( ok ) {
setFreqOffsetForRestore ( f , false ) ;
}
}
2018-08-09 22:44:25 -04:00
}
// STATION.GET_CALLSIGN - Get the current callsign
// STATION.GET_GRID - Get the current grid locator
// STATION.SET_GRID - Set the current grid locator
2019-02-08 16:57:42 -05:00
// STATION.GET_INFO - Get the current station qth
// STATION.SET_INFO - Set the current station qth
2018-08-09 22:44:25 -04:00
if ( type = = " STATION.GET_CALLSIGN " ) {
2019-02-01 03:34:20 -05:00
sendNetworkMessage ( " STATION.CALLSIGN " , m_config . my_callsign ( ) , {
{ " _ID " , id } ,
} ) ;
2018-08-09 22:44:25 -04:00
return ;
}
if ( type = = " STATION.GET_GRID " ) {
2019-02-01 03:34:20 -05:00
sendNetworkMessage ( " STATION.GRID " , m_config . my_grid ( ) , {
{ " _ID " , id } ,
} ) ;
2018-08-09 22:44:25 -04:00
return ;
}
if ( type = = " STATION.SET_GRID " ) {
2018-08-11 22:12:54 -04:00
m_config . set_dynamic_location ( message . value ( ) ) ;
2019-02-01 03:34:20 -05:00
sendNetworkMessage ( " STATION.GRID " , m_config . my_grid ( ) , {
{ " _ID " , id } ,
} ) ;
2018-08-09 22:44:25 -04:00
return ;
}
2019-02-08 16:57:42 -05:00
if ( type = = " STATION.GET_INFO " ) {
sendNetworkMessage ( " STATION.INFO " , m_config . my_info ( ) , {
2019-02-01 03:34:20 -05:00
{ " _ID " , id } ,
} ) ;
2018-08-11 22:12:54 -04:00
return ;
}
2019-02-08 16:57:42 -05:00
if ( type = = " STATION.SET_INFO " ) {
m_config . set_dynamic_station_info ( message . value ( ) ) ;
sendNetworkMessage ( " STATION.INFO " , m_config . my_info ( ) , {
2019-02-01 03:34:20 -05:00
{ " _ID " , id } ,
} ) ;
2018-08-11 22:12:54 -04:00
return ;
}
2020-03-28 15:10:41 -04:00
if ( type = = " STATION.GET_STATUS " ) {
sendNetworkMessage ( " STATION.STATUS " , m_config . my_status ( ) , {
{ " _ID " , id } ,
} ) ;
return ;
}
if ( type = = " STATION.SET_STATUS " ) {
m_config . set_dynamic_station_status ( message . value ( ) ) ;
sendNetworkMessage ( " STATION.STATUS " , m_config . my_status ( ) , {
{ " _ID " , id } ,
} ) ;
return ;
}
2018-08-09 22:44:25 -04:00
// RX.GET_CALL_ACTIVITY
2018-08-15 22:31:01 -04:00
// RX.GET_CALL_SELECTED
2018-08-09 22:44:25 -04:00
// RX.GET_BAND_ACTIVITY
// RX.GET_TEXT
if ( type = = " RX.GET_CALL_ACTIVITY " ) {
2018-10-18 10:51:53 -04:00
auto now = DriftingDateTime : : currentDateTimeUtc ( ) ;
int callsignAging = m_config . callsign_aging ( ) ;
2024-11-04 17:48:36 -08:00
QVariantMap calls = {
2019-02-01 03:34:20 -05:00
{ " _ID " , id } ,
} ;
2018-08-09 22:44:25 -04:00
foreach ( auto cd , m_callActivity . values ( ) ) {
2018-10-18 10:51:53 -04:00
if ( callsignAging & & cd . utcTimestamp . secsTo ( now ) / 60 > = callsignAging ) {
continue ;
}
2024-11-04 17:48:36 -08:00
QVariantMap detail ;
2018-08-09 22:44:25 -04:00
detail [ " SNR " ] = QVariant ( cd . snr ) ;
detail [ " GRID " ] = QVariant ( cd . grid ) ;
2018-08-10 14:34:21 -04:00
detail [ " UTC " ] = QVariant ( cd . utcTimestamp . toMSecsSinceEpoch ( ) ) ;
2018-08-09 22:44:25 -04:00
calls [ cd . call ] = QVariant ( detail ) ;
}
sendNetworkMessage ( " RX.CALL_ACTIVITY " , " " , calls ) ;
return ;
}
2018-08-15 22:31:01 -04:00
if ( type = = " RX.GET_CALL_SELECTED " ) {
2019-02-01 03:34:20 -05:00
sendNetworkMessage ( " RX.CALL_SELECTED " , callsignSelected ( ) , {
{ " _ID " , id } ,
} ) ;
2018-08-15 22:31:01 -04:00
return ;
}
2018-08-09 22:44:25 -04:00
if ( type = = " RX.GET_BAND_ACTIVITY " ) {
2024-11-04 17:48:36 -08:00
QVariantMap offsets = {
2019-02-01 03:34:20 -05:00
{ " _ID " , id } ,
} ;
2024-11-08 15:07:35 -08:00
for ( auto const [ offset , activity ] : m_bandActivity . asKeyValueRange ( ) )
{
if ( activity . isEmpty ( ) ) continue ;
2018-08-09 22:44:25 -04:00
2024-11-08 15:07:35 -08:00
auto const d = activity . last ( ) ;
2018-08-09 22:44:25 -04:00
2024-11-08 15:07:35 -08:00
offsets [ QString ( " %1 " ) . arg ( offset ) ] = QVariant ( QVariantMap {
{ " FREQ " , QVariant ( d . dial + d . offset ) } ,
{ " DIAL " , QVariant ( d . dial ) } ,
{ " OFFSET " , QVariant ( d . offset ) } ,
{ " TEXT " , QVariant ( d . text ) } ,
{ " SNR " , QVariant ( d . snr ) } ,
{ " UTC " , QVariant ( d . utcTimestamp . toMSecsSinceEpoch ( ) ) }
} ) ;
2018-08-09 22:44:25 -04:00
}
sendNetworkMessage ( " RX.BAND_ACTIVITY " , " " , offsets ) ;
2018-08-08 17:15:49 -04:00
return ;
}
2018-08-09 22:44:25 -04:00
if ( type = = " RX.GET_TEXT " ) {
2019-12-27 20:16:21 -05:00
sendNetworkMessage ( " RX.TEXT " , ui - > textEditRX - > toPlainText ( ) . right ( 1024 ) , {
2019-02-01 03:34:20 -05:00
{ " _ID " , id } ,
} ) ;
2018-08-09 22:44:25 -04:00
return ;
}
// TX.GET_TEXT
// TX.SET_TEXT
// TX.SEND_MESSAGE
if ( type = = " TX.GET_TEXT " ) {
2019-12-27 20:16:21 -05:00
sendNetworkMessage ( " TX.TEXT " , ui - > extFreeTextMsgEdit - > toPlainText ( ) . right ( 1024 ) , {
2019-02-01 03:34:20 -05:00
{ " _ID " , id } ,
} ) ;
2018-08-09 22:44:25 -04:00
return ;
}
if ( type = = " TX.SET_TEXT " ) {
addMessageText ( message . value ( ) , true ) ;
2019-12-27 20:16:21 -05:00
sendNetworkMessage ( " TX.TEXT " , ui - > extFreeTextMsgEdit - > toPlainText ( ) . right ( 1024 ) , {
2019-02-01 03:34:20 -05:00
{ " _ID " , id } ,
} ) ;
2018-08-09 22:44:25 -04:00
return ;
}
if ( type = = " TX.SEND_MESSAGE " ) {
2018-08-08 17:15:49 -04:00
auto text = message . value ( ) ;
if ( ! text . isEmpty ( ) ) {
enqueueMessage ( PriorityNormal , text , - 1 , nullptr ) ;
2020-02-17 20:43:26 -05:00
processTxQueue ( ) ;
2018-08-08 17:15:49 -04:00
return ;
}
}
2019-10-05 23:28:24 -04:00
// MODE.GET_SPEED
// MODE.SET_SPEED
if ( type = = " MODE.GET_SPEED " ) {
sendNetworkMessage ( " MODE.SPEED " , " " , {
{ " _ID " , id } ,
{ " SPEED " , m_nSubMode } ,
} ) ;
return ;
}
if ( type = = " MODE.SET_SPEED " ) {
bool ok = false ;
int speed = message . params ( ) . value ( " SPEED " , QVariant ( m_nSubMode ) ) . toInt ( & ok ) ;
if ( ok ) {
if ( speed = = Varicode : : JS8CallNormal ) {
ui - > actionModeJS8Normal - > setChecked ( true ) ;
}
if ( speed = = Varicode : : JS8CallFast ) {
ui - > actionModeJS8Fast - > setChecked ( true ) ;
}
if ( speed = = Varicode : : JS8CallTurbo ) {
ui - > actionModeJS8Turbo - > setChecked ( true ) ;
}
2019-12-09 14:00:23 -05:00
if ( speed = = Varicode : : JS8CallSlow ) {
ui - > actionModeJS8Slow - > setChecked ( true ) ;
}
if ( speed = = Varicode : : JS8CallUltra ) {
ui - > actionModeJS8Ultra - > setChecked ( true ) ;
2019-10-05 23:28:24 -04:00
}
}
sendNetworkMessage ( " MODE.SPEED " , " " , {
{ " _ID " , id } ,
{ " SPEED " , m_nSubMode } ,
} ) ;
return ;
}
2020-04-05 14:33:10 -04:00
// INBOX.GET_MESSAGES
// INBOX.STORE_MESSAGE
if ( type = = " INBOX.GET_MESSAGES " ) {
QString selectedCall = message . params ( ) . value ( " CALLSIGN " , " " ) . toString ( ) ;
if ( selectedCall . isEmpty ( ) ) {
selectedCall = " % " ;
}
Inbox inbox ( inboxPath ( ) ) ;
if ( ! inbox . open ( ) ) {
return ;
}
QList < QPair < int , Message > > msgs ;
msgs . append ( inbox . values ( " STORE " , " $.params.TO " , selectedCall , 0 , 1000 ) ) ;
msgs . append ( inbox . values ( " READ " , " $.params.FROM " , selectedCall , 0 , 1000 ) ) ;
foreach ( auto pair , inbox . values ( " UNREAD " , " $.params.FROM " , selectedCall , 0 , 1000 ) ) {
msgs . append ( pair ) ;
}
2024-08-31 09:40:28 -07:00
std : : stable_sort ( msgs . begin ( ) , msgs . end ( ) , [ ] ( QPair < int , Message > const & a , QPair < int , Message > const & b ) {
2024-08-31 13:13:39 -07:00
return QVariant : : compare ( a . second . params ( ) . value ( " UTC " ) ,
b . second . params ( ) . value ( " UTC " ) ) = = QPartialOrdering : : Greater ;
2020-04-05 14:33:10 -04:00
} ) ;
QVariantList l ;
foreach ( auto pair , msgs ) {
l < < pair . second . toVariantMap ( ) ;
}
sendNetworkMessage ( " INBOX.MESSAGES " , " " , {
{ " _ID " , id } ,
{ " MESSAGES " , l } ,
} ) ;
2020-04-05 14:34:45 -04:00
return ;
2020-04-05 14:33:10 -04:00
}
if ( type = = " INBOX.STORE_MESSAGE " ) {
QString selectedCall = message . params ( ) . value ( " CALLSIGN " , " " ) . toString ( ) ;
if ( selectedCall . isEmpty ( ) ) {
return ;
}
QString text = message . params ( ) . value ( " TEXT " , " " ) . toString ( ) ;
if ( text . isEmpty ( ) ) {
return ;
}
CommandDetail d = { } ;
d . cmd = " MSG " ;
d . to = selectedCall ;
d . from = m_config . my_callsign ( ) ;
d . relayPath = d . from ;
d . text = text ;
d . utcTimestamp = DriftingDateTime : : currentDateTimeUtc ( ) ;
d . submode = m_nSubMode ;
auto mid = addCommandToStorage ( " STORE " , d ) ;
sendNetworkMessage ( " INBOX.MESSAGE " , " " , {
{ " _ID " , id } ,
{ " ID " , mid } ,
} ) ;
2020-04-05 14:34:45 -04:00
return ;
2020-04-05 14:33:10 -04:00
}
2019-10-05 23:28:24 -04:00
2018-08-09 22:44:25 -04:00
// WINDOW.RAISE
if ( type = = " WINDOW.RAISE " ) {
setWindowState ( Qt : : WindowActive ) ;
activateWindow ( ) ;
2019-02-01 12:07:15 -05:00
raise ( ) ;
2018-08-09 22:44:25 -04:00
return ;
}
2018-08-08 17:15:49 -04:00
qDebug ( ) < < " Unable to process networkMessage: " < < type ;
}
2019-12-27 20:08:39 -05:00
bool MainWindow : : canSendNetworkMessage ( ) {
2020-04-04 14:58:30 -04:00
return m_config . udpEnabled ( ) | | m_config . tcpEnabled ( ) ;
2019-12-27 20:08:39 -05:00
}
2018-08-08 17:15:49 -04:00
void MainWindow : : sendNetworkMessage ( QString const & type , QString const & message ) {
2020-04-03 22:49:28 -04:00
if ( ! canSendNetworkMessage ( ) ) {
2019-01-03 13:33:08 -05:00
return ;
}
2020-04-03 22:49:28 -04:00
auto m = Message ( type , message ) ;
2020-04-04 14:58:30 -04:00
if ( m_config . udpEnabled ( ) ) {
m_messageClient - > send ( m ) ;
}
if ( m_config . tcpEnabled ( ) ) {
m_messageServer - > send ( m ) ;
}
2018-08-07 23:11:11 -04:00
}
2024-11-04 17:48:36 -08:00
void MainWindow : : sendNetworkMessage ( QString const & type , QString const & message , QVariantMap const & params )
2018-08-07 23:11:11 -04:00
{
2020-04-03 22:49:28 -04:00
if ( ! canSendNetworkMessage ( ) ) {
2019-01-03 13:33:08 -05:00
return ;
}
2020-04-03 22:49:28 -04:00
auto m = Message ( type , message , params ) ;
2020-04-04 14:58:30 -04:00
if ( m_config . udpEnabled ( ) ) {
m_messageClient - > send ( m ) ;
}
if ( m_config . tcpEnabled ( ) ) {
m_messageServer - > send ( m ) ;
}
2018-08-07 23:11:11 -04:00
}
2024-11-09 10:25:45 -08:00
void
MainWindow : : pskReporterError ( QString const & message )
{
qDebug ( ) < < " PSK Reporter Error: " < < message ;
showStatusMessage ( tr ( " Spotting to PSK Reporter unavailable " ) ) ;
}
2018-02-08 21:28:33 -05:00
void MainWindow : : setRig ( Frequency f )
{
if ( f )
2020-05-31 15:34:57 -04:00
{
2018-02-08 21:28:33 -05:00
m_freqNominal = f ;
m_freqTxNominal = m_freqNominal ;
}
2020-05-31 15:34:57 -04:00
2018-09-11 10:52:07 -04:00
if ( m_transmitting & & ! m_config . tx_qsy_allowed ( ) ) return ;
2020-05-31 15:34:57 -04:00
2018-02-08 21:28:33 -05:00
if ( ( m_monitoring | | m_transmitting ) & & m_config . transceiver_online ( ) )
{
if ( m_transmitting & & m_config . split_mode ( ) )
{
2020-05-31 15:34:57 -04:00
Q_EMIT m_config . transceiver_tx_frequency ( m_freqTxNominal ) ;
2018-02-08 21:28:33 -05:00
}
else
{
2020-05-31 15:34:57 -04:00
Q_EMIT m_config . transceiver_frequency ( m_freqNominal ) ;
2018-02-08 21:28:33 -05:00
}
}
}
2019-12-27 20:08:39 -05:00
void MainWindow : : statusUpdate ( )
2018-02-08 21:28:33 -05:00
{
2019-12-27 20:08:39 -05:00
if ( canSendNetworkMessage ( ) ) {
sendNetworkMessage ( " STATION.STATUS " , " " , {
2024-10-20 13:54:14 -07:00
{ " FREQ " , QVariant ( dialFrequency ( ) + freq ( ) ) } ,
2019-12-27 20:08:39 -05:00
{ " DIAL " , QVariant ( dialFrequency ( ) ) } ,
2024-10-20 13:54:14 -07:00
{ " OFFSET " , QVariant ( freq ( ) ) } ,
2019-12-27 20:08:39 -05:00
{ " SPEED " , QVariant ( m_nSubMode ) } ,
{ " SELECTED " , QVariant ( callsignSelected ( ) ) } ,
} ) ;
}
2018-02-08 21:28:33 -05:00
}
void MainWindow : : childEvent ( QChildEvent * e )
{
if ( e - > child ( ) - > isWidgetType ( ) )
{
switch ( e - > type ( ) )
{
case QEvent : : ChildAdded : add_child_to_event_filter ( e - > child ( ) ) ; break ;
case QEvent : : ChildRemoved : remove_child_from_event_filter ( e - > child ( ) ) ; break ;
default : break ;
}
}
QMainWindow : : childEvent ( e ) ;
}
// add widget and any child widgets to our event filter so that we can
// take action on key press ad mouse press events anywhere in the main window
void MainWindow : : add_child_to_event_filter ( QObject * target )
{
if ( target & & target - > isWidgetType ( ) )
{
target - > installEventFilter ( this ) ;
}
auto const & children = target - > children ( ) ;
for ( auto iter = children . begin ( ) ; iter ! = children . end ( ) ; + + iter )
{
add_child_to_event_filter ( * iter ) ;
}
}
// recursively remove widget and any child widgets from our event filter
void MainWindow : : remove_child_from_event_filter ( QObject * target )
{
auto const & children = target - > children ( ) ;
for ( auto iter = children . begin ( ) ; iter ! = children . end ( ) ; + + iter )
{
remove_child_from_event_filter ( * iter ) ;
}
if ( target & & target - > isWidgetType ( ) )
{
target - > removeEventFilter ( this ) ;
}
}
2018-10-28 12:06:37 -04:00
void MainWindow : : resetIdleTimer ( ) {
if ( m_idleMinutes ) {
m_idleMinutes = 0 ;
qDebug ( ) < < " idle " < < m_idleMinutes < < " minutes " ;
}
}
void MainWindow : : incrementIdleTimer ( ) {
m_idleMinutes + + ;
qDebug ( ) < < " increment idle to " < < m_idleMinutes < < " minutes " ;
}
2018-02-08 21:28:33 -05:00
void MainWindow : : tx_watchdog ( bool triggered )
{
auto prior = m_tx_watchdog ;
m_tx_watchdog = triggered ;
if ( triggered )
{
m_bTxTime = false ;
if ( m_tune ) stop_tuning ( ) ;
if ( m_auto ) auto_tx_mode ( false ) ;
2018-09-11 11:20:17 -04:00
stopTx ( ) ;
2018-12-12 22:14:45 -05:00
tx_status_label . setStyleSheet ( " QLabel{background-color: #000000; color:#ffffff; } " ) ;
2019-02-01 19:19:13 -05:00
tx_status_label . setText ( " Idle timeout " ) ;
2018-11-25 22:12:54 -05:00
// if the watchdog is triggered...we're no longer active
2019-08-26 12:08:35 -04:00
bool wasAuto = ui - > actionModeAutoreply - > isChecked ( ) ;
2018-12-14 20:02:46 -05:00
bool wasHB = ui - > hbMacroButton - > isChecked ( ) ;
bool wasCQ = ui - > cqMacroButton - > isChecked ( ) ;
// save the button states
2019-08-26 12:08:35 -04:00
ui - > actionModeAutoreply - > setChecked ( false ) ;
2018-12-14 20:02:46 -05:00
ui - > hbMacroButton - > setChecked ( false ) ;
ui - > cqMacroButton - > setChecked ( false ) ;
2018-10-10 15:36:53 -04:00
2019-01-28 22:31:40 -05:00
// clear the tx queues
resetMessageTransmitQueue ( ) ;
2019-02-01 19:19:13 -05:00
QMessageBox * msgBox = new QMessageBox ( this ) ;
msgBox - > setIcon ( QMessageBox : : Information ) ;
2019-02-07 14:23:46 -05:00
msgBox - > setWindowTitle ( " Idle Timeout " ) ;
2019-02-01 19:19:13 -05:00
msgBox - > setInformativeText ( QString ( " You have been idle for more than %1 minutes. " ) . arg ( m_config . watchdog ( ) ) ) ;
msgBox - > addButton ( QMessageBox : : Ok ) ;
connect ( msgBox , & QMessageBox : : finished , this , [ this , wasAuto , wasHB , wasCQ ] ( int /*result*/ ) {
// restore the button states
2019-08-26 12:08:35 -04:00
ui - > actionModeAutoreply - > setChecked ( wasAuto ) ;
2019-02-01 19:19:13 -05:00
ui - > hbMacroButton - > setChecked ( wasHB ) ;
ui - > cqMacroButton - > setChecked ( wasCQ ) ;
this - > tx_watchdog ( false ) ;
} ) ;
msgBox - > setModal ( true ) ;
msgBox - > show ( ) ;
2018-02-08 21:28:33 -05:00
}
if ( prior ! = triggered ) statusUpdate ( ) ;
}
2020-01-13 11:41:22 -05:00
void MainWindow : : write_frequency_entry ( QString const & file_name ) {
if ( ! m_config . write_logs ( ) ) {
return ;
}
// Write freq changes to ALL.TXT only below 30 MHz.
QFile f2 { m_config . writeable_data_dir ( ) . absoluteFilePath ( file_name ) } ;
if ( f2 . open ( QIODevice : : WriteOnly | QIODevice : : Text | QIODevice : : Append ) ) {
QTextStream out ( & f2 ) ;
out < < DriftingDateTime : : currentDateTimeUtc ( ) . toString ( " yyyy-MM-dd hh:mm:ss " )
< < " " < < qSetRealNumberPrecision ( 12 ) < < ( m_freqNominal / 1.e6 ) < < " MHz "
2024-08-31 09:40:28 -07:00
< < " JS8 " < < Qt : : endl ;
2020-01-13 11:41:22 -05:00
f2 . close ( ) ;
} else {
2024-10-10 08:41:40 -07:00
QTimer : : singleShot ( 0 , [
this ,
message = tr ( " Cannot open \" %1 \" for append: %2 " ) . arg ( f2 . fileName ( ) ) . arg ( f2 . errorString ( ) )
] {
MessageBox : : warning_message ( this , tr ( " Log File Error " ) , message ) ;
} ) ;
2020-01-13 11:41:22 -05:00
}
}
2018-02-08 21:28:33 -05:00
void MainWindow : : write_transmit_entry ( QString const & file_name )
{
2020-01-13 11:41:22 -05:00
if ( ! m_config . write_logs ( ) ) {
return ;
}
2018-02-08 21:28:33 -05:00
QFile f { m_config . writeable_data_dir ( ) . absoluteFilePath ( file_name ) } ;
if ( f . open ( QIODevice : : WriteOnly | QIODevice : : Text | QIODevice : : Append ) )
{
QTextStream out ( & f ) ;
2018-09-18 17:24:07 -04:00
auto time = DriftingDateTime : : currentDateTimeUtc ( ) ;
2018-02-08 21:28:33 -05:00
time = time . addSecs ( - ( time . time ( ) . second ( ) % m_TRperiod ) ) ;
2019-09-05 14:07:24 -04:00
auto dt = DecodedText ( m_currentMessage , m_currentMessageBits , m_nSubMode ) ;
2018-09-06 15:30:33 -04:00
out < < time . toString ( " yyyy-MM-dd hh:mm:ss " )
2018-02-08 21:28:33 -05:00
< < " Transmitting " < < qSetRealNumberPrecision ( 12 ) < < ( m_freqNominal / 1.e6 )
2018-12-01 16:51:14 -05:00
< < " MHz " < < " JS8 "
2024-08-31 09:40:28 -07:00
< < " : " < < dt . message ( ) < < Qt : : endl ;
2018-02-08 21:28:33 -05:00
f . close ( ) ;
}
else
{
2024-10-10 08:41:40 -07:00
QTimer : : singleShot ( 0 , [
this ,
message = tr ( " Cannot open \" %1 \" for append: %2 " ) . arg ( f . fileName ( ) ) . arg ( f . errorString ( ) )
] {
MessageBox : : warning_message ( this , tr ( " Log File Error " ) , message ) ;
} ) ;
2018-02-08 21:28:33 -05:00
}
}
2020-01-13 11:41:22 -05:00
void MainWindow : : writeAllTxt ( QString message , int bits )
{
if ( ! m_config . write_logs ( ) ) {
return ;
}
// Write decoded text to file "ALL.TXT".
QFile f { m_config . writeable_data_dir ( ) . absoluteFilePath ( " ALL.TXT " ) } ;
if ( f . open ( QIODevice : : WriteOnly | QIODevice : : Text | QIODevice : : Append ) ) {
QTextStream out ( & f ) ;
if ( m_RxLog = = 1 ) {
out < < DriftingDateTime : : currentDateTimeUtc ( ) . toString ( " yyyy-MM-dd hh:mm:ss " )
< < " " < < qSetRealNumberPrecision ( 12 ) < < ( m_freqNominal / 1.e6 ) < < " MHz "
2024-08-31 09:40:28 -07:00
< < " JS8 " < < Qt : : endl ;
2020-01-13 11:41:22 -05:00
m_RxLog = 0 ;
}
auto dt = DecodedText ( message , bits , m_nSubMode ) ;
2024-08-31 09:40:28 -07:00
out < < dt . message ( ) < < Qt : : endl ;
2020-01-13 11:41:22 -05:00
f . close ( ) ;
} else {
MessageBox : : warning_message ( this , tr ( " File Open Error " )
, tr ( " Cannot open \" %1 \" for append: %2 " )
. arg ( f . fileName ( ) ) . arg ( f . errorString ( ) ) ) ;
}
}
void MainWindow : : writeMsgTxt ( QString message , int snr )
{
if ( ! m_config . write_logs ( ) ) {
return ;
}
// Write decoded text to file "DIRECTED.TXT".
QFile f { m_config . writeable_data_dir ( ) . absoluteFilePath ( " DIRECTED.TXT " ) } ;
if ( f . open ( QIODevice : : WriteOnly | QIODevice : : Text | QIODevice : : Append ) ) {
QTextStream out ( & f ) ;
QStringList output = {
DriftingDateTime : : currentDateTimeUtc ( ) . toString ( " yyyy-MM-dd hh:mm:ss " ) ,
Radio : : frequency_MHz_string ( m_freqNominal ) ,
2024-10-20 13:54:14 -07:00
QString : : number ( freq ( ) ) ,
2020-01-13 11:41:22 -05:00
Varicode : : formatSNR ( snr ) ,
message
} ;
2024-08-31 09:40:28 -07:00
out < < output . join ( " \t " ) < < Qt : : endl ;
2020-01-13 11:41:22 -05:00
f . close ( ) ;
} else {
MessageBox : : warning_message ( this , tr ( " File Open Error " )
, tr ( " Cannot open \" %1 \" for append: %2 " )
. arg ( f . fileName ( ) ) . arg ( f . errorString ( ) ) ) ;
}
}