WSJT-X/plotter.cpp
Joe Taylor 920fd25537 Starting work toward flexible use of AP decoding in QRA64. Beware, this is still in progress!
git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@6928 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
2016-07-19 13:16:10 +00:00

723 lines
19 KiB
C++

#include "plotter.h"
#include <math.h>
#include <QDebug>
#include "commons.h"
#include "moc_plotter.cpp"
#define MAX_SCREENSIZE 2048
CPlotter::CPlotter(QWidget *parent) : //CPlotter Constructor
QFrame {parent},
m_bScaleOK {false},
m_bReference {false},
m_bReference0 {false},
m_fSpan {2000.0},
m_plotZero {0},
m_plotGain {0},
m_plot2dGain {0},
m_plot2dZero {0},
m_nSubMode {0},
m_Running {false},
m_paintEventBusy {false},
m_fftBinWidth {1500.0/2048.0},
m_dialFreq {0.},
m_sum {},
m_dBStepSize {10},
m_FreqUnits {1},
m_hdivs {HORZ_DIVS},
m_line {0},
m_fSample {12000},
m_nsps {6912},
m_Percent2DScreen {30}, //percent of screen used for 2D display
m_Percent2DScreen0 {0},
m_rxFreq {1020},
m_txFreq {0},
m_startFreq {0}
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setFocusPolicy(Qt::StrongFocus);
setAttribute(Qt::WA_PaintOnScreen,false);
setAutoFillBackground(false);
setAttribute(Qt::WA_OpaquePaintEvent, false);
setAttribute(Qt::WA_NoSystemBackground, true);
}
CPlotter::~CPlotter() { } // Destructor
QSize CPlotter::minimumSizeHint() const
{
return QSize(50, 50);
}
QSize CPlotter::sizeHint() const
{
return QSize(180, 180);
}
void CPlotter::resizeEvent(QResizeEvent* ) //resizeEvent()
{
if(!size().isValid()) return;
if( m_Size != size() or (m_bReference != m_bReference0) or
m_Percent2DScreen != m_Percent2DScreen0) {
m_Size = size();
m_w = m_Size.width();
m_h = m_Size.height();
m_h2 = m_Percent2DScreen*m_h/100.0;
if(m_h2>m_h-30) m_h2=m_h-30;
if(m_bReference) m_h2=m_h-30;
if(m_h2<1) m_h2=1;
m_h1=m_h-m_h2;
m_2DPixmap = QPixmap(m_Size.width(), m_h2);
m_2DPixmap.fill(Qt::black);
m_WaterfallPixmap = QPixmap(m_Size.width(), m_h1);
m_OverlayPixmap = QPixmap(m_Size.width(), m_h2);
m_OverlayPixmap.fill(Qt::black);
m_WaterfallPixmap.fill(Qt::black);
m_2DPixmap.fill(Qt::black);
m_ScalePixmap = QPixmap(m_w,30);
m_ScalePixmap.fill(Qt::white);
m_Percent2DScreen0 = m_Percent2DScreen;
}
DrawOverlay();
}
void CPlotter::paintEvent(QPaintEvent *) // paintEvent()
{
if(m_paintEventBusy) return;
m_paintEventBusy=true;
QPainter painter(this);
painter.drawPixmap(0,0,m_ScalePixmap);
painter.drawPixmap(0,30,m_WaterfallPixmap);
painter.drawPixmap(0,m_h1,m_2DPixmap);
m_paintEventBusy=false;
}
void CPlotter::draw(float swide[], bool bScroll, bool bRed)
{
int j,j0;
static int jtop=0,ktop=0;
float y,y2,ymin;
double fac = sqrt(m_binsPerPixel*m_waterfallAvg/15.0);
double gain = fac*pow(10.0,0.02*m_plotGain);
double gain2d = pow(10.0,0.02*(m_plot2dGain));
if(m_bReference != m_bReference0) resizeEvent(NULL);
m_bReference0=m_bReference;
//move current data down one line (must do this before attaching a QPainter object)
if(bScroll) m_WaterfallPixmap.scroll(0,1,0,0,m_w,m_h1);
QPainter painter1(&m_WaterfallPixmap);
m_2DPixmap = m_OverlayPixmap.copy(0,0,m_w,m_h2);
QPainter painter2D(&m_2DPixmap);
if(!painter2D.isActive()) return;
QFont Font("Arial");
Font.setPointSize(12);
Font.setWeight(QFont::Normal);
painter2D.setFont(Font);
if(m_bLinearAvg) {
painter2D.setPen(Qt::yellow);
} else if(m_bReference) {
painter2D.setPen(Qt::blue);
} else {
painter2D.setPen(Qt::green);
}
static QPoint LineBuf[MAX_SCREENSIZE];
static QPoint LineBuf2[MAX_SCREENSIZE];
j=0;
j0=int(m_startFreq/m_fftBinWidth + 0.5);
int iz=XfromFreq(5000.0);
int jz=iz*m_binsPerPixel;
m_fMax=FreqfromX(iz);
m_line++;
if(m_mode=="QRA64" and bRed) {
double df_qra64=12000.0/(2*6912);
int j0,j1;
int k=0;
float smax,y3max=0;
float y3[MAX_SCREENSIZE];
for(int i=1; i<iz; i++) {
j0=FreqfromX(i-1)/df_qra64;
j1=FreqfromX(i)/df_qra64;
smax=0.0;
for(int jj=j0; jj<=j1; jj++) {
if(dec_data.sred[jj]>smax) smax=dec_data.sred[jj];
}
y3[i]=smax;
if(smax>y3max)y3max=smax;
}
float fac=0.8/qMax(y3max,10.0f);
for(int i=1; i<iz; i++) {
if(y3[i]>0) {
y2=fac*y3[i];
LineBuf2[k].setX(i);
LineBuf2[k].setY(int(m_h2*(0.9-y2)));
k++;
}
}
painter2D.drawPolyline(LineBuf,jtop);
painter2D.setPen(Qt::red);
painter2D.drawPolyline(LineBuf2,k);
ktop=k;
update(); //trigger a new paintEvent
return;
}
if(bScroll) {
flat4_(swide,&iz,&m_Flatten);
flat4_(&dec_data.savg[j0],&jz,&m_Flatten);
}
ymin=1.e30;
if(swide[0]>1.e29 and swide[0]< 1.5e30) painter1.setPen(Qt::green);
if(swide[0]>1.4e30) painter1.setPen(Qt::yellow);
for(int i=0; i<iz; i++) {
y=swide[i];
if(y<ymin) ymin=y;
int y1 = 10.0*gain*y + 10*m_plotZero +40;
if (y1<0) y1=0;
if (y1>254) y1=254;
if (swide[i]<1.e29) painter1.setPen(g_ColorTbl[y1]);
painter1.drawPoint(i,0);
}
float y2min=1.e30;
float y2max=-1.e30;
for(int i=0; i<iz; i++) {
y=swide[i] - ymin;
y2=0;
if(m_bCurrent) y2 = gain2d*y + m_plot2dZero; //Current
if(bScroll) {
float sum=0.0;
int j=j0+m_binsPerPixel*i;
for(int k=0; k<m_binsPerPixel; k++) {
sum+=dec_data.savg[j++];
}
m_sum[i]=sum;
}
if(m_bCumulative) y2=gain2d*(m_sum[i]/m_binsPerPixel + m_plot2dZero);
if(m_Flatten==0) y2 += 15; //### could do better! ###
if(m_bLinearAvg) { //Linear Avg (yellow)
float sum=0.0;
int j=j0+m_binsPerPixel*i;
for(int k=0; k<m_binsPerPixel; k++) {
sum+=spectra_.syellow[j++];
}
y2=gain2d*sum/m_binsPerPixel + m_plot2dZero;
}
if(m_bReference) { //Reference (red)
float df_ref=12000.0/6912.0;
int j=FreqfromX(i)/df_ref + 0.5;
y2=spectra_.ref[j] + m_plot2dZero;
// if(gain2d>1.5) y2=spectra_.filter[j] + m_plot2dZero;
}
if(i==iz-1) {
painter2D.drawPolyline(LineBuf,j);
if(m_mode=="QRA64") {
painter2D.setPen(Qt::red);
painter2D.drawPolyline(LineBuf2,ktop);
}
}
jtop=j;
LineBuf[j].setX(i);
LineBuf[j].setY(int(0.9*m_h2-y2*m_h2/70.0));
if(y2<y2min) y2min=y2;
if(y2>y2max) y2max=y2;
j++;
}
if(swide[0]>1.0e29) m_line=0;
if(m_line == painter1.fontMetrics ().height ()) {
painter1.setPen(Qt::white);
QString t;
if(m_TRperiod < 60) {
qint64 ms = QDateTime::currentMSecsSinceEpoch() % 86400000;
int n=(ms/1000) % m_TRperiod;
QDateTime t1=QDateTime::currentDateTimeUtc().addSecs(-n);
t=t1.toString("hh:mm:ss") + " " + m_rxBand;
} else {
t=QDateTime::currentDateTimeUtc().toString("hh:mm") + " " + m_rxBand;
}
painter1.drawText (5, painter1.fontMetrics ().ascent (), t);
}
if(m_mode=="JT4") {
QPen pen3(Qt::yellow); //Mark freqs of JT4 single-tone msgs
painter2D.setPen(pen3);
Font.setWeight(QFont::Bold);
painter2D.setFont(Font);
int x1=XfromFreq(m_rxFreq);
y=0.2*m_h2;
painter2D.drawText(x1-4,y,"T");
x1=XfromFreq(m_rxFreq+250);
painter2D.drawText(x1-4,y,"M");
x1=XfromFreq(m_rxFreq+500);
painter2D.drawText(x1-4,y,"R");
x1=XfromFreq(m_rxFreq+750);
painter2D.drawText(x1-4,y,"73");
}
update(); //trigger a new paintEvent
m_bScaleOK=true;
}
void CPlotter::drawRed(int ia, int ib, float swide[])
{
m_ia=ia;
m_ib=ib;
draw(swide,false,true);
}
void CPlotter::DrawOverlay() //DrawOverlay()
{
if(m_OverlayPixmap.isNull()) return;
if(m_WaterfallPixmap.isNull()) return;
int w = m_WaterfallPixmap.width();
int x,y,x1,x2,x3,x4,x5,x6;
float pixperdiv;
double df = m_binsPerPixel*m_fftBinWidth;
QRect rect;
QPen penOrange(QColor(255,165,0),3);
QPen penGreen(Qt::green, 3); //Mark Tol range with green line
QPen penRed(Qt::red, 3); //Mark Tx freq with red
QPainter painter(&m_OverlayPixmap);
painter.initFrom(this);
QLinearGradient gradient(0, 0, 0 ,m_h2); //fill background with gradient
gradient.setColorAt(1, Qt::black);
gradient.setColorAt(0, Qt::darkBlue);
painter.setBrush(gradient);
painter.drawRect(0, 0, m_w, m_h2);
painter.setBrush(Qt::SolidPattern);
m_fSpan = w*df;
// int n=m_fSpan/10;
m_freqPerDiv=10;
if(m_fSpan>100) m_freqPerDiv=20;
if(m_fSpan>250) m_freqPerDiv=50;
if(m_fSpan>500) m_freqPerDiv=100;
if(m_fSpan>1000) m_freqPerDiv=200;
if(m_fSpan>2500) m_freqPerDiv=500;
pixperdiv = m_freqPerDiv/df;
m_hdivs = w*df/m_freqPerDiv + 1.9999;
float xx0=float(m_startFreq)/float(m_freqPerDiv);
xx0=xx0-int(xx0);
int x0=xx0*pixperdiv+0.5;
for( int i=1; i<m_hdivs; i++) { //draw vertical grids
x = (int)((float)i*pixperdiv ) - x0;
if(x >= 0 and x<=m_w) {
painter.setPen(QPen(Qt::white, 1,Qt::DotLine));
painter.drawLine(x, 0, x , m_h2);
}
}
pixperdiv = (float)m_h2 / (float)VERT_DIVS;
painter.setPen(QPen(Qt::white, 1,Qt::DotLine));
for( int i=1; i<VERT_DIVS; i++) { //draw horizontal grids
y = (int)( (float)i*pixperdiv );
painter.drawLine(0, y, w, y);
}
QRect rect0;
QPainter painter0(&m_ScalePixmap);
painter0.initFrom(this);
//create Font to use for scales
QFont Font("Arial");
Font.setPointSize(12);
Font.setWeight(QFont::Normal);
painter0.setFont(Font);
painter0.setPen(Qt::black);
if(m_binsPerPixel < 1) m_binsPerPixel=1;
m_hdivs = w*df/m_freqPerDiv + 0.9999;
m_ScalePixmap.fill(Qt::white);
painter0.drawRect(0, 0, w, 30);
MakeFrequencyStrs();
//draw tick marks on upper scale
pixperdiv = m_freqPerDiv/df;
for( int i=0; i<m_hdivs; i++) { //major ticks
x = (int)((m_xOffset+i)*pixperdiv );
painter0.drawLine(x,18,x,30);
}
int minor=5;
if(m_freqPerDiv==200) minor=4;
for( int i=1; i<minor*m_hdivs; i++) { //minor ticks
x = i*pixperdiv/minor;
painter0.drawLine(x,24,x,30);
}
//draw frequency values
for( int i=0; i<=m_hdivs; i++) {
x = (int)((m_xOffset+i)*pixperdiv - pixperdiv/2);
rect0.setRect(x,0, (int)pixperdiv, 20);
painter0.drawText(rect0, Qt::AlignHCenter|Qt::AlignVCenter,m_HDivText[i]);
}
float bw=9.0*12000.0/m_nsps; //JT9
if(m_mode=="JT4") { //JT4
bw=3*11025.0/2520.0; //Max tone spacing (3/4 of actual BW)
if(m_nSubMode==1) bw=2*bw;
if(m_nSubMode==2) bw=4*bw;
if(m_nSubMode==3) bw=9*bw;
if(m_nSubMode==4) bw=18*bw;
if(m_nSubMode==5) bw=36*bw;
if(m_nSubMode==6) bw=72*bw;
painter0.setPen(penGreen);
x1=XfromFreq(m_rxFreq-m_tol);
x2=XfromFreq(m_rxFreq+m_tol);
painter0.drawLine(x1,29,x2,29);
for(int i=0; i<4; i++) {
x1=XfromFreq(m_rxFreq+bw*i/3.0);
int j=24;
if(i==0) j=18;
painter0.drawLine(x1,j,x1,30);
}
painter0.setPen(penRed);
for(int i=0; i<4; i++) {
x1=XfromFreq(m_txFreq+bw*i/3.0);
painter0.drawLine(x1,12,x1,18);
}
}
if(m_modeTx=="JT9" and m_nSubMode>0) { //JT9
bw=8.0*12000.0/m_nsps;
if(m_nSubMode==1) bw=2*bw; //B
if(m_nSubMode==2) bw=4*bw; //C
if(m_nSubMode==3) bw=8*bw; //D
if(m_nSubMode==4) bw=16*bw; //E
if(m_nSubMode==5) bw=32*bw; //F
if(m_nSubMode==6) bw=64*bw; //G
if(m_nSubMode==7) bw=128*bw; //H
}
if(m_mode=="QRA64") { //QRA64
bw=63.0*12000.0/m_nsps;
if(m_nSubMode==1) bw=2*bw; //B
if(m_nSubMode==2) bw=4*bw; //C
if(m_nSubMode==3) bw=8*bw; //D
if(m_nSubMode==4) bw=16*bw; //E
}
if(m_modeTx=="JT65") { //JT65
bw=65.0*11025.0/4096.0;
if(m_nSubMode==1) bw=2*bw; //B
if(m_nSubMode==2) bw=4*bw; //C
}
painter0.setPen(penGreen);
if(m_mode=="WSPR-2") { //### WSPR-15 code needed here, too ###
x1=XfromFreq(1400);
x2=XfromFreq(1600);
painter0.drawLine(x1,29,x2,29);
}
if(m_mode=="JT9" or m_mode=="JT65" or m_mode=="JT9+JT65" or m_mode=="QRA64") {
if(m_mode=="QRA64" or (g_single_decode and m_mode=="JT65")) {
painter0.setPen(penGreen);
x1=XfromFreq(m_rxFreq-m_tol);
x2=XfromFreq(m_rxFreq+m_tol);
painter0.drawLine(x1,28,x2,28);
x1=XfromFreq(m_rxFreq);
painter0.drawLine(x1,24,x1,30);
if(m_mode=="JT65") {
painter0.setPen(penOrange);
x3=XfromFreq(m_rxFreq+20.0*bw/65.0); //RO
painter0.drawLine(x3,24,x3,30);
x4=XfromFreq(m_rxFreq+30.0*bw/65.0); //RRR
painter0.drawLine(x4,24,x4,30);
x5=XfromFreq(m_rxFreq+40.0*bw/65.0); //73
painter0.drawLine(x5,24,x5,30);
}
painter0.setPen(penGreen);
x6=XfromFreq(m_rxFreq+bw); //Highest tone
painter0.drawLine(x6,24,x6,30);
} else {
painter0.setPen(penGreen);
x1=XfromFreq(m_rxFreq);
x2=XfromFreq(m_rxFreq+bw);
painter0.drawLine(x1,24,x1,30);
painter0.drawLine(x1,28,x2,28);
painter0.drawLine(x2,24,x2,30);
}
}
if(m_mode=="JT9" or m_mode=="JT65" or m_mode=="JT9+JT65" or
m_mode.mid(0,4)=="WSPR" or m_mode=="QRA64") {
painter0.setPen(penRed);
x1=XfromFreq(m_txFreq);
x2=XfromFreq(m_txFreq+bw);
if(m_mode=="WSPR-2") { //### WSPR-15 code needed here, too
bw=4*12000.0/8192.0; //WSPR
x1=XfromFreq(m_txFreq-0.5*bw);
x2=XfromFreq(m_txFreq+0.5*bw);
}
painter0.drawLine(x1,17,x1,21);
painter0.drawLine(x1,17,x2,17);
painter0.drawLine(x2,17,x2,21);
}
if(m_mode=="JT9+JT65") {
QPen pen2(Qt::blue, 3); //Mark the JT65 | JT9 divider
painter0.setPen(pen2);
x1=XfromFreq(m_fMin);
if(x1<2) x1=2;
x2=x1+30;
painter0.drawLine(x1,8,x1,28);
}
if(m_dialFreq>10.13 and m_dialFreq< 10.15 and m_mode.mid(0,4)!="WSPR") {
float f1=1.0e6*(10.1401 - m_dialFreq);
float f2=f1+200.0;
x1=XfromFreq(f1);
x2=XfromFreq(f2);
if(x1<=m_w and x2>=0) {
painter0.setPen(penOrange); //Mark WSPR sub-band orange
painter0.drawLine(x1,9,x2,9);
}
}
}
void CPlotter::MakeFrequencyStrs() //MakeFrequencyStrs
{
int f=(m_startFreq+m_freqPerDiv-1)/m_freqPerDiv;
f*=m_freqPerDiv;
m_xOffset=float(f-m_startFreq)/m_freqPerDiv;
for(int i=0; i<=m_hdivs; i++) {
m_HDivText[i].setNum(f);
f+=m_freqPerDiv;
}
}
int CPlotter::XfromFreq(float f) //XfromFreq()
{
// float w = m_WaterfallPixmap.width();
int x = int(m_w * (f - m_startFreq)/m_fSpan + 0.5);
if(x<0 ) return 0;
if(x>m_w) return m_w;
return x;
}
float CPlotter::FreqfromX(int x) //FreqfromX()
{
return float(m_startFreq + x*m_binsPerPixel*m_fftBinWidth);
}
void CPlotter::SetRunningState(bool running) //SetRunningState()
{
m_Running = running;
}
void CPlotter::setPlotZero(int plotZero) //setPlotZero()
{
m_plotZero=plotZero;
}
int CPlotter::plotZero() //PlotZero()
{
return m_plotZero;
}
void CPlotter::setPlotGain(int plotGain) //setPlotGain()
{
m_plotGain=plotGain;
}
int CPlotter::plotGain() //plotGain()
{
return m_plotGain;
}
int CPlotter::plot2dGain() //plot2dGain
{
return m_plot2dGain;
}
void CPlotter::setPlot2dGain(int n) //setPlot2dGain
{
m_plot2dGain=n;
update();
}
int CPlotter::plot2dZero() //plot2dZero
{
return m_plot2dZero;
}
void CPlotter::setPlot2dZero(int plot2dZero) //setPlot2dZero
{
m_plot2dZero=plot2dZero;
}
void CPlotter::setStartFreq(int f) //SetStartFreq()
{
m_startFreq=f;
resizeEvent(NULL);
update();
}
int CPlotter::startFreq() //startFreq()
{
return m_startFreq;
}
int CPlotter::plotWidth(){return m_WaterfallPixmap.width();} //plotWidth
void CPlotter::UpdateOverlay() {DrawOverlay();} //UpdateOverlay
void CPlotter::setDataFromDisk(bool b) {m_dataFromDisk=b;} //setDataFromDisk
void CPlotter::setRxRange(int fMin) //setRxRange
{
m_fMin=fMin;
}
void CPlotter::setBinsPerPixel(int n) //setBinsPerPixel
{
m_binsPerPixel = n;
DrawOverlay(); //Redraw scales and ticks
update(); //trigger a new paintEvent}
}
int CPlotter::binsPerPixel() //binsPerPixel
{
return m_binsPerPixel;
}
void CPlotter::setWaterfallAvg(int n) //setBinsPerPixel
{
m_waterfallAvg = n;
}
void CPlotter::setRxFreq (int x) //setRxFreq
{
m_rxFreq = x; // x is freq in Hz
DrawOverlay();
update();
}
int CPlotter::rxFreq() {return m_rxFreq;} //rxFreq
void CPlotter::mousePressEvent(QMouseEvent *event) //mousePressEvent
{
int x=event->x();
if(x<0) x=0;
if(x>m_Size.width()) x=m_Size.width();
bool ctrl = (event->modifiers() & Qt::ControlModifier);
bool shift = (event->modifiers() & Qt::ShiftModifier);
int newFreq = int(FreqfromX(x)+0.5);
int oldTxFreq = m_txFreq;
int oldRxFreq = m_rxFreq;
if (ctrl or m_lockTxFreq) {
emit setFreq1 (newFreq, newFreq);
}
else if (shift) {
emit setFreq1 (oldRxFreq, newFreq);
}
else {
emit setFreq1(newFreq,oldTxFreq);
}
int n=1;
if(ctrl) n+=100;
emit freezeDecode1(n);
}
void CPlotter::mouseDoubleClickEvent(QMouseEvent *event) //mouse2click
{
bool ctrl = (event->modifiers() & Qt::ControlModifier);
int n=2;
if(ctrl) n+=100;
emit freezeDecode1(n);
}
void CPlotter::setNsps(int ntrperiod, int nsps) //setNsps
{
m_TRperiod=ntrperiod;
m_nsps=nsps;
m_fftBinWidth=1500.0/2048.0;
if(m_nsps==15360) m_fftBinWidth=1500.0/2048.0;
if(m_nsps==40960) m_fftBinWidth=1500.0/6144.0;
if(m_nsps==82944) m_fftBinWidth=1500.0/12288.0;
if(m_nsps==252000) m_fftBinWidth=1500.0/32768.0;
DrawOverlay(); //Redraw scales and ticks
update(); //trigger a new paintEvent}
}
void CPlotter::setTxFreq(int n) //setTxFreq
{
m_txFreq=n;
DrawOverlay();
update();
}
void CPlotter::setMode(QString mode) //setMode
{
m_mode=mode;
}
void CPlotter::setSubMode(int n) //setSubMode
{
m_nSubMode=n;
}
void CPlotter::setModeTx(QString modeTx) //setModeTx
{
m_modeTx=modeTx;
}
int CPlotter::Fmax()
{
return m_fMax;
}
void CPlotter::setDialFreq(double d)
{
m_dialFreq=d;
DrawOverlay();
update();
}
void CPlotter::setRxBand(QString band)
{
m_rxBand=band;
}
void CPlotter::setFlatten(bool b1, bool b2)
{
m_Flatten=0;
if(b1) m_Flatten=1;
if(b2) m_Flatten=2;
}
void CPlotter::setTol(int n) //setTol()
{
m_tol=n;
DrawOverlay();
}
void CPlotter::setColours(QVector<QColor> const& cl)
{
g_ColorTbl = cl;
}
void CPlotter::SetPercent2DScreen(int percent)
{
m_Percent2DScreen=percent;
resizeEvent(NULL);
// DrawOverlay();
update();
}