Merged master 8748
This commit is contained in:
@@ -1,422 +0,0 @@
|
||||
subroutine multimode_decoder(ss,id2,params,nfsample)
|
||||
|
||||
!$ use omp_lib
|
||||
use prog_args
|
||||
use timer_module, only: timer
|
||||
use jt4_decode
|
||||
use jt65_decode
|
||||
use jt9_decode
|
||||
use ft8_decode
|
||||
|
||||
include 'jt9com.f90'
|
||||
include 'timer_common.inc'
|
||||
|
||||
type, extends(jt4_decoder) :: counting_jt4_decoder
|
||||
integer :: decoded
|
||||
end type counting_jt4_decoder
|
||||
|
||||
type, extends(jt65_decoder) :: counting_jt65_decoder
|
||||
integer :: decoded
|
||||
end type counting_jt65_decoder
|
||||
|
||||
type, extends(jt9_decoder) :: counting_jt9_decoder
|
||||
integer :: decoded
|
||||
end type counting_jt9_decoder
|
||||
|
||||
type, extends(ft8_decoder) :: counting_ft8_decoder
|
||||
integer :: decoded
|
||||
end type counting_ft8_decoder
|
||||
|
||||
real ss(184,NSMAX)
|
||||
logical baddata,newdat65,newdat9,single_decode,bVHF,bad0
|
||||
integer*2 id2(NTMAX*12000)
|
||||
type(params_block) :: params
|
||||
real*4 dd(NTMAX*12000)
|
||||
save
|
||||
type(counting_jt4_decoder) :: my_jt4
|
||||
type(counting_jt65_decoder) :: my_jt65
|
||||
type(counting_jt9_decoder) :: my_jt9
|
||||
type(counting_ft8_decoder) :: my_ft8
|
||||
|
||||
! initialize decode counts
|
||||
my_jt4%decoded = 0
|
||||
my_jt65%decoded = 0
|
||||
my_jt9%decoded = 0
|
||||
my_ft8%decoded = 0
|
||||
|
||||
single_decode=iand(params%nexp_decode,32).ne.0
|
||||
bVHF=iand(params%nexp_decode,64).ne.0
|
||||
if(mod(params%nranera,2).eq.0) ntrials=10**(params%nranera/2)
|
||||
if(mod(params%nranera,2).eq.1) ntrials=3*10**(params%nranera/2)
|
||||
if(params%nranera.eq.0) ntrials=0
|
||||
|
||||
nfail=0
|
||||
10 if (params%nagain) then
|
||||
open(13,file=trim(temp_dir)//'/decoded.txt',status='unknown', &
|
||||
position='append',iostat=ios)
|
||||
else
|
||||
open(13,file=trim(temp_dir)//'/decoded.txt',status='unknown', &
|
||||
iostat=ios)
|
||||
end if
|
||||
if(ios.ne.0) then
|
||||
nfail=nfail+1
|
||||
if(nfail.le.3) then
|
||||
call sleep_msec(100)
|
||||
go to 10
|
||||
endif
|
||||
endif
|
||||
|
||||
if(params%nmode.eq.8) then
|
||||
! We're in FT8 mode
|
||||
call timer('decft8 ',0)
|
||||
call my_ft8%decode(ft8_decoded,ss,id2,params%nfqso, &
|
||||
newdat9,params%npts8,params%nutc,params%nfa,params%nfsplit, &
|
||||
params%nfb,params%ntol,params%nzhsym,logical(params%nagain), &
|
||||
params%ndepth,params%nmode,params%nsubmode,params%nexp_decode)
|
||||
call timer('decft8 ',1)
|
||||
go to 800
|
||||
endif
|
||||
|
||||
rms=sqrt(dot_product(float(id2(300000:310000)), &
|
||||
float(id2(300000:310000)))/10000.0)
|
||||
if(rms.lt.2.0) go to 800
|
||||
|
||||
! Zap data at start that might come from T/R switching transient?
|
||||
nadd=100
|
||||
k=0
|
||||
bad0=.false.
|
||||
do i=1,240
|
||||
sq=0.
|
||||
do n=1,nadd
|
||||
k=k+1
|
||||
sq=sq + float(id2(k))**2
|
||||
enddo
|
||||
rms=sqrt(sq/nadd)
|
||||
if(rms.gt.10000.0) then
|
||||
bad0=.true.
|
||||
kbad=k
|
||||
rmsbad=rms
|
||||
endif
|
||||
enddo
|
||||
if(bad0) then
|
||||
nz=min(NTMAX*12000,kbad+100)
|
||||
! id2(1:nz)=0 ! temporarily disabled as it can breaak the JT9 decoder, maybe others
|
||||
endif
|
||||
|
||||
if(params%nmode.eq.4 .or. params%nmode.eq.65) open(14,file=trim(temp_dir)// &
|
||||
'/avemsg.txt',status='unknown')
|
||||
if(params%nmode.eq.164) open(17,file=trim(temp_dir)//'/red.dat', &
|
||||
status='unknown')
|
||||
|
||||
if(params%nmode.eq.4) then
|
||||
jz=52*nfsample
|
||||
if(params%newdat) then
|
||||
if(nfsample.eq.12000) call wav11(id2,jz,dd)
|
||||
if(nfsample.eq.11025) dd(1:jz)=id2(1:jz)
|
||||
endif
|
||||
call my_jt4%decode(jt4_decoded,dd,jz,params%nutc,params%nfqso, &
|
||||
params%ntol,params%emedelay,params%dttol,logical(params%nagain), &
|
||||
params%ndepth,logical(params%nclearave),params%minsync, &
|
||||
params%minw,params%nsubmode,params%mycall,params%hiscall, &
|
||||
params%hisgrid,params%nlist,params%listutc,jt4_average)
|
||||
go to 800
|
||||
endif
|
||||
|
||||
npts65=52*12000
|
||||
if(params%nmode.eq.164) npts65=54*12000
|
||||
if(baddata(id2,npts65)) then
|
||||
nsynced=0
|
||||
ndecoded=0
|
||||
go to 800
|
||||
endif
|
||||
|
||||
ntol65=params%ntol !### is this OK? ###
|
||||
newdat65=params%newdat
|
||||
newdat9=params%newdat
|
||||
|
||||
!$call omp_set_dynamic(.true.)
|
||||
!$omp parallel sections num_threads(2) copyin(/timer_private/) shared(ndecoded) if(.true.) !iif() needed on Mac
|
||||
|
||||
!$omp section
|
||||
if(params%nmode.eq.65 .or. params%nmode.eq.164 .or. &
|
||||
(params%nmode.eq.(65+9) .and. params%ntxmode.eq.65)) then
|
||||
! We're in JT65 or QRA64 mode, or should do JT65 first
|
||||
|
||||
if(newdat65) dd(1:npts65)=id2(1:npts65)
|
||||
nf1=params%nfa
|
||||
nf2=params%nfb
|
||||
call timer('jt65a ',0)
|
||||
call my_jt65%decode(jt65_decoded,dd,npts65,newdat65,params%nutc, &
|
||||
nf1,nf2,params%nfqso,ntol65,params%nsubmode,params%minsync, &
|
||||
logical(params%nagain),params%n2pass,logical(params%nrobust), &
|
||||
ntrials,params%naggressive,params%ndepth,params%emedelay, &
|
||||
logical(params%nclearave),params%mycall,params%hiscall, &
|
||||
params%hisgrid,params%nexp_decode)
|
||||
call timer('jt65a ',1)
|
||||
|
||||
else if(params%nmode.eq.9 .or. (params%nmode.eq.(65+9) .and. params%ntxmode.eq.9)) then
|
||||
! We're in JT9 mode, or should do JT9 first
|
||||
call timer('decjt9 ',0)
|
||||
call my_jt9%decode(jt9_decoded,ss,id2,params%nfqso, &
|
||||
newdat9,params%npts8,params%nfa,params%nfsplit,params%nfb, &
|
||||
params%ntol,params%nzhsym,logical(params%nagain),params%ndepth, &
|
||||
params%nmode,params%nsubmode,params%nexp_decode)
|
||||
call timer('decjt9 ',1)
|
||||
endif
|
||||
|
||||
!$omp section
|
||||
if(params%nmode.eq.(65+9)) then !Do the other mode (we're in dual mode)
|
||||
if (params%ntxmode.eq.9) then
|
||||
if(newdat65) dd(1:npts65)=id2(1:npts65)
|
||||
nf1=params%nfa
|
||||
nf2=params%nfb
|
||||
call timer('jt65a ',0)
|
||||
call my_jt65%decode(jt65_decoded,dd,npts65,newdat65,params%nutc, &
|
||||
nf1,nf2,params%nfqso,ntol65,params%nsubmode,params%minsync, &
|
||||
logical(params%nagain),params%n2pass,logical(params%nrobust), &
|
||||
ntrials,params%naggressive,params%ndepth,params%emedelay, &
|
||||
logical(params%nclearave),params%mycall,params%hiscall, &
|
||||
params%hisgrid,params%nexp_decode)
|
||||
call timer('jt65a ',1)
|
||||
else
|
||||
call timer('decjt9 ',0)
|
||||
call my_jt9%decode(jt9_decoded,ss,id2,params%nfqso, &
|
||||
newdat9,params%npts8,params%nfa,params%nfsplit,params%nfb, &
|
||||
params%ntol,params%nzhsym,logical(params%nagain), &
|
||||
params%ndepth,params%nmode,params%nsubmode,params%nexp_decode)
|
||||
call timer('decjt9 ',1)
|
||||
end if
|
||||
endif
|
||||
|
||||
!$omp end parallel sections
|
||||
|
||||
! JT65 is not yet producing info for nsynced, ndecoded.
|
||||
800 ndecoded = my_jt4%decoded + my_jt65%decoded + my_jt9%decoded + my_ft8%decoded
|
||||
write(*,1010) nsynced,ndecoded
|
||||
1010 format('<DecodeFinished>',2i4)
|
||||
call flush(6)
|
||||
close(13)
|
||||
if(params%nmode.eq.4 .or. params%nmode.eq.65) close(14)
|
||||
|
||||
return
|
||||
|
||||
contains
|
||||
|
||||
subroutine jt4_decoded(this,snr,dt,freq,have_sync,sync,is_deep, &
|
||||
decoded0,qual,ich,is_average,ave)
|
||||
implicit none
|
||||
class(jt4_decoder), intent(inout) :: this
|
||||
integer, intent(in) :: snr
|
||||
real, intent(in) :: dt
|
||||
integer, intent(in) :: freq
|
||||
logical, intent(in) :: have_sync
|
||||
logical, intent(in) :: is_deep
|
||||
character(len=1), intent(in) :: sync
|
||||
character(len=22), intent(in) :: decoded0
|
||||
real, intent(in) :: qual
|
||||
integer, intent(in) :: ich
|
||||
logical, intent(in) :: is_average
|
||||
integer, intent(in) :: ave
|
||||
|
||||
character*22 decoded
|
||||
character*3 cflags
|
||||
|
||||
if(ich.eq.-99) stop !Silence compiler warning
|
||||
if (have_sync) then
|
||||
decoded=decoded0
|
||||
cflags=' '
|
||||
if(decoded.ne.' ') cflags='f '
|
||||
if(is_deep) then
|
||||
cflags(1:2)='d1'
|
||||
write(cflags(3:3),'(i1)') min(int(qual),9)
|
||||
if(qual.ge.10.0) cflags(3:3)='*'
|
||||
if(qual.lt.3.0) decoded(22:22)='?'
|
||||
endif
|
||||
if(is_average) then
|
||||
write(cflags(2:2),'(i1)') min(ave,9)
|
||||
if(ave.ge.10) cflags(2:2)='*'
|
||||
endif
|
||||
write(*,1000) params%nutc,snr,dt,freq,sync,decoded,cflags
|
||||
1000 format(i4.4,i4,f5.1,i5,1x,'$',a1,1x,a22,1x,a3)
|
||||
else
|
||||
write(*,1000) params%nutc,snr,dt,freq
|
||||
end if
|
||||
|
||||
select type(this)
|
||||
type is (counting_jt4_decoder)
|
||||
this%decoded = this%decoded + 1
|
||||
end select
|
||||
end subroutine jt4_decoded
|
||||
|
||||
subroutine jt4_average (this, used, utc, sync, dt, freq, flip)
|
||||
implicit none
|
||||
class(jt4_decoder), intent(inout) :: this
|
||||
logical, intent(in) :: used
|
||||
integer, intent(in) :: utc
|
||||
real, intent(in) :: sync
|
||||
real, intent(in) :: dt
|
||||
integer, intent(in) :: freq
|
||||
logical, intent(in) :: flip
|
||||
character(len=1) :: cused, csync
|
||||
|
||||
cused = '.'
|
||||
csync = '*'
|
||||
if (used) cused = '$'
|
||||
if (flip) csync = '$'
|
||||
write(14,1000) cused,utc,sync,dt,freq,csync
|
||||
1000 format(a1,i5.4,f6.1,f6.2,i6,1x,a1)
|
||||
end subroutine jt4_average
|
||||
|
||||
subroutine jt65_decoded(this,sync,snr,dt,freq,drift,nflip,width, &
|
||||
decoded0,ft,qual,nsmo,nsum,minsync)
|
||||
|
||||
use jt65_decode
|
||||
implicit none
|
||||
|
||||
class(jt65_decoder), intent(inout) :: this
|
||||
real, intent(in) :: sync
|
||||
integer, intent(in) :: snr
|
||||
real, intent(in) :: dt
|
||||
integer, intent(in) :: freq
|
||||
integer, intent(in) :: drift
|
||||
integer, intent(in) :: nflip
|
||||
real, intent(in) :: width
|
||||
character(len=22), intent(in) :: decoded0
|
||||
integer, intent(in) :: ft
|
||||
integer, intent(in) :: qual
|
||||
integer, intent(in) :: nsmo
|
||||
integer, intent(in) :: nsum
|
||||
integer, intent(in) :: minsync
|
||||
|
||||
integer i,nft
|
||||
logical is_deep,is_average
|
||||
character decoded*22,csync*2,cflags*3
|
||||
|
||||
if(width.eq.-9999.0) stop !Silence compiler warning
|
||||
!$omp critical(decode_results)
|
||||
decoded=decoded0
|
||||
cflags=' '
|
||||
is_deep=ft.eq.2
|
||||
|
||||
if(ft.ge.80) then !QRA64 mode
|
||||
nft=ft-100
|
||||
csync=': '
|
||||
if(sync-3.4.ge.float(minsync) .or. nft.ge.0) csync=':*'
|
||||
if(nft.lt.0) then
|
||||
write(*,1009) params%nutc,snr,dt,freq,csync,decoded
|
||||
else
|
||||
write(*,1009) params%nutc,snr,dt,freq,csync,decoded,nft
|
||||
1009 format(i4.4,i4,f5.1,i5,1x,a2,1x,a22,i2)
|
||||
endif
|
||||
write(13,1011) params%nutc,nint(sync),snr,dt,float(freq),drift, &
|
||||
decoded,nft
|
||||
1011 format(i4.4,i4,i5,f6.2,f8.0,i4,3x,a22,' QRA64',i3)
|
||||
go to 100
|
||||
endif
|
||||
|
||||
if(ft.eq.0 .and. minsync.ge.0 .and. int(sync).lt.minsync) then
|
||||
write(*,1010) params%nutc,snr,dt,freq
|
||||
else
|
||||
is_average=nsum.ge.2
|
||||
if(bVHF .and. ft.gt.0) then
|
||||
cflags='f '
|
||||
if(is_deep) then
|
||||
cflags(1:2)='d1'
|
||||
write(cflags(3:3),'(i1)') min(qual,9)
|
||||
if(qual.ge.10) cflags(3:3)='*'
|
||||
if(qual.lt.3) decoded(22:22)='?'
|
||||
endif
|
||||
if(is_average) then
|
||||
write(cflags(2:2),'(i1)') min(nsum,9)
|
||||
if(nsum.ge.10) cflags(2:2)='*'
|
||||
endif
|
||||
endif
|
||||
csync='# '
|
||||
i=0
|
||||
if(bVHF .and. nflip.ne.0 .and. &
|
||||
sync.ge.max(0.0,float(minsync))) then
|
||||
csync='#*'
|
||||
if(nflip.eq.-1) then
|
||||
csync='##'
|
||||
if(decoded.ne.' ') then
|
||||
do i=22,1,-1
|
||||
if(decoded(i:i).ne.' ') exit
|
||||
enddo
|
||||
if(i.gt.18) i=18
|
||||
decoded(i+2:i+4)='OOO'
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
write(*,1010) params%nutc,snr,dt,freq,csync,decoded,cflags
|
||||
1010 format(i4.4,i4,f5.1,i5,1x,a2,1x,a22,1x,a3)
|
||||
endif
|
||||
write(13,1012) params%nutc,nint(sync),snr,dt,float(freq),drift, &
|
||||
decoded,ft,nsum,nsmo
|
||||
1012 format(i4.4,i4,i5,f6.2,f8.0,i4,3x,a22,' JT65',3i3)
|
||||
|
||||
100 call flush(6)
|
||||
|
||||
!$omp end critical(decode_results)
|
||||
select type(this)
|
||||
type is (counting_jt65_decoder)
|
||||
this%decoded = this%decoded + 1
|
||||
end select
|
||||
end subroutine jt65_decoded
|
||||
|
||||
subroutine jt9_decoded (this, sync, snr, dt, freq, drift, decoded)
|
||||
use jt9_decode
|
||||
implicit none
|
||||
|
||||
class(jt9_decoder), intent(inout) :: this
|
||||
real, intent(in) :: sync
|
||||
integer, intent(in) :: snr
|
||||
real, intent(in) :: dt
|
||||
real, intent(in) :: freq
|
||||
integer, intent(in) :: drift
|
||||
character(len=22), intent(in) :: decoded
|
||||
|
||||
!$omp critical(decode_results)
|
||||
write(*,1000) params%nutc,snr,dt,nint(freq),decoded
|
||||
1000 format(i4.4,i4,f5.1,i5,1x,'@ ',1x,a22)
|
||||
write(13,1002) params%nutc,nint(sync),snr,dt,freq,drift,decoded
|
||||
1002 format(i4.4,i4,i5,f6.1,f8.0,i4,3x,a22,' JT9')
|
||||
call flush(6)
|
||||
!$omp end critical(decode_results)
|
||||
select type(this)
|
||||
type is (counting_jt9_decoder)
|
||||
this%decoded = this%decoded + 1
|
||||
end select
|
||||
end subroutine jt9_decoded
|
||||
|
||||
subroutine ft8_decoded (this,sync,snr,dt,freq,nbadcrc,decoded)
|
||||
use ft8_decode
|
||||
implicit none
|
||||
|
||||
class(ft8_decoder), intent(inout) :: this
|
||||
real, intent(in) :: sync
|
||||
integer, intent(in) :: snr
|
||||
real, intent(in) :: dt
|
||||
real, intent(in) :: freq
|
||||
integer, intent(in) :: nbadcrc
|
||||
character(len=22), intent(in) :: decoded
|
||||
|
||||
!### !$omp critical(decode_results)
|
||||
if(nbadcrc.eq.0) then
|
||||
write(*,1000) params%nutc,snr,dt,nint(freq),decoded
|
||||
1000 format(i6.6,i4,f5.1,i5,' ~ ',1x,a22)
|
||||
write(13,1002) params%nutc,nint(sync),snr,dt,freq,0,decoded
|
||||
1002 format(i6.6,i4,i5,f6.1,f8.0,i4,3x,a22,' FT8')
|
||||
call flush(6)
|
||||
call flush(13)
|
||||
endif
|
||||
!### !$omp end critical(decode_results)
|
||||
|
||||
select type(this)
|
||||
type is (counting_ft8_decoder)
|
||||
this%decoded = this%decoded + 1
|
||||
end select
|
||||
|
||||
return
|
||||
end subroutine ft8_decoded
|
||||
|
||||
end subroutine multimode_decoder
|
||||
@@ -1,172 +0,0 @@
|
||||
#include "decodedtext.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <QRegularExpression>
|
||||
|
||||
extern "C" {
|
||||
bool stdmsg_(const char* msg, int len);
|
||||
}
|
||||
|
||||
QString DecodedText::CQersCall()
|
||||
{
|
||||
QRegularExpression callsign_re {R"(\s(CQ|DE|QRZ)(\s?DX|\s([A-Z]{2}|\d{3}))?\s(?<callsign>[A-Z0-9/]{2,})(\s[A-R]{2}[0-9]{2})?)"};
|
||||
return callsign_re.match (_string).captured ("callsign");
|
||||
}
|
||||
|
||||
|
||||
bool DecodedText::isJT65()
|
||||
{
|
||||
return _string.indexOf("#") == column_mode + padding_;
|
||||
}
|
||||
|
||||
bool DecodedText::isJT9()
|
||||
{
|
||||
return _string.indexOf("@") == column_mode + padding_;
|
||||
}
|
||||
|
||||
bool DecodedText::isTX()
|
||||
{
|
||||
int i = _string.indexOf("Tx");
|
||||
return (i >= 0 && i < 15); // TODO guessing those numbers. Does Tx ever move?
|
||||
}
|
||||
|
||||
bool DecodedText::isLowConfidence ()
|
||||
{
|
||||
return QChar {'?'} == _string.mid (padding_ + column_qsoText + 21, 1);
|
||||
}
|
||||
|
||||
int DecodedText::frequencyOffset()
|
||||
{
|
||||
return _string.mid(column_freq + padding_,4).toInt();
|
||||
}
|
||||
|
||||
int DecodedText::snr()
|
||||
{
|
||||
int i1=_string.indexOf(" ")+1;
|
||||
return _string.mid(i1,3).toInt();
|
||||
}
|
||||
|
||||
float DecodedText::dt()
|
||||
{
|
||||
return _string.mid(column_dt + padding_,5).toFloat();
|
||||
}
|
||||
|
||||
/*
|
||||
2343 -11 0.8 1259 # YV6BFE F6GUU R-08
|
||||
2343 -19 0.3 718 # VE6WQ SQ2NIJ -14
|
||||
2343 -7 0.3 815 # KK4DSD W7VP -16
|
||||
2343 -13 0.1 3627 @ CT1FBK IK5YZT R+02
|
||||
|
||||
0605 Tx 1259 # CQ VK3ACF QF22
|
||||
*/
|
||||
|
||||
// find and extract any report. Returns true if this is a standard message
|
||||
bool DecodedText::report(QString const& myBaseCall, QString const& dxBaseCall, /*mod*/QString& report)
|
||||
{
|
||||
QString msg=_string.mid(column_qsoText + padding_).trimmed();
|
||||
if(msg.length() < 1) return false;
|
||||
msg = msg.left (22).remove (QRegularExpression {"[<>]"});
|
||||
int i1=msg.indexOf('\r');
|
||||
if (i1>0)
|
||||
msg=msg.left (i1-1);
|
||||
bool b = stdmsg_ ((msg + " ").toLatin1().constData(),22); // stdmsg is a fortran routine that packs the text, unpacks it and compares the result
|
||||
|
||||
QStringList w=msg.split(" ",QString::SkipEmptyParts);
|
||||
if(w.size ()
|
||||
&& b && (w[0] == myBaseCall
|
||||
|| w[0].endsWith ("/" + myBaseCall)
|
||||
|| w[0].startsWith (myBaseCall + "/")
|
||||
|| (w.size () > 1 && !dxBaseCall.isEmpty ()
|
||||
&& (w[1] == dxBaseCall
|
||||
|| w[1].endsWith ("/" + dxBaseCall)
|
||||
|| w[1].startsWith (dxBaseCall + "/")))))
|
||||
{
|
||||
QString tt="";
|
||||
if(w.size() > 2) tt=w[2];
|
||||
bool ok;
|
||||
i1=tt.toInt(&ok);
|
||||
if (ok and i1>=-50 and i1<50)
|
||||
{
|
||||
report = tt;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tt.mid(0,1)=="R")
|
||||
{
|
||||
i1=tt.mid(1).toInt(&ok);
|
||||
if(ok and i1>=-50 and i1<50)
|
||||
{
|
||||
report = tt.mid(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
// get the first text word, usually the call
|
||||
QString DecodedText::call()
|
||||
{
|
||||
auto call = _string;
|
||||
call = call.replace (QRegularExpression {" CQ ([A-Z]{2,2}|[0-9]{3,3}) "}, " CQ_\\1 ").mid (column_qsoText + padding_);
|
||||
int i = call.indexOf(" ");
|
||||
return call.mid(0,i);
|
||||
}
|
||||
|
||||
// get the second word, most likely the de call and the third word, most likely grid
|
||||
void DecodedText::deCallAndGrid(/*out*/QString& call, QString& grid)
|
||||
{
|
||||
auto msg = _string;
|
||||
if(msg.mid(4,1)!=" ") msg=msg.mid(0,4)+msg.mid(6,-1); //Remove seconds from UTC
|
||||
msg = msg.replace (QRegularExpression {" CQ ([A-Z]{2,2}|[0-9]{3,3}) "}, " CQ_\\1 ").mid (column_qsoText + padding_);
|
||||
int i1 = msg.indexOf (" ");
|
||||
call = msg.mid (i1 + 1);
|
||||
int i2 = call.indexOf (" ");
|
||||
if (" R " == call.mid (i2, 3)) // MSK144 contest mode report
|
||||
{
|
||||
grid = call.mid (i2 + 3, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
grid = call.mid (i2 + 1, 4);
|
||||
}
|
||||
call = call.left (i2).replace (">", "");
|
||||
}
|
||||
|
||||
int DecodedText::timeInSeconds()
|
||||
{
|
||||
return 60*_string.mid(column_time,2).toInt() + _string.mid(2,2).toInt();
|
||||
}
|
||||
|
||||
/*
|
||||
2343 -11 0.8 1259 # YV6BFE F6GUU R-08
|
||||
2343 -19 0.3 718 # VE6WQ SQ2NIJ -14
|
||||
2343 -7 0.3 815 # KK4DSD W7VP -16
|
||||
2343 -13 0.1 3627 @ CT1FBK IK5YZT R+02
|
||||
|
||||
0605 Tx 1259 # CQ VK3ACF QF22
|
||||
*/
|
||||
|
||||
QString DecodedText::report() // returns a string of the SNR field with a leading + or - followed by two digits
|
||||
{
|
||||
int sr = snr();
|
||||
if (sr<-50)
|
||||
sr = -50;
|
||||
else
|
||||
if (sr > 49)
|
||||
sr = 49;
|
||||
|
||||
QString rpt;
|
||||
rpt.sprintf("%d",abs(sr));
|
||||
if (sr > 9)
|
||||
rpt = "+" + rpt;
|
||||
else
|
||||
if (sr >= 0)
|
||||
rpt = "+0" + rpt;
|
||||
else
|
||||
if (sr >= -9)
|
||||
rpt = "-0" + rpt;
|
||||
else
|
||||
rpt = "-" + rpt;
|
||||
return rpt;
|
||||
}
|
||||
@@ -1,536 +0,0 @@
|
||||
#include "StationList.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include <QMetaType>
|
||||
#include <QAbstractTableModel>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QStringList>
|
||||
#include <QMimeData>
|
||||
#include <QDataStream>
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QDebugStateSaver>
|
||||
|
||||
#include "pimpl_impl.hpp"
|
||||
|
||||
#include "Radio.hpp"
|
||||
#include "Bands.hpp"
|
||||
#include "FrequencyList.hpp"
|
||||
|
||||
#if !defined (QT_NO_DEBUG_STREAM)
|
||||
QDebug operator << (QDebug debug, StationList::Station const& station)
|
||||
{
|
||||
QDebugStateSaver saver {debug};
|
||||
debug.nospace () << "Station("
|
||||
<< station.band_name_ << ", "
|
||||
<< station.offset_ << ", "
|
||||
<< station.antenna_description_ << ')';
|
||||
return debug;
|
||||
}
|
||||
#endif
|
||||
|
||||
QDataStream& operator << (QDataStream& os, StationList::Station const& station)
|
||||
{
|
||||
return os << station.band_name_
|
||||
<< station.offset_
|
||||
<< station.antenna_description_;
|
||||
}
|
||||
|
||||
QDataStream& operator >> (QDataStream& is, StationList::Station& station)
|
||||
{
|
||||
return is >> station.band_name_
|
||||
>> station.offset_
|
||||
>> station.antenna_description_;
|
||||
}
|
||||
|
||||
|
||||
class StationList::impl final
|
||||
: public QAbstractTableModel
|
||||
{
|
||||
public:
|
||||
impl (Bands const * bands, Stations stations, QObject * parent)
|
||||
: QAbstractTableModel {parent}
|
||||
, bands_ {bands}
|
||||
, stations_ {stations}
|
||||
{
|
||||
}
|
||||
|
||||
Stations station_list (Stations);
|
||||
QModelIndex add (Station);
|
||||
FrequencyDelta offset (Frequency) const;
|
||||
|
||||
// Implement the QAbstractTableModel interface.
|
||||
int rowCount (QModelIndex const& parent = QModelIndex {}) const override;
|
||||
int columnCount (QModelIndex const& parent = QModelIndex {}) const override;
|
||||
Qt::ItemFlags flags (QModelIndex const& = QModelIndex {}) const override;
|
||||
QVariant data (QModelIndex const&, int role) const override;
|
||||
QVariant headerData (int section, Qt::Orientation, int = Qt::DisplayRole) const override;
|
||||
bool setData (QModelIndex const&, QVariant const& value, int role = Qt::EditRole) override;
|
||||
bool removeRows (int row, int count, QModelIndex const& parent = QModelIndex {}) override;
|
||||
bool insertRows (int row, int count, QModelIndex const& parent = QModelIndex {}) override;
|
||||
Qt::DropActions supportedDropActions () const override;
|
||||
QStringList mimeTypes () const override;
|
||||
QMimeData * mimeData (QModelIndexList const&) const override;
|
||||
bool dropMimeData (QMimeData const *, Qt::DropAction, int row, int column, QModelIndex const& parent) override;
|
||||
|
||||
// Helper method for band validation.
|
||||
QModelIndex first_matching_band (QString const& band_name) const
|
||||
{
|
||||
// find first exact match in bands
|
||||
auto matches = bands_->match (bands_->index (0, 0)
|
||||
, Qt::DisplayRole
|
||||
, band_name
|
||||
, 1
|
||||
, Qt::MatchExactly);
|
||||
return matches.isEmpty () ? QModelIndex {} : matches.first ();
|
||||
}
|
||||
|
||||
static int constexpr num_columns {3};
|
||||
static auto constexpr mime_type = "application/wsjt.antenna-descriptions";
|
||||
|
||||
Bands const * bands_;
|
||||
Stations stations_;
|
||||
};
|
||||
|
||||
StationList::StationList (Bands const * bands, QObject * parent)
|
||||
: StationList {bands, {}, parent}
|
||||
{
|
||||
}
|
||||
|
||||
StationList::StationList (Bands const * bands, Stations stations, QObject * parent)
|
||||
: QSortFilterProxyModel {parent}
|
||||
, m_ {bands, stations, parent}
|
||||
{
|
||||
setSourceModel (&*m_);
|
||||
setSortRole (SortRole);
|
||||
}
|
||||
|
||||
StationList::~StationList ()
|
||||
{
|
||||
}
|
||||
|
||||
auto StationList::station_list (Stations stations) -> Stations
|
||||
{
|
||||
return m_->station_list (stations);
|
||||
}
|
||||
|
||||
auto StationList::station_list () const -> Stations const&
|
||||
{
|
||||
return m_->stations_;
|
||||
}
|
||||
|
||||
QModelIndex StationList::add (Station s)
|
||||
{
|
||||
return mapFromSource (m_->add (s));
|
||||
}
|
||||
|
||||
bool StationList::remove (Station s)
|
||||
{
|
||||
auto row = m_->stations_.indexOf (s);
|
||||
if (0 > row)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return removeRow (row);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
bool row_is_higher (QModelIndex const& lhs, QModelIndex const& rhs)
|
||||
{
|
||||
return lhs.row () > rhs.row ();
|
||||
}
|
||||
}
|
||||
|
||||
bool StationList::removeDisjointRows (QModelIndexList rows)
|
||||
{
|
||||
bool result {true};
|
||||
|
||||
// We must work with source model indexes because we don't want row
|
||||
// removes to invalidate model indexes we haven't yet processed. We
|
||||
// achieve that by processing them in decending row order.
|
||||
for (int r = 0; r < rows.size (); ++r)
|
||||
{
|
||||
rows[r] = mapToSource (rows[r]);
|
||||
}
|
||||
|
||||
// reverse sort by row
|
||||
qSort (rows.begin (), rows.end (), row_is_higher);
|
||||
Q_FOREACH (auto index, rows)
|
||||
{
|
||||
if (result && !m_->removeRow (index.row ()))
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto StationList::offset (Frequency f) const -> FrequencyDelta
|
||||
{
|
||||
return m_->offset (f);
|
||||
}
|
||||
|
||||
|
||||
auto StationList::impl::station_list (Stations stations) -> Stations
|
||||
{
|
||||
beginResetModel ();
|
||||
std::swap (stations_, stations);
|
||||
endResetModel ();
|
||||
return stations;
|
||||
}
|
||||
|
||||
QModelIndex StationList::impl::add (Station s)
|
||||
{
|
||||
// Any band that isn't in the list may be added
|
||||
if (!stations_.contains (s))
|
||||
{
|
||||
auto row = stations_.size ();
|
||||
|
||||
beginInsertRows (QModelIndex {}, row, row);
|
||||
stations_.append (s);
|
||||
endInsertRows ();
|
||||
|
||||
return index (row, 0);
|
||||
}
|
||||
|
||||
return QModelIndex {};
|
||||
}
|
||||
|
||||
auto StationList::impl::offset (Frequency f) const -> FrequencyDelta
|
||||
{
|
||||
// Lookup band for frequency
|
||||
auto const& band = bands_->find (f);
|
||||
if (!band.isEmpty ())
|
||||
{
|
||||
// Lookup station for band
|
||||
for (int i = 0; i < stations_.size (); ++i)
|
||||
{
|
||||
if (stations_[i].band_name_ == band)
|
||||
{
|
||||
return stations_[i].offset_;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0; // no offset
|
||||
}
|
||||
|
||||
int StationList::impl::rowCount (QModelIndex const& parent) const
|
||||
{
|
||||
return parent.isValid () ? 0 : stations_.size ();
|
||||
}
|
||||
|
||||
int StationList::impl::columnCount (QModelIndex const& parent) const
|
||||
{
|
||||
return parent.isValid () ? 0 : num_columns;
|
||||
}
|
||||
|
||||
Qt::ItemFlags StationList::impl::flags (QModelIndex const& index) const
|
||||
{
|
||||
auto result = QAbstractTableModel::flags (index);
|
||||
|
||||
auto row = index.row ();
|
||||
auto column = index.column ();
|
||||
|
||||
if (index.isValid ()
|
||||
&& row < stations_.size ()
|
||||
&& column < num_columns)
|
||||
{
|
||||
if (description_column == column)
|
||||
{
|
||||
result |= Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
result |= Qt::ItemIsEditable | Qt::ItemIsDropEnabled;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result |= Qt::ItemIsDropEnabled;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QVariant StationList::impl::data (QModelIndex const& index, int role) const
|
||||
{
|
||||
QVariant item;
|
||||
|
||||
auto row = index.row ();
|
||||
auto column = index.column ();
|
||||
|
||||
if (index.isValid ()
|
||||
&& row < stations_.size ())
|
||||
{
|
||||
switch (column)
|
||||
{
|
||||
case band_column:
|
||||
switch (role)
|
||||
{
|
||||
case SortRole:
|
||||
{
|
||||
// Lookup band.
|
||||
auto band_index = first_matching_band (stations_.at (row).band_name_);
|
||||
// Use the sort role value of the band.
|
||||
item = band_index.data (Bands::SortRole);
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::DisplayRole:
|
||||
case Qt::EditRole:
|
||||
case Qt::AccessibleTextRole:
|
||||
item = stations_.at (row).band_name_;
|
||||
break;
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
case Qt::AccessibleDescriptionRole:
|
||||
item = tr ("Band name");
|
||||
break;
|
||||
|
||||
case Qt::TextAlignmentRole:
|
||||
item = Qt::AlignHCenter + Qt::AlignVCenter;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case offset_column:
|
||||
{
|
||||
auto frequency_offset = stations_.at (row).offset_;
|
||||
switch (role)
|
||||
{
|
||||
case SortRole:
|
||||
case Qt::EditRole:
|
||||
case Qt::AccessibleTextRole:
|
||||
item = frequency_offset;
|
||||
break;
|
||||
|
||||
case Qt::DisplayRole:
|
||||
item = Radio::pretty_frequency_MHz_string (frequency_offset) + " MHz";
|
||||
break;
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
case Qt::AccessibleDescriptionRole:
|
||||
item = tr ("Frequency offset");
|
||||
break;
|
||||
|
||||
case Qt::TextAlignmentRole:
|
||||
item = Qt::AlignRight + Qt::AlignVCenter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case description_column:
|
||||
switch (role)
|
||||
{
|
||||
case SortRole:
|
||||
case Qt::EditRole:
|
||||
case Qt::DisplayRole:
|
||||
case Qt::AccessibleTextRole:
|
||||
item = stations_.at (row).antenna_description_;
|
||||
break;
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
case Qt::AccessibleDescriptionRole:
|
||||
item = tr ("Antenna description");
|
||||
break;
|
||||
|
||||
case Qt::TextAlignmentRole:
|
||||
item = Qt::AlignLeft + Qt::AlignVCenter;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
QVariant StationList::impl::headerData (int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
QVariant header;
|
||||
|
||||
if (Qt::DisplayRole == role && Qt::Horizontal == orientation)
|
||||
{
|
||||
switch (section)
|
||||
{
|
||||
case band_column: header = tr ("Band"); break;
|
||||
case offset_column: header = tr ("Offset"); break;
|
||||
case description_column: header = tr ("Antenna Description"); break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
header = QAbstractTableModel::headerData (section, orientation, role);
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
bool StationList::impl::setData (QModelIndex const& model_index, QVariant const& value, int role)
|
||||
{
|
||||
bool changed {false};
|
||||
|
||||
auto row = model_index.row ();
|
||||
auto size = stations_.size ();
|
||||
if (model_index.isValid ()
|
||||
&& Qt::EditRole == role
|
||||
&& row < size)
|
||||
{
|
||||
QVector<int> roles;
|
||||
roles << role;
|
||||
|
||||
switch (model_index.column ())
|
||||
{
|
||||
case band_column:
|
||||
{
|
||||
// Check if band name is valid.
|
||||
auto band_index = first_matching_band (value.toString ());
|
||||
if (band_index.isValid ())
|
||||
{
|
||||
stations_[row].band_name_ = band_index.data ().toString ();
|
||||
Q_EMIT dataChanged (model_index, model_index, roles);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case offset_column:
|
||||
{
|
||||
if (value.canConvert<FrequencyDelta> ())
|
||||
{
|
||||
FrequencyDelta offset {qvariant_cast<Radio::FrequencyDelta> (value)};
|
||||
if (offset != stations_[row].offset_)
|
||||
{
|
||||
stations_[row].offset_ = offset;
|
||||
Q_EMIT dataChanged (model_index, model_index, roles);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case description_column:
|
||||
stations_[row].antenna_description_ = value.toString ();
|
||||
Q_EMIT dataChanged (model_index, model_index, roles);
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool StationList::impl::removeRows (int row, int count, QModelIndex const& parent)
|
||||
{
|
||||
if (0 < count && (row + count) <= rowCount (parent))
|
||||
{
|
||||
beginRemoveRows (parent, row, row + count - 1);
|
||||
for (auto r = 0; r < count; ++r)
|
||||
{
|
||||
stations_.removeAt (row);
|
||||
}
|
||||
endRemoveRows ();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StationList::impl::insertRows (int row, int count, QModelIndex const& parent)
|
||||
{
|
||||
if (0 < count)
|
||||
{
|
||||
beginInsertRows (parent, row, row + count - 1);
|
||||
for (auto r = 0; r < count; ++r)
|
||||
{
|
||||
stations_.insert (row, Station ());
|
||||
}
|
||||
endInsertRows ();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Qt::DropActions StationList::impl::supportedDropActions () const
|
||||
{
|
||||
return Qt::CopyAction | Qt::MoveAction;
|
||||
}
|
||||
|
||||
QStringList StationList::impl::mimeTypes () const
|
||||
{
|
||||
QStringList types;
|
||||
types << mime_type;
|
||||
types << "application/wsjt.Frequencies";
|
||||
return types;
|
||||
}
|
||||
|
||||
QMimeData * StationList::impl::mimeData (QModelIndexList const& items) const
|
||||
{
|
||||
QMimeData * mime_data = new QMimeData {};
|
||||
QByteArray encoded_data;
|
||||
QDataStream stream {&encoded_data, QIODevice::WriteOnly};
|
||||
|
||||
Q_FOREACH (auto const& item, items)
|
||||
{
|
||||
if (item.isValid ())
|
||||
{
|
||||
stream << QString {data (item, Qt::DisplayRole).toString ()};
|
||||
}
|
||||
}
|
||||
|
||||
mime_data->setData (mime_type, encoded_data);
|
||||
return mime_data;
|
||||
}
|
||||
|
||||
bool StationList::impl::dropMimeData (QMimeData const * data, Qt::DropAction action, int /* row */, int /* column */, QModelIndex const& parent)
|
||||
{
|
||||
if (Qt::IgnoreAction == action)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (parent.isValid ()
|
||||
&& description_column == parent.column ()
|
||||
&& data->hasFormat (mime_type))
|
||||
{
|
||||
QByteArray encoded_data {data->data (mime_type)};
|
||||
QDataStream stream {&encoded_data, QIODevice::ReadOnly};
|
||||
auto dest_index = parent;
|
||||
while (!stream.atEnd ())
|
||||
{
|
||||
QString text;
|
||||
stream >> text;
|
||||
setData (dest_index, text);
|
||||
dest_index = index (dest_index.row () + 1, dest_index.column (), QModelIndex {});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (data->hasFormat ("application/wsjt.Frequencies"))
|
||||
{
|
||||
QByteArray encoded_data {data->data ("application/wsjt.Frequencies")};
|
||||
QDataStream stream {&encoded_data, QIODevice::ReadOnly};
|
||||
while (!stream.atEnd ())
|
||||
{
|
||||
FrequencyList::Item item;
|
||||
stream >> item;
|
||||
auto const& band = bands_->find (item.frequency_);
|
||||
if (stations_.cend () == std::find_if (stations_.cbegin ()
|
||||
, stations_.cend ()
|
||||
, [&band] (Station const& s) {return s.band_name_ == band;}))
|
||||
{
|
||||
// not found so add it
|
||||
add (Station {band, 0, QString {}});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user