///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 F4EXB                                                      //
// written by Edouard Griffiths                                                  //
//                                                                               //
// 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 
#include "maincore.h"
#include "dsp/spectrumvis.h"
#include "gui/glspectrumview.h"
#include "gui/spectrummeasurements.h"
#include "settings/mainsettings.h"
#include "util/messagequeue.h"
#include "util/db.h"
#include "util/profiler.h"
#include 
MESSAGE_CLASS_DEFINITION(GLSpectrumView::MsgReportSampleRate, Message)
MESSAGE_CLASS_DEFINITION(GLSpectrumView::MsgReportWaterfallShare, Message)
MESSAGE_CLASS_DEFINITION(GLSpectrumView::MsgReportFFTOverlap, Message)
MESSAGE_CLASS_DEFINITION(GLSpectrumView::MsgReportPowerScale, Message)
MESSAGE_CLASS_DEFINITION(GLSpectrumView::MsgReportCalibrationShift, Message)
MESSAGE_CLASS_DEFINITION(GLSpectrumView::MsgReportHistogramMarkersChange, Message)
MESSAGE_CLASS_DEFINITION(GLSpectrumView::MsgReportWaterfallMarkersChange, Message)
const float GLSpectrumView::m_maxFrequencyZoom = 10.0f;
const float GLSpectrumView::m_annotationMarkerHeight = 20.0f;
GLSpectrumView::GLSpectrumView(QWidget* parent) :
    QOpenGLWidget(parent),
    m_markersDisplay(SpectrumSettings::MarkersDisplaySpectrum),
    m_histogramFindPeaks(false),
    m_cursorState(CSNormal),
    m_cursorChannel(0),
    m_spectrumVis(nullptr),
    m_fpsPeriodMs(50),
    m_mouseInside(false),
    m_changesPending(true),
    m_centerFrequency(100000000),
    m_referenceLevel(0),
    m_powerRange(100),
    m_linear(false),
    m_decay(1),
    m_sampleRate(500000),
    m_timingRate(1),
    m_fftOverlap(0),
    m_fftSize(512),
    m_nbBins(512),
    m_displayGrid(true),
    m_displayGridIntensity(5),
    m_displayTraceIntensity(50),
    m_invertedWaterfall(true),
    m_displayMaxHold(false),
    m_currentSpectrum(nullptr),
    m_displayCurrent(false),
    m_leftMargin(0),
    m_rightMargin(0),
    m_topMargin(0),
    m_frequencyScaleHeight(0),
    m_histogramHeight(80),
    m_waterfallHeight(0),
    m_bottomMargin(0),
    m_waterfallBuffer(nullptr),
    m_waterfallBufferPos(0),
    m_waterfallTextureHeight(-1),
    m_waterfallTexturePos(0),
    m_displayWaterfall(true),
    m_ssbSpectrum(false),
    m_lsbDisplay(false),
    m_3DSpectrogramBuffer(nullptr),
    m_3DSpectrogramBufferPos(0),
    m_3DSpectrogramTextureHeight(-1),
    m_3DSpectrogramTexturePos(0),
    m_display3DSpectrogram(false),
    m_rotate3DSpectrogram(false),
    m_pan3DSpectrogram(false),
    m_scaleZ3DSpectrogram(false),
    m_3DSpectrogramStyle(SpectrumSettings::Outline),
    m_colorMapName("Angel"),
    m_scrollFrequency(false),
    m_scrollStartCenterFreq(0),
    m_pinching(false),
    m_pinching3D(false),
    m_frequencyRequested(false),
    m_nextFrequencyValid(false),
    m_histogramBuffer(nullptr),
    m_histogram(nullptr),
    m_displayHistogram(true),
    m_displayChanged(false),
    m_displaySourceOrSink(true),
    m_displayStreamIndex(0),
    m_matrixLoc(0),
    m_colorLoc(0),
    m_useCalibration(false),
    m_calibrationGain(1.0),
    m_calibrationShiftdB(0.0),
    m_calibrationInterpMode(SpectrumSettings::CalibInterpLinear),
    m_messageQueueToGUI(nullptr),
    m_openGLLogger(nullptr),
    m_isDeviceSpectrum(false),
    m_measurements(nullptr),
    m_measurement(SpectrumSettings::MeasurementNone),
    m_measurementCenterFrequencyOffset(0),
    m_measurementBandwidth(10000),
    m_measurementChSpacing(10000),
    m_measurementAdjChBandwidth(10000),
    m_measurementHarmonics(5),
    m_measurementPeaks(5),
    m_measurementHighlight(true),
    m_measurementPrecision(1)
{
    // Enable multisampling anti-aliasing (MSAA)
    int multisamples = MainCore::instance()->getSettings().getMultisampling();
    if (multisamples > 0)
    {
        QSurfaceFormat format;
        format.setSamples(multisamples);
        setFormat(format);
    }
    setObjectName("GLSpectrum");
    setAutoFillBackground(false);
    setAttribute(Qt::WA_OpaquePaintEvent, true);
    setAttribute(Qt::WA_NoSystemBackground, true);
    setMouseTracking(true);
    setMinimumSize(360, 200);
    m_waterfallShare = 0.5;
    for (int i = 0; i <= 239; i++)
    {
        QColor c;
        c.setHsv(239 - i, 255, 15 + i);
        ((quint8*)&m_waterfallPalette[i])[0] = c.red();
        ((quint8*)&m_waterfallPalette[i])[1] = c.green();
        ((quint8*)&m_waterfallPalette[i])[2] = c.blue();
        ((quint8*)&m_waterfallPalette[i])[3] = c.alpha();
    }
    m_waterfallPalette[239] = 0xffffffff;
    m_histogramPalette[0] = 0;
    for (int i = 1; i < 240; i++)
    {
        QColor c;
        int light = i < 60 ? 128 + (60-i) : 128;
        int sat   = i < 60 ? 140 + i : i < 180 ? 200 : 200 - (i-180);
        c.setHsl(239 - i, sat, light);
        ((quint8*)&m_histogramPalette[i])[0] = c.red();
        ((quint8*)&m_histogramPalette[i])[1] = c.green();
        ((quint8*)&m_histogramPalette[i])[2] = c.blue();
        ((quint8*)&m_histogramPalette[i])[3] = c.alpha();
    }
    // 4.2.3 palette
//    for (int i = 1; i < 240; i++)
//    {
//        QColor c;
//        int val = i < 60 ? 255 : 200;
//        int sat = i < 60 ? 128 : i < 180 ? 255 : 180;
//        c.setHsv(239 - i, sat, val);
//        ((quint8*)&m_histogramPalette[i])[0] = c.red();
//        ((quint8*)&m_histogramPalette[i])[1] = c.green();
//        ((quint8*)&m_histogramPalette[i])[2] = c.blue();
//        ((quint8*)&m_histogramPalette[i])[3] = c.alpha();
//    }
    // Original palette:
//	for(int i = 16; i < 240; i++) {
//		 QColor c;
//		 c.setHsv(239 - i, 255 - ((i < 200) ? 0 : (i - 200) * 3), 150 + ((i < 100) ? i : 100));
//		 ((quint8*)&m_histogramPalette[i])[0] = c.red();
//		 ((quint8*)&m_histogramPalette[i])[1] = c.green();
//		 ((quint8*)&m_histogramPalette[i])[2] = c.blue();
//		 ((quint8*)&m_histogramPalette[i])[3] = c.alpha();
//	}
//	for(int i = 1; i < 16; i++) {
//		QColor c;
//		c.setHsv(255, 128, 48 + i * 4);
//		((quint8*)&m_histogramPalette[i])[0] = c.red();
//		((quint8*)&m_histogramPalette[i])[1] = c.green();
//		((quint8*)&m_histogramPalette[i])[2] = c.blue();
//		((quint8*)&m_histogramPalette[i])[3] = c.alpha();
//	}
    m_decayDivisor = 1;
    m_decayDivisorCount = m_decayDivisor;
    m_histogramStroke = 30;
    m_timeScale.setFont(font());
    m_timeScale.setOrientation(Qt::Vertical);
    m_timeScale.setRange(Unit::Time, 0, 1);
    m_powerScale.setFont(font());
    m_powerScale.setOrientation(Qt::Vertical);
    m_frequencyScale.setFont(font());
    m_frequencyScale.setOrientation(Qt::Horizontal);
    m_textOverlayFont = font(); // QFontDatabase::systemFont(QFontDatabase::FixedFont);
    m_textOverlayFont.setBold(true);
    // m_textOverlayFont.setPointSize(font().pointSize() - 1);
    resetFrequencyZoom();
    m_timer.setTimerType(Qt::PreciseTimer);
    connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick()));
    m_timer.start(m_fpsPeriodMs);
    // Handle KeyEvents
    setFocusPolicy(Qt::StrongFocus);
    installEventFilter(this);
    grabGesture(Qt::PinchGesture);
}
GLSpectrumView::~GLSpectrumView()
{
    QMutexLocker mutexLocker(&m_mutex);
    if (m_waterfallBuffer)
    {
        delete m_waterfallBuffer;
        m_waterfallBuffer = nullptr;
    }
    if (m_3DSpectrogramBuffer)
    {
        delete m_3DSpectrogramBuffer;
        m_3DSpectrogramBuffer = nullptr;
    }
    if (m_histogramBuffer)
    {
        delete m_histogramBuffer;
        m_histogramBuffer = nullptr;
    }
    if (m_histogram)
    {
        delete[] m_histogram;
        m_histogram = nullptr;
    }
    if (m_openGLLogger)
    {
        delete m_openGLLogger;
        m_openGLLogger = nullptr;
    }
}
void GLSpectrumView::queueRequestCenterFrequency(qint64 frequency)
{
    if (!m_frequencyRequested)
    {
        m_frequencyRequested = true;
        m_requestedFrequency = frequency;
        emit requestCenterFrequency(frequency);
    }
    else
    {
        m_nextFrequencyValid = true;
        m_nextFrequency = frequency;
    }
}
void GLSpectrumView::setCenterFrequency(qint64 frequency)
{
    m_mutex.lock();
    m_centerFrequency = frequency;
    // Handle queued frequency requests
    if (m_frequencyRequested && (frequency == m_requestedFrequency))
    {
        m_frequencyRequested = false;
        if (m_nextFrequencyValid)
        {
            m_nextFrequencyValid = false;
            queueRequestCenterFrequency(m_nextFrequency);
        }
    }
    if (m_useCalibration) {
        updateCalibrationPoints();
    }
    m_changesPending = true;
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setReferenceLevel(Real referenceLevel)
{
    m_mutex.lock();
    m_referenceLevel = referenceLevel;
    m_changesPending = true;
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setPowerRange(Real powerRange)
{
    m_mutex.lock();
    m_powerRange = powerRange;
    m_changesPending = true;
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setDecay(int decay)
{
    m_decay = decay < 0 ? 0 : decay > 20 ? 20 : decay;
}
void GLSpectrumView::setDecayDivisor(int decayDivisor)
{
    m_decayDivisor = decayDivisor < 1 ? 1 : decayDivisor > 20 ? 20 : decayDivisor;
}
void GLSpectrumView::setHistoStroke(int stroke)
{
    m_histogramStroke = stroke < 1 ? 1 : stroke > 60 ? 60 : stroke;
}
void GLSpectrumView::setSampleRate(qint32 sampleRate)
{
    m_mutex.lock();
    m_sampleRate = sampleRate;
    if (m_messageQueueToGUI) {
        m_messageQueueToGUI->push(new MsgReportSampleRate(m_sampleRate));
    }
    m_changesPending = true;
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setTimingRate(qint32 timingRate)
{
    m_mutex.lock();
    m_timingRate = timingRate;
    m_changesPending = true;
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setFFTOverlap(int overlap)
{
    m_mutex.lock();
    m_fftOverlap = overlap;
    m_changesPending = true;
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setDisplayWaterfall(bool display)
{
    m_mutex.lock();
    m_displayWaterfall = display;
    if (!display)
    {
        m_waterfallMarkers.clear();
        if (m_messageQueueToGUI) {
            m_messageQueueToGUI->push(new MsgReportWaterfallMarkersChange());
        }
    }
    m_changesPending = true;
    stopDrag();
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setDisplay3DSpectrogram(bool display)
{
    m_mutex.lock();
    m_display3DSpectrogram = display;
    m_changesPending = true;
    stopDrag();
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setSpectrumStyle(SpectrumSettings::SpectrumStyle style)
{
    m_spectrumStyle = style;
    update();
}
void GLSpectrumView::set3DSpectrogramStyle(SpectrumSettings::SpectrogramStyle style)
{
    m_3DSpectrogramStyle = style;
    update();
}
void GLSpectrumView::setColorMapName(const QString &colorMapName)
{
    m_mutex.lock();
    m_colorMapName = colorMapName;
    m_changesPending = true;
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setSsbSpectrum(bool ssbSpectrum)
{
    m_ssbSpectrum = ssbSpectrum;
    update();
}
void GLSpectrumView::setLsbDisplay(bool lsbDisplay)
{
    m_lsbDisplay = lsbDisplay;
    update();
}
void GLSpectrumView::setInvertedWaterfall(bool inv)
{
    m_mutex.lock();
    m_invertedWaterfall = inv;
    m_changesPending = true;
    stopDrag();
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setDisplayMaxHold(bool display)
{
    m_mutex.lock();
    m_displayMaxHold = display;
    if (!m_displayMaxHold && !m_displayCurrent && !m_displayHistogram)
    {
        m_histogramMarkers.clear();
        if (m_messageQueueToGUI) {
            m_messageQueueToGUI->push(new MsgReportHistogramMarkersChange());
        }
    }
    m_changesPending = true;
    stopDrag();
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setDisplayCurrent(bool display)
{
    m_mutex.lock();
    m_displayCurrent = display;
    if (!m_displayMaxHold && !m_displayCurrent && !m_displayHistogram)
    {
        m_histogramMarkers.clear();
        if (m_messageQueueToGUI) {
            m_messageQueueToGUI->push(new MsgReportHistogramMarkersChange());
        }
    }
    m_changesPending = true;
    stopDrag();
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setDisplayHistogram(bool display)
{
    m_mutex.lock();
    m_displayHistogram = display;
    if (!m_displayMaxHold && !m_displayCurrent && !m_displayHistogram)
    {
        m_histogramMarkers.clear();
        if (m_messageQueueToGUI) {
            m_messageQueueToGUI->push(new MsgReportHistogramMarkersChange());
        }
    }
    m_changesPending = true;
    stopDrag();
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setDisplayGrid(bool display)
{
    m_displayGrid = display;
    update();
}
void GLSpectrumView::setDisplayGridIntensity(int intensity)
{
    m_displayGridIntensity = intensity;
    if (m_displayGridIntensity > 100) {
        m_displayGridIntensity = 100;
    } else if (m_displayGridIntensity < 0) {
        m_displayGridIntensity = 0;
    }
    update();
}
void GLSpectrumView::setDisplayTraceIntensity(int intensity)
{
    m_displayTraceIntensity = intensity;
    if (m_displayTraceIntensity > 100) {
        m_displayTraceIntensity = 100;
    } else if (m_displayTraceIntensity < 0) {
        m_displayTraceIntensity = 0;
    }
    update();
}
void GLSpectrumView::setFreqScaleTruncationMode(bool mode)
{
    m_frequencyScale.setTruncateMode(mode);
    update();
}
void GLSpectrumView::setLinear(bool linear)
{
    m_mutex.lock();
    m_linear = linear;
    m_changesPending = true;
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setUseCalibration(bool useCalibration)
{
    m_mutex.lock();
    m_useCalibration = useCalibration;
    if (m_messageQueueToGUI) {
        m_messageQueueToGUI->push(new MsgReportCalibrationShift(m_useCalibration ? m_calibrationShiftdB : 0.0));
    }
    m_changesPending = true;
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setMeasurementParams(SpectrumSettings::Measurement measurement,
                                      int centerFrequencyOffset, int bandwidth, int chSpacing, int adjChBandwidth,
                                      int harmonics, int peaks, bool highlight, int precision)
{
    m_mutex.lock();
    m_measurement = measurement;
    m_measurementCenterFrequencyOffset = centerFrequencyOffset;
    m_measurementBandwidth = bandwidth;
    m_measurementChSpacing = chSpacing;
    m_measurementAdjChBandwidth = adjChBandwidth;
    m_measurementHarmonics = harmonics;
    m_measurementPeaks = peaks;
    m_measurementHighlight = highlight;
    m_measurementPrecision = precision;
    m_changesPending = true;
    if (m_measurements) {
        m_measurements->setMeasurementParams(measurement, peaks, precision);
    }
    m_mutex.unlock();
    update();
}
void GLSpectrumView::addChannelMarker(ChannelMarker* channelMarker)
{
    m_mutex.lock();
    connect(channelMarker, SIGNAL(changedByAPI()), this, SLOT(channelMarkerChanged()));
    connect(channelMarker, SIGNAL(destroyed(QObject*)), this, SLOT(channelMarkerDestroyed(QObject*)));
    m_channelMarkerStates.append(new ChannelMarkerState(channelMarker));
    m_changesPending = true;
    stopDrag();
    m_mutex.unlock();
    update();
}
void GLSpectrumView::removeChannelMarker(ChannelMarker* channelMarker)
{
    m_mutex.lock();
    for (int i = 0; i < m_channelMarkerStates.size(); ++i)
    {
        if (m_channelMarkerStates[i]->m_channelMarker == channelMarker)
        {
            channelMarker->disconnect(this);
            delete m_channelMarkerStates.takeAt(i);
            m_changesPending = true;
            stopDrag();
            m_mutex.unlock();
            update();
            return;
        }
    }
    m_mutex.unlock();
}
void GLSpectrumView::setHistogramMarkers(const QList& histogramMarkers)
{
    m_mutex.lock();
    m_histogramMarkers = histogramMarkers;
    updateHistogramMarkers();
    m_changesPending = true;
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setWaterfallMarkers(const QList& waterfallMarkers)
{
    m_mutex.lock();
    m_waterfallMarkers = waterfallMarkers;
    updateWaterfallMarkers();
    m_changesPending = true;
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setAnnotationMarkers(const QList& annotationMarkers)
{
    m_mutex.lock();
    m_annotationMarkers = annotationMarkers;
    updateAnnotationMarkers();
    m_changesPending = true;
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setMarkersDisplay(SpectrumSettings::MarkersDisplay markersDisplay)
{
	m_mutex.lock();
	m_markersDisplay = markersDisplay;
	updateMarkersDisplay();
    m_changesPending = true;
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setCalibrationPoints(const QList& calibrationPoints)
{
    m_mutex.lock();
    m_calibrationPoints = calibrationPoints;
    updateCalibrationPoints();
    m_changesPending = true;
    m_mutex.unlock();
    update();
}
void GLSpectrumView::setCalibrationInterpMode(SpectrumSettings::CalibrationInterpolationMode mode)
{
	m_mutex.lock();
    m_calibrationInterpMode = mode;
    updateCalibrationPoints();
    m_changesPending = true;
    m_mutex.unlock();
    update();
}
float GLSpectrumView::getPowerMax() const
{
    return m_linear ? m_powerScale.getRangeMax() : CalcDb::powerFromdB(m_powerScale.getRangeMax());
}
float GLSpectrumView::getTimeMax() const
{
    return m_timeScale.getRangeMax();
}
void GLSpectrumView::newSpectrum(const Real *spectrum, int nbBins, int fftSize)
{
    QMutexLocker mutexLocker(&m_mutex);
    m_displayChanged = true;
    if (m_changesPending)
    {
        m_fftSize = fftSize;
        m_nbBins = nbBins;
        return;
    }
    if ((fftSize != m_fftSize) || (m_nbBins != nbBins))
    {
        m_fftSize = fftSize;
        m_nbBins = nbBins;
        m_changesPending = true;
        return;
    }
    updateWaterfall(spectrum);
    update3DSpectrogram(spectrum);
    updateHistogram(spectrum);
}
void GLSpectrumView::updateWaterfall(const Real *spectrum)
{
    if (m_waterfallBufferPos < m_waterfallBuffer->height())
    {
        quint32* pix = (quint32*)m_waterfallBuffer->scanLine(m_waterfallBufferPos);
        for (int i = 0; i < m_nbBins; i++)
        {
            int v = (int)((spectrum[i] - m_referenceLevel) * 2.4 * 100.0 / m_powerRange + 240.0);
            if (v > 239) {
                v = 239;
            } else if (v < 0) {
                v = 0;
            }
            *pix++ = m_waterfallPalette[(int)v];
        }
        m_waterfallBufferPos++;
    }
}
void GLSpectrumView::update3DSpectrogram(const Real *spectrum)
{
    if (m_3DSpectrogramBufferPos < m_3DSpectrogramBuffer->height())
    {
        quint8* pix = (quint8*)m_3DSpectrogramBuffer->scanLine(m_3DSpectrogramBufferPos);
        for (int i = 0; i < m_nbBins; i++)
        {
            int v = (int)((spectrum[i] - m_referenceLevel) * 2.4 * 100.0 / m_powerRange + 240.0);
            if (v > 255) {
                v = 255;
            } else if (v < 0) {
                v = 0;
            }
            *pix++ = v;
        }
        m_3DSpectrogramBufferPos++;
    }
}
void GLSpectrumView::updateHistogram(const Real *spectrum)
{
    quint8* b = m_histogram;
    int fftMulSize = 100 * m_nbBins;
    if ((m_displayHistogram || m_displayMaxHold) && (m_decay != 0))
    {
        m_decayDivisorCount--;
        if ((m_decay > 1) || (m_decayDivisorCount <= 0))
        {
            for (int i = 0; i < fftMulSize; i++)
            {
                if (*b > m_decay) {
                    *b = *b - m_decay;
                } else {
                    *b = 0;
                }
                b++;
            }
            m_decayDivisorCount = m_decayDivisor;
        }
    }
    m_currentSpectrum = spectrum; // Store spectrum for current spectrum line display
#if 0 //def USE_SSE2
    if(m_decay >= 0) { // normal
        const __m128 refl = {m_referenceLevel, m_referenceLevel, m_referenceLevel, m_referenceLevel};
        const __m128 power = {m_powerRange, m_powerRange, m_powerRange, m_powerRange};
        const __m128 mul = {100.0f, 100.0f, 100.0f, 100.0f};
        for(int i = 0; i < m_fftSize; i += 4) {
            __m128 abc = _mm_loadu_ps (&spectrum[i]);
            abc = _mm_sub_ps(abc, refl);
            abc = _mm_mul_ps(abc, mul);
            abc = _mm_div_ps(abc, power);
            abc =  _mm_add_ps(abc, mul);
            __m128i result = _mm_cvtps_epi32(abc);
            for(int j = 0; j < 4; j++) {
                int v = ((int*)&result)[j];
                if((v >= 0) && (v <= 99)) {
                    b = m_histogram + (i + j) * 100 + v;
                    if(*b < 220)
                        *b += m_histogramStroke; // was 4
                    else if(*b < 239)
                        *b += 1;
                }
            }
        }
    } else { // draw double pixels
        int add = -m_decay * 4;
        const __m128 refl = {m_referenceLevel, m_referenceLevel, m_referenceLevel, m_referenceLevel};
        const __m128 power = {m_powerRange, m_powerRange, m_powerRange, m_powerRange};
        const __m128 mul = {100.0f, 100.0f, 100.0f, 100.0f};
        for(int i = 0; i < m_fftSize; i += 4) {
            __m128 abc = _mm_loadu_ps (&spectrum[i]);
            abc = _mm_sub_ps(abc, refl);
            abc = _mm_mul_ps(abc, mul);
            abc = _mm_div_ps(abc, power);
            abc =  _mm_add_ps(abc, mul);
            __m128i result = _mm_cvtps_epi32(abc);
            for(int j = 0; j < 4; j++) {
                int v = ((int*)&result)[j];
                if((v >= 1) && (v <= 98)) {
                    b = m_histogram + (i + j) * 100 + v;
                    if(b[-1] < 220)
                        b[-1] += add;
                    else if(b[-1] < 239)
                        b[-1] += 1;
                    if(b[0] < 220)
                        b[0] += add;
                    else if(b[0] < 239)
                        b[0] += 1;
                    if(b[1] < 220)
                        b[1] += add;
                    else if(b[1] < 239)
                        b[1] += 1;
                } else if((v >= 0) && (v <= 99)) {
                    b = m_histogram + (i + j) * 100 + v;
                    if(*b < 220)
                        *b += add;
                    else if(*b < 239)
                        *b += 1;
                }
            }
        }
    }
#else
    for (int i = 0; i < m_nbBins; i++)
    {
        int v = (int)((spectrum[i] - m_referenceLevel) * 100.0 / m_powerRange + 100.0);
        if ((v >= 0) && (v <= 99))
        {
            b = m_histogram + i * 100 + v;
            // capping to 239 as palette values are [0..239]
            if (*b + m_histogramStroke <= 239) {
                *b += m_histogramStroke; // was 4
            } else {
                *b = 239;
            }
        }
    }
#endif
}
void GLSpectrumView::initializeGL()
{
    QOpenGLContext *glCurrentContext =  QOpenGLContext::currentContext();
    int majorVersion = 0;
    int minorVersion = 0;
    if (glCurrentContext)
    {
        if (QOpenGLContext::currentContext()->isValid())
        {
            qDebug() << "GLSpectrumView::initializeGL: context:"
                << " major: " << (QOpenGLContext::currentContext()->format()).majorVersion()
                << " minor: " << (QOpenGLContext::currentContext()->format()).minorVersion()
                << " ES: " << (QOpenGLContext::currentContext()->isOpenGLES() ? "yes" : "no");
            majorVersion = (QOpenGLContext::currentContext()->format()).majorVersion();
            minorVersion = (QOpenGLContext::currentContext()->format()).minorVersion();
        }
        else {
            qDebug() << "GLSpectrumView::initializeGL: current context is invalid";
        }
        // Enable OpenGL debugging
        // Disable for release, as some OpenGL drivers are quite verbose and output
        // info on every frame
        if (false)
        {
            QSurfaceFormat format = glCurrentContext->format();
            format.setOption(QSurfaceFormat::DebugContext);
            glCurrentContext->setFormat(format);
            if (glCurrentContext->hasExtension(QByteArrayLiteral("GL_KHR_debug")))
            {
                m_openGLLogger = new QOpenGLDebugLogger(this);
                m_openGLLogger->initialize();
                connect(m_openGLLogger, &QOpenGLDebugLogger::messageLogged, this, &GLSpectrumView::openGLDebug);
                m_openGLLogger->startLogging(QOpenGLDebugLogger::SynchronousLogging);
            }
            else
            {
                qDebug() << "GLSpectrumView::initializeGL: GL_KHR_debug not available";
            }
        }
    }
    else
    {
        qCritical() << "GLSpectrumView::initializeGL: no current context";
        return;
    }
    QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions();
    glFunctions->initializeOpenGLFunctions();
    //glDisable(GL_DEPTH_TEST);
    m_glShaderSimple.initializeGL(majorVersion, minorVersion);
    m_glShaderLeftScale.initializeGL(majorVersion, minorVersion);
    m_glShaderFrequencyScale.initializeGL(majorVersion, minorVersion);
    m_glShaderWaterfall.initializeGL(majorVersion, minorVersion);
    m_glShaderHistogram.initializeGL(majorVersion, minorVersion);
    m_glShaderColorMap.initializeGL(majorVersion, minorVersion);
    m_glShaderTextOverlay.initializeGL(majorVersion, minorVersion);
    m_glShaderInfo.initializeGL(majorVersion, minorVersion);
    m_glShaderSpectrogram.initializeGL(majorVersion, minorVersion);
    m_glShaderSpectrogramTimeScale.initializeGL(majorVersion, minorVersion);
    m_glShaderSpectrogramPowerScale.initializeGL(majorVersion, minorVersion);
}
void GLSpectrumView::openGLDebug(const QOpenGLDebugMessage &debugMessage)
{
    qDebug() << "GLSpectrumView::openGLDebug: " << debugMessage;
}
void GLSpectrumView::resizeGL(int width, int height)
{
    QMutexLocker mutexLocker(&m_mutex);
    QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions();
    glFunctions->glViewport(0, 0, width, height);
    m_changesPending = true;
}
void GLSpectrumView::clearSpectrumHistogram()
{
    if (!m_mutex.tryLock(2)) {
        return;
    }
    memset(m_histogram, 0x00, 100 * m_nbBins);
    m_mutex.unlock();
    update();
}
void GLSpectrumView::paintGL()
{
    PROFILER_START()
    if (!m_mutex.tryLock(2)) {
        return;
    }
    if (m_changesPending)
    {
        applyChanges();
        m_changesPending = false;
    }
    if (m_nbBins <= 0)
    {
        m_mutex.unlock();
        return;
    }
    QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions();
    glFunctions->glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glFunctions->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    QMatrix4x4 spectrogramGridMatrix;
    float devicePixelRatio;
    if (m_display3DSpectrogram)
    {
        m_glShaderSpectrogram.applyTransform(spectrogramGridMatrix);
        // paint 3D spectrogram
        if (m_3DSpectrogramTexturePos + m_3DSpectrogramBufferPos < m_3DSpectrogramTextureHeight)
        {
            m_glShaderSpectrogram.subTexture(0, m_3DSpectrogramTexturePos, m_nbBins, m_3DSpectrogramBufferPos,  m_3DSpectrogramBuffer->scanLine(0));
            m_3DSpectrogramTexturePos += m_3DSpectrogramBufferPos;
        }
        else
        {
            int breakLine = m_3DSpectrogramTextureHeight - m_3DSpectrogramTexturePos;
            int linesLeft = m_3DSpectrogramTexturePos + m_3DSpectrogramBufferPos - m_3DSpectrogramTextureHeight;
            m_glShaderSpectrogram.subTexture(0, m_3DSpectrogramTexturePos, m_nbBins, breakLine,  m_3DSpectrogramBuffer->scanLine(0));
            m_glShaderSpectrogram.subTexture(0, 0, m_nbBins, linesLeft,  m_3DSpectrogramBuffer->scanLine(breakLine));
            m_3DSpectrogramTexturePos = linesLeft;
        }
        m_3DSpectrogramBufferPos = 0;
        float prop_y = m_3DSpectrogramTexturePos / (m_3DSpectrogramTextureHeight - 1.0);
        // Temporarily reduce viewport to waterfall area so anything outside is clipped
        if (window()->windowHandle()) {
            devicePixelRatio = window()->windowHandle()->devicePixelRatio();
        } else {
            devicePixelRatio = 1.0f;
        }
        glFunctions->glViewport(0, m_3DSpectrogramBottom*devicePixelRatio, width()*devicePixelRatio, m_waterfallHeight*devicePixelRatio);
        m_glShaderSpectrogram.drawSurface(m_3DSpectrogramStyle, spectrogramGridMatrix, prop_y, m_invertedWaterfall);
        glFunctions->glViewport(0, 0, width()*devicePixelRatio, height()*devicePixelRatio);
    }
    else if (m_displayWaterfall)
    {
        // paint 2D waterfall
        {
            GLfloat vtx1[] = {
                    0, m_invertedWaterfall ? 0.0f : 1.0f,
                    1, m_invertedWaterfall ? 0.0f : 1.0f,
                    1, m_invertedWaterfall ? 1.0f : 0.0f,
                    0, m_invertedWaterfall ? 1.0f : 0.0f
            };
            if (m_waterfallTexturePos + m_waterfallBufferPos < m_waterfallTextureHeight)
            {
                m_glShaderWaterfall.subTexture(0, m_waterfallTexturePos, m_nbBins, m_waterfallBufferPos,  m_waterfallBuffer->scanLine(0));
                m_waterfallTexturePos += m_waterfallBufferPos;
            }
            else
            {
                int breakLine = m_waterfallTextureHeight - m_waterfallTexturePos;
                int linesLeft = m_waterfallTexturePos + m_waterfallBufferPos - m_waterfallTextureHeight;
                m_glShaderWaterfall.subTexture(0, m_waterfallTexturePos, m_nbBins, breakLine,  m_waterfallBuffer->scanLine(0));
                m_glShaderWaterfall.subTexture(0, 0, m_nbBins, linesLeft,  m_waterfallBuffer->scanLine(breakLine));
                m_waterfallTexturePos = linesLeft;
            }
            m_waterfallBufferPos = 0;
            float prop_y = m_waterfallTexturePos / (m_waterfallTextureHeight - 1.0);
            float off = 1.0 / (m_waterfallTextureHeight - 1.0);
            GLfloat tex1[] = {
                    0, prop_y + 1 - off,
                    1, prop_y + 1 - off,
                    1, prop_y,
                    0, prop_y
            };
            m_glShaderWaterfall.drawSurface(m_glWaterfallBoxMatrix, tex1, vtx1, 4);
        }
        // paint channels
        if (m_mouseInside)
        {
            for (int i = 0; i < m_channelMarkerStates.size(); ++i)
            {
                ChannelMarkerState* dv = m_channelMarkerStates[i];
                if (dv->m_channelMarker->getVisible()
                    && (dv->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink)
                    && dv->m_channelMarker->streamIndexApplies(m_displayStreamIndex))
                {
                    {
                        GLfloat q3[] {
                            0, 0,
                            1, 0,
                            1, 1,
                            0, 1,
                            0.5, 0,
                            0.5, 1,
                        };
                        QVector4D color(dv->m_channelMarker->getColor().redF(), dv->m_channelMarker->getColor().greenF(), dv->m_channelMarker->getColor().blueF(), 0.3f);
                        m_glShaderSimple.drawSurface(dv->m_glMatrixWaterfall, color, q3, 4);
                        QVector4D colorLine(0.8f, 0.8f, 0.6f, 1.0f);
                        m_glShaderSimple.drawSegments(dv->m_glMatrixDsbWaterfall, colorLine, &q3[8], 2);
                    }
                }
            }
        }
        // draw rect around
        {
            GLfloat q3[] {
                1, 1,
                0, 1,
                0, 0,
                1, 0
            };
            QVector4D color(1.0f, 1.0f, 1.0f, 0.5f);
            m_glShaderSimple.drawContour(m_glWaterfallBoxMatrix, color, q3, 4);
        }
    }
    // paint histogram
    if (m_displayHistogram || m_displayMaxHold || m_displayCurrent)
    {
        if (m_displayHistogram)
        {
            {
                // import new lines into the texture
                quint32* pix;
                quint8* bs = m_histogram;
                for (int y = 0; y < 100; y++)
                {
                    quint8* b = bs;
                    pix = (quint32*)m_histogramBuffer->scanLine(99 - y);
                    for (int x = 0; x < m_nbBins; x++)
                    {
                        *pix = m_histogramPalette[*b];
                        pix++;
                        b += 100;
                    }
                    bs++;
                }
                GLfloat vtx1[] = {
                        0, 0,
                        1, 0,
                        1, 1,
                        0, 1
                };
                GLfloat tex1[] = {
                        0, 0,
                        1, 0,
                        1, 1,
                        0, 1
                };
                m_glShaderHistogram.subTexture(0, 0, m_nbBins, 100,  m_histogramBuffer->scanLine(0));
                m_glShaderHistogram.drawSurface(m_glHistogramBoxMatrix, tex1, vtx1, 4);
            }
        }
        // paint channels
        if (m_mouseInside)
        {
            // Effective BW overlays
            for (int i = 0; i < m_channelMarkerStates.size(); ++i)
            {
                ChannelMarkerState* dv = m_channelMarkerStates[i];
                if (dv->m_channelMarker->getVisible()
                    && (dv->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink)
                    && dv->m_channelMarker->streamIndexApplies(m_displayStreamIndex))
                {
                    {
                        GLfloat q3[] {
                            0, 0,
                            1, 0,
                            1, 1,
                            0, 1,
                            0.5, 0,
                            0.5, 1
                        };
                        QVector4D color(dv->m_channelMarker->getColor().redF(), dv->m_channelMarker->getColor().greenF(), dv->m_channelMarker->getColor().blueF(), 0.3f);
                        m_glShaderSimple.drawSurface(dv->m_glMatrixHistogram, color, q3, 4);
                        QVector4D colorLine(0.8f, 0.8f, 0.6f, 1.0f);
                        if (dv->m_channelMarker->getSidebands() != ChannelMarker::dsb) {
                            q3[6] = 0.5;
                        }
                        m_glShaderSimple.drawSegments(dv->m_glMatrixDsbHistogram, colorLine, &q3[8], 2);
                        m_glShaderSimple.drawSegments(dv->m_glMatrixFreqScale, colorLine, q3, 2);
                    }
                }
            }
        }
    }
    // paint left scales (time and power)
    if (m_displayWaterfall || m_displayMaxHold || m_displayCurrent || m_displayHistogram )
    {
        {
            GLfloat vtx1[] = {
                    0, 1,
                    1, 1,
                    1, 0,
                    0, 0
            };
            GLfloat tex1[] = {
                    0, 1,
                    1, 1,
                    1, 0,
                    0, 0
            };
            m_glShaderLeftScale.drawSurface(m_glLeftScaleBoxMatrix, tex1, vtx1, 4);
        }
    }
    // paint frequency scale
    if (m_displayWaterfall || m_displayMaxHold || m_displayCurrent || m_displayHistogram)
    {
        {
            GLfloat vtx1[] = {
                    0, 1,
                    1, 1,
                    1, 0,
                    0, 0
            };
            GLfloat tex1[] = {
                    0, 1,
                    1, 1,
                    1, 0,
                    0, 0
            };
            m_glShaderFrequencyScale.drawSurface(m_glFrequencyScaleBoxMatrix, tex1, vtx1, 4);
        }
        // paint channels
        // Effective bandwidth overlays
        for (int i = 0; i < m_channelMarkerStates.size(); ++i)
        {
            ChannelMarkerState* dv = m_channelMarkerStates[i];
            // frequency scale channel overlay
            if (dv->m_channelMarker->getVisible()
                && (dv->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink)
                && dv->m_channelMarker->streamIndexApplies(m_displayStreamIndex))
            {
                {
                    GLfloat q3[] {
                        1, 0.2,
                        0, 0.2,
                        0, 0,
                        1, 0,
                        0.5, 0,
                        0.5, 1
                    };
                    QVector4D color(dv->m_channelMarker->getColor().redF(), dv->m_channelMarker->getColor().greenF(), dv->m_channelMarker->getColor().blueF(), 0.5f);
                    m_glShaderSimple.drawSurface(dv->m_glMatrixFreqScale, color, q3, 4);
                    if (dv->m_channelMarker->getHighlighted())
                    {
                        QVector4D colorLine(0.8f, 0.8f, 0.6f, 1.0f);
                        m_glShaderSimple.drawSegments(dv->m_glMatrixDsbFreqScale, colorLine, &q3[8], 2);
                        m_glShaderSimple.drawSegments(dv->m_glMatrixFreqScale, colorLine, &q3[4], 2);
                    }
                }
            }
        }
    }
    // paint 3D spectrogram scales
    if (m_display3DSpectrogram && m_displayGrid)
    {
        glFunctions->glViewport(0, m_3DSpectrogramBottom*devicePixelRatio, width()*devicePixelRatio, m_waterfallHeight*devicePixelRatio);
        {
            GLfloat l = m_spectrogramTimePixmap.width() / (GLfloat) width();
            GLfloat r = m_rightMargin / (GLfloat) width();
            GLfloat h = m_frequencyPixmap.height() / (GLfloat) m_waterfallHeight;
            GLfloat vtx1[] = {
                       -l, -h,
                   1.0f+r, -h,
                   1.0f+r,  0.0f,
                       -l,  0.0f
            };
            GLfloat tex1[] = {
                    0, 1,
                    1, 1,
                    1, 0,
                    0, 0
            };
            m_glShaderFrequencyScale.drawSurface(spectrogramGridMatrix, tex1, vtx1, 4);
        }
        {
            GLfloat w = m_spectrogramTimePixmap.width() / (GLfloat) width();
            GLfloat h = (m_bottomMargin/2) / (GLfloat) m_waterfallHeight;      // m_bottomMargin is fm.ascent
            GLfloat vtx1[] = {
                    -w, 0.0f-h,
                  0.0f, 0.0f-h,
                  0.0f, 1.0f+h,
                    -w, 1.0f+h
            };
            GLfloat tex1[] = {
                    0, 1,
                    1, 1,
                    1, 0,
                    0, 0
            };
            m_glShaderSpectrogramTimeScale.drawSurface(spectrogramGridMatrix, tex1, vtx1, 4);
        }
        {
            GLfloat w = m_spectrogramPowerPixmap.width() / (GLfloat) width();
            GLfloat h = m_topMargin / (GLfloat) m_spectrogramPowerPixmap.height();
            GLfloat vtx1[] = {
                    -w, 1.0f, 0.0f,
                  0.0f, 1.0f, 0.0f,
                  0.0f, 1.0f, 1.0f+h,
                    -w, 1.0f, 1.0f+h,
            };
            GLfloat tex1[] = {
                    0, 1,
                    1, 1,
                    1, 0,
                    0, 0
            };
            m_glShaderSpectrogramPowerScale.drawSurface(spectrogramGridMatrix, tex1, vtx1, 4, 3);
        }
        glFunctions->glViewport(0, 0, width()*devicePixelRatio, height()*devicePixelRatio);
    }
    // paint max hold lines on top of histogram
    if (m_displayMaxHold)
    {
        if (m_maxHold.size() < (uint) m_nbBins) {
            m_maxHold.resize(m_nbBins);
        }
        for (int i = 0; i < m_nbBins; i++)
        {
            int j;
            quint8* bs = m_histogram + i * 100;
            for (j = 99; j >= 0; j--)
            {
                if (bs[j] > 0) {
                    break;
                }
            }
            // m_referenceLevel : top
            // m_referenceLevel - m_powerRange : bottom
            m_maxHold[i] = ((j - 99) * m_powerRange) / 99.0 + m_referenceLevel;
        }
        // Fill under max hold line
        if (m_spectrumStyle != SpectrumSettings::Line)
        {
            GLfloat *q3 = m_q3ColorMap.m_array;
            for (int i = 0; i < m_nbBins; i++)
            {
                Real v = m_maxHold[i] - m_referenceLevel;
                if (v > 0) {
                    v = 0;
                } else if (v < -m_powerRange) {
                    v = -m_powerRange;
                }
                q3[4*i] = (GLfloat)i;
                q3[4*i+1] = -m_powerRange;
                q3[4*i+2] = (GLfloat)i;
                q3[4*i+3] = v;
            }
            // Replicate Nyquist sample to end of positive side
            q3[4*m_nbBins] = (GLfloat) m_nbBins;
            q3[4*m_nbBins+1] = q3[1];
            q3[4*m_nbBins+2] = (GLfloat) m_nbBins;
            q3[4*m_nbBins+3] = q3[3];
            QVector4D color(0.5f, 0.0f, 0.0f, (float) m_displayTraceIntensity / 100.0f);
            m_glShaderSimple.drawSurfaceStrip(m_glHistogramSpectrumMatrix, color, q3, 2*(m_nbBins+1));
        }
        // Max hold line
        {
            GLfloat *q3 = m_q3FFT.m_array;
            for (int i = 0; i < m_nbBins; i++)
            {
                Real v = m_maxHold[i] - m_referenceLevel;
                if (v >= 0) {
                    v = 0;
                } else if (v < -m_powerRange) {
                    v = -m_powerRange;
                }
                q3[2*i] = (Real) i;
                q3[2*i+1] = v;
            }
            // Replicate Nyquist sample to end of positive side
            q3[2*m_nbBins] = (GLfloat) m_nbBins;
            q3[2*m_nbBins+1] = q3[1];
            QVector4D color(1.0f, 0.0f, 0.0f, (float) m_displayTraceIntensity / 100.0f);
            m_glShaderSimple.drawPolyline(m_glHistogramSpectrumMatrix, color, q3, m_nbBins+1);
        }
    }
    // paint current spectrum line on top of histogram
    if (m_displayCurrent && m_currentSpectrum)
    {
        Real bottom = -m_powerRange;
        GLfloat *q3;
        if (m_spectrumStyle != SpectrumSettings::Line)
        {
            q3 = m_q3ColorMap.m_array;
            // Fill under line
            for (int i = 0; i < m_nbBins; i++)
            {
                Real v = m_currentSpectrum[i] - m_referenceLevel;
                if (v > 0) {
                    v = 0;
                } else if (v < bottom) {
                    v = bottom;
                }
                q3[4*i] = (GLfloat)i;
                q3[4*i+1] = bottom;
                q3[4*i+2] = (GLfloat)i;
                q3[4*i+3] = v;
            }
            // Replicate Nyquist sample to end of positive side
            q3[4*m_nbBins] = (GLfloat) m_nbBins;
            q3[4*m_nbBins+1] = q3[1];
            q3[4*m_nbBins+2] = (GLfloat) m_nbBins;
            q3[4*m_nbBins+3] = q3[3];
            QVector4D color(1.0f, 1.0f, 0.25f, (float) m_displayTraceIntensity / 100.0f);
            if (m_spectrumStyle == SpectrumSettings::Gradient) {
                m_glShaderColorMap.drawSurfaceStrip(m_glHistogramSpectrumMatrix, q3, 2*(m_nbBins+1), bottom, 0.75f);
            } else {
                m_glShaderSimple.drawSurfaceStrip(m_glHistogramSpectrumMatrix, color, q3, 2*(m_nbBins+1));
            }
        }
        {
            if (m_histogramFindPeaks) {
                m_peakFinder.init(m_currentSpectrum[0]);
            }
            // Draw line
            q3 = m_q3FFT.m_array;
            for (int i = 0; i < m_nbBins; i++)
            {
                Real v = m_currentSpectrum[i] - m_referenceLevel;
                if (v > 0) {
                    v = 0;
                } else if (v < bottom) {
                    v = bottom;
                }
                q3[2*i] = (Real) i;
                q3[2*i+1] = v;
                if (m_histogramFindPeaks && (i > 0)) {
                    m_peakFinder.push(m_currentSpectrum[i], i == m_nbBins - 1);
                }
            }
            // Replicate Nyquist sample to end of positive side
            q3[2*m_nbBins] = (GLfloat) m_nbBins;
            q3[2*m_nbBins+1] = q3[1];
            QVector4D color;
            if (m_spectrumStyle == SpectrumSettings::Gradient) {
                color = QVector4D(m_colorMap[255*3], m_colorMap[255*3+1], m_colorMap[255*3+2], (float) m_displayTraceIntensity / 100.0f);
            } else {
                color = QVector4D(1.0f, 1.0f, 0.25f, (float) m_displayTraceIntensity / 100.0f);
            }
            m_glShaderSimple.drawPolyline(m_glHistogramSpectrumMatrix, color, q3, m_nbBins+1);
            if (m_histogramFindPeaks) {
                m_peakFinder.sortPeaks();
            }
        }
    }
    if (m_displayCurrent && m_currentSpectrum && (m_markersDisplay & SpectrumSettings::MarkersDisplaySpectrum))
    {
        if (m_histogramFindPeaks) {
            updateHistogramPeaks();
        }
        drawSpectrumMarkers();
    }
    if (m_markersDisplay & SpectrumSettings::MarkersDisplayAnnotations) {
        drawAnnotationMarkers();
    }
    // paint waterfall grid
    if (m_displayWaterfall && m_displayGrid)
    {
        const ScaleEngine::TickList* tickList;
        const ScaleEngine::Tick* tick;
        tickList = &m_timeScale.getTickList();
        {
            GLfloat *q3 = m_q3TickTime.m_array;
            int effectiveTicks = 0;
            for (int i= 0; i < tickList->count(); i++)
            {
                tick = &(*tickList)[i];
                if (tick->major)
                {
                    if (tick->textSize > 0)
                    {
                        float y = tick->pos / m_timeScale.getSize();
                        q3[4*effectiveTicks] = 0;
                        q3[4*effectiveTicks+1] = y;
                        q3[4*effectiveTicks+2] = 1;
                        q3[4*effectiveTicks+3] = y;
                        effectiveTicks++;
                    }
                }
            }
            QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
            m_glShaderSimple.drawSegments(m_glWaterfallBoxMatrix, color, q3, 2*effectiveTicks);
        }
        tickList = &m_frequencyScale.getTickList();
        {
            GLfloat *q3 = m_q3TickFrequency.m_array;
            int effectiveTicks = 0;
            for (int i= 0; i < tickList->count(); i++)
            {
                tick = &(*tickList)[i];
                if (tick->major)
                {
                    if (tick->textSize > 0)
                    {
                        float x = tick->pos / m_frequencyScale.getSize();
                        q3[4*effectiveTicks] = x;
                        q3[4*effectiveTicks+1] = 0;
                        q3[4*effectiveTicks+2] = x;
                        q3[4*effectiveTicks+3] = 1;
                        effectiveTicks++;
                    }
                }
            }
            QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
            m_glShaderSimple.drawSegments(m_glWaterfallBoxMatrix, color, q3, 2*effectiveTicks);
        }
    }
    // paint 3D spectrogram grid - this is drawn on top of signal, so that appears slightly transparent
    // x-axis is freq, y time and z power
    if (m_displayGrid && m_display3DSpectrogram)
    {
        const ScaleEngine::TickList* tickList;
        const ScaleEngine::Tick* tick;
        glFunctions->glViewport(0, m_3DSpectrogramBottom*devicePixelRatio, width()*devicePixelRatio, m_waterfallHeight*devicePixelRatio);
        tickList = &m_powerScale.getTickList();
        {
            GLfloat *q3 = m_q3TickPower.m_array;
            int effectiveTicks = 0;
            for (int i= 0; i < tickList->count(); i++)
            {
                tick = &(*tickList)[i];
                if (tick->major)
                {
                    if (tick->textSize > 0)
                    {
                        float y = tick->pos / m_powerScale.getSize();
                        q3[6*effectiveTicks] = 0.0;
                        q3[6*effectiveTicks+1] = 1.0;
                        q3[6*effectiveTicks+2] = y;
                        q3[6*effectiveTicks+3] = 1.0;
                        q3[6*effectiveTicks+4] = 1.0;
                        q3[6*effectiveTicks+5] = y;
                        effectiveTicks++;
                    }
                }
            }
            QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
            m_glShaderSimple.drawSegments(spectrogramGridMatrix, color, q3, 2*effectiveTicks, 3);
        }
        tickList = &m_timeScale.getTickList();
        {
            GLfloat *q3 = m_q3TickTime.m_array;
            int effectiveTicks = 0;
            for (int i= 0; i < tickList->count(); i++)
            {
                tick = &(*tickList)[i];
                if (tick->major)
                {
                    if (tick->textSize > 0)
                    {
                        float y = tick->pos / m_timeScale.getSize();
                        q3[4*effectiveTicks] = 0.0;
                        q3[4*effectiveTicks+1] = 1.0 - y;
                        q3[4*effectiveTicks+2] = 1.0;
                        q3[4*effectiveTicks+3] = 1.0 - y;
                        effectiveTicks++;
                    }
                }
            }
            QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
            m_glShaderSimple.drawSegments(spectrogramGridMatrix, color, q3, 2*effectiveTicks);
        }
        tickList = &m_frequencyScale.getTickList();
        {
            GLfloat *q3 = m_q3TickFrequency.m_array;
            int effectiveTicks = 0;
            for (int i= 0; i < tickList->count(); i++)
            {
                tick = &(*tickList)[i];
                if (tick->major)
                {
                    if (tick->textSize > 0)
                    {
                        float x = tick->pos / m_frequencyScale.getSize();
                        q3[4*effectiveTicks] = x;
                        q3[4*effectiveTicks+1] = -0.0;
                        q3[4*effectiveTicks+2] = x;
                        q3[4*effectiveTicks+3] = 1.0;
                        effectiveTicks++;
                    }
                }
            }
            QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
            m_glShaderSimple.drawSegments(spectrogramGridMatrix, color, q3, 2*effectiveTicks);
        }
        {
            GLfloat *q3 = m_q3TickFrequency.m_array;
            int effectiveTicks = 0;
            for (int i= 0; i < tickList->count(); i++)
            {
                tick = &(*tickList)[i];
                if (tick->major)
                {
                    if (tick->textSize > 0)
                    {
                        float x = tick->pos / m_frequencyScale.getSize();
                        q3[6*effectiveTicks] = x;
                        q3[6*effectiveTicks+1] = 1.0;
                        q3[6*effectiveTicks+2] = 0.0;
                        q3[6*effectiveTicks+3] = x;
                        q3[6*effectiveTicks+4] = 1.0;
                        q3[6*effectiveTicks+5] = 1.0;
                        effectiveTicks++;
                    }
                }
            }
            QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
            m_glShaderSimple.drawSegments(spectrogramGridMatrix, color, q3, 2*effectiveTicks, 3);
        }
        glFunctions->glViewport(0, 0, width()*devicePixelRatio, height()*devicePixelRatio);
    }
    // paint histogram grid
    if ((m_displayHistogram || m_displayMaxHold || m_displayCurrent) && (m_displayGrid))
    {
        const ScaleEngine::TickList* tickList;
        const ScaleEngine::Tick* tick;
        tickList = &m_powerScale.getTickList();
        {
            GLfloat *q3 = m_q3TickPower.m_array;
            int effectiveTicks = 0;
            for (int i= 0; i < tickList->count(); i++)
            {
                tick = &(*tickList)[i];
                if (tick->major)
                {
                    if (tick->textSize > 0)
                    {
                        float y = tick->pos / m_powerScale.getSize();
                        q3[4*effectiveTicks] = 0;
                        q3[4*effectiveTicks+1] = 1-y;
                        q3[4*effectiveTicks+2] = 1;
                        q3[4*effectiveTicks+3] = 1-y;
                        effectiveTicks++;
                    }
                }
            }
            QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
            m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, color, q3, 2*effectiveTicks);
        }
        tickList = &m_frequencyScale.getTickList();
        {
            GLfloat *q3 = m_q3TickFrequency.m_array;
            int effectiveTicks = 0;
            for (int i= 0; i < tickList->count(); i++)
            {
                tick = &(*tickList)[i];
                if (tick->major)
                {
                    if (tick->textSize > 0)
                    {
                        float x = tick->pos / m_frequencyScale.getSize();
                        q3[4*effectiveTicks] = x;
                        q3[4*effectiveTicks+1] = 0;
                        q3[4*effectiveTicks+2] = x;
                        q3[4*effectiveTicks+3] = 1;
                        effectiveTicks++;
                    }
                }
            }
            QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
            m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, color, q3, 2*effectiveTicks);
        }
    }
    // paint rect around histogram (do last, so on top of filled spectrum)
    if (m_displayHistogram || m_displayMaxHold || m_displayCurrent)
    {
        {
            GLfloat q3[] {
                1, 1,
                0, 1,
                0, 0,
                1, 0
            };
            QVector4D color(1.0f, 1.0f, 1.0f, 0.5f);
            m_glShaderSimple.drawContour(m_glHistogramBoxMatrix, color, q3, 4);
        }
    }
    // Paint info line
    {
        GLfloat vtx1[] = {
                0, 1,
                1, 1,
                1, 0,
                0, 0
        };
        GLfloat tex1[] = {
                0, 1,
                1, 1,
                1, 0,
                0, 0
        };
        m_glShaderInfo.drawSurface(m_glInfoBoxMatrix, tex1, vtx1, 4);
    }
    if (m_currentSpectrum)
    {
        switch (m_measurement)
        {
        case SpectrumSettings::MeasurementPeaks:
            measurePeaks();
            break;
        case SpectrumSettings::MeasurementChannelPower:
            measureChannelPower();
            break;
        case SpectrumSettings::MeasurementAdjacentChannelPower:
            measureAdjacentChannelPower();
            break;
        case SpectrumSettings::MeasurementOccupiedBandwidth:
            measureOccupiedBandwidth();
            break;
        case SpectrumSettings::Measurement3dBBandwidth:
            measure3dBBandwidth();
            break;
        case SpectrumSettings::MeasurementSNR:
            measureSNR();
            measureSFDR();
            break;
        default:
            break;
        }
    }
    m_mutex.unlock();
#ifdef ENABLE_PROFILER
    if (m_profileName.isEmpty())
    {
        // Try to use the window name for the profile name
        QString windowTitle;
        for (QWidget *widget = parentWidget(); widget != nullptr; widget = widget->parentWidget())
        {
            windowTitle = widget->windowTitle();
            if (!windowTitle.isEmpty()) {
                break;
            }
        }
        // Add this address so we get per-spectrum profile data
        if (windowTitle.isEmpty()) {
            m_profileName = QString("Spectrum @%1").arg((quint64)this, 0, 16);
        } else {
            m_profileName = QString("%1 @%2").arg(windowTitle).arg((quint64)this, 0, 16);
        }
    }
#endif
    PROFILER_STOP(m_profileName)
} // paintGL
// Hightlight power band for SFDR
void GLSpectrumView::drawPowerBandMarkers(float max, float min, const QVector4D &color)
{
    float p1 = (m_powerScale.getRangeMax() - min) / m_powerScale.getRange();
    float p2 = (m_powerScale.getRangeMax() - max) / m_powerScale.getRange();
    GLfloat q3[] {
        1, p2,
        0, p2,
        0, p1,
        1, p1,
        0, p1,
        0, p2
    };
    m_glShaderSimple.drawSurface(m_glHistogramBoxMatrix, color, q3, 4);
}
// Hightlight bandwidth being measured
void GLSpectrumView::drawBandwidthMarkers(int64_t centerFrequency, int bandwidth, const QVector4D &color)
{
    float f1 = (centerFrequency - bandwidth / 2);
    float f2 = (centerFrequency + bandwidth / 2);
    float x1 = (f1 - m_frequencyScale.getRangeMin()) / m_frequencyScale.getRange();
    float x2 = (f2 - m_frequencyScale.getRangeMin()) / m_frequencyScale.getRange();
    GLfloat q3[] {
        x2, 1,
        x1, 1,
        x1, 0,
        x2, 0,
        x1, 0,
        x1, 1
    };
    m_glShaderSimple.drawSurface(m_glHistogramBoxMatrix, color, q3, 4);
}
// Hightlight peak being measured. Note that the peak isn't always at the center
void GLSpectrumView::drawPeakMarkers(int64_t startFrequency, int64_t endFrequency, const QVector4D &color)
{
    float x1 = (startFrequency - m_frequencyScale.getRangeMin()) / m_frequencyScale.getRange();
    float x2 = (endFrequency - m_frequencyScale.getRangeMin()) / m_frequencyScale.getRange();
    GLfloat q3[] {
        x2, 1,
        x1, 1,
        x1, 0,
        x2, 0,
        x1, 0,
        x1, 1
    };
    m_glShaderSimple.drawSurface(m_glHistogramBoxMatrix, color, q3, 4);
}
void GLSpectrumView::drawSpectrumMarkers()
{
    if (!m_currentSpectrum) {
        return;
    }
    QVector4D lineColor(1.0f, 1.0f, 1.0f, 0.3f);
    // paint histogram markers
    if (m_histogramMarkers.size() > 0)
    {
        for (int i = 0; i < m_histogramMarkers.size(); i++)
        {
            if (!m_histogramMarkers.at(i).m_show) {
                continue;
            }
            QPointF ypoint = m_histogramMarkers.at(i).m_point;
            QString powerStr = m_histogramMarkers.at(i).m_powerStr;
            if (m_histogramMarkers.at(i).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePower)
            {
                float power = m_linear ?
                    m_currentSpectrum[m_histogramMarkers.at(i).m_fftBin] * (m_useCalibration ? m_calibrationGain : 1.0f):
                    m_currentSpectrum[m_histogramMarkers.at(i).m_fftBin] + (m_useCalibration ? m_calibrationShiftdB : 0.0f);
                ypoint.ry() =
                    (m_powerScale.getRangeMax() - power) / m_powerScale.getRange();
                ypoint.ry() = ypoint.ry() < 0 ?
                    0 :
                    ypoint.ry() > 1 ? 1 : ypoint.ry();
                powerStr = displayPower(
                    power,
                    m_linear ? 'e' : 'f',
                    m_linear ? 3 : 1
                );
            }
            else if (m_histogramMarkers.at(i).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePowerMax)
            {
                float power = m_currentSpectrum[m_histogramMarkers.at(i).m_fftBin];
                if ((m_histogramMarkers.at(i).m_holdReset) || (power > m_histogramMarkers[i].m_powerMax))
                {
                    m_histogramMarkers[i].m_powerMax = power;
                    m_histogramMarkers[i].m_holdReset = false;
                }
                float powerMax = m_linear ?
                    m_histogramMarkers[i].m_powerMax * (m_useCalibration ? m_calibrationGain : 1.0f) :
                    m_histogramMarkers[i].m_powerMax + (m_useCalibration ? m_calibrationShiftdB : 0.0f);
                ypoint.ry() =
                    (m_powerScale.getRangeMax() - powerMax) / m_powerScale.getRange();
                ypoint.ry() = ypoint.ry() < 0 ?
                    0 : ypoint.ry() > 1 ?
                        1 : ypoint.ry();
                powerStr = displayPower(
                    powerMax,
                    m_linear ? 'e' : 'f',
                    m_linear ? 3 : 1
                );
            }
            // crosshairs
            GLfloat h[] {
                (float) m_histogramMarkers.at(i).m_point.x(), 0,
                (float) m_histogramMarkers.at(i).m_point.x(), 1
            };
            m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, lineColor, h, 2);
            GLfloat v[] {
                0, (float) ypoint.y(),
                1, (float) ypoint.y()
            };
            m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, lineColor, v, 2);
            QColor textColor = m_histogramMarkers.at(i).m_markerColor;
            // text
            if (i == 0)
            {
                drawTextOverlay(
                    m_histogramMarkers.at(i).m_frequencyStr,
                    textColor,
                    m_textOverlayFont,
                    m_histogramMarkers.at(i).m_point.x() * m_histogramRect.width(),
                    (m_invertedWaterfall || (m_waterfallHeight == 0)) ? m_histogramRect.height() : 0,
                    m_histogramMarkers.at(i).m_point.x() < 0.5f,
                    !m_invertedWaterfall && (m_waterfallHeight != 0),
                    m_histogramRect);
                drawTextOverlay(
                    powerStr,
                    textColor,
                    m_textOverlayFont,
                    0,
                    ypoint.y() * m_histogramRect.height(),
                    true,
                    ypoint.y() < 0.5f,
                    m_histogramRect);
            }
            else
            {
                textColor.setAlpha(192);
                float power0, poweri;
                if (m_histogramMarkers.at(0).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePower) {
                    power0 = m_currentSpectrum[m_histogramMarkers.at(0).m_fftBin];
                } else if (m_histogramMarkers.at(0).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePowerMax) {
                    power0 = m_histogramMarkers.at(0).m_powerMax;
                } else {
                    power0 = m_linear ? m_histogramMarkers.at(0).m_power : CalcDb::dbPower(m_histogramMarkers.at(0).m_power);
                }
                if (m_histogramMarkers.at(i).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePower) {
                    poweri = m_currentSpectrum[m_histogramMarkers.at(i).m_fftBin];
                } else if (m_histogramMarkers.at(i).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePowerMax) {
                    poweri = m_histogramMarkers.at(i).m_powerMax;
                } else {
                    poweri = m_linear ? m_histogramMarkers.at(i).m_power : CalcDb::dbPower(m_histogramMarkers.at(i).m_power);
                }
                QString deltaPowerStr;
                if (m_linear) {
                    deltaPowerStr = QString::number(poweri - power0, 'e', 3);
                } else {
                    deltaPowerStr = QString::number(poweri - power0, 'f', 1);
                }
                drawTextOverlay(
                    m_histogramMarkers.at(i).m_deltaFrequencyStr,
                    textColor,
                    m_textOverlayFont,
                    m_histogramMarkers.at(i).m_point.x() * m_histogramRect.width(),
                    (m_invertedWaterfall || (m_waterfallHeight == 0)) ? 0 : m_histogramRect.height(),
                    m_histogramMarkers.at(i).m_point.x() < 0.5f,
                    (m_invertedWaterfall || (m_waterfallHeight == 0)),
                    m_histogramRect);
                drawTextOverlay(
                    deltaPowerStr,
                    textColor,
                    m_textOverlayFont,
                    m_histogramRect.width(),
                    ypoint.y() * m_histogramRect.height(),
                    false,
                    ypoint.y() < 0.5f,
                    m_histogramRect);
            }
        }
    }
    // paint waterfall markers
    if (m_waterfallMarkers.size() > 0)
    {
        // crosshairs
        for (int i = 0; i < m_waterfallMarkers.size(); i++)
        {
            if (!m_waterfallMarkers.at(i).m_show) {
                continue;
            }
            GLfloat h[] {
                (float) m_waterfallMarkers.at(i).m_point.x(), 0,
                (float) m_waterfallMarkers.at(i).m_point.x(), 1
            };
            m_glShaderSimple.drawSegments(m_glWaterfallBoxMatrix, lineColor, h, 2);
            GLfloat v[] {
                0, (float) m_waterfallMarkers.at(i).m_point.y(),
                1, (float) m_waterfallMarkers.at(i).m_point.y()
            };
            m_glShaderSimple.drawSegments(m_glWaterfallBoxMatrix, lineColor, v, 2);
        // }
        // text
        // for (int i = 0; i < m_waterfallMarkers.size(); i++)
        // {
            QColor textColor = m_waterfallMarkers.at(i).m_markerColor;
            textColor.setAlpha(192);
            if (i == 0)
            {
                drawTextOverlay(
                    m_waterfallMarkers.at(i).m_frequencyStr,
                    textColor,
                    m_textOverlayFont,
                    m_waterfallMarkers.at(i).m_point.x() * m_waterfallRect.width(),
                    (!m_invertedWaterfall || (m_histogramHeight == 0)) ? m_waterfallRect.height() : 0,
                    m_waterfallMarkers.at(i).m_point.x() < 0.5f,
                    m_invertedWaterfall && (m_histogramHeight != 0),
                    m_waterfallRect);
                drawTextOverlay(
                    m_waterfallMarkers.at(i).m_timeStr,
                    textColor,
                    m_textOverlayFont,
                    0,
                    m_waterfallMarkers.at(i).m_point.y() * m_waterfallRect.height(),
                    true,
                    m_waterfallMarkers.at(i).m_point.y() < 0.5f,
                    m_waterfallRect);
            }
            else
            {
                drawTextOverlay(
                    m_waterfallMarkers.at(i).m_deltaFrequencyStr,
                    textColor,
                    m_textOverlayFont,
                    m_waterfallMarkers.at(i).m_point.x() * m_waterfallRect.width(),
                    (!m_invertedWaterfall || (m_histogramHeight == 0)) ? 0 : m_waterfallRect.height(),
                    m_waterfallMarkers.at(i).m_point.x() < 0.5f,
                    !m_invertedWaterfall || (m_histogramHeight == 0),
                    m_waterfallRect);
                drawTextOverlay(
                    m_waterfallMarkers.at(i).m_deltaTimeStr,
                    textColor,
                    m_textOverlayFont,
                    m_waterfallRect.width(),
                    m_waterfallMarkers.at(i).m_point.y() * m_waterfallRect.height(),
                    false,
                    m_waterfallMarkers.at(i).m_point.y() < 0.5f,
                    m_waterfallRect);
            }
        }
    }
}
void GLSpectrumView::drawAnnotationMarkers()
{
    if ((!m_currentSpectrum) || (m_visibleAnnotationMarkers.size() == 0)) {
        return;
    }
    float h = m_annotationMarkerHeight / (float) m_histogramHeight;
	float htop = 1.0f / (float) m_histogramHeight;
    for (const auto &marker : m_visibleAnnotationMarkers)
    {
		if (marker->m_show == SpectrumAnnotationMarker::Hidden) {
			continue;
		}
        QVector4D color(marker->m_markerColor.redF(), marker->m_markerColor.greenF(), marker->m_markerColor.blueF(), 0.5f);
        if (marker->m_bandwidth == 0)
        {
            GLfloat d[] {
                marker->m_startPos, htop,
                marker->m_startPos, h
            };
            m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, color, d, 2);
        }
        else
        {
            GLfloat q3[] {
                marker->m_stopPos, h,
                marker->m_startPos, h,
                marker->m_startPos, htop,
                marker->m_stopPos, htop
            };
            m_glShaderSimple.drawSurface(m_glHistogramBoxMatrix, color, q3, 4);
        }
        // Always draw a line in the top area, so we can see where bands start/stop when contiguous
        // When show is ShowFull, we draw at full height of spectrum
        bool full = marker->m_show == SpectrumAnnotationMarker::ShowFull;
        GLfloat d1[] {
            marker->m_startPos, full ? 0 : htop,
            marker->m_startPos, full ? 1 : h,
        };
        m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, color, d1, 2);
        if (marker->m_bandwidth != 0)
        {
            GLfloat d2[] {
                marker->m_stopPos, full ? 0 : htop,
                marker->m_stopPos, full ? 1 : h,
            };
            m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, color, d2, 2);
        }
        if ((marker->m_show == SpectrumAnnotationMarker::ShowFull) || (marker->m_show == SpectrumAnnotationMarker::ShowText))
        {
            float txtpos = marker->m_startPos < 0.5f ?
                marker->m_startPos :
                marker->m_stopPos;
            drawTextOverlay(
                marker->m_text,
                QColor(255, 255, 255, 192),
                m_textOverlayFont,
                txtpos * m_histogramRect.width(),
                0,
                marker->m_startPos < 0.5f,
                true,
                m_histogramRect);
        }
    }
}
// Find and display peak in info line
void GLSpectrumView::measurePeak()
{
    float power, frequency;
    findPeak(power, frequency);
    drawTextsRight(
        {"Peak: ", ""},
        {
         displayPower(power, m_linear ? 'e' : 'f', m_linear ? 3 : 1),
         displayFull(frequency)
        },
        {m_peakPowerMaxStr, m_peakFrequencyMaxStr},
        {m_peakPowerUnits, "Hz"}
    );
    if (m_measurements) {
        m_measurements->setPeak(0, frequency, power);
    }
}
// Find and display peaks
void GLSpectrumView::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
void GLSpectrumView::measureChannelPower()
{
    float power;
    power = calcChannelPower(m_centerFrequency + m_measurementCenterFrequencyOffset, m_measurementBandwidth);
    if (m_measurements) {
        m_measurements->setChannelPower(power);
    }
    if (m_measurementHighlight) {
        drawBandwidthMarkers(m_centerFrequency + m_measurementCenterFrequencyOffset, m_measurementBandwidth, m_measurementLightMarkerColor);
    }
}
// Calculate and display channel power and adjacent channel power
void GLSpectrumView::measureAdjacentChannelPower()
{
    float power, powerLeft, powerRight;
    power = calcChannelPower(m_centerFrequency + m_measurementCenterFrequencyOffset, m_measurementBandwidth);
    powerLeft = calcChannelPower(m_centerFrequency + m_measurementCenterFrequencyOffset - m_measurementChSpacing, m_measurementAdjChBandwidth);
    powerRight = calcChannelPower(m_centerFrequency + m_measurementCenterFrequencyOffset + m_measurementChSpacing, m_measurementAdjChBandwidth);
    float leftDiff = powerLeft - power;
    float rightDiff = powerRight - power;
    if (m_measurements) {
        m_measurements->setAdjacentChannelPower(powerLeft, leftDiff, power, powerRight, rightDiff);
    }
    if (m_measurementHighlight)
    {
        drawBandwidthMarkers(m_centerFrequency + m_measurementCenterFrequencyOffset, m_measurementBandwidth, m_measurementLightMarkerColor);
        drawBandwidthMarkers(m_centerFrequency + m_measurementCenterFrequencyOffset - m_measurementChSpacing, m_measurementAdjChBandwidth, m_measurementDarkMarkerColor);
        drawBandwidthMarkers(m_centerFrequency + m_measurementCenterFrequencyOffset + m_measurementChSpacing, m_measurementAdjChBandwidth, m_measurementDarkMarkerColor);
    }
}
// Measure bandwidth that has 99% of power
void GLSpectrumView::measureOccupiedBandwidth()
{
    float hzPerBin = m_sampleRate / (float) m_fftSize;
    int start = frequencyToBin(m_centerFrequency + m_measurementCenterFrequencyOffset);
    float totalPower, power = 0.0f;
    int step = 0;
    int width = 0;
    int idx = start;
    float gain = m_useCalibration ? m_calibrationGain : 1.0f;
    float shift = m_useCalibration ? m_calibrationShiftdB : 0.0f;
    totalPower = CalcDb::powerFromdB(calcChannelPower(m_centerFrequency + m_measurementCenterFrequencyOffset, m_measurementBandwidth));
    do
    {
        if ((idx >= 0) && (idx < m_nbBins))
        {
            if (m_linear) {
                power += m_currentSpectrum[idx] * gain;
            } else {
                power += CalcDb::powerFromdB(m_currentSpectrum[idx]) + shift;
            }
            width++;
        }
        step++;
        if ((step & 1) == 1) {
            idx -= step;
        } else {
            idx += step;
        }
    }
    while (((power / totalPower) < 0.99f) && (step < m_nbBins));
    float occupiedBandwidth = width * hzPerBin;
    if (m_measurements) {
        m_measurements->setOccupiedBandwidth(occupiedBandwidth);
    }
    if (m_measurementHighlight)
    {
        drawBandwidthMarkers(m_centerFrequency + m_measurementCenterFrequencyOffset, m_measurementBandwidth, m_measurementDarkMarkerColor);
        drawBandwidthMarkers(m_centerFrequency + m_measurementCenterFrequencyOffset, occupiedBandwidth, m_measurementLightMarkerColor);
    }
}
// Measure bandwidth -3dB from peak
void GLSpectrumView::measure3dBBandwidth()
{
    // Find max peak and it's power in dB
    int peakBin = findPeakBin(m_currentSpectrum);
    float peakPower = m_linear ? CalcDb::dbPower(m_currentSpectrum[peakBin]) : m_currentSpectrum[peakBin];
    // Search right until 3dB from peak
    int rightBin = peakBin;
    for (int i = peakBin + 1; i < m_nbBins; i++)
    {
        float power = m_linear ? CalcDb::dbPower(m_currentSpectrum[i]) : m_currentSpectrum[i];
        if (peakPower - power > 3.0f)
        {
            rightBin = i - 1;
            break;
        }
    }
    // Search left until 3dB from peak
    int leftBin = peakBin;
    for (int i = peakBin - 1; i >= 0; i--)
    {
        float power = m_linear ? CalcDb::dbPower(m_currentSpectrum[i]) : m_currentSpectrum[i];
        if (peakPower - power > 3.0f)
        {
            leftBin = i + 1;
            break;
        }
    }
    // Calcualte bandwidth
    int bins = rightBin - leftBin - 1;
    bins = std::max(1, bins);
    float hzPerBin = m_sampleRate / (float) m_fftSize;
    float bandwidth = bins * hzPerBin;
    int centerBin = leftBin + (rightBin - leftBin) / 2;
    float centerFrequency = binToFrequency(centerBin);
    if (m_measurements) {
        m_measurements->set3dBBandwidth(bandwidth);
    }
    if (m_measurementHighlight) {
        drawBandwidthMarkers(centerFrequency, bandwidth, m_measurementLightMarkerColor);
    }
}
const QVector4D GLSpectrumView::m_measurementLightMarkerColor = QVector4D(0.6f, 0.6f, 0.6f, 0.2f);
const QVector4D GLSpectrumView::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 GLSpectrumView::peakWidth(const Real *spectrum, int center, int &left, int &right, int maxLeft, int maxRight) const
{
    float prevLeft = spectrum[center];
    float prevRight = spectrum[center];
    left = center - 1;
    right = center + 1;
    while ((left > maxLeft) && (spectrum[left] < prevLeft) && (right < maxRight) && (spectrum[right] < prevRight))
    {
        prevLeft = spectrum[left];
        left--;
        prevRight = spectrum[right];
        right++;
    }
}
int GLSpectrumView::findPeakBin(const Real *spectrum) const
{
    int bin;
    float power;
    bin = 0;
    power = spectrum[0];
    for (int i = 1; i < m_nbBins; i++)
    {
        if (spectrum[i] > power)
        {
            power = spectrum[i];
            bin = i;
        }
    }
    return bin;
}
float GLSpectrumView::calPower(float power) const
{
    if (m_linear) {
        return power * (m_useCalibration ? m_calibrationGain : 1.0f);
    } else {
        return CalcDb::powerFromdB(power) + (m_useCalibration ? m_calibrationShiftdB : 0.0f);
    }
}
int GLSpectrumView::frequencyToBin(int64_t frequency) const
{
    float rbw = m_sampleRate / (float)m_fftSize;
    return (frequency - m_frequencyScale.getRangeMin()) / rbw;
}
int64_t GLSpectrumView::binToFrequency(int bin) const
{
    float rbw = m_sampleRate / (float)m_fftSize;
    return m_frequencyScale.getRangeMin() + bin * rbw;
}
// Find a peak and measure SNR / THD / SINAD
void GLSpectrumView::measureSNR()
{
    // Find bin with max peak - that will be our signal
    int sig = findPeakBin(m_currentSpectrum);
    int sigLeft, sigRight;
    peakWidth(m_currentSpectrum, sig, sigLeft, sigRight, 0, m_nbBins);
    int sigBins = sigRight - sigLeft - 1;
    int binsLeft = sig - sigLeft;
    int binsRight = sigRight - sig;
    // Highlight the signal
    float sigFreq = binToFrequency(sig);
    if (m_measurementHighlight) {
        drawPeakMarkers(binToFrequency(sigLeft+1), binToFrequency(sigRight-1), m_measurementLightMarkerColor);
    }
    // Find the harmonics and highlight them
    QList hBinsLeft;
    QList hBinsRight;
    QList hBinsBins;
    for (int h = 2; h < m_measurementHarmonics + 2; h++)
    {
        float hFreq = sigFreq * h;
        if (hFreq < m_frequencyScale.getRangeMax())
        {
            int hBin = frequencyToBin(hFreq);
            // Check if peak is an adjacent bin
            if (m_currentSpectrum[hBin-1] > m_currentSpectrum[hBin]) {
                hBin--;
            } else if (m_currentSpectrum[hBin+1] > m_currentSpectrum[hBin]) {
                hBin++;
            }
            hFreq = binToFrequency(hBin);
            int hLeft, hRight;
            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);
            }
            hBinsLeft.append(hLeft);
            hBinsRight.append(hRight);
            hBinsBins.append(hBins);
        }
    }
    // Integrate signal, harmonic and noise power
    float sigPower = 0.0f;
    float noisePower = 0.0f;
    float harmonicPower = 0.0f;
    QList noise;
    float gain = m_useCalibration ? m_calibrationGain : 1.0f;
    float shift = m_useCalibration ? m_calibrationShiftdB : 0.0f;
    for (int i = 0; i < m_nbBins; i++)
    {
        float power;
        if (m_linear) {
            power = m_currentSpectrum[i] * gain;
        } else {
            power = CalcDb::powerFromdB(m_currentSpectrum[i]) + shift;
        }
        // Signal power
        if ((i > sigLeft) && (i < sigRight))
        {
            sigPower += power;
            continue;
        }
        // Harmonics
        for (int h = 0; h < hBinsLeft.size(); h++)
        {
            if ((i > hBinsLeft[h]) && (i < hBinsRight[h]))
            {
                harmonicPower += power;
                continue;
            }
        }
        // Noise
        noisePower += power;
        noise.append(power);
    }
    // Calculate median of noise
    float noiseMedian = 0.0;
    if (noise.size() > 0)
    {
        auto m = noise.begin() + noise.size()/2;
        std::nth_element(noise.begin(), m, noise.end());
        noiseMedian = noise[noise.size()/2];
    }
    // Assume we have similar noise where the signal and harmonics are
    float inBandNoise = noiseMedian * sigBins;
    noisePower += inBandNoise;
    sigPower -= inBandNoise;
    for (auto hBins : hBinsBins)
    {
        float hNoise = noiseMedian * hBins;
        noisePower += hNoise;
        harmonicPower -= hNoise;
    }
    if (m_measurements)
    {
        // Calculate SNR in dB over full bandwidth
        float snr = CalcDb::dbPower(sigPower / noisePower);
        // Calculate SNR, where noise is median of noise summed over signal b/w
        float snfr = CalcDb::dbPower(sigPower / inBandNoise);
        // Calculate THD - Total harmonic distortion
        float thd = harmonicPower / sigPower;
        float thdDB = CalcDb::dbPower(thd);
        // Calculate THD+N - Total harmonic distortion plus noise
        float thdpn = CalcDb::dbPower((harmonicPower + noisePower) / sigPower);
        // Calculate SINAD - Signal to noise and distotion ratio (Should be -THD+N)
        float sinad = CalcDb::dbPower((sigPower + harmonicPower + noisePower) / (harmonicPower + noisePower));
        m_measurements->setSNR(snr, snfr, thdDB, thdpn, sinad);
    }
}
void GLSpectrumView::measureSFDR()
{
    // Find first peak which is our signal
    int peakBin = findPeakBin(m_currentSpectrum);
    int peakLeft, peakRight;
    peakWidth(m_currentSpectrum, peakBin, peakLeft, peakRight, 0, m_nbBins);
    // Find next largest peak, which is the spur
    int nextPeakBin = -1;
    float nextPeakPower = -std::numeric_limits::max();
    for (int i = 0; i < m_nbBins; i++)
    {
        if ((i < peakLeft) || (i > peakRight))
        {
            if (m_currentSpectrum[i] > nextPeakPower)
            {
                nextPeakBin = i;
                nextPeakPower = m_currentSpectrum[i];
            }
        }
    }
    if (nextPeakBin != -1)
    {
        // Calculate SFDR in dB from difference between two peaks
        float peakPower = calPower(m_currentSpectrum[peakBin]);
        float nextPeakPower = calPower(m_currentSpectrum[nextPeakBin]);
        float peakPowerDB = CalcDb::dbPower(peakPower);
        float nextPeakPowerDB = CalcDb::dbPower(nextPeakPower);
        float sfdr = peakPowerDB - nextPeakPowerDB;
        // Display
        if (m_measurements) {
            m_measurements->setSFDR(sfdr);
        }
        if (m_measurementHighlight)
        {
            if (m_linear) {
                drawPowerBandMarkers(peakPower, nextPeakPower, m_measurementDarkMarkerColor);
            } else {
                drawPowerBandMarkers(peakPowerDB, nextPeakPowerDB, m_measurementDarkMarkerColor);
            }
        }
    }
}
// Find power and frequency of max peak in current spectrum
void GLSpectrumView::findPeak(float &power, float &frequency) const
{
    int bin;
    bin = 0;
    power = m_currentSpectrum[0];
    for (int i = 1; i < m_nbBins; i++)
    {
        if (m_currentSpectrum[i] > power)
        {
            power = m_currentSpectrum[i];
            bin = i;
        }
    }
    power = m_linear ?
                power * (m_useCalibration ? m_calibrationGain : 1.0f) :
                power + (m_useCalibration ? m_calibrationShiftdB : 0.0f);
    frequency = binToFrequency(bin);
}
// Calculate channel power in dB
float GLSpectrumView::calcChannelPower(int64_t centerFrequency, int channelBandwidth) const
{
    float hzPerBin = m_sampleRate / (float) m_fftSize;
    int bins = channelBandwidth / hzPerBin;
    int start = frequencyToBin(centerFrequency) - (bins / 2);
    int end = start + bins;
    float power = 0.0;
    start = std::max(start, 0);
    end = std::min(end, m_nbBins);
    if (m_linear)
    {
        float gain = m_useCalibration ? m_calibrationGain : 1.0f;
        for (int i = start; i < end; i++) {
            power += m_currentSpectrum[i] * gain;
        }
    }
    else
    {
        float shift = m_useCalibration ? m_calibrationShiftdB : 0.0f;
        for (int i = start; i < end; i++) {
            power += CalcDb::powerFromdB(m_currentSpectrum[i]) + shift;
        }
    }
    return CalcDb::dbPower(power);
}
void GLSpectrumView::stopDrag()
{
    if (m_cursorState != CSNormal)
    {
        if ((m_cursorState == CSSplitterMoving) || (m_cursorState == CSChannelMoving)) {
            releaseMouse();
        }
        setCursor(Qt::ArrowCursor);
        m_cursorState = CSNormal;
    }
}
void GLSpectrumView::applyChanges()
{
    if (m_nbBins <= 0) {
        return;
    }
    QFontMetrics fm(font());
    int M = fm.horizontalAdvance("-");
    m_topMargin = fm.ascent() * 2.0;
    m_bottomMargin = fm.ascent() * 1.0;
    m_infoHeight = fm.height() * 3;
    int waterfallTop = 0;
    m_frequencyScaleHeight = fm.height() * 3; // +1 line for marker frequency scale
    int frequencyScaleTop = 0;
    int histogramTop = 0;
    //int m_leftMargin;
    m_rightMargin = fm.horizontalAdvance("000");
    // displays both histogram and waterfall
    if ((m_displayWaterfall || m_display3DSpectrogram) && (m_displayHistogram | m_displayMaxHold | m_displayCurrent))
    {
        m_waterfallHeight = height() * m_waterfallShare - 1;
        if (m_waterfallHeight < 0) {
            m_waterfallHeight = 0;
        }
        if (m_invertedWaterfall)
        {
            histogramTop = m_topMargin;
            m_histogramHeight = height() - m_topMargin - m_waterfallHeight - m_frequencyScaleHeight - m_bottomMargin;
            waterfallTop = histogramTop + m_histogramHeight + m_frequencyScaleHeight + 1;
            frequencyScaleTop = histogramTop + m_histogramHeight + 1;
        }
        else
        {
            waterfallTop = m_topMargin;
            frequencyScaleTop = waterfallTop + m_waterfallHeight + 1;
            histogramTop = waterfallTop + m_waterfallHeight + m_frequencyScaleHeight + 1;
            m_histogramHeight = height() - m_topMargin - m_waterfallHeight - m_frequencyScaleHeight - m_bottomMargin;
        }
        m_timeScale.setSize(m_waterfallHeight);
        if (m_sampleRate > 0)
        {
            float timeScaleDiv = ((float)m_sampleRate / (float)m_timingRate);
            if (m_fftSize > m_fftOverlap) {
                timeScaleDiv *= m_fftSize / (float)(m_fftSize - m_fftOverlap);
            }
            if (!m_invertedWaterfall) {
                m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, (m_waterfallHeight * m_fftSize) / timeScaleDiv, 0);
            } else {
                m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, 0, (m_waterfallHeight * m_fftSize) / timeScaleDiv);
            }
        }
        else
        {
            m_timeScale.setRange(Unit::Time, 0, 1);
        }
        m_leftMargin = m_timeScale.getScaleWidth();
        setPowerScale(m_histogramHeight);
        m_leftMargin += 2 * M;
        setFrequencyScale();
        m_glWaterfallBoxMatrix.setToIdentity();
        m_glWaterfallBoxMatrix.translate(
            -1.0f + ((float)(2*m_leftMargin)   / (float) width()),
             1.0f - ((float)(2*waterfallTop) / (float) height())
        );
        m_glWaterfallBoxMatrix.scale(
            ((float) 2 * (width() - m_leftMargin - m_rightMargin)) / (float) width(),
            (float) (-2*m_waterfallHeight) / (float) height()
        );
        m_glHistogramBoxMatrix.setToIdentity();
        m_glHistogramBoxMatrix.translate(
            -1.0f + ((float)(2*m_leftMargin)   / (float) width()),
             1.0f - ((float)(2*histogramTop) / (float) height())
        );
        m_glHistogramBoxMatrix.scale(
            ((float) 2 * (width() - m_leftMargin - m_rightMargin)) / (float) width(),
            (float) (-2*m_histogramHeight) / (float) height()
        );
        m_glHistogramSpectrumMatrix.setToIdentity();
        m_glHistogramSpectrumMatrix.translate(
            -1.0f + ((float)(2*m_leftMargin)   / (float) width()),
             1.0f - ((float)(2*histogramTop) / (float) height())
        );
        m_glHistogramSpectrumMatrix.scale(
            ((float) 2 * (width() - m_leftMargin - m_rightMargin)) / ((float) width() * (float)(m_nbBins)),
            ((float) 2*m_histogramHeight / height()) / m_powerRange
        );
        // m_frequencyScaleRect = QRect(
        // 	0,
        // 	frequencyScaleTop,
        // 	width(),
        // 	m_frequencyScaleHeight
        // );
        m_glFrequencyScaleBoxMatrix.setToIdentity();
        m_glFrequencyScaleBoxMatrix.translate (
            -1.0f,
             1.0f - ((float) 2*frequencyScaleTop / (float) height())
        );
        m_glFrequencyScaleBoxMatrix.scale (
            2.0f,
            (float) -2*m_frequencyScaleHeight / (float) height()
        );
        m_glLeftScaleBoxMatrix.setToIdentity();
        m_glLeftScaleBoxMatrix.translate(-1.0f, 1.0f);
        m_glLeftScaleBoxMatrix.scale(
            (float)(2*(m_leftMargin - 1)) / (float) width(),
            -2.0f
        );
    }
    // displays waterfall/3D spectrogram only
    else if (m_displayWaterfall || m_display3DSpectrogram)
    {
        m_histogramHeight = 0;
        histogramTop = 0;
        m_bottomMargin = m_frequencyScaleHeight;
        m_waterfallHeight = height() - m_topMargin - m_frequencyScaleHeight;
        waterfallTop = m_topMargin;
        frequencyScaleTop = m_topMargin + m_waterfallHeight + 1;
        m_timeScale.setSize(m_waterfallHeight);
        if (m_sampleRate > 0)
        {
            float timeScaleDiv = ((float)m_sampleRate / (float)m_timingRate);
            if (m_fftSize > m_fftOverlap) {
                timeScaleDiv *= m_fftSize / (float)(m_fftSize - m_fftOverlap);
            }
            if (!m_invertedWaterfall) {
                m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, (m_waterfallHeight * m_fftSize) / timeScaleDiv, 0);
            } else {
                m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, 0, (m_waterfallHeight * m_fftSize) / timeScaleDiv);
            }
        }
        else
        {
            if (!m_invertedWaterfall) {
                m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, 10, 0);
            } else {
                m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, 0, 10);
            }
        }
        m_leftMargin = m_timeScale.getScaleWidth();
        setPowerScale((height() - m_topMargin - m_bottomMargin) / 2.0);
        m_leftMargin += 2 * M;
        setFrequencyScale();
        m_glWaterfallBoxMatrix.setToIdentity();
        m_glWaterfallBoxMatrix.translate(
            -1.0f + ((float)(2*m_leftMargin)   / (float) width()),
             1.0f - ((float)(2*m_topMargin) / (float) height())
        );
        m_glWaterfallBoxMatrix.scale(
            ((float) 2 * (width() - m_leftMargin - m_rightMargin)) / (float) width(),
            (float) (-2*m_waterfallHeight) / (float) height()
        );
        // m_frequencyScaleRect = QRect(
        // 	0,
        // 	frequencyScaleTop,
        // 	width(),
        // 	m_frequencyScaleHeight
        // );
        m_glFrequencyScaleBoxMatrix.setToIdentity();
        m_glFrequencyScaleBoxMatrix.translate (
            -1.0f,
             1.0f - ((float) 2*frequencyScaleTop / (float) height())
        );
        m_glFrequencyScaleBoxMatrix.scale (
            2.0f,
            (float) -2*m_frequencyScaleHeight / (float) height()
        );
        m_glLeftScaleBoxMatrix.setToIdentity();
        m_glLeftScaleBoxMatrix.translate(-1.0f, 1.0f);
        m_glLeftScaleBoxMatrix.scale(
            (float)(2*(m_leftMargin - 1)) / (float) width(),
            -2.0f
        );
    }
    // displays histogram only
    else if (m_displayHistogram || m_displayMaxHold || m_displayCurrent)
    {
        m_bottomMargin = m_frequencyScaleHeight;
        frequencyScaleTop = height() - m_bottomMargin;
        histogramTop = m_topMargin - 1;
        m_waterfallHeight = 0;
        m_histogramHeight = height() - m_topMargin - m_frequencyScaleHeight;
        m_leftMargin = 0;
        setPowerScale(m_histogramHeight);
        m_leftMargin += 2 * M;
        setFrequencyScale();
        m_glHistogramSpectrumMatrix.setToIdentity();
        m_glHistogramSpectrumMatrix.translate(
            -1.0f + ((float)(2*m_leftMargin)   / (float) width()),
             1.0f - ((float)(2*histogramTop) / (float) height())
        );
        m_glHistogramSpectrumMatrix.scale(
            ((float) 2 * (width() - m_leftMargin - m_rightMargin)) / ((float) width() * (float)(m_nbBins)),
            ((float) 2*(height() - m_topMargin - m_frequencyScaleHeight)) / (height()*m_powerRange)
        );
        m_glHistogramBoxMatrix.setToIdentity();
        m_glHistogramBoxMatrix.translate(
            -1.0f + ((float)(2*m_leftMargin)   / (float) width()),
             1.0f - ((float)(2*histogramTop) / (float) height())
        );
        m_glHistogramBoxMatrix.scale(
            ((float) 2 * (width() - m_leftMargin - m_rightMargin)) / (float) width(),
            (float) (-2*(height() - m_topMargin - m_frequencyScaleHeight)) / (float) height()
        );
        // m_frequencyScaleRect = QRect(
        // 	0,
        // 	frequencyScaleTop,
        // 	width(),
        // 	m_frequencyScaleHeight
        // );
        m_glFrequencyScaleBoxMatrix.setToIdentity();
        m_glFrequencyScaleBoxMatrix.translate (
            -1.0f,
             1.0f - ((float) 2*frequencyScaleTop / (float) height())
        );
        m_glFrequencyScaleBoxMatrix.scale (
            2.0f,
            (float) -2*m_frequencyScaleHeight / (float) height()
        );
        m_glLeftScaleBoxMatrix.setToIdentity();
        m_glLeftScaleBoxMatrix.translate(-1.0f, 1.0f);
        m_glLeftScaleBoxMatrix.scale(
            (float)(2*(m_leftMargin - 1)) / (float) width(),
            -2.0f
        );
    }
    else
    {
        m_leftMargin = 2;
        m_waterfallHeight = 0;
    }
    m_glShaderSpectrogram.setScaleX(((width() - m_leftMargin - m_rightMargin) / (float)m_waterfallHeight));
    m_glShaderSpectrogram.setScaleZ((m_histogramHeight != 0 ? m_histogramHeight : m_waterfallHeight / 4)  / (float)(width() - m_leftMargin - m_rightMargin));
    // bounding boxes
    m_frequencyScaleRect = QRect(
        0,
        frequencyScaleTop,
        width(),
        m_frequencyScaleHeight
    );
    if ((m_invertedWaterfall) || (m_waterfallHeight == 0))
    {
        m_histogramRect = QRectF(
            (float) m_leftMargin / (float) width(),
            (float) m_topMargin / (float) height(),
            (float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
            (float) (m_histogramHeight) / (float) height()
        );
    }
    else
    {
        m_histogramRect = QRectF(
            (float) m_leftMargin / (float) width(),
            (float) (waterfallTop + m_waterfallHeight + m_frequencyScaleHeight) / (float) height(),
            (float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
            (float) m_histogramHeight / (float) height()
        );
    }
    if (!m_invertedWaterfall || (m_histogramHeight == 0))
    {
        m_waterfallRect = QRectF(
            (float) m_leftMargin / (float) width(),
            (float) m_topMargin / (float) height(),
            (float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
            (float) m_waterfallHeight / (float) height()
        );
    }
    else
    {
        m_waterfallRect = QRectF(
            (float) m_leftMargin / (float) width(),
            (float) (m_topMargin + m_histogramHeight + m_frequencyScaleHeight) / (float) height(),
            (float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
            (float) (m_waterfallHeight) / (float) height()
        );
    }
    m_glShaderSpectrogram.setAspectRatio((width() - m_leftMargin - m_rightMargin) / (float)m_waterfallHeight);
    m_3DSpectrogramBottom = m_bottomMargin;
    if (!m_invertedWaterfall) {
        m_3DSpectrogramBottom += m_histogramHeight + m_frequencyScaleHeight + 1;
    }
    // channel overlays
    int64_t centerFrequency;
    int frequencySpan;
    getFrequencyZoom(centerFrequency, frequencySpan);
    for (int i = 0; i < m_channelMarkerStates.size(); ++i)
    {
        ChannelMarkerState* dv = m_channelMarkerStates[i];
        qreal xc, pw, nw, dsbw;
        ChannelMarker::sidebands_t sidebands = dv->m_channelMarker->getSidebands();
        xc = m_centerFrequency + dv->m_channelMarker->getCenterFrequency(); // marker center frequency
        dsbw = dv->m_channelMarker->getBandwidth();
        if (sidebands == ChannelMarker::usb) {
            nw = dv->m_channelMarker->getLowCutoff();     // negative bandwidth
            int bw = dv->m_channelMarker->getBandwidth() / 2;
            pw = (qreal) bw; // positive bandwidth
        } else if (sidebands == ChannelMarker::lsb) {
            pw = dv->m_channelMarker->getLowCutoff();
            int bw = dv->m_channelMarker->getBandwidth() / 2;
            nw = (qreal) bw;
        } else if (sidebands == ChannelMarker::vusb) {
            nw = -dv->m_channelMarker->getOppositeBandwidth(); // negative bandwidth
            pw = dv->m_channelMarker->getBandwidth(); // positive bandwidth
        } else if (sidebands == ChannelMarker::vlsb) {
            pw = dv->m_channelMarker->getOppositeBandwidth(); // positive bandwidth
            nw = -dv->m_channelMarker->getBandwidth(); // negative bandwidth
        } else {
            pw = dsbw / 2;
            nw = -pw;
        }
        // draw the DSB rectangle
        QMatrix4x4 glMatrixDsb;
        glMatrixDsb.setToIdentity();
        glMatrixDsb.translate(
            -1.0f + 2.0f * ((m_leftMargin + m_frequencyScale.getPosFromValue(xc - (dsbw/2))) / (float) width()),
             1.0f
        );
        glMatrixDsb.scale(
            2.0f * (dsbw / (float) frequencySpan),
            -2.0f
        );
        dv->m_glMatrixDsbWaterfall = glMatrixDsb;
        dv->m_glMatrixDsbWaterfall.translate(
             0.0f,
             (float) waterfallTop / (float) height()
        );
        dv->m_glMatrixDsbWaterfall.scale(
            (float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
            (float) m_waterfallHeight / (float) height()
        );
        dv->m_glMatrixDsbHistogram = glMatrixDsb;
        dv->m_glMatrixDsbHistogram.translate(
             0.0f,
             (float) histogramTop / (float) height()
        );
        dv->m_glMatrixDsbHistogram.scale(
            (float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
            (float) m_histogramHeight / (float) height()
        );
        dv->m_glMatrixDsbFreqScale = glMatrixDsb;
        dv->m_glMatrixDsbFreqScale.translate(
             0.0f,
             (float) frequencyScaleTop / (float) height()
        );
        dv->m_glMatrixDsbFreqScale.scale(
            (float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
            (float) m_frequencyScaleHeight / (float) height()
        );
        // draw the effective BW rectangle
        QMatrix4x4 glMatrix;
        glMatrix.setToIdentity();
        glMatrix.translate(
            -1.0f + 2.0f * ((m_leftMargin + m_frequencyScale.getPosFromValue(xc + nw)) / (float) width()),
             1.0f
        );
        glMatrix.scale(
            2.0f * ((pw-nw) / (float) frequencySpan),
            -2.0f
        );
        dv->m_glMatrixWaterfall = glMatrix;
        dv->m_glMatrixWaterfall.translate(
             0.0f,
             (float) waterfallTop / (float) height()
        );
        dv->m_glMatrixWaterfall.scale(
            (float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
            (float) m_waterfallHeight / (float) height()
        );
        dv->m_glMatrixHistogram = glMatrix;
        dv->m_glMatrixHistogram.translate(
             0.0f,
             (float) histogramTop / (float) height()
        );
        dv->m_glMatrixHistogram.scale(
            (float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
            (float) m_histogramHeight / (float) height()
        );
        dv->m_glMatrixFreqScale = glMatrix;
        dv->m_glMatrixFreqScale.translate(
             0.0f,
             (float) frequencyScaleTop / (float) height()
        );
        dv->m_glMatrixFreqScale.scale(
            (float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
            (float) m_frequencyScaleHeight / (float) height()
        );
        /*
        dv->m_glRect.setRect(
            m_frequencyScale.getPosFromValue(m_centerFrequency + dv->m_channelMarker->getCenterFrequency() - dv->m_channelMarker->getBandwidth() / 2) / (float)(width() - m_leftMargin - m_rightMargin),
            0,
            (dv->m_channelMarker->getBandwidth() / (float)m_sampleRate),
            1);
        */
        if (m_displayHistogram || m_displayMaxHold || m_displayCurrent || m_displayWaterfall)
        {
            dv->m_rect.setRect(m_frequencyScale.getPosFromValue(xc) + m_leftMargin - 1,
            m_topMargin,
            5,
            height() - m_topMargin - m_bottomMargin);
        }
        /*
        if(m_displayHistogram || m_displayMaxHold || m_displayWaterfall) {
            dv->m_rect.setRect(m_frequencyScale.getPosFromValue(m_centerFrequency + dv->m_channelMarker->getCenterFrequency()) + m_leftMargin - 1,
            m_topMargin,
            5,
            height() - m_topMargin - m_bottomMargin);
        }
        */
    }
    // prepare left scales (time and power)
    {
        m_leftMarginPixmap = QPixmap(m_leftMargin - 1, height());
        m_leftMarginPixmap.fill(Qt::transparent);
        {
            QPainter painter(&m_leftMarginPixmap);
            painter.setPen(QColor(0xf0, 0xf0, 0xff));
            painter.setFont(font());
            const ScaleEngine::TickList* tickList;
            const ScaleEngine::Tick* tick;
            if (m_displayWaterfall) {
                tickList = &m_timeScale.getTickList();
                for (int i = 0; i < tickList->count(); i++) {
                    tick = &(*tickList)[i];
                    if (tick->major) {
                        if (tick->textSize > 0)
                            painter.drawText(QPointF(m_leftMargin - M - tick->textSize, waterfallTop + fm.ascent() + tick->textPos), tick->text);
                    }
                }
            }
            if (m_displayHistogram || m_displayMaxHold || m_displayCurrent) {
                tickList = &m_powerScale.getTickList();
                for (int i = 0; i < tickList->count(); i++) {
                    tick = &(*tickList)[i];
                    if (tick->major) {
                        if (tick->textSize > 0)
                            painter.drawText(QPointF(m_leftMargin - M - tick->textSize, histogramTop + m_histogramHeight - tick->textPos - 1), tick->text);
                    }
                }
            }
        }
        m_glShaderLeftScale.initTexture(m_leftMarginPixmap.toImage());
    }
    // prepare frequency scale
    if (m_displayWaterfall || m_display3DSpectrogram || m_displayHistogram || m_displayMaxHold || m_displayCurrent) {
        m_frequencyPixmap = QPixmap(width(), m_frequencyScaleHeight);
        m_frequencyPixmap.fill(Qt::transparent);
        {
            QPainter painter(&m_frequencyPixmap);
            painter.setPen(Qt::NoPen);
            painter.setBrush(Qt::black);
            painter.setBrush(Qt::transparent);
            painter.drawRect(m_leftMargin, 0, width() - m_leftMargin, m_frequencyScaleHeight);
            painter.setPen(QColor(0xf0, 0xf0, 0xff));
            painter.setFont(font());
            const ScaleEngine::TickList* tickList = &m_frequencyScale.getTickList();
            const ScaleEngine::Tick* tick;
            for (int i = 0; i < tickList->count(); i++) {
                tick = &(*tickList)[i];
                if (tick->major) {
                    if (tick->textSize > 0)
                        painter.drawText(QPointF(m_leftMargin + tick->textPos, fm.height() + fm.ascent() / 2 - 1), tick->text);
                }
            }
            // Frequency overlay on highlighted marker
            for (int i = 0; i < m_channelMarkerStates.size(); ++i)
            {
                ChannelMarkerState* dv = m_channelMarkerStates[i];
                if (dv->m_channelMarker->getHighlighted()
                    && (dv->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink)
                    && dv->m_channelMarker->streamIndexApplies(m_displayStreamIndex))
                {
                    qreal xc;
                    int shift;
                    //ChannelMarker::sidebands_t sidebands = dv->m_channelMarker->getSidebands();
                    xc = m_centerFrequency + dv->m_channelMarker->getCenterFrequency(); // marker center frequency
                    QString ftext;
                    switch (dv->m_channelMarker->getFrequencyScaleDisplayType())
                    {
                    case ChannelMarker::FScaleDisplay_freq:
                        ftext = QString::number((m_centerFrequency + dv->m_channelMarker->getCenterFrequency())/1e6, 'f', 6);
                        break;
                    case ChannelMarker::FScaleDisplay_title:
                        ftext = dv->m_channelMarker->getTitle();
                        break;
                    case ChannelMarker::FScaleDisplay_addressSend:
                        ftext = dv->m_channelMarker->getDisplayAddressSend();
                        break;
                    case ChannelMarker::FScaleDisplay_addressReceive:
                        ftext = dv->m_channelMarker->getDisplayAddressReceive();
                        break;
                    default:
                        ftext = QString::number((m_centerFrequency + dv->m_channelMarker->getCenterFrequency())/1e6, 'f', 6);
                        break;
                    }
                    if (dv->m_channelMarker->getCenterFrequency() < 0) { // left half of scale
                        ftext = " " + ftext;
                        shift = 0;
                    } else { // right half of scale
                        ftext = ftext + " ";
                        shift = - fm.horizontalAdvance(ftext);
                    }
                    painter.drawText(QPointF(m_leftMargin + m_frequencyScale.getPosFromValue(xc) + shift, 2*fm.height() + fm.ascent() / 2 - 1), ftext);
                }
            }
        }
        m_glShaderFrequencyScale.initTexture(m_frequencyPixmap.toImage());
    }
    // prepare left scale for spectrogram (time)
    {
        m_spectrogramTimePixmap = QPixmap(m_leftMargin - 1, fm.ascent() + m_waterfallHeight);
        m_spectrogramTimePixmap.fill(Qt::transparent);
        {
            QPainter painter(&m_spectrogramTimePixmap);
            painter.setPen(QColor(0xf0, 0xf0, 0xff));
            painter.setFont(font());
            const ScaleEngine::TickList* tickList;
            const ScaleEngine::Tick* tick;
            if (m_display3DSpectrogram) {
                tickList = &m_timeScale.getTickList();
                for (int i = 0; i < tickList->count(); i++) {
                    tick = &(*tickList)[i];
                    if (tick->major) {
                        if (tick->textSize > 0)
                            painter.drawText(QPointF(m_leftMargin - M - tick->textSize, fm.height() + tick->textPos), tick->text);
                    }
                }
            }
        }
        m_glShaderSpectrogramTimeScale.initTexture(m_spectrogramTimePixmap.toImage());
    }
    // prepare vertical scale for spectrogram (power)
    {
        int h = m_histogramHeight != 0 ? m_histogramHeight : m_waterfallHeight / 4;
        m_spectrogramPowerPixmap = QPixmap(m_leftMargin - 1, m_topMargin + h);
        m_spectrogramPowerPixmap.fill(Qt::transparent);
        {
            QPainter painter(&m_spectrogramPowerPixmap);
            painter.setPen(QColor(0xf0, 0xf0, 0xff));
            painter.setFont(font());
            const ScaleEngine::TickList* tickList;
            const ScaleEngine::Tick* tick;
            if (m_display3DSpectrogram) {
                tickList = &m_powerScale.getTickList();
                for (int i = 0; i < tickList->count(); i++) {
                    tick = &(*tickList)[i];
                    if (tick->major) {
                        if (tick->textSize > 0)
                            painter.drawText(QPointF(m_leftMargin - M - tick->textSize, m_topMargin + h - tick->textPos - 1), tick->text);
                    }
                }
            }
        }
        m_glShaderSpectrogramPowerScale.initTexture(m_spectrogramPowerPixmap.toImage());
    }
    // Top info line
    m_glInfoBoxMatrix.setToIdentity();
    m_glInfoBoxMatrix.translate (
        -1.0f,
        1.0f
    );
    m_glInfoBoxMatrix.scale (
        2.0f,
        (float) -2*m_infoHeight / (float) height()
    );
    m_infoRect = QRect(
        0,
        0,
        width(),
        m_infoHeight
    );
    QString infoText;
    formatTextInfo(infoText);
    m_infoPixmap = QPixmap(width(), m_infoHeight);
    m_infoPixmap.fill(Qt::transparent);
    {
        QPainter painter(&m_infoPixmap);
        painter.setPen(Qt::NoPen);
        painter.setBrush(Qt::black);
        painter.setBrush(Qt::transparent);
        painter.drawRect(m_leftMargin, 0, width() - m_leftMargin, m_infoHeight);
        painter.setPen(QColor(0xf0, 0xf0, 0xff));
        painter.setFont(font());
        painter.drawText(QPointF(m_leftMargin, fm.height() + fm.ascent() / 2 - 2), infoText);
    }
    m_glShaderInfo.initTexture(m_infoPixmap.toImage());
    // Peak details in top info line
    QString minFrequencyStr = displayFull(m_centerFrequency - m_sampleRate/2); // This can be wider if negative, while max is positive
    QString maxFrequencyStr = displayFull(m_centerFrequency + m_sampleRate/2);
    m_peakFrequencyMaxStr = minFrequencyStr.size() > maxFrequencyStr.size() ? minFrequencyStr : maxFrequencyStr;
    m_peakFrequencyMaxStr = m_peakFrequencyMaxStr.append("Hz");
    m_peakPowerMaxStr = m_linear ? "8.000e-10" : "-100.0";
    m_peakPowerUnits = m_linear ? "" : "dB";
    bool waterfallFFTSizeChanged = true;
    if (m_waterfallBuffer) {
        waterfallFFTSizeChanged = m_waterfallBuffer->width() != m_nbBins;
    }
    bool windowSizeChanged = m_waterfallTextureHeight != m_waterfallHeight;
    if (waterfallFFTSizeChanged || windowSizeChanged)
    {
        if (m_waterfallBuffer) {
            delete m_waterfallBuffer;
        }
        m_waterfallBuffer = new QImage(m_nbBins, m_waterfallHeight, QImage::Format_ARGB32);
        m_waterfallBuffer->fill(qRgb(0x00, 0x00, 0x00));
        if (m_waterfallHeight > 0) {
            m_glShaderWaterfall.initTexture(*m_waterfallBuffer);
        }
        m_waterfallBufferPos = 0;
        if (m_3DSpectrogramBuffer) {
            delete m_3DSpectrogramBuffer;
        }
        m_3DSpectrogramBuffer = new QImage(m_nbBins, m_waterfallHeight, QImage::Format_Grayscale8);
        m_3DSpectrogramBuffer->fill(qRgb(0x00, 0x00, 0x00));
        if (m_waterfallHeight > 0) {
            m_glShaderSpectrogram.initTexture(*m_3DSpectrogramBuffer);
        }
        m_3DSpectrogramBufferPos = 0;
        m_waterfallTextureHeight = m_waterfallHeight;
        m_waterfallTexturePos = 0;
        m_3DSpectrogramTextureHeight = m_waterfallHeight;
        m_3DSpectrogramTexturePos = 0;
    }
    m_glShaderSpectrogram.initColorMapTexture(m_colorMapName);
    m_glShaderColorMap.initColorMapTexture(m_colorMapName);
    m_colorMap = ColorMap::getColorMap(m_colorMapName);
    // Why only 240 entries in the palette?
    for (int i = 0; i <= 239; i++)
    {
        ((quint8*)&m_waterfallPalette[i])[0] = (quint8)(m_colorMap[i*3] * 255.0);
        ((quint8*)&m_waterfallPalette[i])[1] = (quint8)(m_colorMap[i*3+1] * 255.0);
        ((quint8*)&m_waterfallPalette[i])[2] = (quint8)(m_colorMap[i*3+2] * 255.0);
        ((quint8*)&m_waterfallPalette[i])[3] = 255;
    }
    bool histogramFFTSizeChanged = true;
    if (m_histogramBuffer) {
        histogramFFTSizeChanged = m_histogramBuffer->width() != m_nbBins;
    }
    if (histogramFFTSizeChanged)
    {
        if (m_histogramBuffer) {
            delete m_histogramBuffer;
        }
        m_histogramBuffer = new QImage(m_nbBins, 100, QImage::Format_RGB32);
        m_histogramBuffer->fill(qRgb(0x00, 0x00, 0x00));
        m_glShaderHistogram.initTexture(*m_histogramBuffer, QOpenGLTexture::ClampToEdge);
        if (m_histogram) {
            delete[] m_histogram;
        }
        m_histogram = new quint8[100 * m_nbBins];
        memset(m_histogram, 0x00, 100 * m_nbBins);
        m_q3FFT.allocate(2*(m_nbBins+1));
        m_q3ColorMap.allocate(4*(m_nbBins+1));
        std::fill(m_q3ColorMap.m_array, m_q3ColorMap.m_array+4*(m_nbBins+1), 0.0f);
    }
    m_q3TickTime.allocate(4*m_timeScale.getTickList().count());
    m_q3TickFrequency.allocate(4*m_frequencyScale.getTickList().count());
    m_q3TickPower.allocate(6*m_powerScale.getTickList().count());   // 6 as we need 3d points for 3D spectrogram
    updateHistogramMarkers();
    updateWaterfallMarkers();
    updateSortedAnnotationMarkers();
} // applyChanges
void GLSpectrumView::updateHistogramMarkers()
{
    int64_t centerFrequency;
    int frequencySpan;
    getFrequencyZoom(centerFrequency, frequencySpan);
    int effFftSize = m_fftSize * ((float) frequencySpan / (float) m_sampleRate);
    for (int i = 0; i < m_histogramMarkers.size(); i++)
    {
        float powerI = m_linear ?
            m_histogramMarkers.at(i).m_power * (m_useCalibration ? m_calibrationGain : 1.0f) :
            CalcDb::dbPower(m_histogramMarkers.at(i).m_power) + (m_useCalibration ? m_calibrationShiftdB : 0.0f);
        m_histogramMarkers[i].m_point.rx() =
            (m_histogramMarkers[i].m_frequency - m_frequencyScale.getRangeMin()) / m_frequencyScale.getRange();
        m_histogramMarkers[i].m_point.ry() =
            (m_powerScale.getRangeMax() - powerI) / m_powerScale.getRange();
        // m_histogramMarkers[i].m_fftBin =
        //     (((m_histogramMarkers[i].m_frequency - m_centerFrequency) / (float) m_sampleRate) + 0.5) * m_fftSize;
        m_histogramMarkers[i].m_fftBin =
            (((m_histogramMarkers[i].m_frequency - centerFrequency) / (float) frequencySpan) + 0.5) * effFftSize;
        m_histogramMarkers[i].m_point.rx() = m_histogramMarkers[i].m_point.rx() < 0 ?
            0 : m_histogramMarkers[i].m_point.rx() > 1 ?
                1 : m_histogramMarkers[i].m_point.rx();
        m_histogramMarkers[i].m_point.ry() = m_histogramMarkers[i].m_point.ry() < 0 ?
            0 : m_histogramMarkers[i].m_point.ry() > 1 ?
                1 : m_histogramMarkers[i].m_point.ry();
        m_histogramMarkers[i].m_fftBin = m_histogramMarkers[i].m_fftBin < 0 ?
            0 : m_histogramMarkers[i].m_fftBin > m_fftSize - 1 ?
                m_fftSize - 1 : m_histogramMarkers[i].m_fftBin;
        m_histogramMarkers[i].m_frequencyStr = displayScaled(
            m_histogramMarkers[i].m_frequency,
            'f',
            getPrecision((m_centerFrequency*1000)/m_sampleRate),
            false);
        m_histogramMarkers[i].m_powerStr = displayPower(
            powerI,
            m_linear ? 'e' : 'f',
            m_linear ? 3 : 1);
        if (i > 0)
        {
            int64_t deltaFrequency = m_histogramMarkers.at(i).m_frequency - m_histogramMarkers.at(0).m_frequency;
            m_histogramMarkers[i].m_deltaFrequencyStr = displayScaled(
                deltaFrequency,
                'f',
                getPrecision(deltaFrequency/m_sampleRate),
                true);
            float power0 = m_linear ?
                m_histogramMarkers.at(0).m_power * (m_useCalibration ? m_calibrationGain : 1.0f) :
                CalcDb::dbPower(m_histogramMarkers.at(0).m_power) + (m_useCalibration ? m_calibrationShiftdB : 0.0f);
            m_histogramMarkers[i].m_deltaPowerStr = displayPower(
                powerI - power0,
                m_linear ? 'e' : 'f',
                m_linear ? 3 : 1);
        }
    }
}
void GLSpectrumView::updateHistogramPeaks()
{
    int j = 0;
    for (int i = 0; i < m_histogramMarkers.size(); i++)
    {
        if (j >= (int) m_peakFinder.getPeaks().size()) {
            break;
        }
        int fftBin = m_peakFinder.getPeaks()[j].second;
        Real power = m_peakFinder.getPeaks()[j].first;
        // qDebug("GLSpectrumView::updateHistogramPeaks: %d %d %f", j, fftBin, power);
        if ((m_histogramMarkers.at(i).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePower) ||
            ((m_histogramMarkers.at(i).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePowerMax) &&
             (m_histogramMarkers.at(i).m_holdReset || (power > m_histogramMarkers.at(i).m_powerMax))))
        {
            float binSize = m_frequencyScale.getRange() / m_nbBins;
            m_histogramMarkers[i].m_fftBin = fftBin;
            m_histogramMarkers[i].m_frequency = m_frequencyScale.getRangeMin() + binSize*fftBin;
            m_histogramMarkers[i].m_point.rx() = binSize*fftBin / m_frequencyScale.getRange();
            if (i == 0)
            {
                m_histogramMarkers[i].m_frequencyStr = displayScaled(
                    m_histogramMarkers[i].m_frequency,
                    'f',
                    getPrecision((m_centerFrequency*1000)/m_sampleRate),
                    false
                );
            }
            else
            {
                int64_t deltaFrequency = m_histogramMarkers.at(i).m_frequency - m_histogramMarkers.at(0).m_frequency;
                m_histogramMarkers[i].m_deltaFrequencyStr = displayScaled(
                    deltaFrequency,
                    'f',
                    getPrecision(deltaFrequency/m_sampleRate),
                    true
                );
            }
        }
        else
        {
            continue;
        }
        j++;
    }
}
void GLSpectrumView::updateWaterfallMarkers()
{
    for (int i = 0; i < m_waterfallMarkers.size(); i++)
    {
        m_waterfallMarkers[i].m_point.rx() =
            (m_waterfallMarkers[i].m_frequency - m_frequencyScale.getRangeMin()) / m_frequencyScale.getRange();
        m_waterfallMarkers[i].m_point.ry() =
            (m_waterfallMarkers[i].m_time - m_timeScale.getRangeMin()) / m_timeScale.getRange();
        m_waterfallMarkers[i].m_point.rx() = m_waterfallMarkers[i].m_point.rx() < 0 ?
            0 : m_waterfallMarkers[i].m_point.rx() > 1 ?
                1 : m_waterfallMarkers[i].m_point.rx();
        m_waterfallMarkers[i].m_point.ry() = m_waterfallMarkers[i].m_point.ry() < 0 ?
            0 : m_waterfallMarkers[i].m_point.ry() > 1 ?
                1 : m_waterfallMarkers[i].m_point.ry();
        m_waterfallMarkers[i].m_frequencyStr = displayScaled(
            m_waterfallMarkers[i].m_frequency,
            'f',
            getPrecision((m_centerFrequency*1000)/m_sampleRate),
            false);
        m_waterfallMarkers[i].m_timeStr = displayScaledF(
            m_waterfallMarkers[i].m_time,
            'f',
            3,
            true);
        if (i > 0)
        {
            int64_t deltaFrequency = m_waterfallMarkers.at(i).m_frequency - m_waterfallMarkers.at(0).m_frequency;
            m_waterfallMarkers.back().m_deltaFrequencyStr = displayScaled(
                deltaFrequency,
                'f',
                getPrecision(deltaFrequency/m_sampleRate),
                true);
            m_waterfallMarkers.back().m_deltaTimeStr = displayScaledF(
                m_waterfallMarkers.at(i).m_time - m_waterfallMarkers.at(0).m_time,
                'f',
                3,
                true);
        }
    }
}
void GLSpectrumView::updateAnnotationMarkers()
{
    emit updateAnnotations(); // Notify other plugins we have updated annotations
    if (!(m_markersDisplay & SpectrumSettings::MarkersDisplayAnnotations)) {
        return;
    }
    m_sortedAnnotationMarkers.clear();
    for (auto &marker : m_annotationMarkers) {
        m_sortedAnnotationMarkers.push_back(&marker);
    }
    std::sort(m_sortedAnnotationMarkers.begin(), m_sortedAnnotationMarkers.end(), annotationDisplayLessThan);
    updateSortedAnnotationMarkers();
}
void GLSpectrumView::updateSortedAnnotationMarkers()
{
    if (!(m_markersDisplay & SpectrumSettings::MarkersDisplayAnnotations)) {
        return;
    }
    m_visibleAnnotationMarkers.clear();
    for (auto &marker : m_sortedAnnotationMarkers)
    {
        float startPos = (marker->m_startFrequency - m_frequencyScale.getRangeMin()) / m_frequencyScale.getRange();
        float stopPos = ((marker->m_startFrequency + marker->m_bandwidth) - m_frequencyScale.getRangeMin()) / m_frequencyScale.getRange();
        if ((startPos > 1.0f) || (stopPos < 0.0f)) // out of range
		{
            continue;
        }
        m_visibleAnnotationMarkers.push_back(marker);
        m_visibleAnnotationMarkers.back()->m_startPos = startPos < 0.0f ? 0.0f : startPos;
        m_visibleAnnotationMarkers.back()->m_stopPos = stopPos > 1.0f ? 1.0f : stopPos;
    }
}
void GLSpectrumView::updateMarkersDisplay()
{
    if (m_markersDisplay & SpectrumSettings::MarkersDisplayAnnotations) {
        updateAnnotationMarkers();
    }
}
void GLSpectrumView::updateCalibrationPoints()
{
    if (m_calibrationPoints.size() == 0)
    {
        m_calibrationGain = 1.0;
        m_calibrationShiftdB = 0.0;
    }
    else if (m_calibrationPoints.size() == 1)
    {
        m_calibrationGain = m_calibrationPoints.first().m_powerCalibratedReference /
             m_calibrationPoints.first().m_powerRelativeReference;
        m_calibrationShiftdB = CalcDb::dbPower(m_calibrationGain);
    }
    else
    {
        QList sortedCalibrationPoints = m_calibrationPoints;
        std::sort(sortedCalibrationPoints.begin(), sortedCalibrationPoints.end(), calibrationPointsLessThan);
        if (m_centerFrequency <= sortedCalibrationPoints.first().m_frequency)
        {
            m_calibrationGain = m_calibrationPoints.first().m_powerCalibratedReference /
                m_calibrationPoints.first().m_powerRelativeReference;
            m_calibrationShiftdB = CalcDb::dbPower(m_calibrationGain);
        }
        else if (m_centerFrequency >= sortedCalibrationPoints.last().m_frequency)
        {
            m_calibrationGain = m_calibrationPoints.last().m_powerCalibratedReference /
                m_calibrationPoints.last().m_powerRelativeReference;
            m_calibrationShiftdB = CalcDb::dbPower(m_calibrationGain);
        }
        else
        {
            int lowIndex = 0;
            int highIndex = sortedCalibrationPoints.size() - 1;
            for (int index = 0; index < sortedCalibrationPoints.size(); index++)
            {
                if (m_centerFrequency < sortedCalibrationPoints[index].m_frequency)
                {
                    highIndex = index;
                    break;
                }
                else
                {
                    lowIndex = index;
                }
            }
            // frequency interpolation is always linear
            double deltaFrequency = sortedCalibrationPoints[highIndex].m_frequency -
                sortedCalibrationPoints[lowIndex].m_frequency;
            double shiftFrequency = m_centerFrequency - sortedCalibrationPoints[lowIndex].m_frequency;
            double interpolationRatio = shiftFrequency / deltaFrequency;
            // calculate low and high gains in linear mode
            double gainLow = sortedCalibrationPoints[lowIndex].m_powerCalibratedReference /
                sortedCalibrationPoints[lowIndex].m_powerRelativeReference;
            double gainHigh = sortedCalibrationPoints[highIndex].m_powerCalibratedReference /
                sortedCalibrationPoints[highIndex].m_powerRelativeReference;
            // power interpolation depends on interpolation options
            if (m_calibrationInterpMode == SpectrumSettings::CalibInterpLinear)
            {
                m_calibrationGain = gainLow + interpolationRatio*(gainHigh - gainLow); // linear driven
                m_calibrationShiftdB = CalcDb::dbPower(m_calibrationGain);
            }
            else if (m_calibrationInterpMode == SpectrumSettings::CalibInterpLog)
            {
                m_calibrationShiftdB = CalcDb::dbPower(gainLow)
                    + interpolationRatio*(CalcDb::dbPower(gainHigh) - CalcDb::dbPower(gainLow)); // log driven
                m_calibrationGain = CalcDb::powerFromdB(m_calibrationShiftdB);
            }
        }
    }
    updateHistogramMarkers();
    if (m_messageQueueToGUI && m_useCalibration) {
        m_messageQueueToGUI->push(new MsgReportCalibrationShift(m_calibrationShiftdB));
    }
    m_changesPending = true;
}
bool GLSpectrumView::event(QEvent* event)
{
    if (event->type() == QEvent::Gesture)
    {
        QGestureEvent *gestureEvent = static_cast(event);
        if (QPanGesture *pan = static_cast(gestureEvent->gesture(Qt::PanGesture)))
        {
            if (pan->state() == Qt::GestureStarted)
            {
                m_scrollStartCenterFreq = m_centerFrequency;
            }
            else if (pan->state() == Qt::GestureUpdated)
            {
                QPointF offset = pan->offset();
                float histogramWidth = width() - m_leftMargin - m_rightMargin;
                qint64 frequency = (qint64)(m_scrollStartCenterFreq + -offset.x()/histogramWidth * m_frequencyScale.getRange());
                queueRequestCenterFrequency(frequency);
            }
            return true;
        }
        else if (QPinchGesture *pinch = static_cast(gestureEvent->gesture(Qt::PinchGesture)))
        {
            // Don't get GestureStarted and startCenterPoint is always 0,0
            // https://bugreports.qt.io/browse/QTBUG-109205
            if (!m_pinching)
            {
                m_scrollStartCenterFreq = m_centerFrequency;
                m_pinchStart = pinch->centerPoint();
                m_pinching = true;
                m_pinching3D = m_display3DSpectrogram && pointInWaterfallOrSpectrogram(mapFromGlobal(m_pinchStart.toPoint()));
            }
            else
            {
                if (pinch->changeFlags() & QPinchGesture::CenterPointChanged)
                {
                    if (!m_pinching3D)
                    {
                        // Scroll frequency up or down
                        QPointF offset = pinch->centerPoint() - m_pinchStart;
                        float histogramWidth = width() - m_leftMargin - m_rightMargin;
                        qint64 frequency = (qint64)(m_scrollStartCenterFreq + -offset.x()/histogramWidth * m_frequencyScale.getRange());
                        queueRequestCenterFrequency(frequency);
                    }
                }
                if (pinch->changeFlags() & QPinchGesture::ScaleFactorChanged)
                {
                    if (!m_pinching3D)
                    {
                        // Zoom in/out of spectrum
                        QPoint p = mapFromGlobal(pinch->centerPoint().toPoint());
                        zoomFactor(p, pinch->scaleFactor());
                    }
                    else
                    {
                        // Scale Z axis of 3D spectragram
                        m_glShaderSpectrogram.userScaleZ(pinch->scaleFactor());
                    }
                }
                if (pinch->state() == Qt::GestureFinished)
                {
                    m_pinching = false;
                    m_pinching3D = false;
                }
            }
            return true;
        }
    }
    return QOpenGLWidget::event(event);
}
void GLSpectrumView::mouseMoveEvent(QMouseEvent* event)
{
    if (m_rotate3DSpectrogram && !m_pinching3D)
    {
        // Rotate 3D Spectrogram
        QPointF delta = m_mousePrevLocalPos - event->localPos();
        m_mousePrevLocalPos = event->localPos();
        m_glShaderSpectrogram.rotateZ(-delta.x()/2.0f);
        m_glShaderSpectrogram.rotateX(-delta.y()/2.0f);
        repaint(); // Force repaint in case acquisition is stopped
        return;
    }
    if (m_pan3DSpectrogram)
    {
        // Pan 3D Spectrogram
        QPointF delta = m_mousePrevLocalPos - event->localPos();
        m_mousePrevLocalPos = event->localPos();
        m_glShaderSpectrogram.translateX(-delta.x()/2.0f/500.0f);
        m_glShaderSpectrogram.translateY(delta.y()/2.0f/500.0f);
        repaint(); // Force repaint in case acquisition is stopped
        return;
    }
    if (m_scaleZ3DSpectrogram)
    {
        // Scale 3D Spectrogram in Z dimension
        QPointF delta = m_mousePrevLocalPos - event->localPos();
        m_mousePrevLocalPos = event->localPos();
        m_glShaderSpectrogram.userScaleZ(1.0+(float)delta.y()/20.0);
        repaint(); // Force repaint in case acquisition is stopped
        return;
    }
    if (m_scrollFrequency)
    {
        // Request containing widget to adjust center frequency
        // Not all containers will support this - mainly for MainSpectrumGUI
        // This can be a little slow on some SDRs, so we use delta from where
        // button was originally pressed rather than do it incrementally
        QPointF delta = m_mousePrevLocalPos - event->localPos();
        float histogramWidth = width() - m_leftMargin - m_rightMargin;
        qint64 frequency = (qint64)(m_scrollStartCenterFreq + delta.x()/histogramWidth * m_frequencyScale.getRange());
        queueRequestCenterFrequency(frequency);
        return;
    }
    if (m_displayWaterfall || m_displayHistogram || m_displayMaxHold || m_displayCurrent)
    {
        if (m_frequencyScaleRect.contains(event->pos()))
        {
            if (m_cursorState == CSNormal)
            {
                setCursor(Qt::SizeVerCursor);
                m_cursorState = CSSplitter;
                return;
            }
        }
        else
        {
            if (m_cursorState == CSSplitter)
            {
                setCursor(Qt::ArrowCursor);
                m_cursorState = CSNormal;
                return;
            }
        }
    }
    if (m_cursorState == CSSplitterMoving)
    {
        QMutexLocker mutexLocker(&m_mutex);
        float newShare;
        if (!m_invertedWaterfall) {
            newShare = (float) (event->y() - m_frequencyScaleRect.height()) / (float) height();
        } else {
            newShare = 1.0 - (float) (event->y() + m_frequencyScaleRect.height()) / (float) height();
        }
        if (newShare < 0.1) {
            newShare = 0.1f;
        } else if (newShare > 0.8) {
            newShare = 0.8f;
        }
        m_waterfallShare = newShare;
        m_changesPending = true;
        if (m_messageQueueToGUI) {
            m_messageQueueToGUI->push(new MsgReportWaterfallShare(m_waterfallShare));
        }
        update();
        return;
    }
    else if (m_cursorState == CSChannelMoving)
    {
        // Determine if user is trying to move the channel outside of the current frequency range
        // and if so, request an adjustment to the center frequency
        // FIXME: This doesn't take zoom into account, so only works when zoomed out
        Real freqAbs = m_frequencyScale.getValueFromPos(event->x() - m_leftMarginPixmap.width() - 1);
        Real freqMin = m_centerFrequency - m_sampleRate / 2.0f;
        Real freqMax = m_centerFrequency + m_sampleRate / 2.0f;
        if (freqAbs < freqMin) {
            queueRequestCenterFrequency(m_centerFrequency - (freqMin - freqAbs));
        } else if (freqAbs > freqMax) {
            queueRequestCenterFrequency(m_centerFrequency + (freqAbs - freqMax));
        }
        Real freq = freqAbs - m_centerFrequency;
        if (m_channelMarkerStates[m_cursorChannel]->m_channelMarker->getMovable()
            && (m_channelMarkerStates[m_cursorChannel]->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink)
            && m_channelMarkerStates[m_cursorChannel]->m_channelMarker->streamIndexApplies(m_displayStreamIndex))
        {
            m_channelMarkerStates[m_cursorChannel]->m_channelMarker->setCenterFrequencyByCursor(freq);
            channelMarkerChanged();
        }
    }
    if (m_displayWaterfall || m_displayHistogram || m_displayMaxHold || m_displayCurrent)
    {
        for (int i = 0; i < m_channelMarkerStates.size(); ++i)
        {
            if ((m_channelMarkerStates[i]->m_channelMarker->getSourceOrSinkStream() != m_displaySourceOrSink)
                || !m_channelMarkerStates[i]->m_channelMarker->streamIndexApplies(m_displayStreamIndex))
            {
                continue;
            }
            if (m_channelMarkerStates[i]->m_rect.contains(event->pos()))
            {
                if (m_cursorState == CSNormal)
                {
                    setCursor(Qt::SizeHorCursor);
                    m_cursorState = CSChannel;
                    m_cursorChannel = i;
                    m_channelMarkerStates[i]->m_channelMarker->setHighlightedByCursor(true);
                    channelMarkerChanged();
                    return;
                }
                else if (m_cursorState == CSChannel)
                {
                    return;
                }
            }
            else if (m_channelMarkerStates[i]->m_channelMarker->getHighlighted())
            {
                // Don't clear highlight while dragging a channel, as we want the
                // frequency of the channel to be continuously displayed
                if (m_cursorState != CSChannelMoving)
                {
                    m_channelMarkerStates[i]->m_channelMarker->setHighlightedByCursor(false);
                    channelMarkerChanged();
                }
            }
        }
    }
    if (m_cursorState == CSChannel)
    {
        setCursor(Qt::ArrowCursor);
        m_cursorState = CSNormal;
        return;
    }
    event->setAccepted(false);
}
void GLSpectrumView::mousePressEvent(QMouseEvent* event)
{
    const QPointF& ep = event->localPos();
    if ((event->button() == Qt::MiddleButton) && (m_displayMaxHold || m_displayCurrent || m_displayHistogram) && pointInHistogram(ep))
    {
        m_scrollFrequency = true;
        m_scrollStartCenterFreq = m_centerFrequency;
        m_mousePrevLocalPos = ep;
        return;
    }
    if ((event->button() == Qt::MiddleButton) && m_display3DSpectrogram && pointInWaterfallOrSpectrogram(ep))
    {
        m_pan3DSpectrogram = true;
        m_mousePrevLocalPos = ep;
        return;
    }
    if ((event->button() == Qt::RightButton) && m_display3DSpectrogram && pointInWaterfallOrSpectrogram(ep))
    {
        m_scaleZ3DSpectrogram = true;
        m_mousePrevLocalPos = ep;
        return;
    }
    if (event->button() == Qt::RightButton)
    {
        QPointF pHis = ep;
        bool doUpdate = false;
        pHis.rx() = (ep.x()/width() - m_histogramRect.left()) / m_histogramRect.width();
        pHis.ry() = (ep.y()/height() - m_histogramRect.top()) / m_histogramRect.height();
        if (event->modifiers() & Qt::ShiftModifier)
        {
            if ((pHis.x() >= 0) && (pHis.x() <= 1) && (pHis.y() >= 0) && (pHis.y() <= 1))
            {
                m_histogramMarkers.clear();
                doUpdate = true;
                if (m_messageQueueToGUI) {
                    m_messageQueueToGUI->push(new MsgReportHistogramMarkersChange());
                }
            }
        }
        else
        {
            if ((m_histogramMarkers.size() > 0) && (pHis.x() >= 0) && (pHis.x() <= 1) && (pHis.y() >= 0) && (pHis.y() <= 1))
            {
                m_histogramMarkers.pop_back();
                doUpdate = true;
                if (m_messageQueueToGUI) {
                    m_messageQueueToGUI->push(new MsgReportHistogramMarkersChange());
                }
            }
        }
        QPointF pWat = ep;
        pWat.rx() = (ep.x()/width() - m_waterfallRect.left()) / m_waterfallRect.width();
        pWat.ry() = (ep.y()/height() - m_waterfallRect.top()) / m_waterfallRect.height();
        if (event->modifiers() & Qt::ShiftModifier)
        {
            if ((pWat.x() >= 0) && (pWat.x() <= 1) && (pWat.y() >= 0) && (pWat.y() <= 1))
            {
                m_waterfallMarkers.clear();
                doUpdate = true;
                if (m_messageQueueToGUI) {
                    m_messageQueueToGUI->push(new MsgReportWaterfallMarkersChange());
                }
            }
        }
        else
        {
            if ((m_waterfallMarkers.size() > 0) && (pWat.x() >= 0) && (pWat.x() <= 1) && (pWat.y() >= 0) && (pWat.y() <= 1))
            {
                m_waterfallMarkers.pop_back();
                doUpdate = true;
                if (m_messageQueueToGUI) {
                    m_messageQueueToGUI->push(new MsgReportWaterfallMarkersChange());
                }
            }
        }
        if (doUpdate) {
            update();
        }
    }
    else if (event->button() == Qt::LeftButton)
    {
        if (event->modifiers() & Qt::ShiftModifier)
        {
            QPointF pHis = ep;
            bool doUpdate = false;
            pHis.rx() = (ep.x()/width() - m_histogramRect.left()) / m_histogramRect.width();
            pHis.ry() = (ep.y()/height() - m_histogramRect.top()) / m_histogramRect.height();
            float frequency = m_frequencyScale.getRangeMin() + pHis.x()*m_frequencyScale.getRange();
            float powerVal = m_powerScale.getRangeMax() - pHis.y()*m_powerScale.getRange();
            float power = m_linear ? powerVal : CalcDb::powerFromdB(powerVal);
            int fftBin = (((frequency - m_centerFrequency) / (float) m_sampleRate) * m_fftSize) + (m_fftSize / 2);
            if ((pHis.x() >= 0) && (pHis.x() <= 1) && (pHis.y() >= 0) && (pHis.y() <= 1))
            {
                if (m_histogramMarkers.size() < SpectrumHistogramMarker::m_maxNbOfMarkers)
                {
                    m_histogramMarkers.push_back(SpectrumHistogramMarker());
                    m_histogramMarkers.back().m_point = pHis;
                    m_histogramMarkers.back().m_frequency = frequency;
                    m_histogramMarkers.back().m_fftBin = fftBin;
                    m_histogramMarkers.back().m_frequencyStr = displayScaled(
                        frequency,
                        'f',
                        getPrecision((m_centerFrequency*1000)/m_sampleRate),
                        false);
                    m_histogramMarkers.back().m_power = power;
                    m_histogramMarkers.back().m_powerStr = displayPower(
                        powerVal,
                        m_linear ? 'e' : 'f',
                        m_linear ? 3 : 1);
                    if (m_histogramMarkers.size() > 1)
                    {
                        int64_t deltaFrequency = frequency - m_histogramMarkers.at(0).m_frequency;
                        m_histogramMarkers.back().m_deltaFrequencyStr = displayScaled(
                            deltaFrequency,
                            'f',
                            getPrecision(deltaFrequency/m_sampleRate),
                            true);
                        float power0 = m_linear ?
                            m_histogramMarkers.at(0).m_power :
                            CalcDb::dbPower(m_histogramMarkers.at(0).m_power);
                        m_histogramMarkers.back().m_deltaPowerStr = displayPower(
                            power - power0,
                            m_linear ? 'e' : 'f',
                            m_linear ? 3 : 1);
                    }
                    if (m_messageQueueToGUI) {
                        m_messageQueueToGUI->push(new MsgReportHistogramMarkersChange());
                    }
                    doUpdate = true;
                }
            }
            QPointF pWat = ep;
            pWat.rx() = (ep.x()/width() - m_waterfallRect.left()) / m_waterfallRect.width();
            pWat.ry() = (ep.y()/height() - m_waterfallRect.top()) / m_waterfallRect.height();
            frequency = m_frequencyScale.getRangeMin() + pWat.x()*m_frequencyScale.getRange();
            float time = m_timeScale.getRangeMin() + pWat.y()*m_timeScale.getRange();
            if ((pWat.x() >= 0) && (pWat.x() <= 1) && (pWat.y() >= 0) && (pWat.y() <= 1) && !m_display3DSpectrogram)
            {
                if (m_waterfallMarkers.size() < SpectrumWaterfallMarker::m_maxNbOfMarkers)
                {
                    m_waterfallMarkers.push_back(SpectrumWaterfallMarker());
                    m_waterfallMarkers.back().m_point = pWat;
                    m_waterfallMarkers.back().m_frequency = frequency;
                    m_waterfallMarkers.back().m_frequencyStr = displayScaled(
                        frequency,
                        'f',
                        getPrecision((m_centerFrequency*1000)/m_sampleRate),
                        false);
                    m_waterfallMarkers.back().m_time = time;
                    m_waterfallMarkers.back().m_timeStr = displayScaledF(
                        time,
                        'f',
                        3,
                        true);
                    if (m_waterfallMarkers.size() > 1)
                    {
                        int64_t deltaFrequency = frequency - m_waterfallMarkers.at(0).m_frequency;
                        m_waterfallMarkers.back().m_deltaFrequencyStr = displayScaled(
                            deltaFrequency,
                            'f',
                            getPrecision(deltaFrequency/m_sampleRate),
                            true);
                        m_waterfallMarkers.back().m_deltaTimeStr = displayScaledF(
                            time - m_waterfallMarkers.at(0).m_time,
                            'f',
                            3,
                            true);
                    }
                    if (m_messageQueueToGUI) {
                        m_messageQueueToGUI->push(new MsgReportWaterfallMarkersChange());
                    }
                    doUpdate = true;
                }
            }
            if (doUpdate) {
                update();
            }
        }
        else if (event->modifiers() & Qt::AltModifier)
        {
            frequencyPan(event);
        }
        else if (m_display3DSpectrogram)
        {
            // Detect click and drag to rotate 3D spectrogram
            if (pointInWaterfallOrSpectrogram(ep))
            {
                m_rotate3DSpectrogram = true;
                m_mousePrevLocalPos = ep;
                return;
            }
        }
        if ((m_markersDisplay & SpectrumSettings::MarkersDisplayAnnotations) &&
            (ep.y() <= m_histogramRect.top()*height() + m_annotationMarkerHeight + 2.0f))
        {
            QPointF pHis;
            pHis.rx() = (ep.x()/width() - m_histogramRect.left()) / m_histogramRect.width();
            qint64 selectedFrequency = m_frequencyScale.getRangeMin() + pHis.x() * m_frequencyScale.getRange();
            bool selected = false;
            for (auto iMarker = m_visibleAnnotationMarkers.rbegin(); iMarker != m_visibleAnnotationMarkers.rend(); ++iMarker)
            {
				if ((*iMarker)->m_show == SpectrumAnnotationMarker::Hidden) {
					continue;
				}
                qint64 stopFrequency = (*iMarker)->m_startFrequency +
                    ((*iMarker)->m_bandwidth == 0 ? m_frequencyScale.getRange()*0.01f : (*iMarker)->m_bandwidth);
                if (((*iMarker)->m_startFrequency < selectedFrequency) && (selectedFrequency <= stopFrequency) && !selected)
                {
                    switch ((*iMarker)->m_show)
                    {
                    case SpectrumAnnotationMarker::ShowTop:
                        (*iMarker)->m_show = SpectrumAnnotationMarker::ShowText;
                        break;
                    case SpectrumAnnotationMarker::ShowText:
                        (*iMarker)->m_show = SpectrumAnnotationMarker::ShowFull;
                        break;
                    case SpectrumAnnotationMarker::ShowFull:
                        (*iMarker)->m_show = SpectrumAnnotationMarker::ShowTop;
                        break;
                    case SpectrumAnnotationMarker::Hidden:
                        break;
                    }
                    selected = true;
                }
            }
        }
        if  (m_cursorState == CSSplitter)
        {
            grabMouse();
            m_cursorState = CSSplitterMoving;
            return;
        }
        else if (m_cursorState == CSChannel)
        {
            grabMouse();
            m_cursorState = CSChannelMoving;
            return;
        }
        else if ((m_cursorState == CSNormal) &&
            (m_channelMarkerStates.size() == 1) &&
            !(event->modifiers() & Qt::ShiftModifier) &&
            !(event->modifiers() & Qt::AltModifier) &&
            !(event->modifiers() & Qt::ControlModifier) &&
            (ep.y() > m_histogramRect.top()*height() + m_annotationMarkerHeight + 2.0f)) // out of annotation selection zone
        {
            grabMouse();
            setCursor(Qt::SizeHorCursor);
            m_cursorState = CSChannelMoving;
            m_cursorChannel = 0;
            Real freq = m_frequencyScale.getValueFromPos(event->x() - m_leftMarginPixmap.width() - 1) - m_centerFrequency;
            if (m_channelMarkerStates[m_cursorChannel]->m_channelMarker->getMovable()
                && (m_channelMarkerStates[m_cursorChannel]->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink)
                && m_channelMarkerStates[m_cursorChannel]->m_channelMarker->streamIndexApplies(m_displayStreamIndex))
            {
                m_channelMarkerStates[m_cursorChannel]->m_channelMarker->setCenterFrequencyByCursor(freq);
                channelMarkerChanged();
            }
            return;
        }
    }
}
void GLSpectrumView::mouseReleaseEvent(QMouseEvent*)
{
    m_scrollFrequency = false;
    m_pan3DSpectrogram = false;
    m_rotate3DSpectrogram = false;
    m_scaleZ3DSpectrogram = false;
    if (m_cursorState == CSSplitterMoving)
    {
        releaseMouse();
        m_cursorState = CSSplitter;
    }
    else if (m_cursorState == CSChannelMoving)
    {
        releaseMouse();
        m_cursorState = CSChannel;
    }
}
void GLSpectrumView::wheelEvent(QWheelEvent *event)
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
    const QPointF& ep = event->position();
#else
    const QPointF& ep = event->pos();
#endif
    if (m_display3DSpectrogram && pointInWaterfallOrSpectrogram(ep))
    {
        // Scale 3D spectrogram when mouse wheel moved
        // Some mice use delta in steps of 120 for 15 degrees
        // for one step of mouse wheel
        // Other mice/trackpads use smaller values
        int delta = event->angleDelta().y();
        if (delta != 0) {
             m_glShaderSpectrogram.verticalAngle(-5.0*delta/120.0);
        }
        repaint(); // Force repaint in case acquisition is stopped
    }
    else
    {
        if (event->modifiers() & Qt::ShiftModifier) {
            channelMarkerMove(event, 100);
        } else if (event->modifiers() & Qt::ControlModifier) {
            channelMarkerMove(event, 10);
        } else {
            channelMarkerMove(event, 1);
        }
    }
    event->accept();
}
void GLSpectrumView::zoomFactor(const QPointF& p, float factor)
{
    float pwx = (p.x() - m_leftMargin) / (width() - m_leftMargin - m_rightMargin); // x position in window
    if ((pwx >= 0.0f) && (pwx <= 1.0f))
    {
        // When we zoom, we want the frequency under the cursor to remain the same
        // Determine frequency at cursor position
        float zoomFreq = m_frequencyScale.getRangeMin() + pwx*m_frequencyScale.getRange();
        // Calculate current centre frequency
        float currentCF = (m_frequencyZoomFactor == 1) ? m_centerFrequency : ((m_frequencyZoomPos - 0.5) * m_sampleRate + m_centerFrequency);
        // Calculate difference from frequency under cursor to centre frequency
        float freqDiff = (currentCF - zoomFreq);
        // Calculate what that difference would be if there was no zoom
        float freqDiffZoom1 = freqDiff * m_frequencyZoomFactor;
        m_frequencyZoomFactor *= factor;
        m_frequencyZoomFactor = std::min(m_frequencyZoomFactor, m_maxFrequencyZoom);
        m_frequencyZoomFactor = std::max(m_frequencyZoomFactor, 1.0f);
        // Calculate what frequency difference should be at new zoom
        float zoomedFreqDiff = freqDiffZoom1 / m_frequencyZoomFactor;
        // Then calculate what the center frequency should be
        float zoomedCF = zoomFreq + zoomedFreqDiff;
        // Calculate zoom position which will set the desired center frequency
        float zoomPos = (zoomedCF - m_centerFrequency) / m_sampleRate + 0.5;
        zoomPos = std::max(0.0f, zoomPos);
        zoomPos = std::min(1.0f, zoomPos);
        frequencyZoom(zoomPos);
    }
 }
void GLSpectrumView::zoom(const QPointF& p, int y)
{
    float pwx = (p.x() - m_leftMargin) / (width() - m_leftMargin - m_rightMargin); // x position in window
    if ((pwx >= 0.0f) && (pwx <= 1.0f))
    {
        // When we zoom, we want the frequency under the cursor to remain the same
        // Determine frequency at cursor position
        float zoomFreq = m_frequencyScale.getRangeMin() + pwx*m_frequencyScale.getRange();
        // Calculate current centre frequency
        int adjSampleRate = m_ssbSpectrum ? m_sampleRate/2 : m_sampleRate;
        qint64 adjCenterFrequency = m_centerFrequency + (m_ssbSpectrum ? m_sampleRate/4 : 0);
        float currentCF = (m_frequencyZoomFactor == 1) ?
            adjCenterFrequency : (m_frequencyZoomPos - 0.5) * adjSampleRate + adjCenterFrequency;
        // Calculate difference from frequency under cursor to centre frequency
        float freqDiff = (currentCF - zoomFreq);
        // Calculate what that difference would be if there was no zoom
        float freqDiffZoom1 = freqDiff * m_frequencyZoomFactor;
        if (y > 0) // zoom in
        {
            if (m_frequencyZoomFactor < m_maxFrequencyZoom) {
                m_frequencyZoomFactor += 0.5f;
            } else {
                return;
            }
        }
        else
        {
            if (m_frequencyZoomFactor > 1.0f) {
                m_frequencyZoomFactor -= 0.5f;
            } else {
                return;
            }
        }
        // Calculate what frequency difference should be at new zoom
        float zoomedFreqDiff = freqDiffZoom1 / m_frequencyZoomFactor;
        // Then calculate what the center frequency should be
        float zoomedCF = zoomFreq + zoomedFreqDiff;
        // Calculate zoom position which will set the desired center frequency
        float zoomPos = (zoomedCF - adjCenterFrequency) / adjSampleRate + 0.5;
        zoomPos = std::max(0.0f, zoomPos);
        zoomPos = std::min(1.0f, zoomPos);
        frequencyZoom(zoomPos);
    }
    else
    {
        float pwyh, pwyw;
        if (m_invertedWaterfall) // histo on top
        {
            pwyh = (p.y() - m_topMargin) / m_histogramHeight;
            pwyw = (p.y() - m_topMargin - m_histogramHeight - m_frequencyScaleHeight) / m_waterfallHeight;
        }
        else // waterfall on top
        {
            pwyw = (p.y() - m_topMargin) / m_waterfallHeight;
            pwyh = (p.y() - m_topMargin - m_waterfallHeight - m_frequencyScaleHeight) / m_histogramHeight;
        }
        //qDebug("GLSpectrumView::zoom: pwyh: %f pwyw: %f", pwyh, pwyw);
        if ((pwyw >= 0.0f) && (pwyw <= 1.0f)) {
            timeZoom(y > 0);
        }
        if ((pwyh >= 0.0f) && (pwyh <= 1.0f) && !m_linear) {
            powerZoom(pwyh, y > 0);
        }
    }
}
void GLSpectrumView::frequencyZoom(float zoomPos)
{
    m_frequencyZoomPos = zoomPos;
    updateFFTLimits();
}
void GLSpectrumView::frequencyPan(QMouseEvent *event)
{
    if (m_frequencyZoomFactor == 1.0f) {
        return;
    }
    const QPointF& p = event->pos();
    float pw = (p.x() - m_leftMargin) / (width() - m_leftMargin - m_rightMargin); // position in window
    pw = pw < 0.0f ? 0.0f : pw > 1.0f ? 1.0 : pw;
    float dw = pw - 0.5f;
    m_frequencyZoomPos += dw * (1.0f / m_frequencyZoomFactor);
    float lim = 0.5f / m_frequencyZoomFactor;
    m_frequencyZoomPos = m_frequencyZoomPos < lim ? lim : m_frequencyZoomPos > 1 - lim ? 1 - lim : m_frequencyZoomPos;
    qDebug("GLSpectrumView::frequencyPan: pw: %f p: %f", pw, m_frequencyZoomPos);
    updateFFTLimits();
}
void GLSpectrumView::timeZoom(bool zoomInElseOut)
{
    if ((m_fftOverlap  == 0) && !zoomInElseOut) {
        return;
    }
	if (zoomInElseOut && (m_fftOverlap == m_fftSize - 1)) {
        return;
    }
    m_fftOverlap = m_fftOverlap + (zoomInElseOut ? 1 : -1);
    m_changesPending = true;
    if (m_messageQueueToGUI) {
        m_messageQueueToGUI->push(new MsgReportFFTOverlap(m_fftOverlap));
    }
}
void GLSpectrumView::powerZoom(float pw, bool zoomInElseOut)
{
    m_powerRange = m_powerRange + (zoomInElseOut ? -2 : 2);
    if (pw > 2.0/3.0) { // bottom
        m_referenceLevel = m_referenceLevel + (zoomInElseOut ? -2 : 2);
    } else if (pw > 1.0/3.0) { // middle
        m_referenceLevel = m_referenceLevel + (zoomInElseOut ? -1 : 1);
    } // top
    m_powerRange = m_powerRange < 1 ? 1 : m_powerRange > 100 ? 100 : m_powerRange;
    m_referenceLevel = m_referenceLevel < -110 ? -110 : m_referenceLevel > 0 ? 0 : m_referenceLevel;
    m_changesPending = true;
    if (m_messageQueueToGUI) {
        m_messageQueueToGUI->push(new MsgReportPowerScale(m_referenceLevel, m_powerRange));
    }
}
void GLSpectrumView::resetFrequencyZoom()
{
    m_frequencyZoomFactor = 1.0f;
    m_frequencyZoomPos = 0.5f;
    updateFFTLimits();
}
void GLSpectrumView::updateFFTLimits()
{
    if (!m_spectrumVis) {
        return;
    }
    SpectrumVis::MsgFrequencyZooming *msg = SpectrumVis::MsgFrequencyZooming::create(
        m_frequencyZoomFactor, m_frequencyZoomPos
    );
    m_spectrumVis->getInputMessageQueue()->push(msg);
    m_changesPending = true;
}
void GLSpectrumView::setFrequencyScale()
{
    int frequencySpan;
    int64_t centerFrequency;
    getFrequencyZoom(centerFrequency, frequencySpan);
    m_frequencyScale.setSize(width() - m_leftMargin - m_rightMargin);
    m_frequencyScale.setRange(Unit::Frequency, centerFrequency - frequencySpan / 2.0, centerFrequency + frequencySpan / 2.0);
    m_frequencyScale.setMakeOpposite(m_lsbDisplay);
}
void GLSpectrumView::setPowerScale(int height)
{
    m_powerScale.setSize(height);
    if (m_linear)
    {
        Real referenceLevel = m_useCalibration ? m_referenceLevel * m_calibrationGain : m_referenceLevel;
        m_powerScale.setRange(Unit::Scientific, 0.0f, referenceLevel);
    }
    else
    {
        Real referenceLevel = m_useCalibration ? m_referenceLevel + m_calibrationShiftdB : m_referenceLevel;
        m_powerScale.setRange(Unit::Decibel, referenceLevel - m_powerRange, referenceLevel);
    }
    if (m_powerScale.getScaleWidth() > m_leftMargin) {
        m_leftMargin = m_powerScale.getScaleWidth();
    }
}
void GLSpectrumView::getFrequencyZoom(int64_t& centerFrequency, int& frequencySpan)
{
    int adjSampleRate = m_ssbSpectrum ? m_sampleRate/2 : m_sampleRate;
    qint64 adjCenterFrequency = m_centerFrequency + (m_ssbSpectrum ? m_sampleRate/4 : 0);
    frequencySpan = (m_frequencyZoomFactor == 1) ?
        adjSampleRate : adjSampleRate * (1.0 / m_frequencyZoomFactor);
    centerFrequency = (m_frequencyZoomFactor == 1) ?
        adjCenterFrequency : (m_frequencyZoomPos - 0.5) * adjSampleRate + adjCenterFrequency;
}
// void GLSpectrumView::updateFFTLimits()
// {
// 	m_fftMin = m_frequencyZoomFactor == 1 ? 0 : (m_frequencyZoomPos - (0.5f / m_frequencyZoomFactor)) * m_fftSize;
// 	m_fftMax = m_frequencyZoomFactor == 1 ? m_fftSize : (m_frequencyZoomPos - (0.5f / m_frequencyZoomFactor)) * m_fftSize;
// }
void GLSpectrumView::channelMarkerMove(QWheelEvent *event, int mul)
{
    for (int i = 0; i < m_channelMarkerStates.size(); ++i)
    {
        if ((m_channelMarkerStates[i]->m_channelMarker->getSourceOrSinkStream() != m_displaySourceOrSink)
            || !m_channelMarkerStates[i]->m_channelMarker->streamIndexApplies(m_displayStreamIndex))
        {
            continue;
        }
        if (m_channelMarkerStates[i]->m_rect.contains(event->position()))
        {
            int freq = m_channelMarkerStates[i]->m_channelMarker->getCenterFrequency();
            if (event->angleDelta().y() > 0) {
                freq += 10 * mul;
            } else if (event->angleDelta().y() < 0) {
                freq -= 10 * mul;
            }
            // calculate scale relative cursor position for new frequency
            float x_pos = m_frequencyScale.getPosFromValue(m_centerFrequency + freq);
            if ((x_pos >= 0.0) && (x_pos < m_frequencyScale.getSize())) // cursor must be in scale
            {
                m_channelMarkerStates[i]->m_channelMarker->setCenterFrequencyByCursor(freq);
                m_channelMarkerStates[i]->m_channelMarker->setCenterFrequency(freq);
                // cursor follow-up
                int xd = x_pos + m_leftMargin;
                QCursor c = cursor();
                QPoint cp_a = c.pos();
                QPoint cp_w = mapFromGlobal(cp_a);
                cp_w.setX(xd);
                cp_a = mapToGlobal(cp_w);
                c.setPos(cp_a);
                setCursor(c);
            }
            return;
        }
    }
    zoom(event->position(), event->angleDelta().y());
}
// Return if specified point is within the bounds of the waterfall / 3D spectrogram screen area
bool GLSpectrumView::pointInWaterfallOrSpectrogram(const QPointF &point) const
{
    // m_waterfallRect is normalised to [0,1]
    QPointF pWat = point;
    pWat.rx() = (point.x()/width() - m_waterfallRect.left()) / m_waterfallRect.width();
    pWat.ry() = (point.y()/height() - m_waterfallRect.top()) / m_waterfallRect.height();
    return (pWat.x() >= 0) && (pWat.x() <= 1) && (pWat.y() >= 0) && (pWat.y() <= 1);
}
// Return if specified point is within the bounds of the histogram screen area
bool GLSpectrumView::pointInHistogram(const QPointF &point) const
{
    // m_histogramRect is normalised to [0,1]
    QPointF p = point;
    p.rx() = (point.x()/width() - m_histogramRect.left()) / m_histogramRect.width();
    p.ry() = (point.y()/height() - m_histogramRect.top()) / m_histogramRect.height();
    return (p.x() >= 0) && (p.x() <= 1) && (p.y() >= 0) && (p.y() <= 1);
}
void GLSpectrumView::enterEvent(EnterEventType* event)
{
    m_mouseInside = true;
    update();
    QOpenGLWidget::enterEvent(event);
}
void GLSpectrumView::leaveEvent(QEvent* event)
{
    m_mouseInside = false;
    update();
    QOpenGLWidget::leaveEvent(event);
}
void GLSpectrumView::tick()
{
    if (m_displayChanged)
    {
        m_displayChanged = false;
        update();
    }
}
void GLSpectrumView::channelMarkerChanged()
{
    QMutexLocker mutexLocker(&m_mutex);
    m_changesPending = true;
    update();
}
void GLSpectrumView::channelMarkerDestroyed(QObject* object)
{
    removeChannelMarker((ChannelMarker*)object);
}
void GLSpectrumView::setWaterfallShare(Real waterfallShare)
{
    QMutexLocker mutexLocker(&m_mutex);
    if (waterfallShare < 0.1f) {
        m_waterfallShare = 0.1f;
    } else if (waterfallShare > 0.8f) {
        m_waterfallShare = 0.8f;
    } else {
        m_waterfallShare = waterfallShare;
    }
    m_changesPending = true;
}
void GLSpectrumView::setFPSPeriodMs(int fpsPeriodMs)
{
    if (fpsPeriodMs == 0)
    {
        disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(tick()));
        m_timer.stop();
    }
    else
    {
        connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick()));
        m_timer.start(fpsPeriodMs);
    }
    m_fpsPeriodMs = fpsPeriodMs;
}
void GLSpectrumView::cleanup()
{
    //makeCurrent();
    m_glShaderSimple.cleanup();
    m_glShaderFrequencyScale.cleanup();
    m_glShaderHistogram.cleanup();
    m_glShaderLeftScale.cleanup();
    m_glShaderWaterfall.cleanup();
    m_glShaderTextOverlay.cleanup();
    m_glShaderInfo.cleanup();
    m_glShaderSpectrogram.cleanup();
    m_glShaderSpectrogramTimeScale.cleanup();
    m_glShaderSpectrogramPowerScale.cleanup();
    //doneCurrent();
}
// Display number with full precision, group separators and eng. unit suffixes
// E.g:
// -1.505,123,304G
//    456.034,123M
//        300.345k
//            789
QString GLSpectrumView::displayFull(int64_t value)
{
    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;
}
QString GLSpectrumView::displayScaled(int64_t value, char type, int precision, bool showMult)
{
    int64_t posValue = (value < 0) ? -value : value;
    if (posValue < 1000) {
        return tr("%1").arg(QString::number(value, type, precision));
    } else if (posValue < 1000000) {
        return tr("%1%2").arg(QString::number(value / 1000.0, type, precision)).arg(showMult ? "k" : "");
    } else if (posValue < 1000000000) {
        return tr("%1%2").arg(QString::number(value / 1000000.0, type, precision)).arg(showMult ? "M" : "");
    } else if (posValue < 1000000000000) {
        return tr("%1%2").arg(QString::number(value / 1000000000.0, type, precision)).arg(showMult ? "G" : "");
    } else {
        return tr("%1").arg(QString::number(value, 'e', precision));
    }
}
QString GLSpectrumView::displayPower(float value, char type, int precision)
{
    return tr("%1").arg(QString::number(value, type, precision));
}
QString GLSpectrumView::displayScaledF(float value, char type, int precision, bool showMult)
{
    float posValue = (value < 0) ? -value : value;
    if (posValue == 0)
    {
        return tr("%1").arg(QString::number(value, 'f', precision));
    }
    else if (posValue < 1)
    {
        if (posValue > 0.001) {
            return tr("%1%2").arg(QString::number(value * 1000.0, type, precision)).arg(showMult ? "m" : "");
        } else if (posValue > 0.000001) {
            return tr("%1%2").arg(QString::number(value * 1000000.0, type, precision)).arg(showMult ? "u" : "");
        } else if (posValue > 1e-9) {
            return tr("%1%2").arg(QString::number(value * 1e9, type, precision)).arg(showMult ? "n" : "");
        } else if (posValue > 1e-12) {
            return tr("%1%2").arg(QString::number(value * 1e12, type, precision)).arg(showMult ? "p" : "");
        } else {
            return tr("%1").arg(QString::number(value, 'e', precision));
        }
    }
    else
    {
        if (posValue < 1e3) {
            return tr("%1").arg(QString::number(value, type, precision));
        } else if (posValue < 1e6) {
            return tr("%1%2").arg(QString::number(value / 1000.0, type, precision)).arg(showMult ? "k" : "");
        } else if (posValue < 1e9) {
            return tr("%1%2").arg(QString::number(value / 1000000.0, type, precision)).arg(showMult ? "M" : "");
        } else if (posValue < 1e12) {
            return tr("%1%2").arg(QString::number(value / 1000000000.0, type, precision)).arg(showMult ? "G" : "");
        } else {
            return tr("%1").arg(QString::number(value, 'e', precision));
        }
    }
}
int GLSpectrumView::getPrecision(int value)
{
    int posValue = (value < 0) ? -value : value;
    if (posValue < 1000) {
        return 3;
    } else if (posValue < 10000) {
        return 4;
    } else if (posValue < 100000) {
        return 5;
    } else {
        return 6;
    }
}
// Draw text right justified in top info bar - currently unused
void GLSpectrumView::drawTextRight(const QString &text, const QString &value, const QString &max, const QString &units)
{
    drawTextsRight({text}, {value}, {max}, {units});
}
void GLSpectrumView::drawTextsRight(const QStringList &text, const QStringList &value, const QStringList &max, const QStringList &units)
{
    QFontMetrics fm(font());
    m_infoPixmap.fill(Qt::transparent);
    QPainter painter(&m_infoPixmap);
    painter.setPen(Qt::NoPen);
    painter.setBrush(Qt::black);
    painter.setBrush(Qt::transparent);
    painter.drawRect(m_leftMargin, 0, width() - m_leftMargin, m_infoHeight);
    painter.setPen(QColor(0xf0, 0xf0, 0xff));
    painter.setFont(font());
    int x = width() - m_rightMargin;
    int y = fm.height() + fm.ascent() / 2 - 2;
    int textWidth, maxWidth;
    for (int i = text.length() - 1; i >= 0; i--)
    {
        textWidth = fm.horizontalAdvance(units[i]);
        painter.drawText(QPointF(x - textWidth, y), units[i]);
        x -= textWidth;
        textWidth = fm.horizontalAdvance(value[i]);
        maxWidth = fm.horizontalAdvance(max[i]);
        painter.drawText(QPointF(x - textWidth, y), value[i]);
        x -= maxWidth;
        textWidth = fm.horizontalAdvance(text[i]);
        painter.drawText(QPointF(x - textWidth, y), text[i]);
        x -= textWidth;
    }
    m_glShaderTextOverlay.initTexture(m_infoPixmap.toImage());
    GLfloat vtx1[] = {
            0, 1,
            1, 1,
            1, 0,
            0, 0
    };
    GLfloat tex1[] = {
            0, 1,
            1, 1,
            1, 0,
            0, 0
    };
    m_glShaderTextOverlay.drawSurface(m_glInfoBoxMatrix, tex1, vtx1, 4);
}
void GLSpectrumView::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 GLSpectrumView::drawTextOverlay(
    const QString &text,
    const QColor &color,
    const QFont& font,
    float shiftX,
    float shiftY,
    bool leftHalf,
    bool topHalf,
    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 shiftX = glRect.width() - ((overlayRect.width() + 4.0f) / width());
        // float shiftY = 4.0f / height();
        float rectX = glRect.x() + shiftX - (leftHalf ? 0 : (overlayRect.width()+1)/width());
        float rectY = glRect.y() + shiftY + (4.0f / height()) - (topHalf ? 0 : (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 GLSpectrumView::formatTextInfo(QString& info)
{
    if (m_useCalibration) {
        info.append(tr("CAL:%1dB ").arg(QString::number(m_calibrationShiftdB, 'f', 1)));
    }
    if (m_frequencyZoomFactor != 1.0f) {
        info.append(tr("%1x ").arg(QString::number(m_frequencyZoomFactor, 'f', 1)));
    }
    if (m_sampleRate == 0)
    {
        info.append(tr("CF:%1 SP:%2").arg(m_centerFrequency).arg(m_sampleRate));
    }
    else
    {
        int64_t centerFrequency;
        int frequencySpan;
        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)));
    }
}
bool GLSpectrumView::eventFilter(QObject *object, QEvent *event)
{
    if (event->type() == QEvent::KeyPress)
    {
        QKeyEvent *keyEvent = static_cast(event);
        switch (keyEvent->key())
        {
        case Qt::Key_Up:
            if (keyEvent->modifiers() & Qt::ShiftModifier) {
                m_glShaderSpectrogram.lightRotateX(-5.0f);
            } else if (keyEvent->modifiers() & Qt::AltModifier) {
                m_glShaderSpectrogram.lightTranslateY(0.05);
            } else if (keyEvent->modifiers() & Qt::ControlModifier) {
                m_glShaderSpectrogram.translateY(0.05);
            } else {
                m_glShaderSpectrogram.rotateX(-5.0f);
            }
            break;
        case Qt::Key_Down:
            if (keyEvent->modifiers() & Qt::ShiftModifier) {
                m_glShaderSpectrogram.lightRotateX(5.0f);
            } else if (keyEvent->modifiers() & Qt::AltModifier) {
                m_glShaderSpectrogram.lightTranslateY(-0.05);
            } else if (keyEvent->modifiers() & Qt::ControlModifier) {
                m_glShaderSpectrogram.translateY(-0.05);
            } else {
                m_glShaderSpectrogram.rotateX(5.0f);
            }
            break;
        case Qt::Key_Left:
            if (keyEvent->modifiers() & Qt::ShiftModifier) {
                m_glShaderSpectrogram.lightRotateZ(5.0f);
            } else if (keyEvent->modifiers() & Qt::AltModifier) {
                m_glShaderSpectrogram.lightTranslateX(-0.05);
            } else if (keyEvent->modifiers() & Qt::ControlModifier) {
                m_glShaderSpectrogram.translateX(-0.05);
            } else {
                m_glShaderSpectrogram.rotateZ(5.0f);
            }
            break;
        case Qt::Key_Right:
            if (keyEvent->modifiers() & Qt::ShiftModifier) {
                m_glShaderSpectrogram.lightRotateZ(-5.0f);
            } else if (keyEvent->modifiers() & Qt::AltModifier) {
                m_glShaderSpectrogram.lightTranslateX(0.05);
            } else if (keyEvent->modifiers() & Qt::ControlModifier) {
                m_glShaderSpectrogram.translateX(0.05);
            } else {
                m_glShaderSpectrogram.rotateZ(-5.0f);
            }
            break;
        case Qt::Key_Equal: // So you don't need to press shift
        case Qt::Key_Plus:
            if (keyEvent->modifiers() & Qt::ControlModifier) {
                m_glShaderSpectrogram.userScaleZ(1.1f);
            } else {
                m_glShaderSpectrogram.verticalAngle(-1.0f);
            }
            break;
        case Qt::Key_Minus:
            if (keyEvent->modifiers() & Qt::ControlModifier) {
                m_glShaderSpectrogram.userScaleZ(0.9f);
            } else {
                m_glShaderSpectrogram.verticalAngle(1.0f);
            }
            break;
        case Qt::Key_R:
            m_glShaderSpectrogram.reset();
            break;
        case Qt::Key_F:
            // Project straight down and scale to view, so it's a bit like 2D
            m_glShaderSpectrogram.reset();
            m_glShaderSpectrogram.rotateX(45.0f);
            m_glShaderSpectrogram.verticalAngle(-9.0f);
            m_glShaderSpectrogram.userScaleZ(0.0f);
            break;
        }
        repaint(); // Force repaint in case acquisition is stopped
        return true;
    }
    else
    {
        return QOpenGLWidget::eventFilter(object, event);
    }
}