2018-02-08 21:28:33 -05:00
|
|
|
|
#include "plotter.h"
|
2024-10-19 08:46:49 -07:00
|
|
|
|
#include <algorithm>
|
2024-10-26 06:31:47 -07:00
|
|
|
|
#include <cmath>
|
2024-11-16 04:50:41 -08:00
|
|
|
|
#include <iterator>
|
2024-11-10 10:52:40 -08:00
|
|
|
|
#include <numeric>
|
2024-10-26 06:31:47 -07:00
|
|
|
|
#include <type_traits>
|
2024-10-24 07:16:12 -07:00
|
|
|
|
#include <utility>
|
2024-11-15 13:03:11 -08:00
|
|
|
|
#include <QBitArray>
|
2018-02-08 21:28:33 -05:00
|
|
|
|
#include <QDebug>
|
2024-11-16 03:42:58 -08:00
|
|
|
|
#include <QLineF>
|
2024-09-01 20:03:35 -07:00
|
|
|
|
#include <QMouseEvent>
|
|
|
|
|
|
#include <QPainter>
|
2024-11-15 13:03:11 -08:00
|
|
|
|
#include <QPair>
|
2024-09-01 20:03:35 -07:00
|
|
|
|
#include <QPen>
|
2024-11-15 13:03:11 -08:00
|
|
|
|
#include <QStack>
|
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"
|
2018-09-18 17:24:07 -04:00
|
|
|
|
#include "DriftingDateTime.h"
|
2024-10-08 06:04:28 -07:00
|
|
|
|
#include "JS8Submode.hpp"
|
2018-09-18 17:24:07 -04:00
|
|
|
|
|
2024-09-08 17:00:48 -07:00
|
|
|
|
namespace
|
|
|
|
|
|
{
|
2024-11-15 19:14:16 -08:00
|
|
|
|
// Default epsilon value for RDP point reduction; adjust to taste.
|
|
|
|
|
|
|
|
|
|
|
|
constexpr qreal RDP_EPSILON = 2.0;
|
|
|
|
|
|
|
2024-12-02 09:44:36 -08:00
|
|
|
|
// The Qt Raster engine seems to have terrible performance when
|
|
|
|
|
|
// drawing large polylines; the size at which we should split
|
|
|
|
|
|
// drawing into smaller lines.
|
|
|
|
|
|
|
|
|
|
|
|
constexpr qsizetype POLYLINE_SIZE = 6;
|
|
|
|
|
|
|
2024-11-15 21:53:22 -08:00
|
|
|
|
// Resize debounce interval, in milliseconds; adjust to taste.
|
2024-09-15 08:20:24 -07:00
|
|
|
|
|
2024-11-15 21:53:22 -08:00
|
|
|
|
constexpr auto RESIZE_DEBOUNCE_INTERVAL = 100;
|
2024-09-15 08:20:24 -07:00
|
|
|
|
|
2024-11-15 21:53:22 -08:00
|
|
|
|
// Vertical divisions in the spectrum display.
|
|
|
|
|
|
|
|
|
|
|
|
constexpr std::size_t VERT_DIVS = 7;
|
2024-09-15 08:20:24 -07:00
|
|
|
|
|
2024-10-19 15:16:30 -07:00
|
|
|
|
// FFT bin width, as with NSPS, a constant; see the JT9 documentation
|
2024-10-19 15:10:31 -07:00
|
|
|
|
// for the reasoning behind the values used here, but in short, since
|
|
|
|
|
|
// NSPS is always 6912, 1500 for nsps2 and 2048 for nfft3 are optimal.
|
|
|
|
|
|
|
2024-11-11 15:42:33 -08:00
|
|
|
|
constexpr float FFT_BIN_WIDTH = 1500.0 / 2048.0;
|
2024-10-19 15:10:31 -07:00
|
|
|
|
|
2024-11-15 21:53:22 -08:00
|
|
|
|
// 30 meter band: 10.130-10.140 RTTY
|
|
|
|
|
|
// 10.140-10.150 Packet
|
2024-09-09 10:11:31 -07:00
|
|
|
|
|
2024-11-15 21:53:22 -08:00
|
|
|
|
constexpr float BAND_30M_START = 10.13f;
|
|
|
|
|
|
constexpr float BAND_30M_END = 10.15f;
|
|
|
|
|
|
|
|
|
|
|
|
// The WSPR range starts at 10.1401 MHz and runs for 200 Hz.
|
2024-11-13 22:17:15 -08:00
|
|
|
|
|
2024-11-15 21:53:22 -08:00
|
|
|
|
constexpr float WSPR_START = 10.1401f;
|
|
|
|
|
|
constexpr int WSPR_RANGE = 200;
|
2024-11-13 22:17:15 -08:00
|
|
|
|
|
2024-11-10 14:36:44 -08:00
|
|
|
|
// Band colors, always drawn with a 3-pixel pen.
|
|
|
|
|
|
|
|
|
|
|
|
constexpr auto BAND_EDGE = QColor{149, 165, 166}; // Gray
|
|
|
|
|
|
constexpr auto BAND_GOOD = QColor{ 46, 204, 113}; // Green
|
|
|
|
|
|
constexpr auto BAND_WARN = QColor{241, 196, 15}; // Yellow
|
|
|
|
|
|
constexpr auto BAND_WSPR = QColor{230, 126, 34}; // Orange
|
|
|
|
|
|
|
2024-10-26 06:31:47 -07:00
|
|
|
|
// Given a floating point value, return the fractional portion of the
|
|
|
|
|
|
// value e.g., 42.7 -> 0.7.
|
|
|
|
|
|
|
|
|
|
|
|
template <typename T,
|
|
|
|
|
|
typename = std::enable_if_t<std::is_floating_point_v<T>>>
|
2024-10-26 06:35:35 -07:00
|
|
|
|
constexpr auto
|
2024-10-26 06:31:47 -07:00
|
|
|
|
fractionalPart(T const v)
|
|
|
|
|
|
{
|
|
|
|
|
|
T integralPart;
|
|
|
|
|
|
return std::modf(v, &integralPart);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-16 04:50:41 -08:00
|
|
|
|
// Standard overload template for use in visitation.
|
|
|
|
|
|
|
|
|
|
|
|
template<typename... Ts>
|
|
|
|
|
|
struct overload : Ts ... {
|
|
|
|
|
|
using Ts::operator() ...;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// While C++20 can deduce the above, C++17 can't; this guide
|
|
|
|
|
|
// can be removed when we move to C++20 as a requirement.
|
|
|
|
|
|
|
|
|
|
|
|
template<typename... Ts> overload(Ts...) -> overload<Ts...>;
|
|
|
|
|
|
|
|
|
|
|
|
// An algorithm similar to std::remove_if(), but passing indices
|
|
|
|
|
|
// to its predicate.
|
|
|
|
|
|
|
|
|
|
|
|
template<typename ForwardIt,
|
|
|
|
|
|
typename UnaryPredicate>
|
|
|
|
|
|
ForwardIt
|
|
|
|
|
|
remove_if_index(ForwardIt first,
|
|
|
|
|
|
ForwardIt last,
|
|
|
|
|
|
UnaryPredicate p)
|
|
|
|
|
|
{
|
|
|
|
|
|
ForwardIt dest = first;
|
|
|
|
|
|
for (ForwardIt i = first; i != last; ++i)
|
|
|
|
|
|
if (!p(std::distance(first, i)))
|
|
|
|
|
|
*dest++ = std::move(*i);
|
|
|
|
|
|
return dest;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-26 06:31:47 -07:00
|
|
|
|
// Given the frequency span of the entire viewable plot region, return
|
|
|
|
|
|
// the frequency span that each division should occupy.
|
|
|
|
|
|
|
2024-11-10 09:25:11 -08:00
|
|
|
|
auto
|
2024-11-11 15:42:33 -08:00
|
|
|
|
freqPerDiv(float const fSpan)
|
2024-09-08 17:00:48 -07:00
|
|
|
|
{
|
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;
|
2024-09-08 17:00:48 -07:00
|
|
|
|
}
|
2024-11-10 09:25:11 -08:00
|
|
|
|
|
2024-11-15 11:32:10 -08:00
|
|
|
|
// We'll typically end up with a ton of points to draw for the spectrum,
|
|
|
|
|
|
// and some simplification is worthwhile; use the Ramer–Douglas–Peucker
|
2024-11-15 19:14:16 -08:00
|
|
|
|
// algorithm to reduce to a smaller number of points.
|
2024-11-15 18:42:48 -08:00
|
|
|
|
//
|
2024-11-15 19:14:16 -08:00
|
|
|
|
// We'll modify the inbound polygon in place, such that anything we want
|
|
|
|
|
|
// to keep is at the start of the polygon and anything we want to omit
|
|
|
|
|
|
// is at the end, returning an iterator to the new end, i.e., the point
|
2024-11-15 18:42:48 -08:00
|
|
|
|
// one past the last point we want to keep.
|
2024-11-16 10:27:32 -08:00
|
|
|
|
//
|
|
|
|
|
|
// Our goal here is to avoid reallocations. Since we're at worst going to
|
|
|
|
|
|
// be leaving this the same size, we should be able to work with what we
|
|
|
|
|
|
// have already.
|
2024-11-15 11:32:10 -08:00
|
|
|
|
|
2024-11-15 13:03:11 -08:00
|
|
|
|
auto
|
2024-11-15 19:14:16 -08:00
|
|
|
|
rdp(QPolygonF & polygon,
|
|
|
|
|
|
qreal const epsilon = RDP_EPSILON)
|
2024-11-15 13:30:36 -08:00
|
|
|
|
{
|
2024-11-16 04:02:21 -08:00
|
|
|
|
// There's no point in proceeding with less than 3 points.
|
|
|
|
|
|
|
|
|
|
|
|
if (polygon.size() < 3) return polygon.end();
|
|
|
|
|
|
|
2024-11-15 13:30:36 -08:00
|
|
|
|
// Prime our array such that all points are initially in play, and
|
2024-11-16 04:02:21 -08:00
|
|
|
|
// our stack to consider the full span; run the stack machine until
|
|
|
|
|
|
// it empties.
|
2024-11-15 13:30:36 -08:00
|
|
|
|
|
2024-11-16 10:27:32 -08:00
|
|
|
|
auto elide = QBitArray{polygon.size()};
|
2024-11-16 04:02:21 -08:00
|
|
|
|
auto stack = QStack<QPair<qsizetype, qsizetype>>
|
|
|
|
|
|
{{
|
2024-11-16 03:42:58 -08:00
|
|
|
|
{qsizetype{0}, polygon.size() - 1}
|
|
|
|
|
|
}};
|
2024-11-15 11:32:10 -08:00
|
|
|
|
|
2024-11-15 13:03:11 -08:00
|
|
|
|
while (!stack.isEmpty())
|
|
|
|
|
|
{
|
|
|
|
|
|
auto const [
|
2024-11-15 20:49:27 -08:00
|
|
|
|
index1,
|
|
|
|
|
|
index2
|
2024-11-15 13:03:11 -08:00
|
|
|
|
] = stack.pop();
|
2024-11-15 11:32:10 -08:00
|
|
|
|
|
2024-11-16 10:27:32 -08:00
|
|
|
|
// Create a theoretical line between the first and last points
|
|
|
|
|
|
// in the span we're presently considering. Compute the length
|
|
|
|
|
|
// of the line and the vector components. While the components
|
|
|
|
|
|
// are cheap to compute, the length is expensive.
|
2024-11-15 20:49:27 -08:00
|
|
|
|
|
2024-11-16 04:02:21 -08:00
|
|
|
|
auto const line = QLineF(polygon.at(index1), polygon.at(index2));
|
|
|
|
|
|
auto const ll = line.length();
|
|
|
|
|
|
auto const dx = line.dx();
|
|
|
|
|
|
auto const dy = line.dy();
|
2024-11-15 20:49:27 -08:00
|
|
|
|
|
2024-11-16 10:27:32 -08:00
|
|
|
|
// Find the point within the span at the largest perpendicular
|
|
|
|
|
|
// distance from the line.
|
|
|
|
|
|
|
2024-11-15 20:49:27 -08:00
|
|
|
|
auto index = index1;
|
|
|
|
|
|
qreal dMax = 0.0;
|
|
|
|
|
|
|
|
|
|
|
|
for (auto i = index1 + 1;
|
|
|
|
|
|
i < index2;
|
2024-11-15 13:18:14 -08:00
|
|
|
|
++i)
|
2024-11-15 11:32:10 -08:00
|
|
|
|
{
|
2024-11-16 10:27:32 -08:00
|
|
|
|
// We want to consider this point only if hasn't already been
|
|
|
|
|
|
// marked for death. If it's still in play, see if it's got a
|
|
|
|
|
|
// larger perpendicular distance from the line.
|
|
|
|
|
|
|
|
|
|
|
|
if (!elide.at(i))
|
2024-11-15 13:03:11 -08:00
|
|
|
|
{
|
2024-11-16 04:02:21 -08:00
|
|
|
|
auto const & point = polygon.at(i);
|
2024-11-16 03:42:58 -08:00
|
|
|
|
|
2024-11-16 04:06:48 -08:00
|
|
|
|
if (auto const d = std::abs(dy * (point.x() - line.x1()) -
|
|
|
|
|
|
dx * (point.y() - line.y1())) / ll;
|
|
|
|
|
|
d > dMax)
|
2024-11-15 13:03:11 -08:00
|
|
|
|
{
|
|
|
|
|
|
index = i;
|
|
|
|
|
|
dMax = d;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-11-15 11:32:10 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-15 13:03:11 -08:00
|
|
|
|
// If the max distance is above epsilon, then we have to keep
|
|
|
|
|
|
// working the problem. If not, cull the indices of points that
|
2024-11-15 13:30:36 -08:00
|
|
|
|
// are not relevant to the result, i.e., everything but for the
|
|
|
|
|
|
// first and last.
|
2024-11-15 11:32:10 -08:00
|
|
|
|
|
2024-11-15 13:03:11 -08:00
|
|
|
|
if (dMax > epsilon)
|
|
|
|
|
|
{
|
2024-11-15 20:49:27 -08:00
|
|
|
|
stack.push({index1, index});
|
|
|
|
|
|
stack.push({index, index2});
|
2024-11-15 13:03:11 -08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2024-11-15 20:49:27 -08:00
|
|
|
|
for (auto i = index1 + 1;
|
|
|
|
|
|
i < index2;
|
2024-11-15 13:18:14 -08:00
|
|
|
|
++i)
|
2024-11-15 13:03:11 -08:00
|
|
|
|
{
|
2024-11-16 10:27:32 -08:00
|
|
|
|
elide.setBit(i);
|
2024-11-15 13:03:11 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-11-15 11:32:10 -08:00
|
|
|
|
|
2024-11-15 19:14:16 -08:00
|
|
|
|
// Our array now contains bits set to true for every point that
|
2024-11-16 04:50:41 -08:00
|
|
|
|
// should be removed, false for those that should be kept. Move
|
|
|
|
|
|
// everything we want to keep to the front and return the first
|
|
|
|
|
|
// element to remove.
|
2024-11-15 18:42:48 -08:00
|
|
|
|
|
2024-11-16 04:50:41 -08:00
|
|
|
|
return remove_if_index(polygon.begin(),
|
|
|
|
|
|
polygon.end(),
|
2024-11-16 10:27:32 -08:00
|
|
|
|
[&elide = std::as_const(elide)]
|
|
|
|
|
|
(auto const i)
|
2024-11-15 13:03:11 -08:00
|
|
|
|
{
|
2024-11-16 10:27:32 -08:00
|
|
|
|
return elide.at(i);
|
2024-11-16 04:50:41 -08:00
|
|
|
|
});
|
2024-11-15 11:32:10 -08:00
|
|
|
|
}
|
2024-09-08 17:00:48 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-19 13:44:02 -07:00
|
|
|
|
CPlotter::CPlotter(QWidget * parent)
|
2024-11-09 05:51:37 -08:00
|
|
|
|
: QWidget {parent}
|
2024-11-13 22:17:15 -08:00
|
|
|
|
, m_resize {new QTimer(this)}
|
2024-11-08 07:12:52 -08:00
|
|
|
|
, m_freqPerPixel {m_binsPerPixel * FFT_BIN_WIDTH}
|
2018-02-08 21:28:33 -05:00
|
|
|
|
{
|
|
|
|
|
|
setFocusPolicy(Qt::StrongFocus);
|
2024-11-09 05:51:37 -08:00
|
|
|
|
setMouseTracking(true);
|
2024-11-13 22:17:15 -08:00
|
|
|
|
|
|
|
|
|
|
// Debounce resize events such that resize() doesn't actually get called
|
|
|
|
|
|
// until the debounce time has elapsed without any further resize events.
|
|
|
|
|
|
|
|
|
|
|
|
m_resize->setSingleShot(true);
|
|
|
|
|
|
m_resize->setInterval(RESIZE_DEBOUNCE_INTERVAL);
|
|
|
|
|
|
|
|
|
|
|
|
connect(m_resize, &QTimer::timeout, this, &CPlotter::resize);
|
2018-02-08 21:28:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-22 18:34:32 -08:00
|
|
|
|
CPlotter::~CPlotter() = default;
|
|
|
|
|
|
|
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
|
2024-10-20 21:25:26 -07:00
|
|
|
|
CPlotter::paintEvent(QPaintEvent *)
|
2018-02-08 21:28:33 -05:00
|
|
|
|
{
|
2024-11-09 05:17:21 -08:00
|
|
|
|
QPainter p(this);
|
2024-10-19 08:46:49 -07:00
|
|
|
|
|
2024-11-10 13:35:17 -08:00
|
|
|
|
p.drawPixmap(0, 0, m_ScalePixmap);
|
|
|
|
|
|
p.drawPixmap(0, 30, m_WaterfallPixmap);
|
2024-10-31 06:53:30 -07:00
|
|
|
|
p.drawPixmap(0, m_h1, m_SpectrumPixmap);
|
2018-08-10 14:04:03 -04:00
|
|
|
|
|
2024-11-09 07:37:04 -08:00
|
|
|
|
p.drawPixmap(xFromFreq(m_freq), 30, m_DialPixmap[0]);
|
2018-08-29 11:06:46 -04:00
|
|
|
|
|
2024-11-09 07:37:04 -08:00
|
|
|
|
if (m_lastMouseX >= 0)
|
2024-09-09 07:35:27 -07:00
|
|
|
|
{
|
2024-11-07 13:10:26 -08:00
|
|
|
|
p.drawPixmap(m_lastMouseX, 30, m_DialPixmap[1]);
|
2018-08-29 11:06:46 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-09 07:35:27 -07:00
|
|
|
|
if (m_filterEnabled && m_filterWidth > 0)
|
|
|
|
|
|
{
|
2024-11-08 04:49:04 -08:00
|
|
|
|
p.drawPixmap( 0, 0, m_FilterPixmap[0]);
|
|
|
|
|
|
p.drawPixmap(m_w - m_FilterPixmap[1].deviceIndependentSize().width(), 0, m_FilterPixmap[1]);
|
2019-12-16 13:42:44 -05:00
|
|
|
|
}
|
2018-02-08 21:28:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 04:49:21 -08:00
|
|
|
|
void
|
|
|
|
|
|
CPlotter::resizeEvent(QResizeEvent *)
|
|
|
|
|
|
{
|
2024-11-14 04:43:02 -08:00
|
|
|
|
m_resize->start();
|
2024-11-12 04:49:21 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-13 14:08:06 -07:00
|
|
|
|
void
|
2024-11-13 16:27:39 -08:00
|
|
|
|
CPlotter::drawLine(QString const & text)
|
2018-02-08 21:28:33 -05:00
|
|
|
|
{
|
2024-11-13 06:35:44 -08:00
|
|
|
|
m_WaterfallPixmap.scroll(0, 1, m_WaterfallPixmap.rect());
|
|
|
|
|
|
|
|
|
|
|
|
QPainter p(&m_WaterfallPixmap);
|
|
|
|
|
|
|
|
|
|
|
|
// Draw a green line across the complete span.
|
|
|
|
|
|
|
|
|
|
|
|
p.setPen(Qt::green);
|
|
|
|
|
|
p.drawLine(0, 0, m_w, 0);
|
|
|
|
|
|
|
2024-11-13 16:27:39 -08:00
|
|
|
|
// Compute the number of lines required before we need to draw the
|
|
|
|
|
|
// text, and note the text to draw, saving it against a potential
|
|
|
|
|
|
// replot request.
|
2024-11-13 06:35:44 -08:00
|
|
|
|
|
2024-11-13 16:27:39 -08:00
|
|
|
|
m_text = text;
|
2024-11-13 06:35:44 -08:00
|
|
|
|
m_line = p.fontMetrics().height() * devicePixelRatio();
|
|
|
|
|
|
m_replot.push_front(m_text);
|
|
|
|
|
|
|
|
|
|
|
|
update();
|
|
|
|
|
|
}
|
2024-09-08 08:13:34 -07:00
|
|
|
|
|
2024-11-13 06:35:44 -08:00
|
|
|
|
void
|
2024-11-14 09:25:16 -08:00
|
|
|
|
CPlotter::drawData(WF::SWide swide)
|
2024-11-13 06:35:44 -08:00
|
|
|
|
{
|
2024-11-10 07:41:10 -08:00
|
|
|
|
m_WaterfallPixmap.scroll(0, 1, m_WaterfallPixmap.rect());
|
2024-09-08 09:00:09 -07:00
|
|
|
|
|
2024-10-31 06:53:30 -07:00
|
|
|
|
QPainter p(&m_WaterfallPixmap);
|
2024-09-08 20:09:29 -07:00
|
|
|
|
|
2024-11-14 18:24:56 -08:00
|
|
|
|
// Convert the power scale we receive to dB. Note that while we could
|
|
|
|
|
|
// convert only m_w elements here for immediate display, we want to
|
|
|
|
|
|
// convert the full range of the data so that we can be resized and
|
|
|
|
|
|
// still properly display without having to process the data again.
|
2024-11-10 23:50:02 -08:00
|
|
|
|
|
2024-11-15 07:14:34 -08:00
|
|
|
|
std::transform(swide.begin(),
|
|
|
|
|
|
swide.end(),
|
|
|
|
|
|
swide.begin(),
|
|
|
|
|
|
[](auto const value)
|
|
|
|
|
|
{
|
2024-11-15 07:30:56 -08:00
|
|
|
|
return 10.0f * std::log10(value);
|
2024-11-15 07:14:34 -08:00
|
|
|
|
});
|
|
|
|
|
|
|
2024-12-01 13:04:31 -08:00
|
|
|
|
// Flattening, we just process the visible width; tends to be the best
|
|
|
|
|
|
// approach in terms of what happens when resizing to a larger size.
|
2024-11-15 07:14:34 -08:00
|
|
|
|
|
2024-11-25 19:49:38 -08:00
|
|
|
|
m_flatten(swide.data(), m_w);
|
2024-11-14 06:37:58 -08:00
|
|
|
|
|
|
|
|
|
|
// Display the processed data in the waterfall, drawing only the range
|
|
|
|
|
|
// that's displayed.
|
|
|
|
|
|
|
|
|
|
|
|
auto const gain = gainFactor();
|
2024-11-13 06:35:44 -08:00
|
|
|
|
|
2024-12-01 15:15:33 -08:00
|
|
|
|
for (auto x = 0; x < m_w; ++x)
|
2024-09-08 20:09:29 -07:00
|
|
|
|
{
|
2024-12-01 14:51:18 -08:00
|
|
|
|
p.setPen(m_colors[std::clamp(m_plotZero + static_cast<int>(swide[x] * gain), 0, 254)]);
|
2024-11-14 06:37:58 -08:00
|
|
|
|
p.drawPoint(x, 0);
|
2024-11-13 06:35:44 -08:00
|
|
|
|
}
|
2024-11-10 23:50:02 -08:00
|
|
|
|
|
2024-11-13 06:35:44 -08:00
|
|
|
|
// See if we've reached the point where we should draw previously computed
|
|
|
|
|
|
// line text.
|
2024-11-10 23:50:02 -08:00
|
|
|
|
|
2024-11-13 06:35:44 -08:00
|
|
|
|
if (--m_line == 0)
|
2024-09-08 20:01:56 -07:00
|
|
|
|
{
|
2024-11-13 06:35:44 -08:00
|
|
|
|
m_line = std::numeric_limits<int>::max();
|
|
|
|
|
|
|
|
|
|
|
|
p.setPen(Qt::white);
|
|
|
|
|
|
p.drawText(5, p.fontMetrics().ascent(), m_text);
|
2024-11-10 13:35:17 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-10 14:58:20 -08:00
|
|
|
|
// Our spectrum might be of zero height, in which case our overlay pixmap
|
|
|
|
|
|
// isn't going to be usable; proceed to spectrum work only if it's usable.
|
2024-11-10 13:35:17 -08:00
|
|
|
|
|
2024-11-10 14:58:20 -08:00
|
|
|
|
if (!m_OverlayPixmap.isNull())
|
2024-11-10 13:35:17 -08:00
|
|
|
|
{
|
2024-11-15 05:03:56 -08:00
|
|
|
|
// We draw the spectrum by copying the overlay prototype and drawing our
|
|
|
|
|
|
// points into it.
|
2024-11-09 13:15:00 -08:00
|
|
|
|
|
2024-11-15 05:03:56 -08:00
|
|
|
|
m_SpectrumPixmap = m_OverlayPixmap.copy();
|
|
|
|
|
|
|
|
|
|
|
|
QPainter p(&m_SpectrumPixmap);
|
|
|
|
|
|
|
2024-11-28 14:21:41 -08:00
|
|
|
|
// Add a point to the polyline.
|
2024-11-10 10:02:27 -08:00
|
|
|
|
|
2024-11-14 21:04:06 -08:00
|
|
|
|
auto const addPoint = [this,
|
|
|
|
|
|
gain = std::pow(10.0f, 0.02f * m_plot2dGain),
|
2024-11-15 05:03:56 -08:00
|
|
|
|
view = m_h2 * 0.9f,
|
2024-12-01 15:17:21 -08:00
|
|
|
|
span = m_h2 / 70.0f](int const x,
|
2024-12-01 14:51:18 -08:00
|
|
|
|
float const y)
|
2024-11-14 21:04:06 -08:00
|
|
|
|
{
|
2024-12-01 14:51:18 -08:00
|
|
|
|
m_points.emplace_back(x, view - span * ((m_plot2dZero + gain * y)));
|
2024-11-14 21:04:06 -08:00
|
|
|
|
};
|
2024-09-13 14:08:06 -07:00
|
|
|
|
|
2024-12-01 14:51:18 -08:00
|
|
|
|
// Add points from one of the ranges of adjunct data instead of the
|
|
|
|
|
|
// spectrum data.
|
2024-11-15 05:03:56 -08:00
|
|
|
|
|
2024-12-01 14:51:18 -08:00
|
|
|
|
auto const addPoints = [this, &addPoint](auto const begin,
|
|
|
|
|
|
auto const value)
|
|
|
|
|
|
|
2024-11-15 05:03:56 -08:00
|
|
|
|
{
|
2024-12-01 15:25:00 -08:00
|
|
|
|
// Determine the starting bin offset of the adjunct data.
|
2024-12-01 14:51:18 -08:00
|
|
|
|
|
|
|
|
|
|
auto const start = begin + static_cast<std::size_t>(m_startFreq / FFT_BIN_WIDTH + 0.5f);
|
|
|
|
|
|
|
2024-12-01 15:25:00 -08:00
|
|
|
|
// Average the values in each range of adjunct data bins
|
|
|
|
|
|
// and convert to points, passing the average through the
|
|
|
|
|
|
// supplied value function.
|
2024-12-01 14:51:18 -08:00
|
|
|
|
|
|
|
|
|
|
for (auto x = 0; x < m_w; ++x)
|
|
|
|
|
|
{
|
|
|
|
|
|
auto const first = start + x * m_binsPerPixel;
|
|
|
|
|
|
|
|
|
|
|
|
addPoint(x, value(std::reduce(first,
|
|
|
|
|
|
first + m_binsPerPixel) /
|
|
|
|
|
|
m_binsPerPixel));
|
|
|
|
|
|
}
|
2024-11-15 05:03:56 -08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Clear the current points and ensure space exists to add all the
|
|
|
|
|
|
// points we require without reallocation.
|
|
|
|
|
|
|
|
|
|
|
|
m_points.clear();
|
|
|
|
|
|
m_points.reserve(m_w);
|
|
|
|
|
|
|
2024-11-14 21:04:06 -08:00
|
|
|
|
switch (m_spectrum)
|
2024-09-08 20:09:29 -07:00
|
|
|
|
{
|
2024-12-01 09:55:44 -08:00
|
|
|
|
// Current spectrum is displayed as a green line. Find the minimum
|
|
|
|
|
|
// value within the displayed spectrum, then display each point as
|
|
|
|
|
|
// the delta above that value.
|
|
|
|
|
|
|
2024-11-14 21:04:06 -08:00
|
|
|
|
case Spectrum::Current:
|
|
|
|
|
|
{
|
2024-11-15 05:03:56 -08:00
|
|
|
|
p.setPen(Qt::green);
|
|
|
|
|
|
|
2024-12-01 14:51:18 -08:00
|
|
|
|
auto const min = *std::min_element(swide.begin(),
|
|
|
|
|
|
swide.begin() + m_w);
|
2024-11-14 21:04:06 -08:00
|
|
|
|
|
2024-12-01 14:51:18 -08:00
|
|
|
|
for (auto x = 0; x < m_w; ++x) addPoint(x, swide[x] - min);
|
2024-11-14 21:04:06 -08:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
2024-11-10 14:58:20 -08:00
|
|
|
|
|
2024-12-01 14:51:18 -08:00
|
|
|
|
// Cumulative spectrum is displayed as a cyan line; use the average
|
|
|
|
|
|
// data, which is power scaled and must be converted to dB scale.
|
2024-12-01 09:55:44 -08:00
|
|
|
|
|
2024-11-14 21:04:06 -08:00
|
|
|
|
case Spectrum::Cumulative:
|
2024-11-10 14:58:20 -08:00
|
|
|
|
{
|
2024-11-15 05:03:56 -08:00
|
|
|
|
p.setPen(Qt::cyan);
|
2024-12-01 14:51:18 -08:00
|
|
|
|
addPoints(std::begin(dec_data.savg), [](auto const value)
|
2024-11-14 21:04:06 -08:00
|
|
|
|
{
|
2024-12-01 14:51:18 -08:00
|
|
|
|
return 30.0f + 10.0f * std::log10(value);
|
|
|
|
|
|
});
|
2024-11-10 14:58:20 -08:00
|
|
|
|
}
|
2024-11-14 21:04:06 -08:00
|
|
|
|
break;
|
2024-12-01 09:55:44 -08:00
|
|
|
|
|
2024-12-01 14:51:18 -08:00
|
|
|
|
// Linear Average spectrum is displayed as a yellow line; use the
|
|
|
|
|
|
// the precomputed linear average data.
|
2024-11-14 21:04:06 -08:00
|
|
|
|
|
|
|
|
|
|
case Spectrum::LinearAvg:
|
|
|
|
|
|
{
|
2024-11-15 05:03:56 -08:00
|
|
|
|
p.setPen(Qt::yellow);
|
2024-12-01 14:51:18 -08:00
|
|
|
|
addPoints(std::begin(spectra_.syellow), [](auto const value)
|
2024-11-15 05:03:56 -08:00
|
|
|
|
{
|
2024-12-01 14:51:18 -08:00
|
|
|
|
return value;
|
|
|
|
|
|
});
|
2024-11-14 21:04:06 -08:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
2018-02-08 21:28:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-15 11:32:10 -08:00
|
|
|
|
// Draw the spectrum line, reducing the resulting points prior to
|
2024-12-02 09:44:36 -08:00
|
|
|
|
// drawing them, but keeping the collection capacity. We also work
|
|
|
|
|
|
// around what seems to be a performance bug in all versions of Qt
|
|
|
|
|
|
// up to and including 6.8, when drawing large polylines; this was
|
|
|
|
|
|
// culled from the Qwt library's workaround for the issue. Doubles
|
|
|
|
|
|
// overall program performance, pretty much.
|
2024-11-15 11:32:10 -08:00
|
|
|
|
|
2024-11-15 18:42:48 -08:00
|
|
|
|
m_points.erase(rdp(m_points), m_points.end());
|
2024-11-10 14:58:20 -08:00
|
|
|
|
p.setRenderHint(QPainter::Antialiasing);
|
2024-12-02 09:44:36 -08:00
|
|
|
|
|
|
|
|
|
|
for (qsizetype i = 0;
|
|
|
|
|
|
i < m_points.size();
|
|
|
|
|
|
i += POLYLINE_SIZE)
|
|
|
|
|
|
{
|
|
|
|
|
|
p.drawPolyline(m_points.data() + i, qMin(POLYLINE_SIZE + 1,
|
|
|
|
|
|
m_points.size() - i));
|
|
|
|
|
|
}
|
2024-11-10 14:58:20 -08:00
|
|
|
|
}
|
2024-11-10 13:35:17 -08:00
|
|
|
|
|
2024-11-13 21:01:28 -08:00
|
|
|
|
// Save the data against a potential replot requirement.
|
|
|
|
|
|
|
|
|
|
|
|
m_replot.push_front(std::move(swide));
|
|
|
|
|
|
|
2024-11-10 13:35:17 -08:00
|
|
|
|
update();
|
2024-10-31 06:53:30 -07: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
|
|
|
|
{
|
2024-10-19 09:14:10 -07:00
|
|
|
|
auto const x1 = xFromFreq(ia);
|
|
|
|
|
|
auto const x2 = xFromFreq(ib);
|
2020-05-06 11:15:41 -04:00
|
|
|
|
|
2024-10-25 20:41:55 -07:00
|
|
|
|
QPainter p(&m_WaterfallPixmap);
|
2024-09-08 17:53:35 -07:00
|
|
|
|
|
2024-10-25 20:41:55 -07:00
|
|
|
|
p.setPen(color);
|
|
|
|
|
|
p.drawLine(qMin(x1, x2), 4, qMax(x1, x2), 4);
|
|
|
|
|
|
p.drawLine(qMin(x1, x2), 0, qMin(x1, x2), 9);
|
|
|
|
|
|
p.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)
|
2020-05-07 15:47:50 -04:00
|
|
|
|
{
|
2024-10-25 20:41:55 -07:00
|
|
|
|
QPainter p(&m_WaterfallPixmap);
|
2024-09-08 19:54:14 -07:00
|
|
|
|
|
2024-10-25 20:41:55 -07:00
|
|
|
|
p.setPen(color);
|
|
|
|
|
|
p.drawLine(x, 0, width <= 0 ? m_w : x + width, 0);
|
2020-05-07 15:47:50 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-19 09:14:10 -07:00
|
|
|
|
void
|
2024-11-10 14:36:44 -08:00
|
|
|
|
CPlotter::drawMetrics()
|
2024-09-09 07:50:19 -07:00
|
|
|
|
{
|
2024-11-10 14:42:37 -08:00
|
|
|
|
if (m_ScalePixmap.isNull()) return;
|
|
|
|
|
|
|
2024-09-12 12:40:59 -07:00
|
|
|
|
m_ScalePixmap.fill(Qt::white);
|
2024-11-10 14:36:44 -08:00
|
|
|
|
|
2024-09-09 07:50:19 -07:00
|
|
|
|
QPainter p(&m_ScalePixmap);
|
|
|
|
|
|
|
|
|
|
|
|
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-11-10 14:36:44 -08:00
|
|
|
|
auto const fSpan = m_w * m_freqPerPixel;
|
|
|
|
|
|
auto const fpd = freqPerDiv(fSpan);
|
|
|
|
|
|
float const ppdV = fpd / m_freqPerPixel;
|
2024-11-11 15:42:33 -08:00
|
|
|
|
std::size_t const hdivs = fSpan / fpd + 1.9999f;
|
2024-10-19 12:50:43 -07:00
|
|
|
|
int const fOffset = ((m_startFreq + fpd - 1) / fpd) * fpd;
|
2024-11-11 15:42:33 -08:00
|
|
|
|
auto const xOffset = float(fOffset - m_startFreq) / fpd;
|
2024-10-25 13:32:16 -07:00
|
|
|
|
std::size_t const nMajor = hdivs - 1;
|
2024-10-19 12:50:43 -07:00
|
|
|
|
std::size_t const nMinor = fpd == 200 ? 4: 5;
|
2024-09-12 19:00:35 -07:00
|
|
|
|
float const ppdVM = ppdV / nMinor;
|
|
|
|
|
|
float const ppdVL = ppdV / 2;
|
2024-09-09 10:11:31 -07:00
|
|
|
|
|
2024-09-12 19:00:35 -07:00
|
|
|
|
// Draw ticks and labels.
|
2024-09-09 07:50:19 -07:00
|
|
|
|
|
2024-10-25 13:32:16 -07:00
|
|
|
|
for (std::size_t iMajor = 0; iMajor < nMajor; iMajor++)
|
2024-09-08 14:32:15 -07:00
|
|
|
|
{
|
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 12:37:10 -07:00
|
|
|
|
|
2024-09-12 19:00:35 -07:00
|
|
|
|
for (std::size_t iMinor = 1; iMinor < nMinor; iMinor++)
|
2024-09-12 12:37:10 -07:00
|
|
|
|
{
|
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 12:37:10 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
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,
|
2024-10-19 12:50:43 -07:00
|
|
|
|
QString::number(fOffset + iMajor * fpd));
|
2024-09-12 19:00:35 -07:00
|
|
|
|
}
|
2018-02-08 21:28:33 -05:00
|
|
|
|
}
|
2024-09-12 12:40:59 -07:00
|
|
|
|
|
2024-10-24 07:16:12 -07:00
|
|
|
|
// Given a starting frequency and range to cover, return corresponding
|
|
|
|
|
|
// X values for the sub-band.
|
|
|
|
|
|
|
|
|
|
|
|
auto const bandX = [this](float const start,
|
|
|
|
|
|
int const range)
|
|
|
|
|
|
{
|
|
|
|
|
|
return std::make_pair(xFromFreq(start),
|
|
|
|
|
|
xFromFreq(start + range));
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Given a pair of X values, draw a band line, if visible.
|
2024-09-12 12:37:10 -07:00
|
|
|
|
|
2024-10-24 07:16:12 -07:00
|
|
|
|
auto const drawBand = [this, &p](auto const & bandX)
|
2024-09-08 14:32:15 -07:00
|
|
|
|
{
|
2024-10-24 07:16:12 -07:00
|
|
|
|
auto const [x1, x2] = bandX;
|
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-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
|
|
|
|
}
|
2024-10-24 07:16:12 -07:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Colorize the JS8 sub-bands.
|
|
|
|
|
|
|
2024-11-10 14:36:44 -08:00
|
|
|
|
p.setPen(QPen(BAND_EDGE, 3)); drawBand(bandX( 0.0f, 4000));
|
|
|
|
|
|
p.setPen(QPen(BAND_WARN, 3)); drawBand(bandX( 500.0f, 2500));
|
|
|
|
|
|
p.setPen(QPen(BAND_GOOD, 3)); drawBand(bandX(1000.0f, 1500));
|
2024-09-09 07:35:27 -07:00
|
|
|
|
|
2024-09-15 08:20:24 -07: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.
|
|
|
|
|
|
|
2024-10-19 09:14:10 -07:00
|
|
|
|
if (in30MBand())
|
2024-09-15 08:20:24 -07:00
|
|
|
|
{
|
2024-10-24 21:31:34 -07:00
|
|
|
|
auto const wspr = bandX(1.0e6f * (WSPR_START - m_dialFreq), WSPR_RANGE);
|
2024-11-10 15:56:07 -08:00
|
|
|
|
auto font = QFont();
|
2024-09-15 08:20:24 -07:00
|
|
|
|
|
2024-11-10 15:56:07 -08:00
|
|
|
|
font.setBold(true);
|
|
|
|
|
|
font.setPointSize(10);
|
|
|
|
|
|
|
|
|
|
|
|
p.setFont(font);
|
2024-11-10 14:36:44 -08:00
|
|
|
|
p.setPen(QPen(BAND_WSPR, 3));
|
2024-10-24 07:16:12 -07:00
|
|
|
|
drawBand(wspr);
|
|
|
|
|
|
p.drawText(QRect(wspr.first, 0, wspr.second - wspr.first, 25),
|
2024-09-15 13:24:27 -07:00
|
|
|
|
Qt::AlignHCenter|Qt::AlignBottom,
|
|
|
|
|
|
"WSPR");
|
2024-09-15 08:20:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-10 14:58:20 -08:00
|
|
|
|
// Our spectrum might be of zero height, in which case our overlay pixmap
|
|
|
|
|
|
// isn't going to be usable; proceed only if it's usable.
|
2024-09-15 08:20:24 -07:00
|
|
|
|
|
2024-11-10 14:58:20 -08:00
|
|
|
|
if (!m_OverlayPixmap.isNull())
|
|
|
|
|
|
{
|
|
|
|
|
|
QLinearGradient gradient(0, 0, 0, m_h2);
|
2024-11-10 14:36:44 -08:00
|
|
|
|
|
2024-11-10 14:58:20 -08:00
|
|
|
|
gradient.setColorAt(1, Qt::black);
|
|
|
|
|
|
gradient.setColorAt(0, Qt::darkBlue);
|
2024-11-10 14:36:44 -08:00
|
|
|
|
|
2024-11-10 14:58:20 -08:00
|
|
|
|
QPainter p(&m_OverlayPixmap);
|
2024-11-10 14:36:44 -08:00
|
|
|
|
|
2024-11-10 14:58:20 -08:00
|
|
|
|
p.setBrush(gradient);
|
|
|
|
|
|
p.drawRect(0, 0, m_w, m_h2);
|
|
|
|
|
|
p.setBrush(Qt::SolidPattern);
|
|
|
|
|
|
p.setPen(QPen(Qt::darkGray, 1, Qt::DotLine));
|
2024-11-10 14:36:44 -08:00
|
|
|
|
|
2024-11-10 14:58:20 -08:00
|
|
|
|
// Draw vertical grids.
|
2024-11-10 14:36:44 -08:00
|
|
|
|
|
2024-11-11 15:42:33 -08:00
|
|
|
|
auto const x0 = static_cast<int>(fractionalPart((float)m_startFreq / fpd) * ppdV + 0.5f);
|
2024-11-10 14:36:44 -08:00
|
|
|
|
|
2024-11-10 14:58:20 -08:00
|
|
|
|
for (std::size_t i = 1; i < hdivs; i++)
|
2024-11-10 14:36:44 -08:00
|
|
|
|
{
|
2024-11-10 14:58:20 -08:00
|
|
|
|
if (auto const x = static_cast<int>(i * ppdV) - x0;
|
|
|
|
|
|
x >= 0 &&
|
|
|
|
|
|
x <= m_w)
|
|
|
|
|
|
{
|
|
|
|
|
|
p.drawLine(x, 0, x , m_h2);
|
|
|
|
|
|
}
|
2024-11-10 14:36:44 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-10 14:58:20 -08:00
|
|
|
|
// Draw horizontal grids.
|
|
|
|
|
|
|
|
|
|
|
|
float const ppdH = (float)m_h2 / VERT_DIVS;
|
2024-11-10 14:36:44 -08:00
|
|
|
|
|
2024-11-10 14:58:20 -08:00
|
|
|
|
for (std::size_t i = 1; i < VERT_DIVS; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
auto const y = static_cast<int>(i * ppdH);
|
|
|
|
|
|
p.drawLine(0, y, m_w, y);
|
|
|
|
|
|
}
|
2024-11-10 14:36:44 -08:00
|
|
|
|
}
|
2024-09-09 07:35:27 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-08 04:49:04 -08:00
|
|
|
|
// Draw the filter overlay pixmaps, if the filter is enabled and has a width
|
2024-09-09 07:10:00 -07:00
|
|
|
|
// 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.
|
2019-12-16 13:42:44 -05:00
|
|
|
|
|
2024-09-09 07:10:00 -07:00
|
|
|
|
void
|
2024-11-07 17:51:23 -08:00
|
|
|
|
CPlotter::drawFilter()
|
2024-09-09 07:10:00 -07:00
|
|
|
|
{
|
2024-11-08 07:08:09 -08:00
|
|
|
|
if (m_filterEnabled && m_filterWidth > 0 && !size().isEmpty())
|
2024-09-09 07:10:00 -07:00
|
|
|
|
{
|
2024-11-08 07:08:09 -08:00
|
|
|
|
auto const filterPixmap = [height = size().height(),
|
|
|
|
|
|
fill = QColor(0, 0, 0, std::clamp(m_filterOpacity, 0, 255)),
|
2024-11-08 04:49:04 -08:00
|
|
|
|
dpr = devicePixelRatio()](int const width,
|
|
|
|
|
|
int const lineX)
|
|
|
|
|
|
{
|
2024-11-08 07:51:56 -08:00
|
|
|
|
// Ending up with an unusable size here is expected, as in the case
|
|
|
|
|
|
// where the combination of the filter center and width shifts one
|
|
|
|
|
|
// or both ends of the filter out of the displayed range. Thus, no
|
|
|
|
|
|
// matter what, we're going to return a pixmap here, though it may
|
|
|
|
|
|
// be an empty one.
|
|
|
|
|
|
|
2024-11-08 07:08:09 -08:00
|
|
|
|
if (auto const size = QSize(width, height);
|
2024-11-08 07:51:56 -08:00
|
|
|
|
size.isEmpty())
|
|
|
|
|
|
{
|
|
|
|
|
|
return QPixmap();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2024-11-08 07:08:09 -08:00
|
|
|
|
{
|
|
|
|
|
|
QPixmap pixmap = QPixmap(size * dpr);
|
|
|
|
|
|
pixmap.setDevicePixelRatio(dpr);
|
|
|
|
|
|
pixmap.fill(fill);
|
2019-12-16 13:42:44 -05:00
|
|
|
|
|
2024-11-08 07:08:09 -08:00
|
|
|
|
QPainter p(&pixmap);
|
2024-09-09 07:10:00 -07:00
|
|
|
|
|
2024-11-08 07:08:09 -08:00
|
|
|
|
p.setPen(Qt::yellow);
|
|
|
|
|
|
p.drawLine(lineX, 1, lineX, height);
|
2024-11-07 07:00:04 -08:00
|
|
|
|
|
2024-11-08 07:08:09 -08:00
|
|
|
|
return pixmap;
|
|
|
|
|
|
}
|
2024-11-08 04:49:04 -08:00
|
|
|
|
};
|
2024-11-07 07:00:04 -08:00
|
|
|
|
|
2024-11-08 05:50:43 -08:00
|
|
|
|
auto const width = m_filterWidth / 2.0f;
|
|
|
|
|
|
auto const start = xFromFreq(m_filterCenter - width);
|
|
|
|
|
|
auto const end = xFromFreq(m_filterCenter + width);
|
2024-09-09 07:10:00 -07:00
|
|
|
|
|
2024-11-08 04:49:04 -08:00
|
|
|
|
m_FilterPixmap = {
|
|
|
|
|
|
filterPixmap(start, start),
|
|
|
|
|
|
filterPixmap(size().width() - end, 0)
|
|
|
|
|
|
};
|
2018-08-10 14:04:03 -04:00
|
|
|
|
}
|
2018-02-08 21:28:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-08 04:49:04 -08:00
|
|
|
|
// Draw the two dials, the first of which will be used to display the selected
|
|
|
|
|
|
// offset and bandwith, the second prospective offset and bandwidth. These are
|
|
|
|
|
|
// not reliant on anything but height, submode, and bins per pixel.
|
|
|
|
|
|
|
2024-11-07 13:10:26 -08:00
|
|
|
|
void
|
2024-11-07 17:03:59 -08:00
|
|
|
|
CPlotter::drawDials()
|
2024-11-07 13:10:26 -08:00
|
|
|
|
{
|
2024-11-08 07:08:09 -08:00
|
|
|
|
if (auto const height = size().height() - 30;
|
|
|
|
|
|
height > 0)
|
2024-11-07 13:10:26 -08:00
|
|
|
|
{
|
2024-11-11 17:57:47 -08:00
|
|
|
|
auto const width = static_cast<int>(JS8::Submode::bandwidth(m_nSubMode) / m_freqPerPixel + 0.5f);
|
2024-11-08 07:08:09 -08:00
|
|
|
|
auto const dialPixmap = [size = QSize(width, height),
|
|
|
|
|
|
rect = QRect(1, 1, width - 2, height - 2),
|
|
|
|
|
|
dpr = devicePixelRatio()](QColor const & color,
|
|
|
|
|
|
QBrush const & brush)
|
|
|
|
|
|
{
|
|
|
|
|
|
QPixmap pixmap = QPixmap(size * dpr);
|
|
|
|
|
|
pixmap.setDevicePixelRatio(dpr);
|
|
|
|
|
|
pixmap.fill(Qt::transparent);
|
2024-11-07 13:10:26 -08:00
|
|
|
|
|
2024-11-08 07:08:09 -08:00
|
|
|
|
QPainter p(&pixmap);
|
2024-11-07 13:10:26 -08:00
|
|
|
|
|
2024-11-08 07:08:09 -08:00
|
|
|
|
p.setBrush(brush);
|
|
|
|
|
|
p.setPen(QPen(QBrush(color), 2, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin));
|
|
|
|
|
|
p.drawRect(rect);
|
2024-11-07 13:10:26 -08:00
|
|
|
|
|
2024-11-08 07:08:09 -08:00
|
|
|
|
return pixmap;
|
|
|
|
|
|
};
|
2024-11-07 13:10:26 -08:00
|
|
|
|
|
2024-11-08 07:08:09 -08:00
|
|
|
|
m_DialPixmap = {
|
|
|
|
|
|
dialPixmap(Qt::red, QBrush(QColor(255, 255, 255, 75), Qt::Dense4Pattern)),
|
|
|
|
|
|
dialPixmap(Qt::white, Qt::transparent)
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2024-11-07 13:10:26 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-11 07:23:53 -08:00
|
|
|
|
// Replot the waterfall display, using the data present in the replot
|
2024-11-14 09:00:35 -08:00
|
|
|
|
// buffer, if any.
|
2024-11-11 07:23:53 -08:00
|
|
|
|
|
2024-11-10 07:41:10 -08:00
|
|
|
|
void
|
|
|
|
|
|
CPlotter::replot()
|
|
|
|
|
|
{
|
2024-11-11 12:24:56 -08:00
|
|
|
|
if (m_WaterfallPixmap.isNull()) return;
|
|
|
|
|
|
|
2024-11-11 07:23:53 -08:00
|
|
|
|
// Whack anything currently in the waterfall pixmap; we must do this
|
|
|
|
|
|
// before attaching a painter.
|
|
|
|
|
|
|
2024-11-10 07:41:10 -08:00
|
|
|
|
m_WaterfallPixmap.fill(Qt::black);
|
|
|
|
|
|
|
2024-11-11 07:23:53 -08:00
|
|
|
|
// Given a value, return color a to use for a point, based on the
|
|
|
|
|
|
// zero, gain, and color palette settings.
|
|
|
|
|
|
|
|
|
|
|
|
auto const color = [this,
|
|
|
|
|
|
gain = gainFactor()](auto const value)
|
|
|
|
|
|
{
|
|
|
|
|
|
return m_colors[std::clamp(m_plotZero + static_cast<int>(gain * value), 0, 254)];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// We need to consider that entries have been added to the replot
|
|
|
|
|
|
// buffer at a rate proportional to the display pixel ratio, i.e.,
|
|
|
|
|
|
// it deals in device pixels, not logical pixels, so we must deal
|
|
|
|
|
|
// with scaling in the y dimension for this to work out.
|
|
|
|
|
|
|
2024-11-10 17:55:09 -08:00
|
|
|
|
QPainter p(&m_WaterfallPixmap);
|
|
|
|
|
|
|
2024-11-11 15:11:56 -08:00
|
|
|
|
p.scale(1, 1 / m_WaterfallPixmap.devicePixelRatio());
|
|
|
|
|
|
|
|
|
|
|
|
auto y = 0;
|
|
|
|
|
|
auto o = overload
|
2024-11-11 07:23:53 -08:00
|
|
|
|
{
|
2024-11-13 22:17:15 -08:00
|
|
|
|
// Null drawing; a monostate is constructed as the default when we
|
|
|
|
|
|
// resize but have no backing data. Nothing to do here; just data
|
|
|
|
|
|
// that we didn't have when we were resized.
|
|
|
|
|
|
|
|
|
|
|
|
[](std::monostate const &){},
|
|
|
|
|
|
|
2024-11-13 03:57:33 -08:00
|
|
|
|
// Line drawing; draw the usual green line across the width of the
|
|
|
|
|
|
// pixmap, annotated by the text provided.
|
2024-11-11 15:42:33 -08:00
|
|
|
|
|
2024-11-11 15:11:56 -08:00
|
|
|
|
[ratio = m_WaterfallPixmap.devicePixelRatio(),
|
|
|
|
|
|
width = m_WaterfallPixmap.size().width(),
|
|
|
|
|
|
extra = p.fontMetrics().descent(),
|
|
|
|
|
|
&y = std::as_const(y),
|
|
|
|
|
|
&p
|
|
|
|
|
|
](QString const & text)
|
2024-11-11 07:23:53 -08:00
|
|
|
|
{
|
|
|
|
|
|
p.setPen(Qt::white);
|
|
|
|
|
|
p.save();
|
2024-11-11 15:11:56 -08:00
|
|
|
|
p.scale(1, ratio);
|
|
|
|
|
|
p.drawText(5, y / ratio - extra, text);
|
2024-11-11 07:23:53 -08:00
|
|
|
|
p.restore();
|
|
|
|
|
|
p.setPen(Qt::green);
|
|
|
|
|
|
p.drawLine(0, y, width, y);
|
|
|
|
|
|
},
|
2024-11-11 15:11:56 -08:00
|
|
|
|
|
|
|
|
|
|
// Standard waterfall data display; run through the vector of data
|
|
|
|
|
|
// and color each corresponding point in the pixmap appropriately.
|
|
|
|
|
|
|
2024-11-13 21:01:28 -08:00
|
|
|
|
[width = m_WaterfallPixmap.size().width(),
|
|
|
|
|
|
&color = std::as_const(color),
|
2024-11-11 15:11:56 -08:00
|
|
|
|
&y = std::as_const(y),
|
|
|
|
|
|
&p
|
2024-11-13 21:01:28 -08:00
|
|
|
|
](WF::SWide const & swide)
|
2024-11-11 07:23:53 -08:00
|
|
|
|
{
|
2024-11-13 21:01:28 -08:00
|
|
|
|
auto x = 0;
|
|
|
|
|
|
auto it = swide.begin();
|
|
|
|
|
|
auto const end = it + width;
|
|
|
|
|
|
for (; it != end; ++it)
|
2024-11-11 07:23:53 -08:00
|
|
|
|
{
|
2024-11-13 21:01:28 -08:00
|
|
|
|
p.setPen(color(*it));
|
2024-11-11 07:23:53 -08:00
|
|
|
|
p.drawPoint(x, y);
|
|
|
|
|
|
x++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2024-11-10 17:55:09 -08:00
|
|
|
|
|
2024-11-11 07:23:53 -08:00
|
|
|
|
// Our draw routine pushed entries to the front of the buffer, so we
|
|
|
|
|
|
// can iterate in forward order here, the Qt coordinate system having
|
2024-11-11 15:11:56 -08:00
|
|
|
|
// (0, 0) as the upper-left point.
|
2024-11-11 07:23:53 -08:00
|
|
|
|
|
2024-11-11 15:11:56 -08:00
|
|
|
|
for (auto && v : m_replot)
|
2024-11-11 06:40:12 -08:00
|
|
|
|
{
|
2024-11-11 15:11:56 -08:00
|
|
|
|
std::visit(o, v);
|
2024-11-10 17:55:09 -08:00
|
|
|
|
y++;
|
2024-11-10 07:41:10 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-11 07:23:53 -08:00
|
|
|
|
// The waterfall pixmap should now look as it did before, but with the
|
|
|
|
|
|
// current zero, gain, and color palette applied; schedule a repaint.
|
|
|
|
|
|
|
2024-11-10 07:41:10 -08:00
|
|
|
|
update();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-14 04:43:02 -08:00
|
|
|
|
// Called (indirectly, debounced) from our resize event handler and from
|
|
|
|
|
|
// setPercent2DScreen() after a change to the 2D screen percentage.
|
2024-11-12 04:49:21 -08:00
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
CPlotter::resize()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (size().isValid())
|
|
|
|
|
|
{
|
|
|
|
|
|
auto const makePixmap = [dpr = devicePixelRatio()](QSize const & size,
|
|
|
|
|
|
QColor const & fill)
|
|
|
|
|
|
{
|
|
|
|
|
|
auto pixmap = QPixmap(size * dpr);
|
|
|
|
|
|
|
|
|
|
|
|
pixmap.setDevicePixelRatio(dpr);
|
|
|
|
|
|
pixmap.fill(fill);
|
|
|
|
|
|
|
|
|
|
|
|
return pixmap;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2024-11-14 04:43:02 -08:00
|
|
|
|
m_w = size().width();
|
|
|
|
|
|
m_h2 = m_percent2DScreen * (size().height() - 30) / 100.0;
|
|
|
|
|
|
m_h1 = size().height() - m_h2;
|
2024-11-12 04:49:21 -08:00
|
|
|
|
|
|
|
|
|
|
// We want our 3 main pixmaps sized to occupy our entire height,
|
|
|
|
|
|
// and to be completely filled with an opaque color, since we're
|
|
|
|
|
|
// going to take the opaque paint even optimization path. If this
|
|
|
|
|
|
// is a high-DPI display, scale the pixmaps to avoid text looking
|
|
|
|
|
|
// pixelated.
|
|
|
|
|
|
|
|
|
|
|
|
m_ScalePixmap = makePixmap({m_w, 30}, Qt::white);
|
|
|
|
|
|
m_WaterfallPixmap = makePixmap({m_w, m_h1}, Qt::black);
|
|
|
|
|
|
m_OverlayPixmap = makePixmap({m_w, m_h2}, Qt::black);
|
|
|
|
|
|
|
|
|
|
|
|
// The replot circular buffer should have capacity to hold the full
|
|
|
|
|
|
// height of the waterfall pixmap, in device, not logical, pixels.
|
2024-11-13 22:17:15 -08:00
|
|
|
|
// Since our variant lists std::monostate as the first alternative,
|
|
|
|
|
|
// if we get larger here, the added items will be constructed using
|
|
|
|
|
|
// std::monostate as the alternative.
|
2024-11-12 04:49:21 -08:00
|
|
|
|
|
2024-11-13 22:17:15 -08:00
|
|
|
|
m_replot.resize(m_WaterfallPixmap.size().height());
|
2024-11-12 04:49:21 -08:00
|
|
|
|
|
|
|
|
|
|
// The dials, filter, scale and overlay pixmaps don't depend on
|
|
|
|
|
|
// inbound data, so we can draw them now.
|
|
|
|
|
|
|
|
|
|
|
|
drawDials();
|
|
|
|
|
|
drawFilter();
|
|
|
|
|
|
drawMetrics();
|
|
|
|
|
|
|
|
|
|
|
|
// The overlay pixmap acts as a prototype for the spectrum pixmap;
|
|
|
|
|
|
// each time we draw the spectrum, we do so by first making a copy
|
|
|
|
|
|
// of the overlay, then drawing the spectrum line into it.
|
|
|
|
|
|
|
2024-11-13 22:17:15 -08:00
|
|
|
|
m_SpectrumPixmap = m_OverlayPixmap.copy();
|
|
|
|
|
|
|
|
|
|
|
|
replot();
|
2024-11-12 04:49:21 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-15 08:20:24 -07:00
|
|
|
|
bool
|
2024-10-19 09:14:10 -07:00
|
|
|
|
CPlotter::in30MBand() const
|
2024-09-15 08:20:24 -07:00
|
|
|
|
{
|
|
|
|
|
|
return (m_dialFreq >= BAND_30M_START &&
|
|
|
|
|
|
m_dialFreq <= BAND_30M_END);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-19 09:14:10 -07:00
|
|
|
|
int
|
2024-10-20 21:20:32 -07:00
|
|
|
|
CPlotter::xFromFreq(float const f) const
|
2018-02-08 21:28:33 -05:00
|
|
|
|
{
|
2024-11-11 15:42:33 -08:00
|
|
|
|
return std::clamp(static_cast<int>((f - m_startFreq) / m_freqPerPixel + 0.5f), 0, m_w);
|
2018-02-08 21:28:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-19 09:14:10 -07:00
|
|
|
|
float
|
2024-10-20 21:20:32 -07:00
|
|
|
|
CPlotter::freqFromX(int const x) const
|
2018-02-08 21:28:33 -05:00
|
|
|
|
{
|
2024-10-26 04:30:16 -07:00
|
|
|
|
return m_startFreq + x * m_freqPerPixel;
|
2018-02-08 21:28:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-10 17:55:09 -08:00
|
|
|
|
float
|
|
|
|
|
|
CPlotter::gainFactor() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return 10.f * std::sqrt(m_binsPerPixel * m_waterfallAvg / 15.0f)
|
|
|
|
|
|
* std::pow(10.0f, 0.015f * m_plotGain);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-19 09:14:10 -07:00
|
|
|
|
void
|
|
|
|
|
|
CPlotter::leaveEvent(QEvent * event)
|
2018-08-29 11:06:46 -04:00
|
|
|
|
{
|
2024-10-19 09:19:01 -07:00
|
|
|
|
m_lastMouseX = -1;
|
|
|
|
|
|
event->ignore();
|
2018-08-29 11:06:46 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-19 09:14:10 -07:00
|
|
|
|
void
|
|
|
|
|
|
CPlotter::wheelEvent(QWheelEvent * event)
|
2024-09-08 19:54:14 -07:00
|
|
|
|
{
|
2024-10-20 21:20:32 -07:00
|
|
|
|
auto const y = event->angleDelta().y();
|
2024-09-08 19:54:14 -07:00
|
|
|
|
|
2024-10-20 21:20:32 -07:00
|
|
|
|
if (auto const d = ((y > 0) - (y < 0)))
|
2024-09-08 19:54:14 -07:00
|
|
|
|
{
|
2024-11-16 15:39:56 -08:00
|
|
|
|
Q_EMIT changeFreq(event->modifiers() & Qt::ControlModifier
|
|
|
|
|
|
? freq() + d
|
|
|
|
|
|
: freq() / 10 * 10 + d * 10);
|
2024-09-08 19:54:14 -07:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2024-10-20 21:20:32 -07:00
|
|
|
|
event->ignore();
|
2018-08-29 11:06:46 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-19 09:14:10 -07:00
|
|
|
|
void
|
|
|
|
|
|
CPlotter::mouseMoveEvent(QMouseEvent * event)
|
2018-08-29 11:06:46 -04:00
|
|
|
|
{
|
2024-10-19 13:49:37 -07:00
|
|
|
|
m_lastMouseX = std::clamp(static_cast<int>(event->position().x()), 0, m_w);
|
2018-08-29 11:06:46 -04:00
|
|
|
|
|
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(),
|
2024-10-19 09:14:10 -07:00
|
|
|
|
QString::number(static_cast<int>(freqFromX(m_lastMouseX))));
|
2024-09-08 09:00:09 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-19 09:14:10 -07:00
|
|
|
|
void
|
|
|
|
|
|
CPlotter::mouseReleaseEvent(QMouseEvent * event)
|
2024-09-08 09:00:09 -07:00
|
|
|
|
{
|
|
|
|
|
|
if (Qt::LeftButton == event->button())
|
|
|
|
|
|
{
|
2024-11-16 15:39:56 -08:00
|
|
|
|
Q_EMIT changeFreq(static_cast<int>(freqFromX(m_lastMouseX)));
|
2018-02-08 21:28:33 -05:00
|
|
|
|
}
|
2024-09-08 09:00:09 -07:00
|
|
|
|
else
|
|
|
|
|
|
{
|
2024-10-20 21:20:32 -07:00
|
|
|
|
event->ignore();
|
2018-02-08 21:28:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-19 09:14:10 -07:00
|
|
|
|
void
|
2024-10-26 04:30:16 -07:00
|
|
|
|
CPlotter::setBinsPerPixel(int const binsPerPixel)
|
2018-02-08 21:28:33 -05:00
|
|
|
|
{
|
2024-11-07 13:44:04 -08:00
|
|
|
|
if (m_binsPerPixel != binsPerPixel)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_binsPerPixel = std::max(1, binsPerPixel);
|
|
|
|
|
|
m_freqPerPixel = m_binsPerPixel * FFT_BIN_WIDTH;
|
2024-11-10 14:36:44 -08:00
|
|
|
|
drawMetrics();
|
2024-11-07 17:51:23 -08:00
|
|
|
|
drawFilter();
|
2024-11-07 17:54:31 -08:00
|
|
|
|
drawDials();
|
2024-11-07 13:44:04 -08:00
|
|
|
|
update();
|
|
|
|
|
|
}
|
2018-02-08 21:28:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-10 07:41:10 -08:00
|
|
|
|
void
|
|
|
|
|
|
CPlotter::setColors(Colors const & colors)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_colors != colors)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_colors = colors;
|
|
|
|
|
|
replot();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-19 09:14:10 -07:00
|
|
|
|
void
|
2024-10-24 21:25:49 -07:00
|
|
|
|
CPlotter::setDialFreq(float const dialFreq)
|
2018-02-08 21:28:33 -05:00
|
|
|
|
{
|
2024-11-07 13:44:04 -08:00
|
|
|
|
if (m_dialFreq != dialFreq)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_dialFreq = dialFreq;
|
2024-11-10 14:36:44 -08:00
|
|
|
|
drawMetrics();
|
2024-11-07 13:44:04 -08:00
|
|
|
|
update();
|
|
|
|
|
|
}
|
2018-02-08 21:28:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-19 09:14:10 -07:00
|
|
|
|
void
|
2024-11-08 07:31:19 -08:00
|
|
|
|
CPlotter::setFilter(int const filterCenter,
|
|
|
|
|
|
int const filterWidth)
|
2018-02-08 21:28:33 -05:00
|
|
|
|
{
|
2024-11-08 07:31:19 -08:00
|
|
|
|
if (m_filterCenter != filterCenter ||
|
|
|
|
|
|
m_filterWidth != filterWidth)
|
2024-11-07 13:44:04 -08:00
|
|
|
|
{
|
|
|
|
|
|
m_filterCenter = filterCenter;
|
2024-11-08 07:31:19 -08:00
|
|
|
|
m_filterWidth = filterWidth;
|
2024-11-07 17:51:23 -08:00
|
|
|
|
drawFilter();
|
2024-11-07 13:44:04 -08:00
|
|
|
|
update();
|
|
|
|
|
|
}
|
2018-11-04 22:37:14 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-19 09:14:10 -07:00
|
|
|
|
void
|
2024-11-07 13:44:04 -08:00
|
|
|
|
CPlotter::setFilterEnabled(bool const filterEnabled)
|
2024-09-08 09:00:09 -07:00
|
|
|
|
{
|
2024-11-07 13:44:04 -08:00
|
|
|
|
if (m_filterEnabled != filterEnabled)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_filterEnabled = filterEnabled;
|
2024-11-07 17:51:23 -08:00
|
|
|
|
drawFilter();
|
2024-11-07 13:44:04 -08:00
|
|
|
|
update();
|
|
|
|
|
|
}
|
2024-10-26 04:30:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
2024-11-07 13:44:04 -08:00
|
|
|
|
CPlotter::setFilterOpacity(int const filterOpacity)
|
2024-10-26 04:30:16 -07:00
|
|
|
|
{
|
2024-11-07 13:44:04 -08:00
|
|
|
|
if (m_filterOpacity != filterOpacity)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_filterOpacity = filterOpacity;
|
2024-11-07 17:51:23 -08:00
|
|
|
|
drawFilter();
|
2024-11-07 13:44:04 -08:00
|
|
|
|
update();
|
|
|
|
|
|
}
|
2019-12-16 21:28:45 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-19 09:14:10 -07:00
|
|
|
|
void
|
2024-10-26 04:30:16 -07:00
|
|
|
|
CPlotter::setFreq(int const freq)
|
2024-09-08 09:00:09 -07:00
|
|
|
|
{
|
2024-11-07 13:44:04 -08:00
|
|
|
|
if (m_freq != freq)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_freq = freq;
|
2024-11-10 14:36:44 -08:00
|
|
|
|
drawMetrics();
|
2024-11-07 13:44:04 -08:00
|
|
|
|
update();
|
|
|
|
|
|
}
|
2019-12-16 13:59:10 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-19 09:14:10 -07:00
|
|
|
|
void
|
2024-11-07 13:44:04 -08:00
|
|
|
|
CPlotter::setPercent2DScreen(int percent2DScreen)
|
2024-09-08 09:00:09 -07:00
|
|
|
|
{
|
2024-11-07 13:44:04 -08:00
|
|
|
|
if (m_percent2DScreen != percent2DScreen)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_percent2DScreen = percent2DScreen;
|
2024-11-12 04:49:21 -08:00
|
|
|
|
resize();
|
2024-11-07 13:44:04 -08:00
|
|
|
|
update();
|
|
|
|
|
|
}
|
2019-12-19 10:06:37 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-19 09:14:10 -07:00
|
|
|
|
void
|
2024-11-10 07:41:10 -08:00
|
|
|
|
CPlotter::setPlotGain(int const plotGain)
|
2018-02-08 21:28:33 -05:00
|
|
|
|
{
|
2024-11-10 07:41:10 -08:00
|
|
|
|
if (m_plotGain != plotGain)
|
2024-11-07 13:44:04 -08:00
|
|
|
|
{
|
2024-11-10 07:41:10 -08:00
|
|
|
|
m_plotGain = plotGain;
|
|
|
|
|
|
replot();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
CPlotter::setPlotZero(int const plotZero)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_plotZero != plotZero)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_plotZero = plotZero;
|
|
|
|
|
|
replot();
|
2024-11-07 13:44:04 -08:00
|
|
|
|
}
|
2024-10-26 04:30:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
CPlotter::setStartFreq(int const startFreq)
|
|
|
|
|
|
{
|
2024-11-07 13:44:04 -08:00
|
|
|
|
if (m_startFreq != startFreq)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_startFreq = startFreq;
|
2024-11-10 14:36:44 -08:00
|
|
|
|
drawMetrics();
|
2024-11-07 21:13:59 -08:00
|
|
|
|
drawFilter();
|
2024-11-07 13:44:04 -08:00
|
|
|
|
update();
|
|
|
|
|
|
}
|
2024-10-26 04:30:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
CPlotter::setSubMode(int const nSubMode)
|
|
|
|
|
|
{
|
2024-11-07 13:10:26 -08:00
|
|
|
|
if (m_nSubMode != nSubMode)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_nSubMode = nSubMode;
|
2024-11-07 17:03:59 -08:00
|
|
|
|
drawDials();
|
2024-11-07 13:10:26 -08:00
|
|
|
|
update();
|
|
|
|
|
|
}
|
2018-02-08 21:28:33 -05:00
|
|
|
|
}
|