js8call/plotter.cpp

821 lines
20 KiB
C++
Raw Normal View History

2018-02-08 21:28:33 -05:00
#include "plotter.h"
#include <algorithm>
2018-02-08 21:28:33 -05:00
#include <QDebug>
2024-09-01 20:03:35 -07:00
#include <QMouseEvent>
#include <QPainter>
#include <QPen>
#include <QScopedValueRollback>
2024-09-01 08:01:23 -07:00
#include <QToolTip>
2024-09-01 20:03:35 -07:00
#include <QWheelEvent>
2018-02-08 21:28:33 -05:00
#include "commons.h"
#include "moc_plotter.cpp"
#include "DriftingDateTime.h"
#include "JS8Submode.hpp"
2018-03-05 14:49:51 -05:00
extern "C" {
void flat4_(float swide[], int* iz, int* nflatten);
void plotsave_(float swide[], int* m_w , int* m_h1, int* irow);
}
namespace
{
// 30 meter band: 10.130-10.140 RTTY
// 10.140-10.150 Packet
constexpr double BAND_30M_START = 10.13;
constexpr double BAND_30M_END = 10.15;
// The WSPR range is 200Hz in the 30m band, starting at 10.1401 MHz.
constexpr double WSPR_RANGE = 200.0;
constexpr double WSPR_START = 10.1401;
// Vertical divisions in the spectrum display.
constexpr std::size_t VERT_DIVS = 7;
qint32
freqPerDiv(float const fSpan)
{
2024-09-08 20:13:33 -07:00
if (fSpan > 2500) { return 500; }
if (fSpan > 1000) { return 200; }
if (fSpan > 500) { return 100; }
if (fSpan > 250) { return 50; }
if (fSpan > 100) { return 20; }
return 10;
}
double
fftBinWidth(qint32 const nsps)
{
2024-09-09 05:34:41 -07:00
switch (nsps)
{
case 252000: return 1500.0 / 32768.0;
case 82944: return 1500.0 / 12288.0;
case 40960: return 1500.0 / 6144.0;
default: return 1500.0 / 2048.0;
}
}
}
2018-02-08 21:28:33 -05:00
CPlotter::CPlotter(QWidget *parent) : //CPlotter Constructor
QFrame {parent},
m_bScaleOK {false},
m_fSpan {2000.0},
m_plotZero {0},
m_plotGain {0},
m_plot2dGain {0},
m_plot2dZero {0},
m_nSubMode {0},
m_filterEnabled{false},
m_paintEventBusy{false},
m_filterCenter {0},
m_filterWidth {0},
2018-02-08 21:28:33 -05:00
m_fftBinWidth {1500.0/2048.0},
m_dialFreq {0.},
m_line {0},
m_nsps {6912},
m_Percent2DScreen {0}, //percent of screen used for 2D display
2018-02-08 21:28:33 -05:00
m_Percent2DScreen0 {0},
m_rxFreq {1020},
m_txFreq {0},
m_startFreq {0},
m_lastMouseX {-1}
2018-02-08 21:28:33 -05:00
{
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);
setMouseTracking(true);
m_bReplot=false;
2018-02-08 21:28:33 -05:00
}
2024-09-08 12:43:10 -07:00
CPlotter::~CPlotter() = default;
2018-02-08 21:28:33 -05:00
2024-10-19 09:19:01 -07:00
QSize
CPlotter::minimumSizeHint() const
2018-02-08 21:28:33 -05:00
{
return QSize(50, 50);
}
2024-10-19 09:19:01 -07:00
QSize
CPlotter::sizeHint() const
2018-02-08 21:28:33 -05:00
{
return QSize(180, 180);
}
2024-10-19 09:19:01 -07:00
void
CPlotter::resizeEvent(QResizeEvent *) //resizeEvent()
2018-02-08 21:28:33 -05:00
{
2024-09-08 10:00:57 -07:00
if (!size().isValid()) return;
if ((m_Size != size()) ||
(m_Percent2DScreen != m_Percent2DScreen0))
{
2018-02-08 21:28:33 -05:00
m_Size = size();
2024-09-08 10:00:57 -07:00
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_h2 < 1) m_h2 = 1;
m_h1 = m_h - m_h2;
2018-03-05 14:49:51 -05:00
// m_line=0;
2024-09-08 14:17:32 -07:00
m_FilterOverlayPixmap = QPixmap(m_Size);
m_FilterOverlayPixmap.fill(Qt::transparent);
2024-09-08 14:17:32 -07:00
m_DialOverlayPixmap = QPixmap(m_Size);
m_DialOverlayPixmap.fill(Qt::transparent);
2024-09-08 14:17:32 -07:00
m_HoverOverlayPixmap = QPixmap(m_Size);
m_HoverOverlayPixmap.fill(Qt::transparent);
2024-09-08 14:17:32 -07:00
m_2DPixmap = QPixmap(m_w, m_h2);
2018-02-08 21:28:33 -05:00
m_2DPixmap.fill(Qt::black);
2024-09-08 14:17:32 -07:00
m_WaterfallPixmap = QPixmap(m_w, m_h1);
m_WaterfallPixmap.fill(Qt::black);
2024-09-08 14:17:32 -07:00
m_OverlayPixmap = QPixmap(m_w, m_h2);
2018-02-08 21:28:33 -05:00
m_OverlayPixmap.fill(Qt::black);
2024-08-31 22:19:32 -07:00
// Address scale font looking terrible, since it's drawn into this
// intermediate pixmap, so if we don't scale it to match the device,
// the text will look pixelated.
//
// Same is true of the decode lines in the waterfall; they look
// pixelated, but the fix doesn't appear to be straightforward, and
// it's arguably an effect there, a bit like a Tektronix display.
m_ScalePixmap = QPixmap(QSize(m_w, 30) * devicePixelRatio());
m_ScalePixmap.setDevicePixelRatio(devicePixelRatio());
2018-02-08 21:28:33 -05:00
m_ScalePixmap.fill(Qt::white);
2024-08-31 22:19:32 -07:00
2018-02-08 21:28:33 -05:00
m_Percent2DScreen0 = m_Percent2DScreen;
}
drawOverlay();
2018-02-08 21:28:33 -05:00
}
2024-10-19 09:19:01 -07:00
void
CPlotter::paintEvent(QPaintEvent *) // paintEvent()
2018-02-08 21:28:33 -05:00
{
if (m_paintEventBusy) return;
QScopedValueRollback scoped(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);
auto const x = xFromFreq(m_rxFreq);
painter.drawPixmap(x, 0, m_DialOverlayPixmap);
if (m_lastMouseX >= 0 &&
m_lastMouseX != x)
{
painter.drawPixmap(m_lastMouseX, 0, m_HoverOverlayPixmap);
}
if (m_filterEnabled && m_filterWidth > 0)
{
painter.drawPixmap(0, 0, m_FilterOverlayPixmap);
}
2018-02-08 21:28:33 -05:00
}
2024-09-13 14:08:06 -07:00
void
CPlotter::draw(float swide[],
bool const bScroll)
2018-02-08 21:28:33 -05:00
{
// Move current data down one line (must do this before attaching a QPainter object)
if(bScroll && !m_bReplot)
{
m_WaterfallPixmap.scroll(0, 1, m_WaterfallPixmap.rect());
}
2024-09-08 09:00:09 -07:00
2018-02-08 21:28:33 -05:00
QPainter painter1(&m_WaterfallPixmap);
m_2DPixmap = m_OverlayPixmap.copy();
2018-02-08 21:28:33 -05:00
QPainter painter2D(&m_2DPixmap);
if(!painter2D.isActive()) return;
2024-09-08 20:09:29 -07:00
auto iz = xFromFreq(5000.0);
m_fMax = freqFromX(iz);
2024-09-08 20:09:29 -07:00
if (bScroll && swide[0] < 1.e29)
2024-09-08 20:09:29 -07:00
{
flat4_(swide, &iz, &m_flatten);
2018-02-08 21:28:33 -05:00
}
2024-09-08 20:09:29 -07:00
if(swide[0] > 1.e29 && swide[0] < 1.5e30) painter1.setPen(Qt::green); // horizontal line
if(swide[0] > 1.4e30 ) painter1.setPen(Qt::yellow);
if(!m_bReplot)
{
m_j = 0;
int irow = -1;
plotsave_(swide, &m_w, &m_h1, &irow);
2018-03-05 14:49:51 -05:00
}
2024-09-08 20:09:29 -07:00
double const fac = sqrt(m_binsPerPixel * m_waterfallAvg / 15.0);
double const gain = fac * pow(10.0, 0.015 * m_plotGain);
double const gain2d = pow(10.0, 0.02 * m_plot2dGain);
2024-09-13 14:08:06 -07:00
auto const base = static_cast<int>(m_startFreq / m_fftBinWidth + 0.5);
auto ymin = 1.e30f;
// First loop; draws points into the waterfall and determines the
// minimum y extent.
2024-09-08 20:01:56 -07:00
for(int i = 0; i < iz; i++)
{
float const y = swide[i];
2024-09-13 10:49:58 -07:00
if (y < ymin ) ymin = y;
if (y < 1.e29) painter1.setPen(g_ColorTbl[std::clamp(static_cast<int>(10.0 * gain * y + m_plotZero), 0, 254)]);
painter1.drawPoint(i, m_j);
2018-02-08 21:28:33 -05:00
}
2018-03-05 14:49:51 -05:00
m_line++;
2024-09-13 14:08:06 -07:00
// Summarization method, used when scrolling and during computation of
// linear average.
auto const sum = [base,
bins = m_binsPerPixel](float const * const data,
auto const index)
{
float sum = 0.0f;
int k = base + bins * index;
for (int l = 0; l < bins; l++)
{
sum += data[k++];
}
return sum;
};
// Second loop, determines how we're going to draw the spectrum.
2024-10-19 09:19:01 -07:00
// Updates the sums if we're scrolling, updates the points to draw.
2024-09-13 14:08:06 -07:00
2024-09-08 20:01:56 -07:00
for (int i = 0; i < iz; i++)
{
2024-09-13 13:03:50 -07:00
if (bScroll)
2024-09-08 20:09:29 -07:00
{
2024-09-13 14:08:06 -07:00
m_sum[i] = sum(dec_data.savg, i);
2018-02-08 21:28:33 -05:00
}
2024-09-14 22:26:48 -07:00
float y = 0;
2024-09-13 13:03:50 -07:00
switch (m_spectrum)
2024-09-08 20:09:29 -07:00
{
case Spectrum::Current:
y = gain2d * (swide[i] - ymin) + m_plot2dZero + (m_flatten == 0 ? 15 : 0);
break;
case Spectrum::Cumulative:
y = gain2d * (m_sum[i] / m_binsPerPixel + m_plot2dZero) + (m_flatten == 0 ? 15 : 0);
break;
case Spectrum::LinearAvg:
y = 2.0 * gain2d * sum(spectra_.syellow, i) / m_binsPerPixel + m_plot2dZero;
break;
2018-02-08 21:28:33 -05:00
}
m_points[i].setX(i);
2024-09-13 13:03:50 -07:00
m_points[i].setY(int(0.9 * m_h2 - y * m_h2 / 70.0));
2018-02-08 21:28:33 -05:00
}
2024-09-08 20:01:56 -07:00
2024-09-13 14:08:06 -07:00
// Draw the computed spectrum line.
painter2D.setPen(m_spectrum == Spectrum::LinearAvg ? Qt::yellow : Qt::green);
2024-09-15 13:32:54 -07:00
painter2D.drawPolyline(m_points.data(), iz - 1);
2024-09-13 14:08:06 -07:00
if (m_bReplot) return;
2018-02-08 21:28:33 -05:00
2024-09-08 12:08:52 -07:00
if (swide[0] > 1.0e29) m_line = 0;
2024-09-08 17:53:35 -07:00
if (m_line == painter1.fontMetrics().height())
{
2024-09-08 06:18:53 -07:00
qint64 const ms = DriftingDateTime::currentMSecsSinceEpoch() % 86400000;
int const n = (ms/1000) % m_TRperiod;
auto const t1 = DriftingDateTime::currentDateTimeUtc().addSecs(-n);
auto const ts = t1.toString(m_TRperiod < 60 ? "hh:mm:ss" : "hh:mm");
2018-02-08 21:28:33 -05:00
painter1.setPen(Qt::white);
2024-09-08 06:18:53 -07:00
painter1.drawText(5,
painter1.fontMetrics().ascent(),
QString("%1 %2").arg(ts).arg(m_band));
2018-02-08 21:28:33 -05:00
}
update(); //trigger a new paintEvent
2024-09-08 20:01:56 -07:00
m_bScaleOK = true;
2018-02-08 21:28:33 -05:00
}
2024-10-19 09:19:01 -07:00
void
CPlotter::drawDecodeLine(QColor const & color,
int const ia,
int const ib)
2018-02-08 21:28:33 -05:00
{
auto const x1 = xFromFreq(ia);
auto const x2 = xFromFreq(ib);
2020-05-06 11:15:41 -04:00
QPainter painter1(&m_WaterfallPixmap);
2024-09-08 17:53:35 -07:00
painter1.setPen(color);
painter1.drawLine(qMin(x1, x2), 4, qMax(x1, x2), 4);
painter1.drawLine(qMin(x1, x2), 0, qMin(x1, x2), 9);
painter1.drawLine(qMax(x1, x2), 0, qMax(x1, x2), 9);
2018-02-08 21:28:33 -05:00
}
2024-10-19 09:19:01 -07:00
void
CPlotter::drawHorizontalLine(QColor const & color,
int const x,
int const width)
{
QPainter painter1(&m_WaterfallPixmap);
2024-09-08 19:54:14 -07:00
2024-09-08 17:53:35 -07:00
painter1.setPen(color);
painter1.drawLine(x, 0, width <= 0 ? m_w : x + width, 0);
}
2024-10-19 09:19:01 -07:00
void
CPlotter::replot()
2018-03-05 14:49:51 -05:00
{
resizeEvent(nullptr);
2018-03-05 14:49:51 -05:00
float swide[m_w];
2024-09-08 19:54:14 -07:00
m_bReplot = true;
for (int irow = 0; irow < m_h1; irow++)
{
m_j = irow;
plotsave_(swide, &m_w, &m_h1, &irow);
draw(swide, false);
2018-03-05 14:49:51 -05:00
}
2024-09-08 19:54:14 -07:00
2018-03-05 14:49:51 -05:00
update(); //trigger a new paintEvent
2024-09-08 19:54:14 -07:00
m_bReplot = false;
2018-03-05 14:49:51 -05:00
}
void
CPlotter::drawOverlay()
2018-02-08 21:28:33 -05:00
{
2024-09-08 14:37:43 -07:00
if (m_OverlayPixmap.isNull() ||
m_WaterfallPixmap.isNull()) return;
2018-02-08 21:28:33 -05:00
2024-09-08 14:37:43 -07:00
QLinearGradient gradient(0, 0, 0, m_h2); //fill background with gradient
2018-02-08 21:28:33 -05:00
gradient.setColorAt(1, Qt::black);
gradient.setColorAt(0, Qt::darkBlue);
QPainter p(&m_OverlayPixmap);
p.setBrush(gradient);
p.drawRect(0, 0, m_w, m_h2);
p.setBrush(Qt::SolidPattern);
double const df = m_binsPerPixel * m_fftBinWidth;
2024-09-08 14:37:43 -07:00
m_fSpan = m_w * df;
m_freqPerDiv = freqPerDiv(m_fSpan);
2024-09-08 10:00:57 -07:00
float const ppdV = m_freqPerDiv / df;
float const ppdH = (float)m_h2 / (float)VERT_DIVS;
std::size_t const hdivs = m_fSpan / m_freqPerDiv + 1.9999;
2024-09-08 10:00:57 -07:00
float xx0 = float(m_startFreq) /float(m_freqPerDiv);
xx0 = xx0 - int(xx0);
int x0 = xx0 * ppdV + 0.5;
2024-09-09 11:40:50 -07:00
p.setPen(QPen(Qt::white, 1, Qt::DotLine));
for (std::size_t i = 1; i < hdivs; i++) //draw vertical grids
{
if (int const x = (int)((float)i * ppdV) - x0;
x >= 0 &&
x <= m_w)
{
p.drawLine(x, 0, x , m_h2);
2018-02-08 21:28:33 -05:00
}
}
for (std::size_t i = 1; i < VERT_DIVS; i++) //draw horizontal grids
{
int const y = (int)( (float)i * ppdH );
p.drawLine(0, y, m_w, y);
2018-02-08 21:28:33 -05:00
}
drawOverlayScale(df, ppdV);
2018-02-08 21:28:33 -05:00
2024-09-09 07:50:19 -07:00
// paint dials and filter overlays
if (m_mode == "FT8")
{
auto const fwidth = xFromFreq(m_rxFreq + JS8::Submode::bandwidth(m_nSubMode)) - xFromFreq(m_rxFreq);
2024-09-09 07:50:19 -07:00
drawOverlayDial(fwidth);
drawOverlayHover(fwidth);
drawOverlayFilter();
2024-09-09 07:50:19 -07:00
}
}
void
CPlotter::drawOverlayScale(double const df,
2024-09-12 19:00:35 -07:00
float const ppdV)
2024-09-09 07:50:19 -07:00
{
QPen const penOrange (QColor(230, 126, 34), 3);
QPen const penGray (QColor(149, 165, 166), 3);
QPen const penLightGreen (QColor( 46, 204, 113), 3);
QPen const penLightYellow(QColor(241, 196, 15), 3);
m_ScalePixmap.fill(Qt::white);
2024-09-09 07:50:19 -07:00
QPainter p(&m_ScalePixmap);
p.setFont({"Arial"});
p.setPen(Qt::black);
2024-09-12 13:04:50 -07:00
p.drawRect(0, 0, m_w, 30);
2024-09-09 07:50:19 -07:00
2024-09-12 14:09:43 -07:00
int const fOffset = ((m_startFreq + m_freqPerDiv - 1) / m_freqPerDiv) * m_freqPerDiv;
double const xOffset = double(fOffset - m_startFreq) / m_freqPerDiv;
2024-09-12 19:00:35 -07:00
std::size_t const nMinor = m_freqPerDiv == 200 ? 4: 5;
std::size_t const nHDivs = m_w * df / m_freqPerDiv + 0.9999;
float const ppdVM = ppdV / nMinor;
float const ppdVL = ppdV / 2;
2024-09-12 19:00:35 -07:00
// Draw ticks and labels.
2024-09-09 07:50:19 -07:00
2024-09-12 19:00:35 -07:00
for (std::size_t iMajor = 0; iMajor < nHDivs; iMajor++)
{
2024-09-12 19:00:35 -07:00
auto const rMajor = (xOffset + iMajor) * ppdV;
auto const xMajor = static_cast<int>(rMajor);
p.drawLine(xMajor, 18, xMajor, 30);
2024-09-12 19:00:35 -07:00
for (std::size_t iMinor = 1; iMinor < nMinor; iMinor++)
{
2024-09-12 19:00:35 -07:00
auto const xMinor = static_cast<int>(rMajor + iMinor * ppdVM);
p.drawLine(xMinor, 22, xMinor, 30);
}
2024-09-12 19:00:35 -07:00
if (xMajor > 70)
{
p.drawText(QRect(xMajor - static_cast<int>(ppdVL), 0, static_cast<int>(ppdV), 20),
Qt::AlignCenter,
QString::number(fOffset + iMajor * m_freqPerDiv));
}
2018-02-08 21:28:33 -05:00
}
// Colorize the JS8 sub-bands.
2024-09-10 06:36:41 -07:00
for (std::size_t i = 0; i <= 3500; i += 500)
{
auto const x1 = xFromFreq(static_cast<float>(i));
auto const x2 = xFromFreq(static_cast<float>(i + 500));
2024-09-10 06:36:41 -07:00
2024-09-10 06:26:15 -07:00
if (x1 <= m_w && x2 > 0)
2024-09-08 21:28:23 -07:00
{
2024-09-10 06:26:15 -07:00
switch (i) {
2024-09-08 21:28:23 -07:00
case 500:
2024-09-09 21:40:04 -07:00
case 2500:
p.setPen(penLightYellow);
break;
2024-09-08 21:28:23 -07:00
case 1000:
case 1500:
case 2000:
2024-09-09 21:40:04 -07:00
p.setPen(penLightGreen);
break;
2024-09-10 06:26:15 -07:00
default:
p.setPen(penGray);
break;
2019-12-30 14:37:11 -05:00
}
2024-09-09 07:50:19 -07:00
p.drawLine(x1 + 1, 26, x2 - 2, 26);
p.drawLine(x1 + 1, 28, x2 - 2, 28);
2024-09-08 21:28:23 -07:00
}
2019-12-30 14:37:11 -05:00
}
// If we're in the 30 meter band, we'd rather that the WSPR sub-band not
// get stomped on; draw an orange indicator in the scale to denote the
// WSPR portion of the band.
//
// Note that given the way XfromFreq() works, we're always going to see
// clamped X values here, either 0 or m_w, if the frequency is outside
// of the range, so we're always going to draw. If the WSPR range is not
// in the displayed range, the effect will be, given the pen size, that
// an orange indicator will indicate in which direction the WSPR range
// lies.
if (in30MBand())
{
auto const wspr = 1.0e6 * (WSPR_START - m_dialFreq);
auto const x1 = xFromFreq(wspr);
auto const x2 = xFromFreq(wspr + WSPR_RANGE);
p.setPen(penOrange);
p.setFont({"Arial", 10, QFont::Bold});
p.drawLine(x1 + 1, 26, x2 - 2, 26);
p.drawLine(x1 + 1, 28, x2 - 2, 28);
p.drawText(QRect(x1, 0, x2 - x1, 25),
Qt::AlignHCenter|Qt::AlignBottom,
"WSPR");
}
// Thin black line below the sub-band indicators; our work is done here.
2024-09-09 07:50:19 -07:00
p.setPen(Qt::black);
p.drawLine(0, 29, m_w, 29);
}
// Paint the dial overlay, showing the chunk of the frequency spectrum
// presently in use.
void
CPlotter::drawOverlayDial(int const fwidth)
{
QPainter p(&m_DialOverlayPixmap);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(rect(), Qt::transparent);
p.setPen(Qt::red);
2024-09-09 11:34:32 -07:00
p.fillRect(0, 26, fwidth + 2, 4, Qt::red);
2024-09-09 14:29:22 -07:00
p.fillRect(0, m_h - 4, fwidth + 2, 4, Qt::red);
p.drawLine(0, 30, 0, m_h); // first slot, left line
p.drawLine(fwidth + 1, 30, fwidth + 1, m_h); // first slot, right line
}
// Paint the hover overlay, showing the prospective chunk of frequency
// spectrum under the mouse.
void
CPlotter::drawOverlayHover(int const fwidth)
{
QPainter p(&m_HoverOverlayPixmap);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(rect(), Qt::transparent);
p.setPen(Qt::white);
p.drawLine(0, 30, 0, m_h); // first slot, left line hover
p.drawLine(fwidth, 30, fwidth, m_h); // first slot, right line hover
}
// Paint the filter overlay pixmap, if the filter is enabled and has a width
// greater than zero. Note that we could be more clever here and ensure the
// filter is actually visible prior to painting, but what we're doing here
// is reasonably trivial, so probably not worth the effort.
void
CPlotter::drawOverlayFilter()
{
if (m_filterEnabled && m_filterWidth > 0)
{
QPainter p(&m_FilterOverlayPixmap);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(rect(), Qt::transparent);
auto const start = xFromFreq(static_cast<float>(m_filterCenter - m_filterWidth / 2));
auto const end = xFromFreq(static_cast<float>(m_filterCenter + m_filterWidth / 2));
// Yellow vertical line, showing the filter location.
p.setPen(Qt::yellow);
p.drawLine(start, 30, start, m_h);
p.drawLine(end, 30, end, m_h);
// Put a mask over everything outside the bandpass.
QColor const blackMask(0, 0, 0, std::clamp(m_filterOpacity, 0, 255));
p.fillRect(0, 30, start, m_h, blackMask);
p.fillRect(end + 1, 30, m_w, m_h, blackMask);
}
2018-02-08 21:28:33 -05:00
}
bool
CPlotter::in30MBand() const
{
return (m_dialFreq >= BAND_30M_START &&
m_dialFreq <= BAND_30M_END);
}
int
CPlotter::xFromFreq(float const f) const //XfromFreq()
2018-02-08 21:28:33 -05:00
{
2024-09-08 19:54:14 -07:00
return std::clamp(static_cast<int>(m_w * (f - m_startFreq) / m_fSpan + 0.5), 0, m_w);
2018-02-08 21:28:33 -05:00
}
float
CPlotter::freqFromX(int const x) const //FreqfromX()
2018-02-08 21:28:33 -05:00
{
2024-09-08 09:00:09 -07:00
return float(m_startFreq + x * m_binsPerPixel * m_fftBinWidth);
2018-02-08 21:28:33 -05:00
}
void
CPlotter::setPlot2dGain(int const n) //setPlot2dGain
2018-02-08 21:28:33 -05:00
{
2024-09-08 09:00:09 -07:00
m_plot2dGain = n;
2018-02-08 21:28:33 -05:00
update();
}
void
CPlotter::setStartFreq(int const f) //SetStartFreq()
2018-02-08 21:28:33 -05:00
{
2024-09-08 09:00:09 -07:00
m_startFreq = f;
m_fMax = freqFromX(xFromFreq(5000.0));
2024-09-08 09:00:09 -07:00
resizeEvent(nullptr);
drawOverlay();
2018-02-08 21:28:33 -05:00
update();
}
void
CPlotter::setBinsPerPixel(int const n) //setBinsPerPixel
2018-02-08 21:28:33 -05:00
{
m_binsPerPixel = n < 1 ? 1 : n;
m_fMax = freqFromX(xFromFreq(5000.0));
drawOverlay(); //Redraw scales and ticks
2018-02-08 21:28:33 -05:00
update(); //trigger a new paintEvent}
}
void
CPlotter::setRxFreq(int const x) //setRxFreq
2018-02-08 21:28:33 -05:00
{
m_rxFreq = x; // x is freq in Hz
drawOverlay();
2018-02-08 21:28:33 -05:00
update();
}
void
CPlotter::leaveEvent(QEvent * event)
{
2024-10-19 09:19:01 -07:00
m_lastMouseX = -1;
event->ignore();
}
void
CPlotter::wheelEvent(QWheelEvent * event)
2024-09-08 19:54:14 -07:00
{
auto const delta = event->angleDelta();
if (delta.isNull())
{
event->ignore();
return;
}
2024-09-08 09:00:09 -07:00
int const dir = delta.y() > 0 ? 1 : -1;
int newFreq = rxFreq();
2024-09-08 19:54:14 -07:00
if(event->modifiers() & Qt::ControlModifier)
2024-09-08 09:00:09 -07:00
{
newFreq += dir;
2024-09-08 19:54:14 -07:00
}
else
{
2024-09-08 09:00:09 -07:00
newFreq = newFreq / 10 * 10 + dir * 10;
}
2024-09-08 09:00:09 -07:00
emit setFreq1(newFreq, newFreq);
}
void
CPlotter::mouseMoveEvent(QMouseEvent * event)
{
m_lastMouseX = std::clamp(static_cast<int>(event->position().x()), 0, m_Size.width());
2024-09-08 09:00:09 -07:00
update();
event->ignore();
2018-02-08 21:28:33 -05:00
2024-09-08 09:00:09 -07:00
QToolTip::showText(event->globalPosition().toPoint(),
QString::number(static_cast<int>(freqFromX(m_lastMouseX))));
2024-09-08 09:00:09 -07:00
}
void
CPlotter::mouseReleaseEvent(QMouseEvent * event)
2024-09-08 09:00:09 -07:00
{
if (Qt::LeftButton == event->button())
{
auto const x = std::clamp(static_cast<int>(event->position().x()), 0, m_Size.width());
bool const ctrl = event->modifiers() & Qt::ControlModifier;
bool const shift = event->modifiers() & Qt::ShiftModifier;
auto const newFreq = static_cast<int>(freqFromX(x) + 0.5);
2024-09-08 09:00:09 -07:00
int const oldTxFreq = m_txFreq;
int const oldRxFreq = m_rxFreq;
if (ctrl) { emit setFreq1(newFreq, newFreq ); }
else if (shift) { emit setFreq1(oldRxFreq, newFreq ); }
else { emit setFreq1(newFreq, oldTxFreq); }
2018-02-08 21:28:33 -05:00
}
2024-09-08 09:00:09 -07:00
else
{
event->ignore(); // let parent handle
2018-02-08 21:28:33 -05:00
}
}
void
CPlotter::setNsps(int const ntrperiod,
int const nsps) //setNsps
2018-02-08 21:28:33 -05:00
{
2024-09-08 09:00:09 -07:00
m_TRperiod = ntrperiod;
m_nsps = nsps;
m_fftBinWidth = fftBinWidth(nsps);
2024-09-08 09:00:09 -07:00
drawOverlay(); //Redraw scales and ticks
2024-09-08 09:00:09 -07:00
update(); //trigger a new paintEvent}
2018-02-08 21:28:33 -05:00
}
void
CPlotter::setTxFreq(int const n) //setTxFreq
2018-02-08 21:28:33 -05:00
{
2024-09-08 09:00:09 -07:00
m_txFreq = n;
drawOverlay();
2018-02-08 21:28:33 -05:00
update();
}
void
CPlotter::setDialFreq(double const d)
2018-02-08 21:28:33 -05:00
{
2024-09-08 09:00:09 -07:00
m_dialFreq = d;
drawOverlay();
2018-02-08 21:28:33 -05:00
update();
}
void
CPlotter::setBand(QString const & band)
2018-02-08 21:28:33 -05:00
{
m_band = band;
drawOverlay();
2018-11-04 22:37:14 -05:00
update();
}
void
CPlotter::setFilterCenter(int const center)
2024-09-08 09:00:09 -07:00
{
m_filterCenter = center;
drawOverlay();
update();
}
void
CPlotter::setFilterWidth(int const width)
{
2024-09-08 09:00:09 -07:00
m_filterWidth = width;
drawOverlay();
update();
}
void
CPlotter::setFilterEnabled(bool const enabled)
2024-09-08 09:00:09 -07:00
{
m_filterEnabled = enabled;
drawOverlay();
update();
}
void
CPlotter::setFilterOpacity(int const alpha)
2024-09-08 09:00:09 -07:00
{
2024-09-09 05:43:55 -07:00
m_filterOpacity = alpha;
drawOverlay();
2024-09-09 05:43:55 -07:00
update();
}
void
CPlotter::setRxRange(int const fMin)
2018-02-08 21:28:33 -05:00
{
m_fMin = fMin;
drawOverlay();
update();
2018-02-08 21:28:33 -05:00
}
void
CPlotter::setMode(QString const & mode)
{
m_mode = mode;
drawOverlay();
update();
}
void
CPlotter::setSubMode(int const nSubMode)
{
m_nSubMode = nSubMode;
drawOverlay();
update();
}
void
CPlotter::setFlatten(bool const flatten)
{
m_flatten = flatten ? 1 : 0;
2018-02-08 21:28:33 -05:00
}
void
CPlotter::setPercent2DScreen(int percent)
2018-02-08 21:28:33 -05:00
{
m_Percent2DScreen = percent;
2024-09-08 09:00:09 -07:00
resizeEvent(nullptr);
2018-02-08 21:28:33 -05:00
update();
}