mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-10-31 13:00:26 -04:00 
			
		
		
		
	Spectrum: Use widget for measurements
This commit is contained in:
		
							parent
							
								
									8b1da4bfef
								
							
						
					
					
						commit
						2d43a5515e
					
				| @ -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; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -899,7 +899,7 @@ | ||||
|      <number>2</number> | ||||
|     </property> | ||||
|     <item> | ||||
|      <widget class="GLSpectrum" name="glSpectrum" native="true"> | ||||
|      <widget class="GLSpectrumTop" name="glSpectrumTop" native="true"> | ||||
|       <property name="sizePolicy"> | ||||
|        <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> | ||||
|         <horstretch>0</horstretch> | ||||
| @ -1018,9 +1018,9 @@ | ||||
|    <container>1</container> | ||||
|   </customwidget> | ||||
|   <customwidget> | ||||
|    <class>GLSpectrum</class> | ||||
|    <class>GLSpectrumTop</class> | ||||
|    <extends>QWidget</extends> | ||||
|    <header>gui/glspectrum.h</header> | ||||
|    <header>gui/glspectrumtop.h</header> | ||||
|    <container>1</container> | ||||
|   </customwidget> | ||||
|   <customwidget> | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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
 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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<float>::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))); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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(); | ||||
| } | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -1073,6 +1073,20 @@ | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="ButtonSwitch" name="measure"> | ||||
|        <property name="toolTip"> | ||||
|         <string>Display measurements</string> | ||||
|        </property> | ||||
|        <property name="icon"> | ||||
|         <iconset resource="../resources/res.qrc"> | ||||
|          <normaloff>:/ruler.png</normaloff>:/ruler.png</iconset> | ||||
|        </property> | ||||
|        <property name="checkable"> | ||||
|         <bool>true</bool> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="ButtonSwitch" name="calibration"> | ||||
|        <property name="toolTip"> | ||||
| @ -1139,14 +1153,12 @@ | ||||
|        <property name="toolTip"> | ||||
|         <string>Measurement</string> | ||||
|        </property> | ||||
|        <property name="currentIndex"> | ||||
|         <number>-1</number> | ||||
|        </property> | ||||
|        <item> | ||||
|         <property name="text"> | ||||
|          <string>None</string> | ||||
|         </property> | ||||
|        </item> | ||||
|        <item> | ||||
|         <property name="text"> | ||||
|          <string>Peak</string> | ||||
|          <string>Peaks</string> | ||||
|         </property> | ||||
|        </item> | ||||
|        <item> | ||||
| @ -1164,31 +1176,6 @@ | ||||
|          <string>SNR</string> | ||||
|         </property> | ||||
|        </item> | ||||
|        <item> | ||||
|         <property name="text"> | ||||
|          <string>SNFR</string> | ||||
|         </property> | ||||
|        </item> | ||||
|        <item> | ||||
|         <property name="text"> | ||||
|          <string>THD</string> | ||||
|         </property> | ||||
|        </item> | ||||
|        <item> | ||||
|         <property name="text"> | ||||
|          <string>THD+N</string> | ||||
|         </property> | ||||
|        </item> | ||||
|        <item> | ||||
|         <property name="text"> | ||||
|          <string>SINAD</string> | ||||
|         </property> | ||||
|        </item> | ||||
|        <item> | ||||
|         <property name="text"> | ||||
|          <string>SFDR</string> | ||||
|         </property> | ||||
|        </item> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
| @ -1214,6 +1201,20 @@ | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QToolButton" name="resetMeasurements"> | ||||
|        <property name="toolTip"> | ||||
|         <string>Reset measurements</string> | ||||
|        </property> | ||||
|        <property name="text"> | ||||
|         <string/> | ||||
|        </property> | ||||
|        <property name="icon"> | ||||
|         <iconset resource="../resources/res.qrc"> | ||||
|          <normaloff>:/bin.png</normaloff>:/bin.png</iconset> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QLabel" name="bandwidthLabel"> | ||||
|        <property name="text"> | ||||
| @ -1309,6 +1310,26 @@ | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QLabel" name="peaksLabel"> | ||||
|        <property name="text"> | ||||
|         <string>Peaks</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QSpinBox" name="peaks"> | ||||
|        <property name="toolTip"> | ||||
|         <string>Number of peaks to display</string> | ||||
|        </property> | ||||
|        <property name="minimum"> | ||||
|         <number>1</number> | ||||
|        </property> | ||||
|        <property name="maximum"> | ||||
|         <number>20</number> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|     </layout> | ||||
|    </item> | ||||
|   </layout> | ||||
|  | ||||
							
								
								
									
										51
									
								
								sdrgui/gui/glspectrumtop.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								sdrgui/gui/glspectrumtop.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -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 <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #include <QMainWindow> | ||||
| #include <QDockWidget> | ||||
| #include <QSplitter> | ||||
| #include <QVBoxLayout> | ||||
| #include <QLabel> | ||||
| 
 | ||||
| #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); | ||||
| } | ||||
							
								
								
									
										48
									
								
								sdrgui/gui/glspectrumtop.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								sdrgui/gui/glspectrumtop.h
									
									
									
									
									
										Normal file
									
								
							| @ -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 <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #ifndef SDRGUI_GLSPECTRUMTOP_H_ | ||||
| #define SDRGUI_GLSPECTRUMTOP_H_ | ||||
| 
 | ||||
| #include <QWidget> | ||||
| 
 | ||||
| #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_
 | ||||
							
								
								
									
										610
									
								
								sdrgui/gui/spectrummeasurements.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										610
									
								
								sdrgui/gui/spectrummeasurements.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -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 <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #include <QTableWidgetItem> | ||||
| #include <QHeaderView> | ||||
| #include <QVBoxLayout> | ||||
| #include <QMenu> | ||||
| #include <QAction> | ||||
| #include <QClipboard> | ||||
| #include <QGuiApplication> | ||||
| #include <QDebug> | ||||
| #include <QPainter> | ||||
| 
 | ||||
| #include "gui/spectrummeasurements.h" | ||||
| 
 | ||||
| #include <QStyledItemDelegate> | ||||
| 
 | ||||
| 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<QAction*>(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<QAction*>(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); | ||||
| } | ||||
							
								
								
									
										146
									
								
								sdrgui/gui/spectrummeasurements.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								sdrgui/gui/spectrummeasurements.h
									
									
									
									
									
										Normal file
									
								
							| @ -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 <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #ifndef SDRGUI_SPECTRUMMEASUREMENTS_H_ | ||||
| #define SDRGUI_SPECTRUMMEASUREMENTS_H_ | ||||
| 
 | ||||
| #include <QWidget> | ||||
| #include <QMenu> | ||||
| #include <QTableWidget> | ||||
| 
 | ||||
| #include "dsp/spectrumsettings.h" | ||||
| #include "export.h" | ||||
| 
 | ||||
| // Displays spectrum measurements in a table
 | ||||
| class SDRGUI_API SpectrumMeasurements : public QWidget { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
|     struct Measurement { | ||||
|         QList<float> 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<float>::max(); | ||||
|             m_max = -std::numeric_limits<float>::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<Measurement> 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_
 | ||||
| @ -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; | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user