From 2d43a5515e1d9693905bd0cac7245f22d057b96c Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Wed, 28 Sep 2022 16:59:35 +0100 Subject: [PATCH] Spectrum: Use widget for measurements --- .../channelrx/chanalyzer/chanalyzergui.cpp | 35 +- plugins/channelrx/chanalyzer/chanalyzergui.ui | 6 +- sdrbase/dsp/spectrumsettings.cpp | 10 +- sdrbase/dsp/spectrumsettings.h | 12 +- sdrgui/CMakeLists.txt | 4 + sdrgui/device/deviceuiset.cpp | 8 +- sdrgui/device/deviceuiset.h | 2 + sdrgui/gui/glspectrum.cpp | 234 ++++--- sdrgui/gui/glspectrum.h | 23 +- sdrgui/gui/glspectrumgui.cpp | 78 ++- sdrgui/gui/glspectrumgui.h | 8 +- sdrgui/gui/glspectrumgui.ui | 83 ++- sdrgui/gui/glspectrumtop.cpp | 51 ++ sdrgui/gui/glspectrumtop.h | 48 ++ sdrgui/gui/spectrummeasurements.cpp | 610 ++++++++++++++++++ sdrgui/gui/spectrummeasurements.h | 146 +++++ sdrgui/mainspectrum/mainspectrumgui.cpp | 8 +- sdrgui/mainspectrum/mainspectrumgui.h | 4 +- 18 files changed, 1205 insertions(+), 165 deletions(-) create mode 100644 sdrgui/gui/glspectrumtop.cpp create mode 100644 sdrgui/gui/glspectrumtop.h create mode 100644 sdrgui/gui/spectrummeasurements.cpp create mode 100644 sdrgui/gui/spectrummeasurements.h diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.cpp b/plugins/channelrx/chanalyzer/chanalyzergui.cpp index e32fc95e9..253739d23 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzergui.cpp @@ -23,6 +23,7 @@ #include "dsp/dspengine.h" #include "dsp/dspcommands.h" #include "gui/glspectrum.h" +#include "gui/glspectrumtop.h" #include "gui/glscope.h" #include "gui/basicchannelsettingsdialog.h" #include "plugin/pluginapi.h" @@ -192,17 +193,17 @@ void ChannelAnalyzerGUI::setSpectrumDisplay() qDebug("ChannelAnalyzerGUI::setSpectrumDisplay: m_sinkSampleRate: %d", sinkSampleRate); if (m_settings.m_ssb) { - ui->glSpectrum->setCenterFrequency(sinkSampleRate/4); - ui->glSpectrum->setSampleRate(sinkSampleRate/2); - ui->glSpectrum->setSsbSpectrum(true); - ui->glSpectrum->setLsbDisplay(ui->BW->value() < 0); + ui->glSpectrumTop->getSpectrum()->setCenterFrequency(sinkSampleRate/4); + ui->glSpectrumTop->getSpectrum()->setSampleRate(sinkSampleRate/2); + ui->glSpectrumTop->getSpectrum()->setSsbSpectrum(true); + ui->glSpectrumTop->getSpectrum()->setLsbDisplay(ui->BW->value() < 0); } else { - ui->glSpectrum->setCenterFrequency(0); - ui->glSpectrum->setSampleRate(sinkSampleRate); - ui->glSpectrum->setSsbSpectrum(false); - ui->glSpectrum->setLsbDisplay(false); + ui->glSpectrumTop->getSpectrum()->setCenterFrequency(0); + ui->glSpectrumTop->getSpectrum()->setSampleRate(sinkSampleRate); + ui->glSpectrumTop->getSpectrum()->setSsbSpectrum(false); + ui->glSpectrumTop->getSpectrum()->setLsbDisplay(false); } } @@ -542,7 +543,7 @@ ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *device m_basebandSampleRate = m_channelAnalyzer->getChannelSampleRate(); qDebug("ChannelAnalyzerGUI::ChannelAnalyzerGUI: m_basebandSampleRate: %d", m_basebandSampleRate); m_spectrumVis = m_channelAnalyzer->getSpectrumVis(); - m_spectrumVis->setGLSpectrum(ui->glSpectrum); + m_spectrumVis->setGLSpectrum(ui->glSpectrumTop->getSpectrum()); m_scopeVis = m_channelAnalyzer->getScopeVis(); m_scopeVis->setGLScope(ui->glScope); m_basebandSampleRate = m_channelAnalyzer->getChannelSampleRate(); @@ -556,12 +557,12 @@ ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *device ui->rationalDownSamplerRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); - ui->glSpectrum->setCenterFrequency(m_basebandSampleRate/2); - ui->glSpectrum->setSampleRate(m_basebandSampleRate); - ui->glSpectrum->setDisplayWaterfall(true); - ui->glSpectrum->setDisplayMaxHold(true); - ui->glSpectrum->setSsbSpectrum(false); - ui->glSpectrum->setLsbDisplay(false); + ui->glSpectrumTop->getSpectrum()->setCenterFrequency(m_basebandSampleRate/2); + ui->glSpectrumTop->getSpectrum()->setSampleRate(m_basebandSampleRate); + ui->glSpectrumTop->getSpectrum()->setDisplayWaterfall(true); + ui->glSpectrumTop->getSpectrum()->setDisplayMaxHold(true); + ui->glSpectrumTop->getSpectrum()->setSsbSpectrum(false); + ui->glSpectrumTop->getSpectrum()->setLsbDisplay(false); ui->glScope->connectTimer(MainCore::instance()->getMasterTimer()); connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); @@ -578,7 +579,7 @@ ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *device m_deviceUISet->addChannelMarker(&m_channelMarker); - ui->spectrumGUI->setBuddies(m_spectrumVis, ui->glSpectrum); + ui->spectrumGUI->setBuddies(m_spectrumVis, ui->glSpectrumTop->getSpectrum(), ui->glSpectrumTop); ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope); m_settings.setChannelMarker(&m_channelMarker); @@ -694,7 +695,7 @@ void ChannelAnalyzerGUI::setFiltersUIBoundaries() void ChannelAnalyzerGUI::blockApplySettings(bool block) { ui->glScope->blockSignals(block); - ui->glSpectrum->blockSignals(block); + ui->glSpectrumTop->getSpectrum()->blockSignals(block); m_doApplySettings = !block; } diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.ui b/plugins/channelrx/chanalyzer/chanalyzergui.ui index 13fe10ab3..dc03c6abb 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.ui +++ b/plugins/channelrx/chanalyzer/chanalyzergui.ui @@ -899,7 +899,7 @@ 2 - + 0 @@ -1018,9 +1018,9 @@ 1 - GLSpectrum + GLSpectrumTop QWidget -
gui/glspectrum.h
+
gui/glspectrumtop.h
1
diff --git a/sdrbase/dsp/spectrumsettings.cpp b/sdrbase/dsp/spectrumsettings.cpp index 1dc4ce700..b1a4045d2 100644 --- a/sdrbase/dsp/spectrumsettings.cpp +++ b/sdrbase/dsp/spectrumsettings.cpp @@ -68,12 +68,14 @@ void SpectrumSettings::resetToDefaults() m_3DSpectrogramStyle = Outline; m_colorMap = "Angel"; m_spectrumStyle = Line; - m_measurement = MeasurementNone; + m_measure = false; + m_measurement = MeasurementPeaks; m_measurementBandwidth = 10000; m_measurementChSpacing = 10000; m_measurementAdjChBandwidth = 10000; m_measurementHarmonics = 5; m_measurementHighlight = true; + m_measurementPeaks = 5; } QByteArray SpectrumSettings::serialize() const @@ -120,6 +122,8 @@ QByteArray SpectrumSettings::serialize() const s.writeS32(39, m_measurementHarmonics); // 41, 42 used below s.writeBool(42, m_measurementHighlight); + s.writeS32(43, m_measurementPeaks); + s.writeBool(44, m_measure); s.writeS32(100, m_histogramMarkers.size()); for (int i = 0; i < m_histogramMarkers.size(); i++) { @@ -221,12 +225,14 @@ bool SpectrumSettings::deserialize(const QByteArray& data) d.readS32(32, (int*)&m_3DSpectrogramStyle, (int)Outline); d.readString(33, &m_colorMap, "Angel"); d.readS32(34, (int*)&m_spectrumStyle, (int)Line); - d.readS32(35, (int*)&m_measurement, (int)MeasurementNone); + d.readS32(35, (int*)&m_measurement, (int)MeasurementPeaks); d.readS32(36, &m_measurementBandwidth, 10000); d.readS32(37, &m_measurementChSpacing, 10000); d.readS32(38, &m_measurementAdjChBandwidth, 10000); d.readS32(39, &m_measurementHarmonics, 5); d.readBool(42, &m_measurementHighlight, true); + d.readS32(43, &m_measurementPeaks, 5); + d.readBool(44, &m_measure, false); int histogramMarkersSize; d.readS32(100, &histogramMarkersSize, 0); diff --git a/sdrbase/dsp/spectrumsettings.h b/sdrbase/dsp/spectrumsettings.h index eec028ee0..c9262cf8d 100644 --- a/sdrbase/dsp/spectrumsettings.h +++ b/sdrbase/dsp/spectrumsettings.h @@ -72,16 +72,10 @@ public: enum Measurement { - MeasurementNone, - MeasurementPeak, + MeasurementPeaks, MeasurementChannelPower, MeasurementAdjacentChannelPower, - MeasurementSNR, - MeasurementSNFR, - MeasurementTHD, - MeasurementTHDPN, - MeasurementSINAD, - MeasurementSFDR + MeasurementSNR }; int m_fftSize; @@ -122,11 +116,13 @@ public: SpectrogramStyle m_3DSpectrogramStyle; QString m_colorMap; SpectrumStyle m_spectrumStyle; + bool m_measure; Measurement m_measurement; int m_measurementBandwidth; int m_measurementChSpacing; int m_measurementAdjChBandwidth; int m_measurementHarmonics; + int m_measurementPeaks; bool m_measurementHighlight; static const int m_log2FFTSizeMin = 6; // 64 static const int m_log2FFTSizeMax = 15; // 32k diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index 0d9a3ad49..7540249e3 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -51,6 +51,7 @@ set(sdrgui_SOURCES gui/glshadertvarray.cpp gui/glspectrum.cpp gui/glspectrumgui.cpp + gui/glspectrumtop.cpp gui/graphicsdialog.cpp gui/graphicsviewzoom.cpp gui/httpdownloadmanagergui.cpp @@ -69,6 +70,7 @@ set(sdrgui_SOURCES gui/sdrangelsplash.cpp gui/spectrumcalibrationpointsdialog.cpp gui/spectrummarkersdialog.cpp + gui/spectrummeasurements.cpp gui/tickedslider.cpp gui/timedelegate.cpp gui/transverterbutton.cpp @@ -154,6 +156,7 @@ set(sdrgui_HEADERS gui/glshadertextured.h gui/glspectrum.h gui/glspectrumgui.h + gui/glspectrumtop.h gui/graphicsdialog.h gui/graphicsviewzoom.h gui/httpdownloadmanagergui.h @@ -173,6 +176,7 @@ set(sdrgui_HEADERS gui/sdrangelsplash.h gui/spectrumcalibrationpointsdialog.h gui/spectrummarkersdialog.h + gui/spectrummeasurements.h gui/tickedslider.h gui/timedelegate.h gui/transverterbutton.h diff --git a/sdrgui/device/deviceuiset.cpp b/sdrgui/device/deviceuiset.cpp index 0affac111..a769eb3c0 100644 --- a/sdrgui/device/deviceuiset.cpp +++ b/sdrgui/device/deviceuiset.cpp @@ -23,6 +23,7 @@ #include "dsp/dspdevicesourceengine.h" #include "dsp/dspdevicesinkengine.h" #include "gui/glspectrum.h" +#include "gui/glspectrumtop.h" #include "gui/glspectrumgui.h" // #include "gui/channelwindow.h" #include "gui/workspace.h" @@ -42,13 +43,14 @@ DeviceUISet::DeviceUISet(int deviceSetIndex, DeviceSet *deviceSet) { - m_spectrum = new GLSpectrum; + m_spectrumTop = new GLSpectrumTop(); + m_spectrum = m_spectrumTop->getSpectrum(); m_spectrum->setIsDeviceSpectrum(true); m_spectrumVis = deviceSet->m_spectrumVis; m_spectrumVis->setGLSpectrum(m_spectrum); m_spectrumGUI = new GLSpectrumGUI; - m_spectrumGUI->setBuddies(m_spectrumVis, m_spectrum); - m_mainSpectrumGUI = new MainSpectrumGUI(m_spectrum, m_spectrumGUI); + m_spectrumGUI->setBuddies(m_spectrumVis, m_spectrum, m_spectrumTop); + m_mainSpectrumGUI = new MainSpectrumGUI(m_spectrumTop, m_spectrum, m_spectrumGUI); // m_channelWindow = new ChannelWindow; m_deviceAPI = nullptr; m_deviceGUI = nullptr; diff --git a/sdrgui/device/deviceuiset.h b/sdrgui/device/deviceuiset.h index 547525206..a30de0989 100644 --- a/sdrgui/device/deviceuiset.h +++ b/sdrgui/device/deviceuiset.h @@ -26,6 +26,7 @@ class SpectrumVis; class GLSpectrum; +class GLSpectrumTop; class GLSpectrumGUI; class MainSpectrumGUI; // class ChannelWindow; @@ -53,6 +54,7 @@ class SDRGUI_API DeviceUISet : public QObject Q_OBJECT public: SpectrumVis *m_spectrumVis; + GLSpectrumTop *m_spectrumTop; GLSpectrum *m_spectrum; GLSpectrumGUI *m_spectrumGUI; MainSpectrumGUI *m_mainSpectrumGUI; diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index c22178e8d..2e7c512ab 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -27,6 +27,7 @@ #include "maincore.h" #include "dsp/spectrumvis.h" #include "gui/glspectrum.h" +#include "gui/spectrummeasurements.h" #include "settings/mainsettings.h" #include "util/messagequeue.h" #include "util/db.h" @@ -109,11 +110,14 @@ GLSpectrum::GLSpectrum(QWidget* parent) : m_messageQueueToGUI(nullptr), m_openGLLogger(nullptr), m_isDeviceSpectrum(false), - m_measurement(SpectrumSettings::MeasurementNone), + m_measurements(nullptr), + m_measure(false), + m_measurement(SpectrumSettings::MeasurementPeaks), m_measurementBandwidth(10000), m_measurementChSpacing(10000), m_measurementAdjChBandwidth(10000), m_measurementHarmonics(5), + m_measurementPeaks(5), m_measurementHighlight(true) { // Enable multisampling anti-aliasing (MSAA) @@ -491,18 +495,23 @@ void GLSpectrum::setUseCalibration(bool useCalibration) update(); } -void GLSpectrum::setMeasurementParams(SpectrumSettings::Measurement measurement, +void GLSpectrum::setMeasurementParams(bool measure, SpectrumSettings::Measurement measurement, int bandwidth, int chSpacing, int adjChBandwidth, - int harmonics, bool highlight) + int harmonics, int peaks, bool highlight) { m_mutex.lock(); + m_measure = measure; m_measurement = measurement; m_measurementBandwidth = bandwidth; m_measurementChSpacing = chSpacing; m_measurementAdjChBandwidth = adjChBandwidth; m_measurementHarmonics = harmonics; + m_measurementPeaks = peaks; m_measurementHighlight = highlight; m_changesPending = true; + if (m_measurements) { + m_measurements->setMeasurementParams(measurement, peaks); + } m_mutex.unlock(); update(); } @@ -1672,12 +1681,12 @@ void GLSpectrum::paintGL() m_glShaderInfo.drawSurface(m_glInfoBoxMatrix, tex1, vtx1, 4); } - if (m_currentSpectrum) + if (m_currentSpectrum && m_measure) { switch (m_measurement) { - case SpectrumSettings::MeasurementPeak: - measurePeak(); + case SpectrumSettings::MeasurementPeaks: + measurePeaks(); break; case SpectrumSettings::MeasurementChannelPower: measureChannelPower(); @@ -1686,13 +1695,7 @@ void GLSpectrum::paintGL() measureAdjacentChannelPower(); break; case SpectrumSettings::MeasurementSNR: - case SpectrumSettings::MeasurementSNFR: - case SpectrumSettings::MeasurementTHD: - case SpectrumSettings::MeasurementTHDPN: - case SpectrumSettings::MeasurementSINAD: measureSNR(); - break; - case SpectrumSettings::MeasurementSFDR: measureSFDR(); break; default: @@ -2069,6 +2072,60 @@ void GLSpectrum::measurePeak() {m_peakPowerMaxStr, m_peakFrequencyMaxStr}, {m_peakPowerUnits, "Hz"} ); + if (m_measurements) { + m_measurements->setPeak(0, frequency, power); + } +} + +// Find and display peaks +void GLSpectrum::measurePeaks() +{ + // Copy current spectrum so we can modify it + Real *spectrum = new Real[m_nbBins]; + std::copy(m_currentSpectrum, m_currentSpectrum + m_nbBins, spectrum); + + for (int i = 0; i < m_measurementPeaks; i++) + { + // Find peak + int peakBin = findPeakBin(spectrum); + int left, right; + peakWidth(spectrum, peakBin, left, right, 0, m_nbBins); + left++; + right--; + + float power = m_linear ? + spectrum[peakBin] * (m_useCalibration ? m_calibrationGain : 1.0f) : + spectrum[peakBin] + (m_useCalibration ? m_calibrationShiftdB : 0.0f); + int64_t frequency = binToFrequency(peakBin); + + // Add to table + if (m_measurements) { + m_measurements->setPeak(i, frequency, power); + } + + if (m_measurementHighlight) + { + float x = peakBin / (float)m_nbBins; + float y = (m_powerScale.getRangeMax() - power) / m_powerScale.getRange(); + + QString text = QString::number(i + 1); + + drawTextOverlayCentered( + text, + QColor(255, 255, 255), + m_textOverlayFont, + x * m_histogramRect.width(), + y * m_histogramRect.height(), + m_histogramRect); + } + + // Remove peak from spectrum so not found on next pass + for (int j = left; j <= right; j++) { + spectrum[j] = -std::numeric_limits::max(); + } + } + + delete spectrum; } // Calculate and display channel power @@ -2077,7 +2134,9 @@ void GLSpectrum::measureChannelPower() float power; power = calcChannelPower(m_centerFrequency, m_measurementBandwidth); - drawTextRight("Power: ", QString::number(power, 'f', 1), "-120.0", "dB"); + if (m_measurements) { + m_measurements->setChannelPower(power); + } if (m_measurementHighlight) { drawBandwidthMarkers(m_centerFrequency, m_measurementBandwidth, m_measurementLightMarkerColor); } @@ -2095,17 +2154,9 @@ void GLSpectrum::measureAdjacentChannelPower() float leftDiff = powerLeft - power; float rightDiff = powerRight - power; - drawTextsRight( - {"L: ", "", " C: ", " R: ", ""}, - { QString::number(powerLeft, 'f', 1), - QString::number(leftDiff, 'f', 1), - QString::number(power, 'f', 1), - QString::number(powerRight, 'f', 1), - QString::number(rightDiff, 'f', 1) - }, - {"-120.0", "-120.0", "-120.0", "-120.0", "-120.0"}, - {"dB", "dBc", "dB", "dB", "dBc"} - ); + if (m_measurements) { + m_measurements->setAdjacentChannelPower(powerLeft, leftDiff, power, powerRight, rightDiff); + } if (m_measurementHighlight) { @@ -2115,38 +2166,38 @@ void GLSpectrum::measureAdjacentChannelPower() } } -const QVector4D GLSpectrum::m_measurementLightMarkerColor = QVector4D(0.5f, 0.5f, 0.5f, 0.4f); -const QVector4D GLSpectrum::m_measurementDarkMarkerColor = QVector4D(0.5f, 0.5f, 0.5f, 0.3f); +const QVector4D GLSpectrum::m_measurementLightMarkerColor = QVector4D(0.6f, 0.6f, 0.6f, 0.2f); +const QVector4D GLSpectrum::m_measurementDarkMarkerColor = QVector4D(0.6f, 0.6f, 0.6f, 0.15f); // Find the width of a peak, by seaching in either direction until // power is no longer falling -void GLSpectrum::peakWidth(int center, int &left, int &right, int maxLeft, int maxRight) const +void GLSpectrum::peakWidth(const Real *spectrum, int center, int &left, int &right, int maxLeft, int maxRight) const { - float prevLeft = m_currentSpectrum[center]; - float prevRight = m_currentSpectrum[center]; + float prevLeft = spectrum[center]; + float prevRight = spectrum[center]; left = center - 1; right = center + 1; - while ((left > maxLeft) && (m_currentSpectrum[left] < prevLeft) && (right < maxRight) && (m_currentSpectrum[right] < prevRight)) + while ((left > maxLeft) && (spectrum[left] < prevLeft) && (right < maxRight) && (spectrum[right] < prevRight)) { - prevLeft = m_currentSpectrum[left]; + prevLeft = spectrum[left]; left--; - prevRight = m_currentSpectrum[right]; + prevRight = spectrum[right]; right++; } } -int GLSpectrum::findPeakBin() const +int GLSpectrum::findPeakBin(const Real *spectrum) const { int bin; float power; bin = 0; - power = m_currentSpectrum[0]; + power = spectrum[0]; for (int i = 1; i < m_nbBins; i++) { - if (m_currentSpectrum[i] > power) + if (spectrum[i] > power) { - power = m_currentSpectrum[i]; + power = spectrum[i]; bin = i; } } @@ -2178,9 +2229,9 @@ int64_t GLSpectrum::binToFrequency(int bin) const void GLSpectrum::measureSNR() { // Find bin with max peak - that will be our signal - int sig = findPeakBin(); + int sig = findPeakBin(m_currentSpectrum); int sigLeft, sigRight; - peakWidth(sig, sigLeft, sigRight, 0, m_nbBins); + peakWidth(m_currentSpectrum, sig, sigLeft, sigRight, 0, m_nbBins); int sigBins = sigRight - sigLeft - 1; int binsLeft = sig - sigLeft; int binsRight = sigRight - sig; @@ -2209,7 +2260,7 @@ void GLSpectrum::measureSNR() } hFreq = binToFrequency(hBin); int hLeft, hRight; - peakWidth(hBin, hLeft, hRight, hBin - binsLeft, hBin + binsRight); + peakWidth(m_currentSpectrum, hBin, hLeft, hRight, hBin - binsLeft, hBin + binsRight); int hBins = hRight - hLeft - 1; if (m_measurementHighlight) { drawPeakMarkers(binToFrequency(hLeft+1), binToFrequency(hRight-1), m_measurementDarkMarkerColor); @@ -2279,55 +2330,34 @@ void GLSpectrum::measureSNR() harmonicPower -= hNoise; } - switch (m_measurement) + if (m_measurements) { - case SpectrumSettings::MeasurementSNR: - { // Calculate SNR in dB over full bandwidth float snr = CalcDb::dbPower(sigPower / noisePower); - drawTextRight("SNR: ", QString::number(snr, 'f', 1), "100.0", "dB"); - break; - } - case SpectrumSettings::MeasurementSNFR: - { + // Calculate SNR, where noise is median of noise summed over signal b/w float snfr = CalcDb::dbPower(sigPower / inBandNoise); - drawTextRight("SNFR: ", QString::number(snfr, 'f', 1), "100.0", "dB"); - break; - } - case SpectrumSettings::MeasurementTHD: - { + // Calculate THD - Total harmonic distortion float thd = harmonicPower / sigPower; float thdDB = CalcDb::dbPower(thd); - drawTextRight("THD: ", QString::number(thdDB, 'f', 1), "-120.0", "dB"); - break; - } - case SpectrumSettings::MeasurementTHDPN: - { + // Calculate THD+N - Total harmonic distortion plus noise float thdpn = CalcDb::dbPower((harmonicPower + noisePower) / sigPower); - drawTextRight("THD+N: ", QString::number(thdpn, 'f', 1), "-120.0", "dB"); - break; - } - case SpectrumSettings::MeasurementSINAD: - { + // Calculate SINAD - Signal to noise and distotion ratio (Should be -THD+N) float sinad = CalcDb::dbPower((sigPower + harmonicPower + noisePower) / (harmonicPower + noisePower)); - drawTextRight("SINAD: ", QString::number(sinad, 'f', 1), "120.0", "dB"); - break; - } - default: - break; + + m_measurements->setSNR(snr, snfr, thdDB, thdpn, sinad); } } void GLSpectrum::measureSFDR() { // Find first peak which is our signal - int peakBin = findPeakBin(); + int peakBin = findPeakBin(m_currentSpectrum); int peakLeft, peakRight; - peakWidth(peakBin, peakLeft, peakRight, 0, m_nbBins); + peakWidth(m_currentSpectrum, peakBin, peakLeft, peakRight, 0, m_nbBins); // Find next largest peak, which is the spur int nextPeakBin = -1; @@ -2353,13 +2383,15 @@ void GLSpectrum::measureSFDR() float sfdr = peakPowerDB - nextPeakPowerDB; // Display - drawTextRight("SFDR: ", QString::number(sfdr, 'f', 1), "100.0", "dB"); + if (m_measurements) { + m_measurements->setSFDR(sfdr); + } if (m_measurementHighlight) { if (m_linear) { - drawPowerBandMarkers(peakPower, nextPeakPower, m_measurementLightMarkerColor); + drawPowerBandMarkers(peakPower, nextPeakPower, m_measurementDarkMarkerColor); } else { - drawPowerBandMarkers(peakPowerDB, nextPeakPowerDB, m_measurementLightMarkerColor); + drawPowerBandMarkers(peakPowerDB, nextPeakPowerDB, m_measurementDarkMarkerColor); } } } @@ -4381,6 +4413,7 @@ int GLSpectrum::getPrecision(int value) } } +// Draw text right justified in top info bar - currently unused void GLSpectrum::drawTextRight(const QString &text, const QString &value, const QString &max, const QString &units) { drawTextsRight({text}, {value}, {max}, {units}); @@ -4437,6 +4470,60 @@ void GLSpectrum::drawTextsRight(const QStringList &text, const QStringList &valu m_glShaderTextOverlay.drawSurface(m_glInfoBoxMatrix, tex1, vtx1, 4); } +void GLSpectrum::drawTextOverlayCentered ( + const QString &text, + const QColor &color, + const QFont& font, + float shiftX, + float shiftY, + const QRectF &glRect) +{ + if (text.isEmpty()) { + return; + } + + QFontMetricsF metrics(font); + QRectF textRect = metrics.boundingRect(text); + QRectF overlayRect(0, 0, textRect.width() * 1.05f + 4.0f, textRect.height()); + QPixmap channelOverlayPixmap = QPixmap(overlayRect.width(), overlayRect.height()); + channelOverlayPixmap.fill(Qt::transparent); + QPainter painter(&channelOverlayPixmap); + painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing, false); + painter.fillRect(overlayRect, QColor(0, 0, 0, 0x80)); + QColor textColor(color); + textColor.setAlpha(0xC0); + painter.setPen(textColor); + painter.setFont(font); + painter.drawText(QPointF(2.0f, overlayRect.height() - 4.0f), text); + painter.end(); + + m_glShaderTextOverlay.initTexture(channelOverlayPixmap.toImage()); + + { + GLfloat vtx1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0}; + GLfloat tex1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0}; + + float rectX = glRect.x() + shiftX - ((overlayRect.width()/2)/width()); + float rectY = glRect.y() + shiftY + (4.0f / height()) - ((overlayRect.height()+5)/height()); + float rectW = overlayRect.width() / (float) width(); + float rectH = overlayRect.height() / (float) height(); + + QMatrix4x4 mat; + mat.setToIdentity(); + mat.translate(-1.0f + 2.0f * rectX, 1.0f - 2.0f * rectY); + mat.scale(2.0f * rectW, -2.0f * rectH); + m_glShaderTextOverlay.drawSurface(mat, tex1, vtx1, 4); + } +} + void GLSpectrum::drawTextOverlay( const QString &text, const QColor &color, @@ -4516,9 +4603,6 @@ void GLSpectrum::formatTextInfo(QString& info) getFrequencyZoom(centerFrequency, frequencySpan); info.append(tr("CF:%1 ").arg(displayScaled(centerFrequency, 'f', getPrecision(centerFrequency/frequencySpan), true))); info.append(tr("SP:%1 ").arg(displayScaled(frequencySpan, 'f', 3, true))); - if (m_measurement != SpectrumSettings::MeasurementNone) { - info.append(tr("RBW:%1 ").arg(displayScaled(m_sampleRate / (float)m_fftSize, 'f', 3, true))); - } } } diff --git a/sdrgui/gui/glspectrum.h b/sdrgui/gui/glspectrum.h index 68bea7fc7..0df10de7a 100644 --- a/sdrgui/gui/glspectrum.h +++ b/sdrgui/gui/glspectrum.h @@ -48,6 +48,7 @@ class QOpenGLShaderProgram; class MessageQueue; class SpectrumVis; class QOpenGLDebugLogger; +class SpectrumMeasurements; class SDRGUI_API GLSpectrum : public QOpenGLWidget, public GLSpectrumInterface { Q_OBJECT @@ -161,9 +162,10 @@ public: void setDisplayTraceIntensity(int intensity); void setLinear(bool linear); void setUseCalibration(bool useCalibration); - void setMeasurementParams(SpectrumSettings::Measurement measurement, int bandwidth, - int chSpacing, int adjChBandwidth, - int harmonics, bool highlight); + void setMeasurements(SpectrumMeasurements *measurements) { m_measurements = measurements; } + void setMeasurementParams(bool measure, SpectrumSettings::Measurement measurement, + int bandwidth, int chSpacing, int adjChBandwidth, + int harmonics, int peaks, bool highlight); qint32 getSampleRate() const { return m_sampleRate; } void addChannelMarker(ChannelMarker* channelMarker); @@ -375,11 +377,14 @@ private: QOpenGLDebugLogger *m_openGLLogger; bool m_isDeviceSpectrum; + SpectrumMeasurements *m_measurements; + bool m_measure; SpectrumSettings::Measurement m_measurement; int m_measurementBandwidth; int m_measurementChSpacing; int m_measurementAdjChBandwidth; int m_measurementHarmonics; + int m_measurementPeaks; bool m_measurementHighlight; static const QVector4D m_measurementLightMarkerColor; static const QVector4D m_measurementDarkMarkerColor; @@ -398,15 +403,16 @@ private: void drawAnnotationMarkers(); void measurePeak(); + void measurePeaks(); void measureChannelPower(); void measureAdjacentChannelPower(); void measureSNR(); void measureSFDR(); float calcChannelPower(int64_t centerFrequency, int channelBandwidth) const; float calPower(float power) const; - int findPeakBin() const; + int findPeakBin(const Real *spectrum) const; void findPeak(float &power, float &frequency) const; - void peakWidth(int center, int &left, int &right, int maxLeft, int maxRight) const; + void peakWidth(const Real *spectrum, int center, int &left, int &right, int maxLeft, int maxRight) const; int frequencyToBin(int64_t frequency) const; int64_t binToFrequency(int bin) const; @@ -441,6 +447,13 @@ private: int getPrecision(int value); void drawTextRight(const QString &text, const QString &value, const QString &max, const QString &units); void drawTextsRight(const QStringList &text, const QStringList &value, const QStringList &max, const QStringList &units); + void drawTextOverlayCentered( + const QString& text, + const QColor& color, + const QFont& font, + float shiftX, + float shiftY, + const QRectF& glRect); void drawTextOverlay( //!< Draws a text overlay const QString& text, const QColor& color, diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index 0ba1915a7..bbb299bf3 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -28,10 +28,12 @@ #include "dsp/fftwindow.h" #include "dsp/spectrumvis.h" #include "gui/glspectrum.h" +#include "gui/glspectrumtop.h" #include "gui/crightclickenabler.h" #include "gui/wsspectrumsettingsdialog.h" #include "gui/spectrummarkersdialog.h" #include "gui/spectrumcalibrationpointsdialog.h" +#include "gui/spectrummeasurements.h" #include "gui/flowlayout.h" #include "util/colormap.h" #include "util/simpleserializer.h" @@ -45,6 +47,7 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) : ui(new Ui::GLSpectrumGUI), m_spectrumVis(nullptr), m_glSpectrum(nullptr), + m_glSpectrumTop(nullptr), m_doApplySettings(true), m_calibrationShiftdB(0.0) { @@ -69,7 +72,7 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) : ui->verticalLayout->addItem(flowLayout); on_linscale_toggled(false); - on_measurement_currentIndexChanged(0); + displayMeasurementGUI(); QString levelStyle = QString( "QSpinBox {background-color: rgb(79, 79, 79);}" @@ -101,13 +104,14 @@ GLSpectrumGUI::~GLSpectrumGUI() delete ui; } -void GLSpectrumGUI::setBuddies(SpectrumVis* spectrumVis, GLSpectrum* glSpectrum) +void GLSpectrumGUI::setBuddies(SpectrumVis* spectrumVis, GLSpectrum* glSpectrum, GLSpectrumTop *glSpectrumTop) { m_spectrumVis = spectrumVis; m_glSpectrum = glSpectrum; m_glSpectrum->setSpectrumVis(spectrumVis); m_glSpectrum->setMessageQueueToGUI(&m_messageQueue); m_spectrumVis->setMessageQueueToGUI(&m_messageQueue); + m_glSpectrumTop = glSpectrumTop; } void GLSpectrumGUI::resetToDefaults() @@ -229,12 +233,15 @@ void GLSpectrumGUI::displaySettings() ui->calibration->setChecked(m_settings.m_useCalibration); displayGotoMarkers(); + ui->measure->setChecked(m_settings.m_measure); ui->measurement->setCurrentIndex((int) m_settings.m_measurement); ui->highlight->setChecked(m_settings.m_measurementHighlight); ui->bandwidth->setValue(m_settings.m_measurementBandwidth); ui->chSpacing->setValue(m_settings.m_measurementChSpacing); ui->adjChBandwidth->setValue(m_settings.m_measurementAdjChBandwidth); ui->harmonics->setValue(m_settings.m_measurementHarmonics); + ui->peaks->setValue(m_settings.m_measurementPeaks); + displayMeasurementGUI(); ui->fftWindow->blockSignals(false); ui->averaging->blockSignals(false); @@ -344,11 +351,13 @@ void GLSpectrumGUI::applySpectrumSettings() m_glSpectrum->setCalibrationInterpMode(m_settings.m_calibrationInterpMode); m_glSpectrum->setMeasurementParams( + m_settings.m_measure, m_settings.m_measurement, m_settings.m_measurementBandwidth, m_settings.m_measurementChSpacing, m_settings.m_measurementAdjChBandwidth, m_settings.m_measurementHarmonics, + m_settings.m_measurementPeaks, m_settings.m_measurementHighlight ); } @@ -1027,29 +1036,51 @@ void GLSpectrumGUI::updateCalibrationPoints() } } -void GLSpectrumGUI::on_measurement_currentIndexChanged(int index) +void GLSpectrumGUI::displayMeasurementGUI() { - m_settings.m_measurement = (SpectrumSettings::Measurement)index; + bool show = m_settings.m_measure; - bool highlight = (m_settings.m_measurement >= SpectrumSettings::MeasurementChannelPower); - ui->highlight->setVisible(highlight); + if (m_glSpectrumTop) { + m_glSpectrumTop->setMeasurementsVisible(show); + } + + ui->measurement->setVisible(show); + ui->highlight->setVisible(show); + + bool reset = (m_settings.m_measurement >= SpectrumSettings::MeasurementChannelPower); + ui->resetMeasurements->setVisible(reset && show); bool bw = (m_settings.m_measurement == SpectrumSettings::MeasurementChannelPower) || (m_settings.m_measurement == SpectrumSettings::MeasurementAdjacentChannelPower); - ui->bandwidthLabel->setVisible(bw); - ui->bandwidth->setVisible(bw); + ui->bandwidthLabel->setVisible(bw && show); + ui->bandwidth->setVisible(bw && show); bool adj = m_settings.m_measurement == SpectrumSettings::MeasurementAdjacentChannelPower; - ui->chSpacingLabel->setVisible(adj); - ui->chSpacing->setVisible(adj); - ui->adjChBandwidthLabel->setVisible(adj); - ui->adjChBandwidth->setVisible(adj); + ui->chSpacingLabel->setVisible(adj && show); + ui->chSpacing->setVisible(adj && show); + ui->adjChBandwidthLabel->setVisible(adj && show); + ui->adjChBandwidth->setVisible(adj && show); - bool harmonics = (m_settings.m_measurement >= SpectrumSettings::MeasurementSNR) - && (m_settings.m_measurement <= SpectrumSettings::MeasurementSINAD); - ui->harmonicsLabel->setVisible(harmonics); - ui->harmonics->setVisible(harmonics); + bool harmonics = (m_settings.m_measurement == SpectrumSettings::MeasurementSNR); + ui->harmonicsLabel->setVisible(harmonics && show); + ui->harmonics->setVisible(harmonics && show); + bool peaks = (m_settings.m_measurement == SpectrumSettings::MeasurementPeaks); + ui->peaksLabel->setVisible(peaks && show); + ui->peaks->setVisible(peaks && show); +} + +void GLSpectrumGUI::on_measure_clicked(bool checked) +{ + m_settings.m_measure = checked; + displayMeasurementGUI(); + applySettings(); +} + +void GLSpectrumGUI::on_measurement_currentIndexChanged(int index) +{ + m_settings.m_measurement = (SpectrumSettings::Measurement)index; + displayMeasurementGUI(); applySettings(); } @@ -1059,6 +1090,15 @@ void GLSpectrumGUI::on_highlight_toggled(bool checked) applySettings(); } +void GLSpectrumGUI::on_resetMeasurements_clicked(bool checked) +{ + (void) checked; + + if (m_glSpectrumTop) { + m_glSpectrumTop->getMeasurements()->reset(); + } +} + void GLSpectrumGUI::on_bandwidth_valueChanged(int value) { m_settings.m_measurementBandwidth = value; @@ -1082,3 +1122,9 @@ void GLSpectrumGUI::on_harmonics_valueChanged(int value) m_settings.m_measurementHarmonics = value; applySettings(); } + +void GLSpectrumGUI::on_peaks_valueChanged(int value) +{ + m_settings.m_measurementPeaks = value; + applySettings(); +} diff --git a/sdrgui/gui/glspectrumgui.h b/sdrgui/gui/glspectrumgui.h index 35c6d937c..1455d273a 100644 --- a/sdrgui/gui/glspectrumgui.h +++ b/sdrgui/gui/glspectrumgui.h @@ -36,6 +36,7 @@ namespace Ui { class SpectrumVis; class GLSpectrum; +class GLSpectrumTop; class SDRGUI_API GLSpectrumGUI : public QWidget, public Serializable { Q_OBJECT @@ -52,7 +53,7 @@ public: explicit GLSpectrumGUI(QWidget* parent = NULL); ~GLSpectrumGUI(); - void setBuddies(SpectrumVis* spectrumVis, GLSpectrum* glSpectrum); + void setBuddies(SpectrumVis* spectrumVis, GLSpectrum* glSpectrum, GLSpectrumTop *glSpectrumTop = nullptr); void setFFTSize(int log2FFTSize); void resetToDefaults(); @@ -67,6 +68,7 @@ private: SpectrumVis* m_spectrumVis; GLSpectrum* m_glSpectrum; + GLSpectrumTop* m_glSpectrumTop; MessageQueue m_messageQueue; SpectrumSettings m_settings; bool m_doApplySettings; @@ -86,6 +88,7 @@ private: bool handleMessage(const Message& message); void displayGotoMarkers(); QString displayScaled(int64_t value, char type, int precision, bool showMult); + void displayMeasurementGUI(); private slots: void on_fftWindow_currentIndexChanged(int index); @@ -123,12 +126,15 @@ private slots: void on_calibration_toggled(bool checked); void on_gotoMarker_currentIndexChanged(int index); + void on_measure_clicked(bool checked); void on_measurement_currentIndexChanged(int index); void on_highlight_toggled(bool checked); + void on_resetMeasurements_clicked(bool checked); void on_bandwidth_valueChanged(int value); void on_chSpacing_valueChanged(int value); void on_adjChBandwidth_valueChanged(int value); void on_harmonics_valueChanged(int value); + void on_peaks_valueChanged(int value); void handleInputMessages(); void openWebsocketSpectrumSettingsDialog(const QPoint& p); diff --git a/sdrgui/gui/glspectrumgui.ui b/sdrgui/gui/glspectrumgui.ui index b7ecc5180..998408784 100644 --- a/sdrgui/gui/glspectrumgui.ui +++ b/sdrgui/gui/glspectrumgui.ui @@ -1073,6 +1073,20 @@
+ + + + Display measurements + + + + :/ruler.png:/ruler.png + + + true + + + @@ -1139,14 +1153,12 @@ Measurement + + -1 + - None - - - - - Peak + Peaks @@ -1164,31 +1176,6 @@ SNR - - - SNFR - - - - - THD - - - - - THD+N - - - - - SINAD - - - - - SFDR - - @@ -1214,6 +1201,20 @@ + + + + Reset measurements + + + + + + + :/bin.png:/bin.png + + + @@ -1309,6 +1310,26 @@ + + + + Peaks + + + + + + + Number of peaks to display + + + 1 + + + 20 + + + diff --git a/sdrgui/gui/glspectrumtop.cpp b/sdrgui/gui/glspectrumtop.cpp new file mode 100644 index 000000000..a677c1974 --- /dev/null +++ b/sdrgui/gui/glspectrumtop.cpp @@ -0,0 +1,51 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#include "gui/glspectrum.h" +#include "gui/glspectrumtop.h" +#include "gui/spectrummeasurements.h" + +GLSpectrumTop::GLSpectrumTop(QWidget *parent) : + QWidget(parent) +{ + m_mainWindow = new QMainWindow(); + m_dock = new QDockWidget(); + m_dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); + //m_dock->setTitleBarWidget(new QLabel("Measurements")); // Could add device or channel R:0 label and dock button? + m_dock->setVisible(false); + m_spectrum = new GLSpectrum(); + m_measurements = new SpectrumMeasurements(); + m_spectrum->setMeasurements(m_measurements); + m_dock->setWidget(m_measurements); + m_mainWindow->setCentralWidget(m_spectrum); + m_mainWindow->addDockWidget(Qt::BottomDockWidgetArea, m_dock); + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(m_mainWindow); + setLayout(layout); +} + +void GLSpectrumTop::setMeasurementsVisible(bool visible) +{ + m_dock->setVisible(visible); +} diff --git a/sdrgui/gui/glspectrumtop.h b/sdrgui/gui/glspectrumtop.h new file mode 100644 index 000000000..658a21c6a --- /dev/null +++ b/sdrgui/gui/glspectrumtop.h @@ -0,0 +1,48 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRGUI_GLSPECTRUMTOP_H_ +#define SDRGUI_GLSPECTRUMTOP_H_ + +#include + +#include "export.h" + +class QMainWindow; +class QDockWidget; +class GLSpectrum; +class SpectrumMeasurements; + +// Combines GLSpectrum in a QMainWindow with SpectrumMeasurements in a QDockWidget +class SDRGUI_API GLSpectrumTop : public QWidget { + Q_OBJECT + +public: + GLSpectrumTop(QWidget *parent = nullptr); + GLSpectrum *getSpectrum() const { return m_spectrum; } + SpectrumMeasurements *getMeasurements() const { return m_measurements; } + void setMeasurementsVisible(bool visible); + +private: + QMainWindow *m_mainWindow; + QDockWidget *m_dock; + GLSpectrum *m_spectrum; + SpectrumMeasurements *m_measurements; + +}; + +#endif // SDRGUI_GLSPECTRUMTOP_H_ diff --git a/sdrgui/gui/spectrummeasurements.cpp b/sdrgui/gui/spectrummeasurements.cpp new file mode 100644 index 000000000..2883316dd --- /dev/null +++ b/sdrgui/gui/spectrummeasurements.cpp @@ -0,0 +1,610 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gui/spectrummeasurements.h" + +#include + +class SDRGUI_API UnitsDelegate : public QStyledItemDelegate { + +public: + UnitsDelegate(QObject *parent = nullptr); + + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + + virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override + { + QString s = text(index); + return QSize(width(s, option.fontMetrics) + 2, option.fontMetrics.height()); + } + + int width(const QString &s, const QFontMetrics &fm) const + { + int left = s.size() > 0 ? fm.leftBearing(s[0]) : 0; + int right = s.size() > 0 ? fm.rightBearing(s[s.size()-1]) : 0; + return fm.horizontalAdvance(s) + left + right; + } + + QString text(const QModelIndex &index) const + { + QString units = index.data(UNITS_ROLE).toString(); + QString s; + if (units == "Hz") + { + s = formatEngineering(index.data().toLongLong()); + } + else + { + int precision = index.data(PRECISION_ROLE).toInt(); + double d = index.data().toDouble(); + s = QString::number(d, 'f', precision); + } + return s + units; + } + + enum Roles { + UNITS_ROLE = Qt::UserRole, + PRECISION_ROLE, + SPEC_ROLE + }; + +protected: + QString formatEngineering(int64_t value) const; + +}; + +UnitsDelegate::UnitsDelegate(QObject *parent) : + QStyledItemDelegate(parent) +{ +} + +QString UnitsDelegate::formatEngineering(int64_t value) const +{ + if (value == 0) { + return "0"; + } + int64_t absValue = std::abs(value); + + QString digits = QString::number(absValue); + int cnt = digits.size(); + + QString point = QLocale::system().decimalPoint(); + QString group = QLocale::system().groupSeparator(); + int i; + for (i = cnt - 3; i >= 4; i -= 3) + { + digits = digits.insert(i, group); + } + if (absValue >= 1000) { + digits = digits.insert(i, point); + } + if (cnt > 9) { + digits = digits.append("G"); + } else if (cnt > 6) { + digits = digits.append("M"); + } else if (cnt > 3) { + digits = digits.append("k"); + } + if (value < 0) { + digits = digits.insert(0, "-"); + } + + return digits; +} + +void UnitsDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QFontMetrics fm = painter->fontMetrics(); + + QString s = text(index); + int sWidth = width(s, fm); + while ((sWidth > option.rect.width()) && !s.isEmpty()) + { + s = s.mid(1); + sWidth = width(s, fm); + } + + int y = option.rect.y() + (option.rect.height()) - ((option.rect.height() - fm.ascent()) / 2); // Align center vertically + + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; + painter->setPen(opt.palette.color(cg, QPalette::Text)); + + painter->drawText(option.rect.x() + option.rect.width() - 1 - sWidth, y, s); +} + + +const QStringList SpectrumMeasurements::m_measurementColumns = { + "Current", + "Mean", + "Min", + "Max", + "Range", + "Std Dev", + "Count", + "Spec", + "Fails", + "" +}; + +const QStringList SpectrumMeasurements::m_tooltips = { + "Current value", + "Mean average of values", + "Minimum value", + "Maximum value", + "Range of values (Max-Min)", + "Standard deviation", + "Count of values", + "Specification for value.\n\nE.g. <-100.5, >34.5 or =10.2", + "Count of values that failed to meet specification", + "" +}; + +SpectrumMeasurements::SpectrumMeasurements(QWidget *parent) : + QWidget(parent), + m_table(nullptr), + m_peakTable(nullptr) +{ + m_textBrush.setColor(Qt::white); // Should get this from the style sheet? + m_redBrush.setColor(Qt::red); + + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + + QVBoxLayout *layout = new QVBoxLayout(); + layout->setContentsMargins(0, 0, 0, 0); + setLayout(layout); +} + +void SpectrumMeasurements::createMeasurementsTable(const QStringList &rows, const QStringList &units) +{ + m_table = new QTableWidget(); + + m_table->horizontalHeader()->setSectionsMovable(true); + m_table->verticalHeader()->setSectionsMovable(true); + + m_table->setColumnCount(m_measurementColumns.size()); + for (int i = 0; i < m_measurementColumns.size(); i++) + { + QTableWidgetItem *item = new QTableWidgetItem(m_measurementColumns[i]); + item->setToolTip(m_tooltips[i]); + m_table->setHorizontalHeaderItem(i, item); + } + + // Cell context menu + m_table->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_table, &QTableWidget::customContextMenuRequested, this, &SpectrumMeasurements::tableContextMenu); + + m_table->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + m_table->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); + + // Fill up space at end of rows + m_table->horizontalHeader()->setSectionResizeMode(COL_EMPTY, QHeaderView::Stretch); + + m_table->setRowCount(rows.size()); + for (int i = 0; i < rows.size(); i++) + { + m_table->setVerticalHeaderItem(i, new QTableWidgetItem(rows[i])); + for (int j = 0; j < m_measurementColumns.size(); j++) + { + QTableWidgetItem *item = new QTableWidgetItem(); + item->setFlags(Qt::ItemIsEnabled); + item->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); + if (j < COL_COUNT) + { + item->setData(UnitsDelegate::UNITS_ROLE, units[i]); + item->setData(UnitsDelegate::PRECISION_ROLE, 1); + } + else if (j == COL_SPEC) + { + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable); + } + m_table->setItem(i, j, item); + } + Measurement m; + m.m_units = units[i]; + m_measurements.append(m); + } + resizeMeasurementsTable(); + for (int i = 0; i < COL_COUNT; i++) { + m_table->setItemDelegateForColumn(i, new UnitsDelegate()); + } + createTableMenus(); + +} + +void SpectrumMeasurements::createPeakTable(int peaks) +{ + m_peakTable = new QTableWidget(); + m_peakTable->horizontalHeader()->setSectionsMovable(true); + + QStringList columns = QStringList{"Frequency", "Power", ""}; + + m_peakTable->setColumnCount(columns.size()); + m_peakTable->setRowCount(peaks); + + for (int i = 0; i < columns.size(); i++) { + m_peakTable->setHorizontalHeaderItem(i, new QTableWidgetItem(columns[i])); + } + for (int i = 0; i < peaks; i++) + { + for (int j = 0; j < 3; j++) + { + QTableWidgetItem *item = new QTableWidgetItem(); + item->setFlags(Qt::ItemIsEnabled); + if (j == COL_FREQUENCY) { + item->setData(UnitsDelegate::UNITS_ROLE, "Hz"); + } else if (j == COL_POWER) { + item->setData(UnitsDelegate::UNITS_ROLE, " dB"); + item->setData(UnitsDelegate::PRECISION_ROLE, 1); + } + m_peakTable->setItem(i, j, item); + } + } + resizePeakTable(); + + m_peakTable->setItemDelegateForColumn(COL_FREQUENCY, new UnitsDelegate()); + m_peakTable->setItemDelegateForColumn(COL_POWER, new UnitsDelegate()); + + // Fill up space at end of rows + m_peakTable->horizontalHeader()->setSectionResizeMode(COL_PEAK_EMPTY, QHeaderView::Stretch); + + m_peakTable->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + m_peakTable->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); + + // Cell context menu + m_peakTable->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_peakTable, &QTableWidget::customContextMenuRequested, this, &SpectrumMeasurements::peakTableContextMenu); +} + +void SpectrumMeasurements::createTableMenus() +{ + // Add context menu to allow hiding/showing of columns + m_rowMenu = new QMenu(m_table); + for (int i = 0; i < m_table->verticalHeader()->count(); i++) + { + QString text = m_table->verticalHeaderItem(i)->text(); + m_rowMenu->addAction(createCheckableItem(text, i, true, true)); + } + m_table->verticalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_table->verticalHeader(), &QTableWidget::customContextMenuRequested, this, &SpectrumMeasurements::rowSelectMenu); + + // Add context menu to allow hiding/showing of rows + m_columnMenu = new QMenu(m_table); + for (int i = 0; i < m_table->horizontalHeader()->count(); i++) + { + QString text = m_table->horizontalHeaderItem(i)->text(); + m_columnMenu->addAction(createCheckableItem(text, i, true, false)); + } + m_table->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_table->horizontalHeader(), &QTableWidget::customContextMenuRequested, this, &SpectrumMeasurements::columnSelectMenu); +} + +void SpectrumMeasurements::createChannelPowerTable() +{ + QStringList rows = {"Channel power"}; + QStringList units = {" dB"}; + + createMeasurementsTable(rows, units); +} + +void SpectrumMeasurements::createAdjacentChannelPowerTable() +{ + QStringList rows = {"Left power", "Left ACPR", "Center power", "Right power", "Right ACPR"}; + QStringList units = {" dB", " dBc", " dB", " dB", " dBc"}; + + createMeasurementsTable(rows, units); +} + +void SpectrumMeasurements::createSNRTable() +{ + QStringList rows = {"SNR", "SNFR", "THD", "THD+N", "SINAD", "SFDR",}; + QStringList units = {" dB", " dB", " dB", " dB", " dB", " dBc"}; + + createMeasurementsTable(rows, units); +} + +// Create column select menu item +QAction *SpectrumMeasurements::createCheckableItem(QString &text, int idx, bool checked, bool row) +{ + QAction *action = new QAction(text, this); + action->setCheckable(true); + action->setChecked(checked); + action->setData(QVariant(idx)); + if (row) { + connect(action, &QAction::triggered, this, &SpectrumMeasurements::rowSelectMenuChecked); + } else { + connect(action, &QAction::triggered, this, &SpectrumMeasurements::columnSelectMenuChecked); + } + return action; +} + +// Right click in table header - show row select menu +void SpectrumMeasurements::rowSelectMenu(QPoint pos) +{ + m_rowMenu->popup(m_table->verticalHeader()->viewport()->mapToGlobal(pos)); +} + +// Hide/show row when menu selected +void SpectrumMeasurements::rowSelectMenuChecked(bool checked) +{ + (void) checked; + QAction* action = qobject_cast(sender()); + if (action != nullptr) + { + int idx = action->data().toInt(nullptr); + m_table->setRowHidden(idx, !action->isChecked()); + } +} + +// Right click in table header - show column select menu +void SpectrumMeasurements::columnSelectMenu(QPoint pos) +{ + m_columnMenu->popup(m_table->horizontalHeader()->viewport()->mapToGlobal(pos)); +} + +// Hide/show column when menu selected +void SpectrumMeasurements::columnSelectMenuChecked(bool checked) +{ + (void) checked; + QAction* action = qobject_cast(sender()); + if (action != nullptr) + { + int idx = action->data().toInt(nullptr); + m_table->setColumnHidden(idx, !action->isChecked()); + } +} + +void SpectrumMeasurements::tableContextMenu(QPoint pos) +{ + QTableWidgetItem *item = m_table->itemAt(pos); + if (item) + { + int row = item->row(); + + QMenu* tableContextMenu = new QMenu(m_table); + connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater); + + // Copy current cell + QAction* copyAction = new QAction("Copy", tableContextMenu); + const QString text = item->text(); + connect(copyAction, &QAction::triggered, this, [text]()->void { + QClipboard *clipboard = QGuiApplication::clipboard(); + clipboard->setText(text); + }); + tableContextMenu->addAction(copyAction); + tableContextMenu->addSeparator(); + + tableContextMenu->popup(m_table->viewport()->mapToGlobal(pos)); + } +} + +void SpectrumMeasurements::peakTableContextMenu(QPoint pos) +{ + QTableWidgetItem *item = m_peakTable->itemAt(pos); + if (item) + { + int row = item->row(); + + QMenu* tableContextMenu = new QMenu(m_peakTable); + connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater); + + // Copy current cell + QAction* copyAction = new QAction("Copy", tableContextMenu); + const QString text = item->text(); + connect(copyAction, &QAction::triggered, this, [text]()->void { + QClipboard *clipboard = QGuiApplication::clipboard(); + clipboard->setText(text); + }); + tableContextMenu->addAction(copyAction); + tableContextMenu->addSeparator(); + + tableContextMenu->popup(m_peakTable->viewport()->mapToGlobal(pos)); + } +} + +void SpectrumMeasurements::resizeMeasurementsTable() +{ + // Fill table with a row of dummy data that will size the columns nicely + int row = m_table->rowCount(); + m_table->setRowCount(row + 1); + m_table->setItem(row, COL_CURRENT, new QTableWidgetItem("-120.0 dBc")); + m_table->setItem(row, COL_MEAN, new QTableWidgetItem("-120.0 dBc")); + m_table->setItem(row, COL_MIN, new QTableWidgetItem("-120.0 dBc")); + m_table->setItem(row, COL_MAX, new QTableWidgetItem("-120.0 dBc")); + m_table->setItem(row, COL_RANGE, new QTableWidgetItem("-120.0 dBc")); + m_table->setItem(row, COL_STD_DEV, new QTableWidgetItem("-120.0 dBc")); + m_table->setItem(row, COL_COUNT, new QTableWidgetItem("100000")); + m_table->setItem(row, COL_SPEC, new QTableWidgetItem(">= -120.0")); + m_table->setItem(row, COL_FAILS, new QTableWidgetItem("100000")); + m_table->resizeColumnsToContents(); + m_table->removeRow(row); +} + +void SpectrumMeasurements::resizePeakTable() +{ + // Fill table with a row of dummy data that will size the columns nicely + int row = m_peakTable->rowCount(); + m_peakTable->setRowCount(row + 1); + m_peakTable->setItem(row, COL_FREQUENCY, new QTableWidgetItem("6.000,000,000GHz")); + m_peakTable->setItem(row, COL_POWER, new QTableWidgetItem("-120.0 dB")); + m_peakTable->resizeColumnsToContents(); + m_peakTable->removeRow(row); +} + +void SpectrumMeasurements::setMeasurementParams(SpectrumSettings::Measurement measurement, int peaks) +{ + // Tried using setVisible(), but that would hang, so delete and recreate + delete m_peakTable; + m_peakTable = nullptr; + delete m_table; + m_table = nullptr; + + switch (measurement) + { + case SpectrumSettings::MeasurementPeaks: + createPeakTable(peaks); + layout()->addWidget(m_peakTable); + break; + case SpectrumSettings::MeasurementChannelPower: + reset(); + createChannelPowerTable(); + layout()->addWidget(m_table); + break; + case SpectrumSettings::MeasurementAdjacentChannelPower: + reset(); + createAdjacentChannelPowerTable(); + layout()->addWidget(m_table); + break; + case SpectrumSettings::MeasurementSNR: + reset(); + createSNRTable(); + layout()->addWidget(m_table); + break; + default: + break; + } +} + +void SpectrumMeasurements::reset() +{ + for (int i = 0; i < m_measurements.size(); i++) { + m_measurements[i].reset(); + } + if (m_table) + { + for (int i = 0; i < m_table->rowCount(); i++) + { + for (int j = 0; j < m_table->columnCount(); j++) + { + if (j != COL_SPEC) { + m_table->item(i, j)->setText(""); + } + } + } + } +} + +// Check the value meets the user-defined specification +bool SpectrumMeasurements::checkSpec(const QString &spec, double value) const +{ + if (spec.isEmpty()) { + return true; + } + if (spec.startsWith("<=")) + { + double limit = spec.mid(2).toDouble(); + return value <= limit; + } + else if (spec[0] == '<') + { + double limit = spec.mid(1).toDouble(); + return value < limit; + } + else if (spec.startsWith(">=")) + { + double limit = spec.mid(2).toDouble(); + return value >= limit; + } + else if (spec[0] == '>') + { + double limit = spec.mid(1).toDouble(); + return value > limit; + } + else if (spec[0] == '=') + { + double limit = spec.mid(1).toDouble(); + return value == limit; + } + return false; +} + +void SpectrumMeasurements::updateMeasurement(int row, float value) +{ + m_measurements[row].add(value); + double mean = m_measurements[row].mean(); + + m_table->item(row, COL_CURRENT)->setData(Qt::DisplayRole, value); + m_table->item(row, COL_MEAN)->setData(Qt::DisplayRole, mean); + m_table->item(row, COL_MIN)->setData(Qt::DisplayRole, m_measurements[row].m_min); + m_table->item(row, COL_MAX)->setData(Qt::DisplayRole, m_measurements[row].m_max); + m_table->item(row, COL_RANGE)->setData(Qt::DisplayRole, m_measurements[row].m_max - m_measurements[row].m_min); + m_table->item(row, COL_STD_DEV)->setData(Qt::DisplayRole, m_measurements[row].stdDev()); + m_table->item(row, COL_COUNT)->setData(Qt::DisplayRole, m_measurements[row].m_values.size()); + + QString spec = m_table->item(row, COL_SPEC)->text(); + bool valueOK = checkSpec(spec, value); + bool meanOK = checkSpec(spec, mean); + bool minOK = checkSpec(spec, m_measurements[row].m_min); + bool mmaxOK = checkSpec(spec, m_measurements[row].m_max); + + if (!valueOK) + { + m_measurements[row].m_fails++; + m_table->item(row, 8)->setData(Qt::DisplayRole, m_measurements[row].m_fails); + } + + // item->setForeground doesn't work, perhaps as we have style sheet applied? + m_table->item(row, COL_CURRENT)->setData(Qt::ForegroundRole, valueOK ? m_textBrush : m_redBrush); + m_table->item(row, COL_MEAN)->setData(Qt::ForegroundRole, meanOK ? m_textBrush : m_redBrush); + m_table->item(row, COL_MIN)->setData(Qt::ForegroundRole, minOK ? m_textBrush : m_redBrush); + m_table->item(row, COL_MAX)->setData(Qt::ForegroundRole, mmaxOK ? m_textBrush : m_redBrush); +} + +void SpectrumMeasurements::setSNR(float snr, float snfr, float thd, float thdpn, float sinad) +{ + updateMeasurement(0, snr); + updateMeasurement(1, snfr); + updateMeasurement(2, thd); + updateMeasurement(3, thdpn); + updateMeasurement(4, sinad); +} + +void SpectrumMeasurements::setSFDR(float sfdr) +{ + updateMeasurement(5, sfdr); +} + +void SpectrumMeasurements::setChannelPower(float power) +{ + updateMeasurement(0, power); +} + +void SpectrumMeasurements::setAdjacentChannelPower(float left, float leftACPR, float center, float right, float rightACPR) +{ + updateMeasurement(0, left); + updateMeasurement(1, leftACPR); + updateMeasurement(2, center); + updateMeasurement(3, right); + updateMeasurement(4, rightACPR); +} + +void SpectrumMeasurements::setPeak(int peak, int64_t frequency, float power) +{ + m_peakTable->item(peak, COL_FREQUENCY)->setData(Qt::DisplayRole, frequency); + m_peakTable->item(peak, COL_POWER)->setData(Qt::DisplayRole, power); +} diff --git a/sdrgui/gui/spectrummeasurements.h b/sdrgui/gui/spectrummeasurements.h new file mode 100644 index 000000000..f65976ab5 --- /dev/null +++ b/sdrgui/gui/spectrummeasurements.h @@ -0,0 +1,146 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRGUI_SPECTRUMMEASUREMENTS_H_ +#define SDRGUI_SPECTRUMMEASUREMENTS_H_ + +#include +#include +#include + +#include "dsp/spectrumsettings.h" +#include "export.h" + +// Displays spectrum measurements in a table +class SDRGUI_API SpectrumMeasurements : public QWidget { + Q_OBJECT + + struct Measurement { + QList m_values; + float m_min; + float m_max; + double m_sum; + int m_fails; + QString m_units; + + Measurement() + { + reset(); + } + + void reset() + { + m_values.clear(); + m_min = std::numeric_limits::max(); + m_max = -std::numeric_limits::max(); + m_sum = 0.0; + m_fails = 0; + } + + void add(float value) + { + m_min = std::min(value, m_min); + m_max = std::max(value, m_max); + m_sum += value; + m_values.append(value); + } + + double mean() const + { + return m_sum / m_values.size(); + } + + double stdDev() const + { + double u = mean(); + double sum = 0.0; + for (int i = 0; i < m_values.size(); i++) + { + double d = m_values[i] - u; + sum += d * d; + } + if (m_values.size() < 2) { + return 0.0; + } else { + return sqrt(sum / (m_values.size() - 1)); + } + } + }; + +public: + SpectrumMeasurements(QWidget *parent = nullptr); + void setMeasurementParams(SpectrumSettings::Measurement measurement, int peaks); + void setSNR(float snr, float snfr, float thd, float thdpn, float sinad); + void setSFDR(float sfdr); + void setChannelPower(float power); + void setAdjacentChannelPower(float left, float leftACPR, float center, float right, float rightACPR); + void setPeak(int peak, int64_t frequency, float power); + void reset(); + +private: + void createMeasurementsTable(const QStringList &rows, const QStringList &units); + void createPeakTable(int peaks); + void createTableMenus(); + void createChannelPowerTable(); + void createAdjacentChannelPowerTable(); + void createSNRTable(); + void tableContextMenu(QPoint pos); + void peakTableContextMenu(QPoint pos); + void rowSelectMenu(QPoint pos); + void rowSelectMenuChecked(bool checked); + void columnSelectMenu(QPoint pos); + void columnSelectMenuChecked(bool checked); + QAction *createCheckableItem(QString &text, int idx, bool checked, bool row); + void resizeMeasurementsTable(); + void resizePeakTable(); + void updateMeasurement(int row, float value); + bool checkSpec(const QString &spec, double value) const; + + QTableWidget *m_table; + QMenu *m_rowMenu; + QMenu *m_columnMenu; + QList m_measurements; + + QTableWidget *m_peakTable; + QBrush m_textBrush; + QBrush m_redBrush; + + enum MeasurementsCol { + COL_CURRENT, + COL_MEAN, + COL_MIN, + COL_MAX, + COL_RANGE, + COL_STD_DEV, + COL_COUNT, + COL_SPEC, + COL_FAILS, + COL_EMPTY + }; + + enum PeakTableCol { + COL_FREQUENCY, + COL_POWER, + COL_PEAK_EMPTY + }; + + static const QStringList m_measurementColumns; + static const QStringList m_tooltips; + +}; + +#endif // SDRGUI_SPECTRUMMEASUREMENTS_H_ diff --git a/sdrgui/mainspectrum/mainspectrumgui.cpp b/sdrgui/mainspectrum/mainspectrumgui.cpp index 5c31dbc12..be30b8859 100644 --- a/sdrgui/mainspectrum/mainspectrumgui.cpp +++ b/sdrgui/mainspectrum/mainspectrumgui.cpp @@ -26,13 +26,15 @@ #include "mainwindow.h" #include "gui/glspectrum.h" +#include "gui/glspectrumtop.h" #include "gui/glspectrumgui.h" #include "gui/workspaceselectiondialog.h" #include "dsp/spectrumvis.h" #include "mainspectrumgui.h" -MainSpectrumGUI::MainSpectrumGUI(GLSpectrum *spectrum, GLSpectrumGUI *spectrumGUI, QWidget *parent) : +MainSpectrumGUI::MainSpectrumGUI(GLSpectrumTop *spectrumTop, GLSpectrum *spectrum, GLSpectrumGUI *spectrumGUI, QWidget *parent) : QMdiSubWindow(parent), + m_spectrumTop(spectrumTop), m_spectrum(spectrum), m_spectrumGUI(spectrumGUI), m_deviceType(DeviceRx), @@ -113,7 +115,7 @@ MainSpectrumGUI::MainSpectrumGUI(GLSpectrum *spectrum, GLSpectrumGUI *spectrumGU m_topLayout->addWidget(m_hideButton); m_spectrumLayout = new QHBoxLayout(); - m_spectrumLayout->addWidget(spectrum); + m_spectrumLayout->addWidget(spectrumTop); spectrum->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); m_spectrumGUILayout = new QHBoxLayout(); m_spectrumGUILayout->addWidget(spectrumGUI); @@ -151,7 +153,7 @@ MainSpectrumGUI::MainSpectrumGUI(GLSpectrum *spectrum, GLSpectrumGUI *spectrumGU MainSpectrumGUI::~MainSpectrumGUI() { qDebug("MainSpectrumGUI::~MainSpectrumGUI"); - m_spectrumLayout->removeWidget(m_spectrum); + m_spectrumLayout->removeWidget(m_spectrumTop); m_spectrumGUILayout->removeWidget(m_spectrumGUI); delete m_sizeGripBottomRight; delete m_bottomLayout; diff --git a/sdrgui/mainspectrum/mainspectrumgui.h b/sdrgui/mainspectrum/mainspectrumgui.h index 1ae135356..a7474b32f 100644 --- a/sdrgui/mainspectrum/mainspectrumgui.h +++ b/sdrgui/mainspectrum/mainspectrumgui.h @@ -26,6 +26,7 @@ #include "export.h" class GLSpectrum; +class GLSpectrumTop; class GLSpectrumGUI; class QLabel; class QPushButton; @@ -44,7 +45,7 @@ public: DeviceMIMO }; - MainSpectrumGUI(GLSpectrum *spectrum, GLSpectrumGUI *spectrumGUI, QWidget *parent = nullptr); + MainSpectrumGUI(GLSpectrumTop *spectrumTop, GLSpectrum *spectrum, GLSpectrumGUI *spectrumGUI, QWidget *parent = nullptr); virtual ~MainSpectrumGUI(); void setDeviceType(DeviceType type); @@ -60,6 +61,7 @@ public: const QByteArray& getGeometryBytes() const { return m_geometryBytes; } private: + GLSpectrumTop *m_spectrumTop; GLSpectrum *m_spectrum; GLSpectrumGUI *m_spectrumGUI; int m_workspaceIndex;