diff --git a/CMakeLists.txt b/CMakeLists.txt index dd9474312..9b9f5d206 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,6 +131,7 @@ set(sdrbase_SOURCES sdrbase/dsp/spectrumscopengcombovis.cpp sdrbase/dsp/scopevis.cpp sdrbase/dsp/scopevisng.cpp + sdrbase/dsp/scopevismulti.cpp sdrbase/dsp/spectrumvis.cpp sdrbase/dsp/threadedbasebandsamplesink.cpp sdrbase/dsp/threadedbasebandsamplesource.cpp @@ -146,7 +147,9 @@ set(sdrbase_SOURCES sdrbase/gui/glscope.cpp sdrbase/gui/glscopegui.cpp sdrbase/gui/glscopeng.cpp + sdrbase/gui/glscopemulti.cpp sdrbase/gui/glscopenggui.cpp + sdrbase/gui/glscopemultigui.cpp sdrbase/gui/glshadersimple.cpp sdrbase/gui/glshadertextured.cpp sdrbase/gui/glspectrum.cpp @@ -248,6 +251,7 @@ set(sdrbase_HEADERS sdrbase/dsp/spectrumscopengcombovis.h sdrbase/dsp/scopevis.h sdrbase/dsp/scopevisng.h + sdrbase/dsp/scopevismulti.h sdrbase/dsp/spectrumvis.h sdrbase/dsp/threadedbasebandsamplesink.h sdrbase/dsp/threadedbasebandsamplesource.h @@ -262,7 +266,9 @@ set(sdrbase_HEADERS sdrbase/gui/glscope.h sdrbase/gui/glscopegui.h sdrbase/gui/glscopeng.h + sdrbase/gui/glscopemulti.h sdrbase/gui/glscopenggui.h + sdrbase/gui/glscopemultigui.h sdrbase/gui/glshadersimple.h sdrbase/gui/glshadertextured.h sdrbase/gui/glspectrum.h @@ -323,6 +329,7 @@ set(sdrbase_FORMS sdrbase/gui/cwkeyergui.ui sdrbase/gui/glscopegui.ui sdrbase/gui/glscopenggui.ui + sdrbase/gui/glscopemultigui.ui sdrbase/gui/glspectrumgui.ui sdrbase/gui/pluginsdialog.ui sdrbase/gui/audiodialog.ui diff --git a/sdrbase/dsp/scopevismulti.cpp b/sdrbase/dsp/scopevismulti.cpp new file mode 100644 index 000000000..ddc7d1715 --- /dev/null +++ b/sdrbase/dsp/scopevismulti.cpp @@ -0,0 +1,848 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 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 // +// // +// 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 "scopevismulti.h" +#include "dsp/dspcommands.h" +#include "gui/glscopemulti.h" + +MESSAGE_CLASS_DEFINITION(ScopeVisMulti::MsgConfigureScopeVisNG, Message) +MESSAGE_CLASS_DEFINITION(ScopeVisMulti::MsgScopeVisNGAddTrigger, Message) +MESSAGE_CLASS_DEFINITION(ScopeVisMulti::MsgScopeVisNGChangeTrigger, Message) +MESSAGE_CLASS_DEFINITION(ScopeVisMulti::MsgScopeVisNGRemoveTrigger, Message) +MESSAGE_CLASS_DEFINITION(ScopeVisMulti::MsgScopeVisNGMoveTrigger, Message) +MESSAGE_CLASS_DEFINITION(ScopeVisMulti::MsgScopeVisNGFocusOnTrigger, Message) +MESSAGE_CLASS_DEFINITION(ScopeVisMulti::MsgScopeVisNGAddTrace, Message) +MESSAGE_CLASS_DEFINITION(ScopeVisMulti::MsgScopeVisNGChangeTrace, Message) +MESSAGE_CLASS_DEFINITION(ScopeVisMulti::MsgScopeVisNGRemoveTrace, Message) +MESSAGE_CLASS_DEFINITION(ScopeVisMulti::MsgScopeVisNGMoveTrace, Message) +MESSAGE_CLASS_DEFINITION(ScopeVisMulti::MsgScopeVisNGFocusOnTrace, Message) +MESSAGE_CLASS_DEFINITION(ScopeVisMulti::MsgScopeVisNGOneShot, Message) +MESSAGE_CLASS_DEFINITION(ScopeVisMulti::MsgScopeVisNGMemoryTrace, Message) + +ScopeVisMulti::ScopeVisMulti(GLScopeMulti* glScope) : + m_glScope(glScope), + m_preTriggerDelay(0), + m_currentTriggerIndex(0), + m_focusedTriggerIndex(0), + m_triggerState(TriggerUntriggered), + m_focusedTraceIndex(0), + m_traceSize(m_traceChunkSize), + m_nbSamples(0), + m_traceStart(true), + m_postTrigBuffering(false), + m_traceFill(0), + m_zTraceIndex(-1), + m_timeBase(1), + m_timeOfsProMill(0), + m_sampleRate(0), + m_freeRun(true), + m_maxTraceDelay(0), + m_triggerOneShot(false), + m_triggerWaitForReset(false), + m_currentTraceMemoryIndex(0), + m_nbSources(0) +{ + setObjectName("ScopeVisNG"); + m_glScope->setTraces(&m_traces.m_tracesData, &m_traces.m_traces[0]); +} + +ScopeVisMulti::~ScopeVisMulti() +{ +} + +void ScopeVisMulti::setSampleRate(int sampleRate) +{ + if (sampleRate != m_sampleRate) + { + m_sampleRate = sampleRate; + if (m_glScope) m_glScope->setSampleRate(m_sampleRate); + } +} + +void ScopeVisMulti::configure( + uint32_t nbSources, + uint32_t traceSize, + uint32_t timeBase, + uint32_t timeOfsProMill, + uint32_t triggerPre, + bool freeRun) +{ + Message* cmd = MsgConfigureScopeVisNG::create(nbSources, traceSize, timeBase, timeOfsProMill, triggerPre, freeRun); + m_inputMessageQueue.push(cmd); +} + +void ScopeVisMulti::addTrace(const TraceData& traceData) +{ + qDebug() << "ScopeVisMulti::addTrace:" + << " m_amp: " << traceData.m_amp + << " m_ofs: " << traceData.m_ofs + << " m_traceDelay: " << traceData.m_traceDelay; + Message* cmd = MsgScopeVisNGAddTrace::create(traceData); + m_inputMessageQueue.push(cmd); +} + +void ScopeVisMulti::changeTrace(const TraceData& traceData, uint32_t traceIndex) +{ + qDebug() << "ScopeVisMulti::changeTrace:" + << " trace: " << traceIndex + << " m_amp: " << traceData.m_amp + << " m_ofs: " << traceData.m_ofs + << " m_traceDelay: " << traceData.m_traceDelay; + Message* cmd = MsgScopeVisNGChangeTrace::create(traceData, traceIndex); + m_inputMessageQueue.push(cmd); +} + +void ScopeVisMulti::removeTrace(uint32_t traceIndex) +{ + qDebug() << "ScopeVisMulti::removeTrace:" + << " trace: " << traceIndex; + Message* cmd = MsgScopeVisNGRemoveTrace::create(traceIndex); + m_inputMessageQueue.push(cmd); +} + +void ScopeVisMulti::moveTrace(uint32_t traceIndex, bool upElseDown) +{ + qDebug() << "ScopeVisMulti::moveTrace:" + << " trace: " << traceIndex + << " up: " << upElseDown; + Message* cmd = MsgScopeVisNGMoveTrace::create(traceIndex, upElseDown); + m_inputMessageQueue.push(cmd); +} + +void ScopeVisMulti::focusOnTrace(uint32_t traceIndex) +{ + Message* cmd = MsgScopeVisNGFocusOnTrace::create(traceIndex); + m_inputMessageQueue.push(cmd); +} + +void ScopeVisMulti::addTrigger(const TriggerData& triggerData) +{ + Message* cmd = MsgScopeVisNGAddTrigger::create(triggerData); + m_inputMessageQueue.push(cmd); +} + +void ScopeVisMulti::changeTrigger(const TriggerData& triggerData, uint32_t triggerIndex) +{ + Message* cmd = MsgScopeVisNGChangeTrigger::create(triggerData, triggerIndex); + m_inputMessageQueue.push(cmd); +} + +void ScopeVisMulti::removeTrigger(uint32_t triggerIndex) +{ + Message* cmd = MsgScopeVisNGRemoveTrigger::create(triggerIndex); + m_inputMessageQueue.push(cmd); +} + +void ScopeVisMulti::moveTrigger(uint32_t triggerIndex, bool upElseDown) +{ + Message* cmd = MsgScopeVisNGMoveTrigger::create(triggerIndex, upElseDown); + m_inputMessageQueue.push(cmd); +} + +void ScopeVisMulti::focusOnTrigger(uint32_t triggerIndex) +{ + Message* cmd = MsgScopeVisNGFocusOnTrigger::create(triggerIndex); + m_inputMessageQueue.push(cmd); +} + +void ScopeVisMulti::setOneShot(bool oneShot) +{ + Message* cmd = MsgScopeVisNGOneShot::create(oneShot); + m_inputMessageQueue.push(cmd); +} + +void ScopeVisMulti::setMemoryIndex(uint32_t memoryIndex) +{ + Message* cmd = MsgScopeVisNGMemoryTrace::create(memoryIndex); + m_inputMessageQueue.push(cmd); +} + +void ScopeVisMulti::feed(const SampleVector::const_iterator& cbegin, const SampleVector::const_iterator& end, uint32_t sourceIndex) +{ + if ((m_triggerWaitForReset) || (m_currentTraceMemoryIndex > 0)) { + return; + } + + if(!m_mutex.tryLock(2)) { // prevent conflicts with configuration process + return; + } + + m_traceDiscreteMemory.feed(cbegin, end, sourceIndex); // memory storage + + if (m_traceDiscreteMemory.minFill() >= m_traceSize) + { + if (m_triggerState == TriggerTriggered) + { + processSources(); + } + else + { + lookForTrigger(); + } + } +} + +void ScopeVisMulti::lookForTrigger() +{ + int count = 0; + + if ((m_freeRun) || (m_triggerConditions.size() == 0)) // immediate re-trigger + { + if (m_triggerState == TriggerUntriggered) + { + m_traceStart = true; // start trace processing + m_nbSamples = m_traceSize + m_maxTraceDelay; + m_triggerState = TriggerTriggered; + } + } + else if ((m_triggerState == TriggerUntriggered) || (m_triggerState == TriggerDelay)) + { + TriggerCondition& triggerCondition = m_triggerConditions[m_currentTriggerIndex]; // current trigger condition + uint32_t sourceIndex = triggerCondition.m_triggerData.m_inputIndex; + + SampleVector::iterator begin = m_traceDiscreteMemory.getBeginIterator(sourceIndex); + SampleVector::iterator end = begin + m_traceSize; + SampleVector::const_iterator cbegin = begin; + + while (begin < end) + { + if (m_triggerState == TriggerDelay) + { + if (triggerCondition.m_triggerDelayCount > 0) // skip samples during delay period + { + triggerCondition.m_triggerDelayCount--; + ++begin; + continue; + } + else // process trigger + { + if (nextTrigger()) // move to next trigger and keep going + { + m_triggerComparator.reset(); + m_triggerState = TriggerUntriggered; + ++begin; + continue; + } + else // this was the last trigger then start trace + { + m_traceStart = true; // start trace processing + m_nbSamples = m_traceSize + m_maxTraceDelay; + m_triggerComparator.reset(); + m_triggerState = TriggerTriggered; + break; + } + } + } + + // look for trigger + if (m_triggerComparator.triggered(*begin, triggerCondition)) + { + if (triggerCondition.m_triggerData.m_triggerDelay > 0) + { + triggerCondition.m_triggerDelayCount = triggerCondition.m_triggerData.m_triggerDelay; // initialize delayed samples counter + m_triggerState = TriggerDelay; + ++begin; + continue; + } + + if (nextTrigger()) // move to next trigger and keep going + { + m_triggerComparator.reset(); + m_triggerState = TriggerUntriggered; + } + else // this was the last trigger then start trace + { + m_traceStart = true; // start trace processing + m_nbSamples = m_traceSize + m_maxTraceDelay; + m_triggerComparator.reset(); + m_triggerState = TriggerTriggered; + break; + } + } + + ++begin; + } + + count = begin - cbegin; + } + + m_traceDiscreteMemory.consume(count); +} + +void ScopeVisMulti::processSources() +{ + if (m_glScope->getDataChanged()) // optimization: process trace only if required by glScope + { + m_triggerState = TriggerUntriggered; + return; + } + + uint32_t nbSources = m_traceDiscreteMemory.getNbSources(); + + for (int is = 0; is < nbSources; is++) + { + SampleVector::iterator begin = m_traceDiscreteMemory.getBeginIterator(is); // trigger position + SampleVector::iterator end = begin + m_traceSize; + + if (m_traceStart) + { + // trace back + if (m_maxTraceDelay > 0) + { + processTraces(begin - m_preTriggerDelay - m_maxTraceDelay, begin - m_preTriggerDelay, true, is); + } + + // pre-trigger + if (m_preTriggerDelay > 0) + { + processTraces(begin - m_preTriggerDelay, begin, false, is); + } + + m_traceStart = false; + } + + processTraces(begin, end, false, is); + m_traceDiscreteMemory.markEnd(is, end); + } // sources loop + + m_glScope->newTraces(&m_traces.m_traces[m_traces.currentBufferIndex()]); + m_traces.switchBuffer(); + m_traceDiscreteMemory.store(); +} + +void ScopeVisMulti::processMemorySources() +{ + if ((m_currentTraceMemoryIndex > 0) && (m_currentTraceMemoryIndex < m_nbTraceMemories)) + { + uint32_t nbSources = m_traceDiscreteMemory.getNbSources(); + + for (int is = 0; is < nbSources; is++) + { + SampleVector::const_iterator mend = m_traceDiscreteMemory.m_traceBackBuffers[is].at(m_currentTraceMemoryIndex).m_endPoint; + SampleVector::const_iterator mbegin = mend - m_traceSize; + SampleVector::const_iterator mbegin_tb = mbegin - m_maxTraceDelay; + processTraces(mbegin_tb, mbegin, true, is); // traceback + processTraces(mbegin, mend, false, is); + } + } +} + + +bool ScopeVisMulti::nextTrigger() +{ + TriggerCondition& triggerCondition = m_triggerConditions[m_currentTriggerIndex]; // current trigger condition + + if (triggerCondition.m_triggerData.m_triggerRepeat > 0) + { + if (triggerCondition.m_triggerCounter < triggerCondition.m_triggerData.m_triggerRepeat) + { + triggerCondition.m_triggerCounter++; + return true; // not final keep going + } + else + { + triggerCondition.m_triggerCounter = 0; // reset for next time + } + } + + if (m_currentTriggerIndex < m_triggerConditions.size() - 1) // check if next trigger is available + { + m_currentTriggerIndex++; + return true; // not final keep going + } + else + { + // now this is really finished + m_currentTriggerIndex = 0; + return false; // final + } +} + +void ScopeVisMulti::processTraces(const SampleVector::const_iterator& cbegin, const SampleVector::const_iterator& end, bool traceBack, int sourceIndex) +{ + SampleVector::const_iterator begin(cbegin); + int shift = (m_timeOfsProMill / 1000.0) * m_traceSize; + int length = m_traceSize / m_timeBase; + int nbSamples = end - begin; + + while (begin < end) + { + std::vector::iterator itCtl = m_traces.m_tracesControl.begin(); + std::vector::iterator itData = m_traces.m_tracesData.begin(); + std::vector::iterator itTrace = m_traces.m_traces[m_traces.currentBufferIndex()].begin(); + + for (; itCtl != m_traces.m_tracesControl.end(); ++itCtl, ++itData, ++itTrace) + { + if (itData->m_inputIndex != sourceIndex) { + continue; + } + + if (traceBack && ((end - begin) > itData->m_traceDelay)) { // before start of trace + continue; + } + + ProjectionType projectionType = itData->m_projectionType; + + if (itCtl->m_traceCount[m_traces.currentBufferIndex()] < m_traceSize) + { + int& traceCount = itCtl->m_traceCount[m_traces.currentBufferIndex()]; // reference for code clarity + float v; + + if (projectionType == ProjectionMagLin) + { + v = (itCtl->m_projector.run(*begin) - itData->m_ofs)*itData->m_amp - 1.0f; + } + else if (projectionType == ProjectionMagDB) + { + // there is no processing advantage in direct calculation without projector +// uint32_t magsq = begin->m_real*begin->m_real + begin->m_imag*begin->m_imag; +// v = ((log10f(magsq/1073741824.0f)*0.2f - 2.0f*itData->m_ofs) + 2.0f)*itData->m_amp - 1.0f; + float pdB = itCtl->m_projector.run(*begin); + float p = pdB - (100.0f * itData->m_ofs); + v = ((p/50.0f) + 2.0f)*itData->m_amp - 1.0f; + + if ((traceCount >= shift) && (traceCount < shift+length)) // power display overlay values construction + { + if (traceCount == shift) + { + itCtl->m_maxPow = -200.0f; + itCtl->m_sumPow = 0.0f; + itCtl->m_nbPow = 1; + } + + if (pdB > -200.0f) + { + if (pdB > itCtl->m_maxPow) + { + itCtl->m_maxPow = pdB; + } + + itCtl->m_sumPow += pdB; + itCtl->m_nbPow++; + } + } + + if ((nbSamples == 1) && (itCtl->m_nbPow > 0)) // on last sample create power display overlay + { + double avgPow = itCtl->m_sumPow / itCtl->m_nbPow; + double peakToAvgPow = itCtl->m_maxPow - avgPow; + itData->m_textOverlay = QString("%1 %2 %3").arg(itCtl->m_maxPow, 0, 'f', 1).arg(avgPow, 0, 'f', 1).arg(peakToAvgPow, 4, 'f', 1, ' '); + itCtl->m_nbPow = 0; + } + } + else + { + v = (itCtl->m_projector.run(*begin) - itData->m_ofs) * itData->m_amp; + } + + if(v > 1.0f) { + v = 1.0f; + } else if (v < -1.0f) { + v = -1.0f; + } + + (*itTrace)[2*traceCount] + = traceCount - shift; // display x + (*itTrace)[2*traceCount + 1] = v; // display y + traceCount++; + } + } + + ++begin; + nbSamples--; + } +} + +bool ScopeVisMulti::handleMessage(const Message& message) +{ + qDebug() << "ScopeVisNG::handleMessage" << message.getIdentifier(); + + if (DSPSignalNotification::match(message)) + { + DSPSignalNotification& notif = (DSPSignalNotification&) message; + setSampleRate(notif.getSampleRate()); + qDebug() << "ScopeVisNG::handleMessage: DSPSignalNotification: m_sampleRate: " << m_sampleRate; + return true; + } + else if (MsgConfigureScopeVisNG::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgConfigureScopeVisNG& conf = (MsgConfigureScopeVisNG&) message; + + uint32_t nbSources = conf.getNbSources(); + uint32_t traceSize = conf.getTraceSize(); + uint32_t timeBase = conf.getTimeBase(); + uint32_t timeOfsProMill = conf.getTimeOfsProMill(); + uint32_t triggerPre = conf.getTriggerPre(); + bool freeRun = conf.getFreeRun(); + + if (m_nbSources != nbSources) + { + if (nbSources == 0) { + m_nbSources = 1; + } else if (nbSources > m_maxNbTraceSources) { + m_nbSources = m_maxNbTraceSources; + } else { + m_nbSources = nbSources; + } + } + + if (m_traceSize != traceSize) + { + m_traceSize = traceSize; + m_traces.resize(m_traceSize); + m_traceDiscreteMemory.resizeBuffers(m_traceSize); + initTraceBuffers(); + + if (m_glScope) { + m_glScope->setTraceSize(m_traceSize); + } + } + + if (m_timeBase != timeBase) + { + m_timeBase = timeBase; + + if (m_glScope) { + m_glScope->setTimeBase(m_timeBase); + } + } + + if (m_timeOfsProMill != timeOfsProMill) + { + m_timeOfsProMill = timeOfsProMill; + + if (m_glScope) { + m_glScope->setTimeOfsProMill(m_timeOfsProMill); + } + } + + if (m_preTriggerDelay != triggerPre) + { + m_preTriggerDelay = triggerPre; + m_glScope->setTriggerPre(m_preTriggerDelay); + } + + if (freeRun != m_freeRun) + { + m_freeRun = freeRun; + } + + qDebug() << "ScopeVisNG::handleMessage: MsgConfigureScopeVisNG:" + << " m_traceSize: " << m_traceSize + << " m_timeOfsProMill: " << m_timeOfsProMill + << " m_preTriggerDelay: " << m_preTriggerDelay + << " m_freeRun: " << m_freeRun; + + if ((m_glScope) && (m_currentTraceMemoryIndex > 0)) { + processMemorySources(); + } + + return true; + } + else if (MsgScopeVisNGAddTrigger::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisNGAddTrigger& conf = (MsgScopeVisNGAddTrigger&) message; + m_triggerConditions.push_back(TriggerCondition(conf.getTriggerData())); + m_triggerConditions.back().initProjector(); + return true; + } + else if (MsgScopeVisNGChangeTrigger::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisNGChangeTrigger& conf = (MsgScopeVisNGChangeTrigger&) message; + int triggerIndex = conf.getTriggerIndex(); + + if (triggerIndex < m_triggerConditions.size()) + { + m_triggerConditions[triggerIndex].setData(conf.getTriggerData()); + + if (triggerIndex == m_focusedTriggerIndex) + { + computeDisplayTriggerLevels(); + m_glScope->setFocusedTriggerData(m_triggerConditions[m_focusedTriggerIndex].m_triggerData); + updateGLScopeDisplay(); + } + } + + return true; + } + else if (MsgScopeVisNGRemoveTrigger::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisNGRemoveTrigger& conf = (MsgScopeVisNGRemoveTrigger&) message; + int triggerIndex = conf.getTriggerIndex(); + + if (triggerIndex < m_triggerConditions.size()) { + m_triggerConditions.erase(m_triggerConditions.begin() + triggerIndex); + } + + return true; + } + else if (MsgScopeVisNGMoveTrigger::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisNGMoveTrigger& conf = (MsgScopeVisNGMoveTrigger&) message; + int triggerIndex = conf.getTriggerIndex(); + + if (!conf.getMoveUp() && (triggerIndex == 0)) { + return true; + } + + int nextTriggerIndex = (triggerIndex + (conf.getMoveUp() ? 1 : -1)) % m_triggerConditions.size(); + + TriggerCondition nextTrigger = m_triggerConditions[nextTriggerIndex]; + m_triggerConditions[nextTriggerIndex] = m_triggerConditions[triggerIndex]; + m_triggerConditions[triggerIndex] = nextTrigger; + + computeDisplayTriggerLevels(); + m_glScope->setFocusedTriggerData(m_triggerConditions[m_focusedTriggerIndex].m_triggerData); + updateGLScopeDisplay(); + + return true; + } + else if (MsgScopeVisNGFocusOnTrigger::match(message)) + { + MsgScopeVisNGFocusOnTrigger& conf = (MsgScopeVisNGFocusOnTrigger&) message; + int triggerIndex = conf.getTriggerIndex(); + + if (triggerIndex < m_triggerConditions.size()) + { + m_focusedTriggerIndex = triggerIndex; + computeDisplayTriggerLevels(); + m_glScope->setFocusedTriggerData(m_triggerConditions[m_focusedTriggerIndex].m_triggerData); + updateGLScopeDisplay(); + } + + return true; + } + else if (MsgScopeVisNGAddTrace::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisNGAddTrace& conf = (MsgScopeVisNGAddTrace&) message; + m_traces.addTrace(conf.getTraceData(), m_traceSize); + initTraceBuffers(); + updateMaxTraceDelay(); + computeDisplayTriggerLevels(); + updateGLScopeDisplay(); + return true; + } + else if (MsgScopeVisNGChangeTrace::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisNGChangeTrace& conf = (MsgScopeVisNGChangeTrace&) message; + bool doComputeTriggerLevelsOnDisplay = m_traces.isVerticalDisplayChange(conf.getTraceData(), conf.getTraceIndex()); + m_traces.changeTrace(conf.getTraceData(), conf.getTraceIndex()); + updateMaxTraceDelay(); + if (doComputeTriggerLevelsOnDisplay) computeDisplayTriggerLevels(); + updateGLScopeDisplay(); + return true; + } + else if (MsgScopeVisNGRemoveTrace::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisNGRemoveTrace& conf = (MsgScopeVisNGRemoveTrace&) message; + m_traces.removeTrace(conf.getTraceIndex()); + updateMaxTraceDelay(); + computeDisplayTriggerLevels(); + updateGLScopeDisplay(); + return true; + } + else if (MsgScopeVisNGMoveTrace::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisNGMoveTrace& conf = (MsgScopeVisNGMoveTrace&) message; + m_traces.moveTrace(conf.getTraceIndex(), conf.getMoveUp()); + //updateMaxTraceDelay(); + computeDisplayTriggerLevels(); + updateGLScopeDisplay(); + return true; + } + else if (MsgScopeVisNGFocusOnTrace::match(message)) + { + MsgScopeVisNGFocusOnTrace& conf = (MsgScopeVisNGFocusOnTrace&) message; + int traceIndex = conf.getTraceIndex(); + + if (traceIndex < m_traces.m_tracesData.size()) + { + m_focusedTraceIndex = traceIndex; + computeDisplayTriggerLevels(); + m_glScope->setFocusedTraceIndex(m_focusedTraceIndex); + updateGLScopeDisplay(); + } + + return true; + } + else if (MsgScopeVisNGOneShot::match(message)) + { + MsgScopeVisNGOneShot& conf = (MsgScopeVisNGOneShot&) message; + bool oneShot = conf.getOneShot(); + m_triggerOneShot = oneShot; + if (m_triggerWaitForReset && !oneShot) m_triggerWaitForReset = false; + return true; + } + else if (MsgScopeVisNGMemoryTrace::match(message)) + { + MsgScopeVisNGMemoryTrace& conf = (MsgScopeVisNGMemoryTrace&) message; + uint32_t memoryIndex = conf.getMemoryIndex(); + + if (memoryIndex != m_currentTraceMemoryIndex) + { + m_currentTraceMemoryIndex = memoryIndex; + + if (m_currentTraceMemoryIndex > 0) { + processMemorySources(); + } + } + return true; + } + else + { + return false; + } +} + +void ScopeVisMulti::updateMaxTraceDelay() +{ + int maxTraceDelay = 0; + bool allocateCache = false; + uint32_t projectorCounts[(int) nbProjectionTypes]; + memset(projectorCounts, 0, ((int) nbProjectionTypes)*sizeof(uint32_t)); + std::vector::iterator itData = m_traces.m_tracesData.begin(); + std::vector::iterator itCtrl = m_traces.m_tracesControl.begin(); + + for (; itData != m_traces.m_tracesData.end(); ++itData, ++itCtrl) + { + if (itData->m_traceDelay > maxTraceDelay) + { + maxTraceDelay = itData->m_traceDelay; + } + + if (projectorCounts[(int) itData->m_projectionType] > 0) + { + allocateCache = true; + itCtrl->m_projector.setCacheMaster(false); + } + else + { + itCtrl->m_projector.setCacheMaster(true); + } + + projectorCounts[(int) itData->m_projectionType]++; + } + + itCtrl = m_traces.m_tracesControl.begin(); + + for (; itCtrl != m_traces.m_tracesControl.end(); ++itCtrl) + { + if (allocateCache) { + itCtrl->m_projector.setCache(m_projectorCache); + } else { + itCtrl->m_projector.setCache(0); + } + } + + m_maxTraceDelay = maxTraceDelay; +} + +void ScopeVisMulti::initTraceBuffers() +{ + int shift = (m_timeOfsProMill / 1000.0) * m_traceSize; + + std::vector::iterator it0 = m_traces.m_traces[0].begin(); + std::vector::iterator it1 = m_traces.m_traces[1].begin(); + + for (; it0 != m_traces.m_traces[0].end(); ++it0, ++it1) + { + for (int i = 0; i < m_traceSize; i++) + { + (*it0)[2*i] = (i - shift); // display x + (*it0)[2*i + 1] = 0.0f; // display y + (*it1)[2*i] = (i - shift); // display x + (*it1)[2*i + 1] = 0.0f; // display y + } + } +} + +void ScopeVisMulti::computeDisplayTriggerLevels() +{ + std::vector::iterator itData = m_traces.m_tracesData.begin(); + + for (; itData != m_traces.m_tracesData.end(); ++itData) + { + if ((m_focusedTriggerIndex < m_triggerConditions.size()) && (m_triggerConditions[m_focusedTriggerIndex].m_projector.getProjectionType() == itData->m_projectionType)) + { + float level = m_triggerConditions[m_focusedTriggerIndex].m_triggerData.m_triggerLevel; + float levelPowerLin = level + 1.0f; + float levelPowerdB = (100.0f * (level - 1.0f)); + float v; + + if (itData->m_projectionType == ProjectionMagLin) + { + v = (levelPowerLin - itData->m_ofs)*itData->m_amp - 1.0f; + } + else if (itData->m_projectionType == ProjectionMagDB) + { + float ofsdB = itData->m_ofs * 100.0f; + v = ((levelPowerdB + 100.0f - ofsdB)*itData->m_amp)/50.0f - 1.0f; + } + else + { + v = (level - itData->m_ofs) * itData->m_amp; + } + + if(v > 1.0f) { + v = 1.0f; + } else if (v < -1.0f) { + v = -1.0f; + } + + itData->m_triggerDisplayLevel = v; + } + else + { + itData->m_triggerDisplayLevel = 2.0f; + } + } +} + +void ScopeVisMulti::updateGLScopeDisplay() +{ + if (m_currentTraceMemoryIndex > 0) { + m_glScope->setConfigChanged(); + processMemorySources(); + } else { + m_glScope->updateDisplay(); + } +} + +void ScopeVisMulti::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} diff --git a/sdrbase/dsp/scopevismulti.h b/sdrbase/dsp/scopevismulti.h new file mode 100644 index 000000000..3dcbc167c --- /dev/null +++ b/sdrbase/dsp/scopevismulti.h @@ -0,0 +1,1162 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 F4EXB // +// written by Edouard Griffiths // +// // +// ScopeVis class specialized for multiple sources handling // +// // +// 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 // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_SCOPEVISMULTI_H_ +#define SDRBASE_DSP_SCOPEVISMULTI_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include "dsp/dsptypes.h" +#include "util/export.h" +#include "util/message.h" +#include "util/messagequeue.h" +#include "util/doublebuffer.h" + +class GLScopeMulti; + +class SDRANGEL_API ScopeVisMulti : public QObject { + Q_OBJECT +public: + enum ProjectionType + { + ProjectionReal = 0, //!< Extract real part + ProjectionImag, //!< Extract imaginary part + ProjectionMagLin, //!< Calculate linear magnitude or modulus + ProjectionMagDB, //!< Calculate logarithmic (dB) of squared magnitude + ProjectionPhase, //!< Calculate phase + ProjectionDPhase, //!< Calculate phase derivative i.e. instantaneous frequency scaled to sample rate + nbProjectionTypes //!< Gives the number of projections in the enum + }; + + struct TraceData + { + ProjectionType m_projectionType; //!< Complex to real projection type + uint32_t m_inputIndex; //!< Input or feed index this trace is associated with + float m_amp; //!< Amplification factor + uint32_t m_ampIndex; //!< Index in list of amplification factors + float m_ofs; //!< Offset factor + int m_ofsCoarse; //!< Coarse offset slider value + int m_ofsFine; //!< Fine offset slider value + int m_traceDelay; //!< Trace delay in number of samples + int m_traceDelayCoarse; //!< Coarse delay slider value + int m_traceDelayFine; //!< Fine delay slider value + float m_triggerDisplayLevel; //!< Displayable trigger display level in -1:+1 scale. Off scale if not displayable. + QColor m_traceColor; //!< Trace display color + float m_traceColorR; //!< Trace display color - red shortcut + float m_traceColorG; //!< Trace display color - green shortcut + float m_traceColorB; //!< Trace display color - blue shortcut + bool m_hasTextOverlay; //!< True if a text overlay has to be displayed + QString m_textOverlay; //!< Text overlay to display + bool m_viewTrace; //!< Trace visibility + + TraceData() : + m_projectionType(ProjectionReal), + m_inputIndex(0), + m_amp(1.0f), + m_ampIndex(0), + m_ofs(0.0f), + m_ofsCoarse(0), + m_ofsFine(0), + m_traceDelay(0), + m_traceDelayCoarse(0), + m_traceDelayFine(0), + m_triggerDisplayLevel(2.0), // OVer scale by default (2.0) + m_traceColor(255,255,64), + m_hasTextOverlay(false), + m_viewTrace(true) + { + setColor(m_traceColor); + } + + void setColor(QColor color) + { + m_traceColor = color; + qreal r,g,b,a; + m_traceColor.getRgbF(&r, &g, &b, &a); + m_traceColorR = r; + m_traceColorG = g; + m_traceColorB = b; + } + }; + + + struct TriggerData + { + ProjectionType m_projectionType; //!< Complex to real projection type + uint32_t m_inputIndex; //!< Input or feed index this trigger is associated with + Real m_triggerLevel; //!< Level in real units + int m_triggerLevelCoarse; + int m_triggerLevelFine; + bool m_triggerPositiveEdge; //!< Trigger on the positive edge (else negative) + bool m_triggerBothEdges; //!< Trigger on both edges (else only one) + uint32_t m_triggerDelay; //!< Delay before the trigger is kicked off in number of samples (trigger delay) + double m_triggerDelayMult; //!< Trigger delay as a multiplier of trace length + int m_triggerDelayCoarse; + int m_triggerDelayFine; + uint32_t m_triggerRepeat; //!< Number of trigger conditions before the final decisive trigger + QColor m_triggerColor; //!< Trigger line display color + float m_triggerColorR; //!< Trigger line display color - red shortcut + float m_triggerColorG; //!< Trigger line display color - green shortcut + float m_triggerColorB; //!< Trigger line display color - blue shortcut + + TriggerData() : + m_projectionType(ProjectionReal), + m_inputIndex(0), + m_triggerLevel(0.0f), + m_triggerLevelCoarse(0), + m_triggerLevelFine(0), + m_triggerPositiveEdge(true), + m_triggerBothEdges(false), + m_triggerDelay(0), + m_triggerDelayMult(0.0), + m_triggerDelayCoarse(0), + m_triggerDelayFine(0), + m_triggerRepeat(0), + m_triggerColor(0,255,0) + { + setColor(m_triggerColor); + } + + void setColor(QColor color) + { + m_triggerColor = color; + qreal r,g,b,a; + m_triggerColor.getRgbF(&r, &g, &b, &a); + m_triggerColorR = r; + m_triggerColorG = g; + m_triggerColorB = b; + } + }; + + static const uint32_t m_traceChunkSize = 4800; + static const uint32_t m_maxNbTriggers = 10; + static const uint32_t m_maxNbTraces = 10; + static const uint32_t m_nbTraceMemories = 16; + static const uint32_t m_maxNbTraceSources = 4; + + ScopeVisMulti(GLScopeMulti* glScope = 0); + ~ScopeVisMulti(); + + void setSampleRate(int sampleRate); + void configure(uint32_t nbSources, uint32_t traceSize, uint32_t timeBase, uint32_t timeOfsProMill, uint32_t triggerPre, bool freeRun); + void addTrace(const TraceData& traceData); + void changeTrace(const TraceData& traceData, uint32_t traceIndex); + void removeTrace(uint32_t traceIndex); + void moveTrace(uint32_t traceIndex, bool upElseDown); + void focusOnTrace(uint32_t traceIndex); + void addTrigger(const TriggerData& triggerData); + void changeTrigger(const TriggerData& triggerData, uint32_t triggerIndex); + void removeTrigger(uint32_t triggerIndex); + void moveTrigger(uint32_t triggerIndex, bool upElseDown); + void focusOnTrigger(uint32_t triggerIndex); + void setOneShot(bool oneShot); + void setMemoryIndex(uint32_t memoryIndex); + + void getTriggerData(TriggerData& triggerData, uint32_t triggerIndex) + { + if (triggerIndex < m_triggerConditions.size()) + { + triggerData = m_triggerConditions[triggerIndex].m_triggerData; + } + } + + void getTraceData(TraceData& traceData, uint32_t traceIndex) + { + if (traceIndex < m_traces.m_tracesData.size()) + { + traceData = m_traces.m_tracesData[traceIndex]; + } + } + + const TriggerData& getTriggerData(uint32_t triggerIndex) const { return m_triggerConditions[triggerIndex].m_triggerData; } + const std::vector& getTracesData() const { return m_traces.m_tracesData; } + uint32_t getNbTriggers() const { return m_triggerConditions.size(); } + + void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, uint32_t sourceIndex); + +protected: + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + MessageQueue m_outputMessageQueue; //!< Queue for asynchronous outbound communication + + bool handleMessage(const Message& message); + +protected slots: + void handleInputMessages(); + +private: + // === messages === + // --------------------------------------------- + class MsgConfigureScopeVisNG : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgConfigureScopeVisNG* create( + uint32_t nbSources, + uint32_t traceSize, + uint32_t timeBase, + uint32_t timeOfsProMill, + uint32_t triggerPre, + bool freeRun) + { + return new MsgConfigureScopeVisNG(nbSources, traceSize, timeBase, timeOfsProMill, triggerPre, freeRun); + } + + uint32_t getNbSources() const { return m_nbSources; } + uint32_t getTraceSize() const { return m_traceSize; } + uint32_t getTimeBase() const { return m_timeBase; } + uint32_t getTimeOfsProMill() const { return m_timeOfsProMill; } + uint32_t getTriggerPre() const { return m_triggerPre; } + bool getFreeRun() const { return m_freeRun; } + + private: + uint32_t m_nbSources; + uint32_t m_traceSize; + uint32_t m_timeBase; + uint32_t m_timeOfsProMill; + uint32_t m_triggerPre; + bool m_freeRun; + + MsgConfigureScopeVisNG( + uint32_t nbSources, + uint32_t traceSize, + uint32_t timeBase, + uint32_t timeOfsProMill, + uint32_t triggerPre, + bool freeRun) : + m_nbSources(nbSources), + m_traceSize(traceSize), + m_timeBase(timeBase), + m_timeOfsProMill(timeOfsProMill), + m_triggerPre(triggerPre), + m_freeRun(freeRun) + {} + }; + + // --------------------------------------------- + class MsgScopeVisNGAddTrigger : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisNGAddTrigger* create( + const TriggerData& triggerData) + { + return new MsgScopeVisNGAddTrigger(triggerData); + } + + const TriggerData& getTriggerData() const { return m_triggerData; } + + private: + TriggerData m_triggerData; + + MsgScopeVisNGAddTrigger(const TriggerData& triggerData) : + m_triggerData(triggerData) + {} + }; + + // --------------------------------------------- + class MsgScopeVisNGChangeTrigger : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisNGChangeTrigger* create( + const TriggerData& triggerData, uint32_t triggerIndex) + { + return new MsgScopeVisNGChangeTrigger(triggerData, triggerIndex); + } + + const TriggerData& getTriggerData() const { return m_triggerData; } + uint32_t getTriggerIndex() const { return m_triggerIndex; } + + private: + TriggerData m_triggerData; + uint32_t m_triggerIndex; + + MsgScopeVisNGChangeTrigger(const TriggerData& triggerData, uint32_t triggerIndex) : + m_triggerData(triggerData), + m_triggerIndex(triggerIndex) + {} + }; + + // --------------------------------------------- + class MsgScopeVisNGRemoveTrigger : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisNGRemoveTrigger* create( + uint32_t triggerIndex) + { + return new MsgScopeVisNGRemoveTrigger(triggerIndex); + } + + uint32_t getTriggerIndex() const { return m_triggerIndex; } + + private: + uint32_t m_triggerIndex; + + MsgScopeVisNGRemoveTrigger(uint32_t triggerIndex) : + m_triggerIndex(triggerIndex) + {} + }; + + // --------------------------------------------- + class MsgScopeVisNGMoveTrigger : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisNGMoveTrigger* create( + uint32_t triggerIndex, + bool moveUpElseDown) + { + return new MsgScopeVisNGMoveTrigger(triggerIndex, moveUpElseDown); + } + + uint32_t getTriggerIndex() const { return m_triggerIndex; } + bool getMoveUp() const { return m_moveUpElseDown; } + + private: + uint32_t m_triggerIndex; + bool m_moveUpElseDown; + + MsgScopeVisNGMoveTrigger(uint32_t triggerIndex, bool moveUpElseDown) : + m_triggerIndex(triggerIndex), + m_moveUpElseDown(moveUpElseDown) + {} + }; + + // --------------------------------------------- + class MsgScopeVisNGFocusOnTrigger : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisNGFocusOnTrigger* create( + uint32_t triggerIndex) + { + return new MsgScopeVisNGFocusOnTrigger(triggerIndex); + } + + uint32_t getTriggerIndex() const { return m_triggerIndex; } + + private: + uint32_t m_triggerIndex; + + MsgScopeVisNGFocusOnTrigger(uint32_t triggerIndex) : + m_triggerIndex(triggerIndex) + {} + }; + + // --------------------------------------------- + class MsgScopeVisNGAddTrace : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisNGAddTrace* create( + const TraceData& traceData) + { + return new MsgScopeVisNGAddTrace(traceData); + } + + const TraceData& getTraceData() const { return m_traceData; } + + private: + TraceData m_traceData; + + MsgScopeVisNGAddTrace(const TraceData& traceData) : + m_traceData(traceData) + {} + }; + + // --------------------------------------------- + class MsgScopeVisNGChangeTrace : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisNGChangeTrace* create( + const TraceData& traceData, uint32_t traceIndex) + { + return new MsgScopeVisNGChangeTrace(traceData, traceIndex); + } + + const TraceData& getTraceData() const { return m_traceData; } + uint32_t getTraceIndex() const { return m_traceIndex; } + + private: + TraceData m_traceData; + uint32_t m_traceIndex; + + MsgScopeVisNGChangeTrace(TraceData traceData, uint32_t traceIndex) : + m_traceData(traceData), + m_traceIndex(traceIndex) + {} + }; + + // --------------------------------------------- + class MsgScopeVisNGRemoveTrace : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisNGRemoveTrace* create( + uint32_t traceIndex) + { + return new MsgScopeVisNGRemoveTrace(traceIndex); + } + + uint32_t getTraceIndex() const { return m_traceIndex; } + + private: + uint32_t m_traceIndex; + + MsgScopeVisNGRemoveTrace(uint32_t traceIndex) : + m_traceIndex(traceIndex) + {} + }; + + // --------------------------------------------- + class MsgScopeVisNGMoveTrace : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisNGMoveTrace* create( + uint32_t traceIndex, + bool moveUpElseDown) + { + return new MsgScopeVisNGMoveTrace(traceIndex, moveUpElseDown); + } + + uint32_t getTraceIndex() const { return m_traceIndex; } + bool getMoveUp() const { return m_moveUpElseDown; } + + private: + uint32_t m_traceIndex; + bool m_moveUpElseDown; + + MsgScopeVisNGMoveTrace(uint32_t traceIndex, bool moveUpElseDown) : + m_traceIndex(traceIndex), + m_moveUpElseDown(moveUpElseDown) + {} + }; + + // --------------------------------------------- + class MsgScopeVisNGFocusOnTrace : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisNGFocusOnTrace* create( + uint32_t traceIndex) + { + return new MsgScopeVisNGFocusOnTrace(traceIndex); + } + + uint32_t getTraceIndex() const { return m_traceIndex; } + + private: + uint32_t m_traceIndex; + + MsgScopeVisNGFocusOnTrace(uint32_t traceIndex) : + m_traceIndex(traceIndex) + {} + }; + + // --------------------------------------------- + class MsgScopeVisNGOneShot : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisNGOneShot* create( + bool oneShot) + { + return new MsgScopeVisNGOneShot(oneShot); + } + + bool getOneShot() const { return m_oneShot; } + + private: + bool m_oneShot; + + MsgScopeVisNGOneShot(bool oneShot) : + m_oneShot(oneShot) + {} + }; + + // --------------------------------------------- + class MsgScopeVisNGMemoryTrace : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisNGMemoryTrace* create( + uint32_t memoryIndex) + { + return new MsgScopeVisNGMemoryTrace(memoryIndex); + } + + uint32_t getMemoryIndex() const { return m_memoryIndex; } + + private: + uint32_t m_memoryIndex; + + MsgScopeVisNGMemoryTrace(uint32_t memoryIndex) : + m_memoryIndex(memoryIndex) + {} + }; + + // --------------------------------------------- + + /** + * Projection stuff + */ + class Projector + { + public: + Projector(ProjectionType projectionType) : + m_projectionType(projectionType), + m_prevArg(0.0f), + m_cache(0), + m_cacheMaster(true) + {} + + ~Projector() + {} + + ProjectionType getProjectionType() const { return m_projectionType; } + void settProjectionType(ProjectionType projectionType) { m_projectionType = projectionType; } + void setCache(Real *cache) { m_cache = cache; } + void setCacheMaster(bool cacheMaster) { m_cacheMaster = cacheMaster; } + + Real run(const Sample& s) + { + Real v; + + if ((m_cache) && !m_cacheMaster) { + return m_cache[(int) m_projectionType]; + } + else + { + switch (m_projectionType) + { + case ProjectionImag: + v = s.m_imag / 32768.0f; + break; + case ProjectionMagLin: + { + uint32_t magsq = s.m_real*s.m_real + s.m_imag*s.m_imag; + v = std::sqrt(magsq/1073741824.0f); + } + break; + case ProjectionMagDB: + { + uint32_t magsq = s.m_real*s.m_real + s.m_imag*s.m_imag; + v = log10f(magsq/1073741824.0f) * 10.0f; + } + break; + case ProjectionPhase: + v = std::atan2((float) s.m_imag, (float) s.m_real) / M_PI; + break; + case ProjectionDPhase: + { + Real curArg = std::atan2((float) s.m_imag, (float) s.m_real); + Real dPhi = (curArg - m_prevArg) / M_PI; + m_prevArg = curArg; + + if (dPhi < -1.0f) { + dPhi += 2.0f; + } else if (dPhi > 1.0f) { + dPhi -= 2.0f; + } + + v = dPhi; + } + break; + case ProjectionReal: + default: + v = s.m_real / 32768.0f; + break; + } + + if (m_cache) { + m_cache[(int) m_projectionType] = v; + } + + return v; + } + } + + private: + ProjectionType m_projectionType; + Real m_prevArg; + Real *m_cache; + bool m_cacheMaster; + }; + + /** + * Trigger stuff + */ + enum TriggerState + { + TriggerUntriggered, //!< Trigger is not kicked off yet (or trigger list is empty) + TriggerTriggered, //!< Trigger has been kicked off + TriggerDelay, //!< Trigger conditions have been kicked off but it is waiting for delay before final kick off + TriggerNewConfig, //!< Special condition when a new configuration has been received + }; + + struct TriggerCondition + { + public: + Projector m_projector; + TriggerData m_triggerData; //!< Trigger data + bool m_prevCondition; //!< Condition (above threshold) at previous sample + uint32_t m_triggerDelayCount; //!< Counter of samples for delay + uint32_t m_triggerCounter; //!< Counter of trigger occurences + + TriggerCondition(const TriggerData& triggerData) : + m_projector(ProjectionReal), + m_triggerData(triggerData), + m_prevCondition(false), + m_triggerDelayCount(0), + m_triggerCounter(0) + { + } + + ~TriggerCondition() + { + } + + void initProjector() + { + m_projector.settProjectionType(m_triggerData.m_projectionType); + } + + void releaseProjector() + { + } + + void setData(const TriggerData& triggerData) + { + m_triggerData = triggerData; + + if (m_projector.getProjectionType() != m_triggerData.m_projectionType) + { + m_projector.settProjectionType(m_triggerData.m_projectionType); + } + + m_prevCondition = false; + m_triggerDelayCount = 0; + m_triggerCounter = 0; + } + + void operator=(const TriggerCondition& other) + { + setData(other.m_triggerData); + } + }; + + /** + * Complex trace stuff + */ + typedef DoubleBufferSimple TraceBuffer; + + struct TraceBackBuffer + { + TraceBuffer m_traceBuffer; + SampleVector::iterator m_endPoint; + + TraceBackBuffer() + { + m_endPoint = m_traceBuffer.getCurrent(); + } + + void resize(uint32_t size) + { + m_traceBuffer.resize(size); + } + + void reset() + { + m_traceBuffer.reset(); + } + + void write(const SampleVector::const_iterator begin, const SampleVector::const_iterator end) + { + m_traceBuffer.write(begin, end); + } + + unsigned int absoluteFill() const { + return m_traceBuffer.absoluteFill(); + } + + SampleVector::iterator getCurrent() { return m_traceBuffer.getCurrent(); } + }; + + typedef std::vector TBBVector; + + struct TraceBackDiscreteMemory + { + std::vector m_traceBackBuffers; + std::vector m_sourceFill; + uint32_t m_nbSources; + uint32_t m_memSize; + uint32_t m_currentMemIndex; + uint32_t m_traceSize; + int m_preTrigCount; + + /** + * Allocate with maximum number of traces + */ + TraceBackDiscreteMemory() : + m_nbSources(1), + m_memSize(m_nbTraceMemories), + m_currentMemIndex(0), + m_traceSize(m_traceChunkSize) + { + m_traceBackBuffers.resize(m_nbSources); + m_sourceFill.resize(m_nbSources); + resizeBuffers(m_traceSize); + } + + /** + * Resize all trace buffers in memory + */ + void resizeBuffers(uint32_t size) + { + m_traceSize = size; + std::vector::iterator itFill = m_sourceFill.begin(); + + for (std::vector::iterator itTBB = m_traceBackBuffers.begin(); itTBB != m_traceBackBuffers.end(); ++itTBB) + { + for (std::vector::iterator it = itTBB->begin(); it != itTBB->end(); ++it) + { + it->resize(4*m_traceSize); + } + + *itFill = 0; + ++itFill; + } + } + + /** + * Set the number of sources + */ + void setNbSources(uint32_t nbSources) + { + if ((nbSources < 1) || (nbSources > m_maxNbTraceSources)) { + return; + } + + if (nbSources != m_nbSources) + { + m_traceBackBuffers.resize(nbSources); + resizeBuffers(m_traceSize); + m_nbSources = nbSources; + } + } + + /** + * Feed current buffer at source index with sample + */ + void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, uint32_t sourceIndex) + { + m_traceBackBuffers[sourceIndex][m_currentMemIndex].write(begin, end); + m_sourceFill[sourceIndex] += end - begin; + } + + /** + * Return the minimum number of samples in buffer + */ + int minFill() const + { + return *std::min_element(m_sourceFill.begin(), m_sourceFill.end()); + } + + /** + * Consume a number of samples from the buffers (that is reduce the fill count) + */ + void consume(int nbSamples) + { + for (std::vector::iterator itFill = m_sourceFill.begin(); itFill != m_sourceFill.end(); ++itFill) + { + (*itFill) -= nbSamples; + } + } + + SampleVector::iterator getBeginIterator(int sourceIndex) + { + return m_traceBackBuffers[sourceIndex][m_currentMemIndex].getCurrent() - m_sourceFill[sourceIndex]; + } + + uint32_t getNbSources() const + { + return m_nbSources; + } + + void markEnd(int sourceIndex, SampleVector::iterator& end) + { + m_traceBackBuffers[sourceIndex][m_currentMemIndex].m_endPoint = end; + } + + void store() + { + uint32_t nextMemIndex = m_currentMemIndex < (m_memSize-1) ? m_currentMemIndex+1 : 0; + + for (std::vector::iterator itTBB = m_traceBackBuffers.begin(); itTBB != m_traceBackBuffers.end(); ++itTBB) + { + (*itTBB)[nextMemIndex].reset(); + (*itTBB)[nextMemIndex].write( + (*itTBB)[m_currentMemIndex].m_endPoint - m_traceSize, + (*itTBB)[m_currentMemIndex].m_endPoint); + } + + m_currentMemIndex = nextMemIndex; + } + }; + + /** + * Displayable trace stuff + */ + struct TraceControl + { + Projector m_projector; //!< Projector transform from complex trace to real (displayable) trace + int m_traceCount[2]; //!< Count of samples processed (double buffered) + Real m_maxPow; //!< Maximum power over the current trace for MagDB overlay display + Real m_sumPow; //!< Cumulative power over the current trace for MagDB overlay display + int m_nbPow; //!< Number of power samples over the current trace for MagDB overlay display + + TraceControl() : m_projector(ProjectionReal) + { + reset(); + } + + ~TraceControl() + { + } + + void initProjector(ProjectionType projectionType) + { + m_projector.settProjectionType(projectionType); + } + + void releaseProjector() + { + } + + void reset() + { + m_traceCount[0] = 0; + m_traceCount[1] = 0; + m_maxPow = 0.0f; + m_sumPow = 0.0f; + m_nbPow = 0; + } + }; + + struct Traces + { + std::vector m_tracesControl; //!< Corresponding traces control data + std::vector m_tracesData; //!< Corresponding traces data + std::vector m_traces[2]; //!< Double buffer of traces processed by glScope + int m_traceSize; //!< Current size of a trace in buffer + int m_maxTraceSize; //!< Maximum Size of a trace in buffer + bool evenOddIndex; //!< Even (true) or odd (false) index + + Traces() : evenOddIndex(true), m_traceSize(0), m_maxTraceSize(0), m_x0(0), m_x1(0) + { + } + + ~Traces() + { + if (m_x0) delete[] m_x0; + if (m_x1) delete[] m_x1; + m_maxTraceSize = 0; + } + + bool isVerticalDisplayChange(const TraceData& traceData, uint32_t traceIndex) + { + return (m_tracesData[traceIndex].m_projectionType != traceData.m_projectionType) + || (m_tracesData[traceIndex].m_amp != traceData.m_amp) + || (m_tracesData[traceIndex].m_ofs != traceData.m_ofs + || (m_tracesData[traceIndex].m_traceColor != traceData.m_traceColor)); + } + + void addTrace(const TraceData& traceData, int traceSize) + { + if (m_traces[0].size() < m_maxNbTraces) + { + m_traces[0].push_back(0); + m_traces[1].push_back(0); + m_tracesData.push_back(traceData); + m_tracesControl.push_back(TraceControl()); + m_tracesControl.back().initProjector(traceData.m_projectionType); + + resize(traceSize); + } + } + + void changeTrace(const TraceData& traceData, uint32_t traceIndex) + { + if (traceIndex < m_tracesControl.size()) { + m_tracesControl[traceIndex].releaseProjector(); + m_tracesControl[traceIndex].initProjector(traceData.m_projectionType); + m_tracesData[traceIndex] = traceData; + } + } + + void removeTrace(uint32_t traceIndex) + { + if (traceIndex < m_tracesControl.size()) + { + m_traces[0].erase(m_traces[0].begin() + traceIndex); + m_traces[1].erase(m_traces[1].begin() + traceIndex); + m_tracesControl[traceIndex].releaseProjector(); + m_tracesControl.erase(m_tracesControl.begin() + traceIndex); + m_tracesData.erase(m_tracesData.begin() + traceIndex); + + resize(m_traceSize); // reallocate pointers + } + } + + void moveTrace(uint32_t traceIndex, bool upElseDown) + { + if ((!upElseDown) && (traceIndex == 0)) { + return; + } + + int nextControlIndex = (traceIndex + (upElseDown ? 1 : -1)) % (m_tracesControl.size()); + int nextDataIndex = (traceIndex + (upElseDown ? 1 : -1)) % (m_tracesData.size()); // should be the same + + m_tracesControl[traceIndex].releaseProjector(); + m_tracesControl[nextControlIndex].releaseProjector(); + + TraceControl nextControl = m_tracesControl[nextControlIndex]; + m_tracesControl[nextControlIndex] = m_tracesControl[traceIndex]; + m_tracesControl[traceIndex] = nextControl; + + TraceData nextData = m_tracesData[nextDataIndex]; + m_tracesData[nextDataIndex] = m_tracesData[traceIndex]; + m_tracesData[traceIndex] = nextData; + + m_tracesControl[traceIndex].initProjector(m_tracesData[traceIndex].m_projectionType); + m_tracesControl[nextControlIndex].initProjector(m_tracesData[nextDataIndex].m_projectionType); + } + + void resize(int traceSize) + { + m_traceSize = traceSize; + + if (m_traceSize > m_maxTraceSize) + { + delete[] m_x0; + delete[] m_x1; + m_x0 = new float[2*m_traceSize*m_maxNbTraces]; + m_x1 = new float[2*m_traceSize*m_maxNbTraces]; + + m_maxTraceSize = m_traceSize; + } + + std::fill_n(m_x0, 2*m_traceSize*m_traces[0].size(), 0.0f); + std::fill_n(m_x1, 2*m_traceSize*m_traces[0].size(), 0.0f); + + for (int i = 0; i < m_traces[0].size(); i++) + { + (m_traces[0])[i] = &m_x0[2*m_traceSize*i]; + (m_traces[1])[i] = &m_x1[2*m_traceSize*i]; + } + } + + uint32_t currentBufferIndex() const { return evenOddIndex? 0 : 1; } + uint32_t size() const { return m_tracesControl.size(); } + + void switchBuffer() + { + evenOddIndex = !evenOddIndex; + + for (std::vector::iterator it = m_tracesControl.begin(); it != m_tracesControl.end(); ++it) + { + it->m_traceCount[currentBufferIndex()] = 0; + } + } + + private: + float *m_x0; + float *m_x1; + }; + + class TriggerComparator + { + public: + TriggerComparator() : m_level(0), m_reset(true) + { + computeLevels(); + } + + bool triggered(const Sample& s, TriggerCondition& triggerCondition) + { + if (triggerCondition.m_triggerData.m_triggerLevel != m_level) + { + m_level = triggerCondition.m_triggerData.m_triggerLevel; + computeLevels(); + } + + bool condition, trigger; + + if (triggerCondition.m_projector.getProjectionType() == ProjectionMagDB) { + condition = triggerCondition.m_projector.run(s) > m_levelPowerDB; + } else if (triggerCondition.m_projector.getProjectionType() == ProjectionMagLin) { + condition = triggerCondition.m_projector.run(s) > m_levelPowerLin; + } else { + condition = triggerCondition.m_projector.run(s) > m_level; + } + + if (m_reset) + { + triggerCondition.m_prevCondition = condition; + m_reset = false; + return false; + } + + if (triggerCondition.m_triggerData.m_triggerBothEdges) { + trigger = triggerCondition.m_prevCondition ? !condition : condition; // This is a XOR between bools + } else if (triggerCondition.m_triggerData.m_triggerPositiveEdge) { + trigger = !triggerCondition.m_prevCondition && condition; + } else { + trigger = triggerCondition.m_prevCondition && !condition; + } + +// if (trigger) { +// qDebug("ScopeVisNG::triggered: %s/%s %f/%f", +// triggerCondition.m_prevCondition ? "T" : "F", +// condition ? "T" : "F", +// triggerCondition.m_projector->run(s), +// triggerCondition.m_triggerData.m_triggerLevel); +// } + + triggerCondition.m_prevCondition = condition; + return trigger; + } + + void reset() + { + m_reset = true; + } + + private: + void computeLevels() + { + m_levelPowerLin = m_level + 1.0f; + m_levelPowerDB = (100.0f * (m_level - 1.0f)); + } + + Real m_level; + Real m_levelPowerDB; + Real m_levelPowerLin; + bool m_reset; + }; + + typedef std::vector TBMemoriesBegins; + + GLScopeMulti* m_glScope; + uint32_t m_preTriggerDelay; //!< Pre-trigger delay in number of samples + std::vector m_triggerConditions; //!< Chain of triggers + int m_currentTriggerIndex; //!< Index of current index in the chain + int m_focusedTriggerIndex; //!< Index of the trigger that has focus + TriggerState m_triggerState; //!< Current trigger state + Traces m_traces; //!< Displayable traces + int m_focusedTraceIndex; //!< Index of the trace that has focus + int m_traceSize; //!< Size of traces in number of samples + int m_nbSamples; //!< Number of samples yet to process in one complex trace + int m_timeBase; //!< Trace display time divisor + int m_timeOfsProMill; //!< Start trace shift in 1/1000 trace size + bool m_traceStart; //!< Trace is at start point + bool m_postTrigBuffering; //!< Buffering after trigger match to get enough samples for the display traces + int m_traceFill; //!< Count of samples accumulated into trace + int m_zTraceIndex; //!< Index of the trace used for Z input (luminance or false colors) + SampleVector::const_iterator m_triggerPoint; //!< Trigger start location in the samples vector + int m_sampleRate; + TraceBackDiscreteMemory m_traceDiscreteMemory; //!< Complex trace memories for triggered states. One trace history per source + bool m_freeRun; //!< True if free running (trigger globally disabled) + int m_maxTraceDelay; //!< Maximum trace delay + TriggerComparator m_triggerComparator; //!< Compares sample level to trigger level + QMutex m_mutex; + Real m_projectorCache[(int) nbProjectionTypes]; + bool m_triggerOneShot; //!< True when one shot mode is active + bool m_triggerWaitForReset; //!< In one shot mode suspended until reset by UI + uint32_t m_currentTraceMemoryIndex; //!< The current index of trace in memory (0: current) + uint32_t m_nbSources; //!< Current number of sample sources + TBMemoriesBegins m_tbMemoriesBegins; //!< Current begin iterators + + /** + * Moves on to the next trigger if any or increments trigger count if in repeat mode + * - If not final it returns true + * - If final i.e. signal is actually triggerd it returns false + */ + bool nextTrigger(); //!< Returns true if not final + + /** + * Process all input sources when ready. Assumes all memoruy traces are fed with enough samples + */ + void processSources(); + + /** + * process a trace in memory at current trace index in memory + */ + void processMemorySources(); + + /** + * Process traces from complex trace memory buffer. + * - if finished it returns the number of unprocessed samples left in the buffer + * - if not finished it returns -1 + */ + void processTraces(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool traceBack, int sourceIndex); + + /** + * Get maximum trace delay + */ + void updateMaxTraceDelay(); + + /** + * Initialize trace buffers + */ + void initTraceBuffers(); + + /** + * Calculate trigger levels on display + * - every time a trigger condition focus changes TBD + * - every time the focused trigger condition changes its projection type or level + * - every time a trace data changes: projection type, amp, offset + * - every time a trace data is added or removed + */ + void computeDisplayTriggerLevels(); + + /** + * Update glScope display + * - Live trace: call glScipe update method + * - Trace in memory: call process memory trace + */ + void updateGLScopeDisplay(); + + void lookForTrigger(); +}; + + + + +#endif /* SDRBASE_DSP_SCOPEVISMULTI_H_ */ diff --git a/sdrbase/gui/glscopemulti.cpp b/sdrbase/gui/glscopemulti.cpp new file mode 100644 index 000000000..ffa81869a --- /dev/null +++ b/sdrbase/gui/glscopemulti.cpp @@ -0,0 +1,1954 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 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 // +// // +// 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 "glscopemulti.h" + +GLScopeMulti::GLScopeMulti(QWidget* parent) : + QGLWidget(parent), + m_tracesData(0), + m_traces(0), + m_bufferIndex(0), + m_displayMode(DisplayX), + m_dataChanged(false), + m_configChanged(false), + m_displayGridIntensity(10), + m_displayTraceIntensity(50), + m_timeBase(1), + m_traceSize(0), + m_sampleRate(0), + m_triggerPre(0), + m_timeOfsProMill(0), + m_focusedTraceIndex(0), + m_timeOffset(0) +{ + setAttribute(Qt::WA_OpaquePaintEvent); + connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); + m_timer.start(50); + + m_y1Scale.setFont(font()); + m_y1Scale.setOrientation(Qt::Vertical); + m_y2Scale.setFont(font()); + m_y2Scale.setOrientation(Qt::Vertical); + m_x1Scale.setFont(font()); + m_x1Scale.setOrientation(Qt::Horizontal); + m_x2Scale.setFont(font()); + m_x2Scale.setOrientation(Qt::Horizontal); + + m_channelOverlayFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); + m_channelOverlayFont.setBold(true); + m_channelOverlayFont.setPointSize(font().pointSize()+1); + + //m_traceCounter = 0; +} + +GLScopeMulti::~GLScopeMulti() +{ + cleanup(); +} + +void GLScopeMulti::setDisplayGridIntensity(int intensity) +{ + m_displayGridIntensity = intensity; + if (m_displayGridIntensity > 100) { + m_displayGridIntensity = 100; + } else if (m_displayGridIntensity < 0) { + m_displayGridIntensity = 0; + } + update(); +} + +void GLScopeMulti::setDisplayTraceIntensity(int intensity) +{ + m_displayTraceIntensity = intensity; + if (m_displayTraceIntensity > 100) { + m_displayTraceIntensity = 100; + } else if (m_displayTraceIntensity < 0) { + m_displayTraceIntensity = 0; + } + update(); +} + +void GLScopeMulti::setTraces(std::vector* tracesData, std::vector* traces) +{ + m_tracesData = tracesData; + m_traces = traces; +} + +void GLScopeMulti::newTraces(std::vector* traces) +{ + if (traces->size() > 0) + { + if(!m_mutex.tryLock(2)) + return; + + m_traces = traces; + m_dataChanged = true; + + m_mutex.unlock(); + } +} + +void GLScopeMulti::initializeGL() +{ + QOpenGLContext *glCurrentContext = QOpenGLContext::currentContext(); + + if (glCurrentContext) { + if (QOpenGLContext::currentContext()->isValid()) { + qDebug() << "GLScopeMulti::initializeGL: context:" + << " major: " << (QOpenGLContext::currentContext()->format()).majorVersion() + << " minor: " << (QOpenGLContext::currentContext()->format()).minorVersion() + << " ES: " << (QOpenGLContext::currentContext()->isOpenGLES() ? "yes" : "no"); + } + else { + qDebug() << "GLScopeMulti::initializeGL: current context is invalid"; + } + } else { + qCritical() << "GLScopeMulti::initializeGL: no current context"; + return; + } + + QSurface *surface = glCurrentContext->surface(); + + if (surface == 0) + { + qCritical() << "GLScopeMulti::initializeGL: no surface attached"; + return; + } + else + { + if (surface->surfaceType() != QSurface::OpenGLSurface) + { + qCritical() << "GLScopeMulti::initializeGL: surface is not an OpenGLSurface: " << surface->surfaceType() + << " cannot use an OpenGL context"; + return; + } + else + { + qDebug() << "GLScopeMulti::initializeGL: OpenGL surface:" + << " class: " << (surface->surfaceClass() == QSurface::Window ? "Window" : "Offscreen"); + } + } + + connect(glCurrentContext, &QOpenGLContext::aboutToBeDestroyed, this, &GLScopeMulti::cleanup); // TODO: when migrating to QOpenGLWidget + + QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions(); + glFunctions->initializeOpenGLFunctions(); + + //glDisable(GL_DEPTH_TEST); + m_glShaderSimple.initializeGL(); + m_glShaderLeft1Scale.initializeGL(); + m_glShaderBottom1Scale.initializeGL(); + m_glShaderLeft2Scale.initializeGL(); + m_glShaderBottom2Scale.initializeGL(); + m_glShaderPowerOverlay.initializeGL(); +} + +void GLScopeMulti::resizeGL(int width, int height) +{ + QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions(); + glFunctions->glViewport(0, 0, width, height); + m_configChanged = true; +} + +void GLScopeMulti::paintGL() +{ + if(!m_mutex.tryLock(2)) + return; + + if(m_configChanged) + applyConfig(); + +// qDebug("GLScopeMulti::paintGL: m_traceCounter: %d", m_traceCounter); +// m_traceCounter = 0; + + m_dataChanged = false; + + QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions(); + glFunctions->glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glFunctions->glClear(GL_COLOR_BUFFER_BIT); + + if ((m_displayMode == DisplayX) || (m_displayMode == DisplayXYV) || (m_displayMode == DisplayXYH)) // display trace #0 + { + // 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_glScopeMatrix1, color, q3, 4); + } + + // paint grid + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + // Y1 (X trace or trace #0) + { + tickList = &m_y1Scale.getTickList(); + + GLfloat q3[4*tickList->count()]; + int effectiveTicks = 0; + + for (int i= 0; i < tickList->count(); i++) + { + tick = &(*tickList)[i]; + + if (tick->major) + { + if (tick->textSize > 0) + { + float y = 1 - (tick->pos / m_y1Scale.getSize()); + q3[4*effectiveTicks] = 0; + q3[4*effectiveTicks+1] = y; + q3[4*effectiveTicks+2] = 1; + q3[4*effectiveTicks+3] = y; + effectiveTicks++; + } + } + } + + float blue = 1.0f; + QVector4D color(1.0f, 1.0f, blue, (float) m_displayGridIntensity / 100.0f); + m_glShaderSimple.drawSegments(m_glScopeMatrix1, color, q3, 2*effectiveTicks); + } + + // X1 (time) + { + tickList = &m_x1Scale.getTickList(); + + GLfloat q3[4*tickList->count()]; + 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_x1Scale.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_glScopeMatrix1, color, q3, 2*effectiveTicks); + } + + // paint left #1 scale + { + GLfloat vtx1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + GLfloat tex1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + m_glShaderLeft1Scale.drawSurface(m_glLeft1ScaleMatrix, tex1, vtx1, 4); + } + + // paint bottom #1 scale + { + GLfloat vtx1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + GLfloat tex1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + m_glShaderBottom1Scale.drawSurface(m_glBot1ScaleMatrix, tex1, vtx1, 4); + } + + // paint trace #1 + if (m_traceSize > 0) + { + const float *trace = (*m_traces)[0]; + const ScopeVisMulti::TraceData& traceData = (*m_tracesData)[0]; + + if (traceData.m_viewTrace) + { + int start = (m_timeOfsProMill/1000.0) * m_traceSize; + int end = std::min(start + m_traceSize/m_timeBase, m_traceSize); + + if(end - start < 2) + start--; + + float rectX = m_glScopeRect1.x(); + float rectY = m_glScopeRect1.y() + m_glScopeRect1.height() / 2.0f; + float rectW = m_glScopeRect1.width() * (float)m_timeBase / (float)(m_traceSize - 1); + //float rectH = -(m_glScopeRect1.height() / 2.0f) * traceData.m_amp; + float rectH = -m_glScopeRect1.height() / 2.0f; + + //QVector4D color(1.0f, 1.0f, 0.25f, m_displayTraceIntensity / 100.0f); + QVector4D color(traceData.m_traceColorR, traceData.m_traceColorG, traceData.m_traceColorB, m_displayTraceIntensity / 100.0f); + 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_glShaderSimple.drawPolyline(mat, color, (GLfloat *) &trace[2*start], end - start); + + // Paint trigger level if any + if ((traceData.m_triggerDisplayLevel > -1.0f) && (traceData.m_triggerDisplayLevel < 1.0f)) + { + GLfloat q3[] { + 0, traceData.m_triggerDisplayLevel, + 1, traceData.m_triggerDisplayLevel + }; + + float rectX = m_glScopeRect1.x(); + float rectY = m_glScopeRect1.y() + m_glScopeRect1.height() / 2.0f; + float rectW = m_glScopeRect1.width(); + float rectH = -m_glScopeRect1.height() / 2.0f; + + QVector4D color( + m_focusedTriggerData.m_triggerColorR, + m_focusedTriggerData.m_triggerColorG, + m_focusedTriggerData.m_triggerColorB, + 0.4f); + 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_glShaderSimple.drawSegments(mat, color, q3, 2); + } // display trigger + + // Paint overlay if any + if ((m_focusedTraceIndex == 0) && (traceData.m_hasTextOverlay)) + { + drawChannelOverlay( + traceData.m_textOverlay, + traceData.m_traceColor, + m_channelOverlayPixmap1, + m_glScopeRect1); + } // display overlay + } // displayable trace + } // trace length > 0 + } // Display X + + if ((m_displayMode == DisplayY) || (m_displayMode == DisplayXYV) || (m_displayMode == DisplayXYH)) // display traces #1..n + { + // 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_glScopeMatrix2, color, q3, 4); + } + + // paint grid + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + // Y2 (Focused Y trace) + { + tickList = &m_y2Scale.getTickList(); + + GLfloat q3[4*tickList->count()]; + int effectiveTicks = 0; + + for (int i= 0; i < tickList->count(); i++) + { + tick = &(*tickList)[i]; + + if (tick->major) + { + if (tick->textSize > 0) + { + float y = 1 - (tick->pos / m_y2Scale.getSize()); + q3[4*effectiveTicks] = 0; + q3[4*effectiveTicks+1] = y; + q3[4*effectiveTicks+2] = 1; + q3[4*effectiveTicks+3] = y; + effectiveTicks++; + } + } + } + + float blue = 1.0f; + QVector4D color(1.0f, 1.0f, blue, (float) m_displayGridIntensity / 100.0f); + m_glShaderSimple.drawSegments(m_glScopeMatrix2, color, q3, 2*effectiveTicks); + } + + // X2 (time) + { + tickList = &m_x2Scale.getTickList(); + + GLfloat q3[4*tickList->count()]; + 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_x2Scale.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_glScopeMatrix2, color, q3, 2*effectiveTicks); + } + + // paint left #2 scale + { + GLfloat vtx1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + GLfloat tex1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + m_glShaderLeft2Scale.drawSurface(m_glLeft2ScaleMatrix, tex1, vtx1, 4); + } + + // paint bottom #2 scale + { + GLfloat vtx1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + GLfloat tex1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + m_glShaderBottom2Scale.drawSurface(m_glBot2ScaleMatrix, tex1, vtx1, 4); + } + + // paint traces #1..n + if (m_traceSize > 0) + { + int start = (m_timeOfsProMill/1000.0) * m_traceSize; + int end = std::min(start + m_traceSize/m_timeBase, m_traceSize); + + if(end - start < 2) + start--; + + for (int i = 1; i < m_traces->size(); i++) + { + const float *trace = (*m_traces)[i]; + const ScopeVisMulti::TraceData& traceData = (*m_tracesData)[i]; + + if (!traceData.m_viewTrace) { + continue; + } + + float rectX = m_glScopeRect2.x(); + float rectY = m_glScopeRect2.y() + m_glScopeRect2.height() / 2.0f; + float rectW = m_glScopeRect2.width() * (float)m_timeBase / (float)(m_traceSize - 1); + //float rectH = -(m_glScopeRect1.height() / 2.0f) * traceData.m_amp; + float rectH = -m_glScopeRect2.height() / 2.0f; + + //QVector4D color(1.0f, 1.0f, 0.25f, m_displayTraceIntensity / 100.0f); + QVector4D color(traceData.m_traceColorR, traceData.m_traceColorG, traceData.m_traceColorB, m_displayTraceIntensity / 100.0f); + 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_glShaderSimple.drawPolyline(mat, color, (GLfloat *) &trace[2*start], end - start); + + // Paint trigger level if any + if ((traceData.m_triggerDisplayLevel > -1.0f) && (traceData.m_triggerDisplayLevel < 1.0f)) + { + GLfloat q3[] { + 0, traceData.m_triggerDisplayLevel, + 1, traceData.m_triggerDisplayLevel + }; + + float rectX = m_glScopeRect2.x(); + float rectY = m_glScopeRect2.y() + m_glScopeRect2.height() / 2.0f; + float rectW = m_glScopeRect2.width(); + float rectH = -m_glScopeRect2.height() / 2.0f; + + QVector4D color( + m_focusedTriggerData.m_triggerColorR, + m_focusedTriggerData.m_triggerColorG, + m_focusedTriggerData.m_triggerColorB, + 0.4f); + 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_glShaderSimple.drawSegments(mat, color, q3, 2); + } + + // Paint overlay if any + if ((i == m_focusedTraceIndex) && (traceData.m_hasTextOverlay)) + { + drawChannelOverlay( + traceData.m_textOverlay, + traceData.m_traceColor, + m_channelOverlayPixmap2, + m_glScopeRect2); + } + + } // one trace display + } // trace length > 0 + } // Display Y + + if (m_displayMode == DisplayPol) + { + // paint left display: mixed XY + + // 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_glScopeMatrix1, color, q3, 4); + + } + + // paint grid + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + // Horizontal Y1 + tickList = &m_y1Scale.getTickList(); + { + GLfloat q3[4*tickList->count()]; + int effectiveTicks = 0; + + for (int i= 0; i < tickList->count(); i++) + { + tick = &(*tickList)[i]; + + if (tick->major) + { + if (tick->textSize > 0) + { + float y = 1 - (tick->pos / m_y1Scale.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, 0.25f, (float) m_displayGridIntensity / 100.0f); + m_glShaderSimple.drawSegments(m_glScopeMatrix1, color, q3, 2*effectiveTicks); + } + + // Vertical X1 + tickList = &m_x1Scale.getTickList(); + { + GLfloat q3[4*tickList->count()]; + 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_x1Scale.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_glScopeMatrix1, color, q3, 2*effectiveTicks); + } + + // paint left #1 scale + { + GLfloat vtx1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + GLfloat tex1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + + m_glShaderLeft1Scale.drawSurface(m_glLeft1ScaleMatrix, tex1, vtx1, 4); + } + + // paint bottom #1 scale + { + GLfloat vtx1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + GLfloat tex1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + + m_glShaderBottom1Scale.drawSurface(m_glBot1ScaleMatrix, tex1, vtx1, 4); + } + + // Paint secondary grid + + // Horizontal Y2 + tickList = &m_y2Scale.getTickList(); + { + GLfloat q3[4*tickList->count()]; + int effectiveTicks = 0; + for(int i= 0; i < tickList->count(); i++) { + tick = &(*tickList)[i]; + if(tick->major) { + if(tick->textSize > 0) { + float y = 1 - (tick->pos / m_y2Scale.getSize()); + q3[4*effectiveTicks] = 0; + q3[4*effectiveTicks+1] = y; + q3[4*effectiveTicks+2] = 1; + q3[4*effectiveTicks+3] = y; + effectiveTicks++; + } + } + } + + QVector4D color(0.25f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f); + m_glShaderSimple.drawSegments(m_glScopeMatrix1, color, q3, 2*effectiveTicks); + } + + // Paint secondary scale + { + GLfloat vtx1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + GLfloat tex1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + + m_glShaderLeft2Scale.drawSurface(m_glRight1ScaleMatrix, tex1, vtx1, 4); + } + + // paint all traces + if (m_traceSize > 0) + { + int start = (m_timeOfsProMill/1000.0) * m_traceSize; + int end = std::min(start + m_traceSize/m_timeBase, m_traceSize); + + if(end - start < 2) + start--; + + for (int i = 0; i < m_traces->size(); i++) + { + const float *trace = (*m_traces)[i]; + const ScopeVisMulti::TraceData& traceData = (*m_tracesData)[i]; + + if (!traceData.m_viewTrace) { + continue; + } + + float rectX = m_glScopeRect1.x(); + float rectY = m_glScopeRect1.y() + m_glScopeRect1.height() / 2.0f; + float rectW = m_glScopeRect1.width() * (float)m_timeBase / (float)(m_traceSize - 1); + //float rectH = -(m_glScopeRect1.height() / 2.0f) * traceData.m_amp; + float rectH = -m_glScopeRect1.height() / 2.0f; + + //QVector4D color(1.0f, 1.0f, 0.25f, m_displayTraceIntensity / 100.0f); + QVector4D color(traceData.m_traceColorR, traceData.m_traceColorG, traceData.m_traceColorB, m_displayTraceIntensity / 100.0f); + 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_glShaderSimple.drawPolyline(mat, color, (GLfloat *) &trace[2*start], end - start); + + // Paint trigger level if any + if ((traceData.m_triggerDisplayLevel > -1.0f) && (traceData.m_triggerDisplayLevel < 1.0f)) + { + GLfloat q3[] { + 0, traceData.m_triggerDisplayLevel, + 1, traceData.m_triggerDisplayLevel + }; + + float rectX = m_glScopeRect1.x(); + float rectY = m_glScopeRect1.y() + m_glScopeRect1.height() / 2.0f; + float rectW = m_glScopeRect1.width(); + float rectH = -m_glScopeRect1.height() / 2.0f; + + QVector4D color( + m_focusedTriggerData.m_triggerColorR, + m_focusedTriggerData.m_triggerColorG, + m_focusedTriggerData.m_triggerColorB, + 0.4f); + 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_glShaderSimple.drawSegments(mat, color, q3, 2); + } + + // Paint overlay if any + if ((i == m_focusedTraceIndex) && (traceData.m_hasTextOverlay)) + { + drawChannelOverlay( + traceData.m_textOverlay, + traceData.m_traceColor, + m_channelOverlayPixmap1, + m_glScopeRect1); + } + } // all traces display + } // trace length > 0 + + // paint right display: polar XY + + // 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_glScopeMatrix2, color, q3, 4); + } + + // paint grid + + // Horizontal Y2 + tickList = &m_y2Scale.getTickList(); + { + GLfloat q3[4*tickList->count()]; + int effectiveTicks = 0; + for(int i= 0; i < tickList->count(); i++) { + tick = &(*tickList)[i]; + if(tick->major) { + if(tick->textSize > 0) { + float y = 1 - (tick->pos / m_y2Scale.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_glScopeMatrix2, color, q3, 2*effectiveTicks); + } + + // Vertical X2 + tickList = &m_x2Scale.getTickList(); + { + GLfloat q3[4*tickList->count()]; + 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_x2Scale.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_glScopeMatrix2, color, q3, 2*effectiveTicks); + } + + // paint left #2 scale + { + GLfloat vtx1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + GLfloat tex1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + + m_glShaderLeft2Scale.drawSurface(m_glLeft2ScaleMatrix, tex1, vtx1, 4); + } + + // paint bottom #2 scale + { + GLfloat vtx1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + GLfloat tex1[] = { + 0, 1, + 1, 1, + 1, 0, + 0, 0 + }; + + m_glShaderBottom2Scale.drawSurface(m_glBot2ScaleMatrix, tex1, vtx1, 4); + } + + // paint polar traces + + if (m_traceSize > 0) + { + int start = (m_timeOfsProMill/1000.0) * m_traceSize; + int end = std::min(start + m_traceSize/m_timeBase, m_traceSize); + + if(end - start < 2) + start--; + + GLfloat q3[2*(end - start)]; + const float *trace0 = (*m_traces)[0]; + memcpy(q3, &(trace0[2*start+1]), (2*(end - start) - 1)*sizeof(float)); // copy X values + + for (int i = 1; i < m_traces->size(); i++) + { + const float *trace = (*m_traces)[i]; + const ScopeVisMulti::TraceData& traceData = (*m_tracesData)[i]; + + if (!traceData.m_viewTrace) { + continue; + } + + for(int i = start; i < end; i++) + { + float y = trace[2*i+1]; + q3[2*(i-start)+1] = y; + } + + float rectX = m_glScopeRect2.x() + m_glScopeRect2.width() / 2.0f; + float rectY = m_glScopeRect2.y() + m_glScopeRect2.height() / 2.0f; + float rectW = m_glScopeRect2.width() / 2.0f; + float rectH = -(m_glScopeRect2.height() / 2.0f); + + QVector4D color(traceData.m_traceColorR, traceData.m_traceColorG, traceData.m_traceColorB, m_displayTraceIntensity / 100.0f); + 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_glShaderSimple.drawPolyline(mat, color, q3, end -start); + } // XY polar display + } // trace length > 0 + } // XY mixed + polar display + + m_mutex.unlock(); +} + +void GLScopeMulti::setSampleRate(int sampleRate) +{ + m_sampleRate = sampleRate; + m_configChanged = true; + update(); + emit sampleRateChanged(m_sampleRate); +} + +void GLScopeMulti::setTimeBase(int timeBase) +{ + m_timeBase = timeBase; + m_configChanged = true; + update(); +} + +void GLScopeMulti::setTriggerPre(uint32_t triggerPre) +{ + m_triggerPre = triggerPre; + m_configChanged = true; + update(); +} + +void GLScopeMulti::setTimeOfsProMill(int timeOfsProMill) +{ + m_timeOfsProMill = timeOfsProMill; + m_configChanged = true; + update(); +} + +void GLScopeMulti::setFocusedTraceIndex(uint32_t traceIndex) +{ + m_focusedTraceIndex = traceIndex; + m_configChanged = true; + update(); +} + +void GLScopeMulti::setDisplayMode(DisplayMode displayMode) +{ + m_displayMode = displayMode; + m_configChanged = true; + update(); +} + +void GLScopeMulti::setTraceSize(int traceSize) +{ + m_traceSize = traceSize; + m_configChanged = true; + update(); +} + +void GLScopeMulti::updateDisplay() +{ + m_configChanged = true; + update(); +} + +void GLScopeMulti::applyConfig() +{ + m_configChanged = false; + + QFontMetrics fm(font()); + int M = fm.width("-"); + //float t_start = ((m_timeOfsProMill / 1000.0) * ((float) m_traceSize / m_sampleRate)) - ((float) m_triggerPre / m_sampleRate); + float t_start = (((m_timeOfsProMill / 1000.0f) * (float) m_traceSize) / m_sampleRate) - ((float) m_triggerPre / m_sampleRate); + float t_len = ((float) m_traceSize / m_sampleRate) / (float) m_timeBase; + + // scales + + m_x1Scale.setRange(Unit::Time, t_start, t_start + t_len); // time scale + + if (m_displayMode == DisplayPol) + { + setYScale(m_x2Scale, 0); // polar scale (X) + } + else + { + m_x2Scale.setRange(Unit::Time, t_start, t_start + t_len); // time scale + } + + if (m_traces->size() > 0) + { + setYScale(m_y1Scale, 0); // This is always the X trace (trace #0) + } + + if ((m_traces->size() > 1) && (m_focusedTraceIndex < m_traces->size())) + { + setYScale(m_y2Scale, m_focusedTraceIndex > 0 ? m_focusedTraceIndex : 1); // if Highlighted trace is #0 (X trace) set it to first Y trace (trace #1) + } + else + { + setYScale(m_y2Scale, 0); // Default to the X trace (trace #0) - If there is only one trace it should not get there (Y displays disabled in the UI) + } + + // display arrangements + + if ((m_displayMode == DisplayX) || (m_displayMode == DisplayY)) // unique displays + { + setUniqueDisplays(); + } + else if (m_displayMode == DisplayXYV) // both displays vertically arranged + { + setVerticalDisplays(); + } + else if (m_displayMode == DisplayXYH) // both displays horizontally arranged + { + setHorizontalDisplays(); + } + else if (m_displayMode == DisplayPol) // horizontal arrangement: XY stacked on left and polar on right + { + setPolarDisplays(); + } +} + +void GLScopeMulti::setUniqueDisplays() +{ + QFontMetrics fm(font()); + int M = fm.width("-"); + int scopeHeight = height() - m_topMargin - m_botMargin; + int scopeWidth = width() - m_leftMargin - m_rightMargin; + + // X display + + m_glScopeRect1 = QRectF( + (float) m_leftMargin / (float) width(), + (float) m_topMargin / (float) height(), + (float) scopeWidth / (float) width(), + (float) scopeHeight / (float) height() + ); + + m_glScopeMatrix1.setToIdentity(); + m_glScopeMatrix1.translate ( + -1.0f + ((float) 2*m_leftMargin / (float) width()), + 1.0f - ((float) 2*m_topMargin / (float) height()) + ); + m_glScopeMatrix1.scale ( + (float) 2*scopeWidth / (float) width(), + (float) -2*scopeHeight / (float) height() + ); + + m_glBot1ScaleMatrix.setToIdentity(); + m_glBot1ScaleMatrix.translate ( + -1.0f + ((float) 2*m_leftMargin / (float) width()), + 1.0f - ((float) 2*(scopeHeight + m_topMargin + 1) / (float) height()) + ); + m_glBot1ScaleMatrix.scale ( + (float) 2*scopeWidth / (float) width(), + (float) -2*(m_botMargin - 1) / (float) height() + ); + + m_glLeft1ScaleMatrix.setToIdentity(); + m_glLeft1ScaleMatrix.translate ( + -1.0f, + 1.0f - ((float) 2*m_topMargin / (float) height()) + ); + m_glLeft1ScaleMatrix.scale ( + (float) 2*(m_leftMargin-1) / (float) width(), + (float) -2*scopeHeight / (float) height() + ); + + // Y displays + + m_glScopeRect2 = QRectF( + (float) m_leftMargin / (float) width(), + (float) m_topMargin / (float) height(), + (float) scopeWidth / (float) width(), + (float) scopeHeight / (float) height() + ); + + m_glScopeMatrix2.setToIdentity(); + m_glScopeMatrix2.translate ( + -1.0f + ((float) 2*m_leftMargin / (float) width()), + 1.0f - ((float) 2*m_topMargin / (float) height()) + ); + m_glScopeMatrix2.scale ( + (float) 2*scopeWidth / (float) width(), + (float) -2*scopeHeight / (float) height() + ); + + m_glBot2ScaleMatrix.setToIdentity(); + m_glBot2ScaleMatrix.translate ( + -1.0f + ((float) 2*m_leftMargin / (float) width()), + 1.0f - ((float) 2*(scopeHeight + m_topMargin + 1) / (float) height()) + ); + m_glBot2ScaleMatrix.scale ( + (float) 2*scopeWidth / (float) width(), + (float) -2*(m_botMargin - 1) / (float) height() + ); + + m_glLeft2ScaleMatrix.setToIdentity(); + m_glLeft2ScaleMatrix.translate ( + -1.0f, + 1.0f - ((float) 2*m_topMargin / (float) height()) + ); + m_glLeft2ScaleMatrix.scale ( + (float) 2*(m_leftMargin-1) / (float) width(), + (float) -2*scopeHeight / (float) height() + ); + + { // X horizontal scale (X1) + m_x1Scale.setSize(scopeWidth); + + m_bot1ScalePixmap = QPixmap( + scopeWidth, + m_botMargin - 1 + ); + + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + m_bot1ScalePixmap.fill(Qt::black); + QPainter painter(&m_bot1ScalePixmap); + painter.setPen(QColor(0xf0, 0xf0, 0xff)); + painter.setFont(font()); + tickList = &m_x1Scale.getTickList(); + + for(int i = 0; i < tickList->count(); i++) { + tick = &(*tickList)[i]; + if(tick->major) { + if(tick->textSize > 0) { + painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text); + } + } + } + + m_glShaderBottom1Scale.initTexture(m_bot1ScalePixmap.toImage()); + } // X horizontal scale + + { // Y horizontal scale (X2) + m_x2Scale.setSize(scopeWidth); + + m_bot2ScalePixmap = QPixmap( + scopeWidth, + m_botMargin - 1 + ); + + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + m_bot2ScalePixmap.fill(Qt::black); + QPainter painter(&m_bot2ScalePixmap); + painter.setPen(QColor(0xf0, 0xf0, 0xff)); + painter.setFont(font()); + tickList = &m_x2Scale.getTickList(); + + for(int i = 0; i < tickList->count(); i++) { + tick = &(*tickList)[i]; + if(tick->major) { + if(tick->textSize > 0) { + painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text); + } + } + } + + m_glShaderBottom2Scale.initTexture(m_bot2ScalePixmap.toImage()); + } // Y horizontal scale + + { // X vertical scale (Y1) + m_y1Scale.setSize(scopeHeight); + + m_left1ScalePixmap = QPixmap( + m_leftMargin - 1, + scopeHeight + ); + + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + m_left1ScalePixmap.fill(Qt::black); + QPainter painter(&m_left1ScalePixmap); + painter.setPen(QColor(0xf0, 0xf0, 0xff)); + painter.setFont(font()); + tickList = &m_y1Scale.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 + scopeHeight - tick->textPos - fm.ascent()/2), tick->text); + } + } + } + + m_glShaderLeft1Scale.initTexture(m_left1ScalePixmap.toImage()); + } // X vertical scale + + { // Y vertical scale (Y2) + m_y2Scale.setSize(scopeHeight); + + m_left2ScalePixmap = QPixmap( + m_leftMargin - 1, + scopeHeight + ); + + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + m_left2ScalePixmap.fill(Qt::black); + QPainter painter(&m_left2ScalePixmap); + painter.setPen(QColor(0xf0, 0xf0, 0xff)); + painter.setFont(font()); + tickList = &m_y2Scale.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 + scopeHeight - tick->textPos - fm.ascent()/2), tick->text); + } + } + } + + m_glShaderLeft2Scale.initTexture(m_left2ScalePixmap.toImage()); + } // Y vertical scale +} + +void GLScopeMulti::setVerticalDisplays() +{ + QFontMetrics fm(font()); + int M = fm.width("-"); + int scopeHeight = (height() - m_topMargin) / 2 - m_botMargin; + int scopeWidth = width() - m_leftMargin - m_rightMargin; + + // X display + + m_glScopeRect1 = QRectF( + (float) m_leftMargin / (float) width(), + (float) m_topMargin / (float) height(), + (float) scopeWidth / (float) width(), + (float) scopeHeight / (float) height() + ); + + m_glScopeMatrix1.setToIdentity(); + m_glScopeMatrix1.translate ( + -1.0f + ((float) 2*m_leftMargin / (float) width()), + 1.0f - ((float) 2*m_topMargin / (float) height()) + ); + m_glScopeMatrix1.scale ( + (float) 2*scopeWidth / (float) width(), + (float) -2*scopeHeight / (float) height() + ); + + m_glBot1ScaleMatrix.setToIdentity(); + m_glBot1ScaleMatrix.translate ( + -1.0f + ((float) 2*m_leftMargin / (float) width()), + 1.0f - ((float) 2*(scopeHeight + m_topMargin + 1) / (float) height()) + ); + m_glBot1ScaleMatrix.scale ( + (float) 2*scopeWidth / (float) width(), + (float) -2*(m_botMargin - 1) / (float) height() + ); + + m_glLeft1ScaleMatrix.setToIdentity(); + m_glLeft1ScaleMatrix.translate ( + -1.0f, + 1.0f - ((float) 2*m_topMargin / (float) height()) + ); + m_glLeft1ScaleMatrix.scale ( + (float) 2*(m_leftMargin-1) / (float) width(), + (float) -2*scopeHeight / (float) height() + ); + + // Y display + + m_glScopeRect2 = QRectF( + (float) m_leftMargin / (float)width(), + (float) (m_botMargin + m_topMargin + scopeHeight) / (float)height(), + (float) scopeWidth / (float)width(), + (float) scopeHeight / (float)height() + ); + + m_glScopeMatrix2.setToIdentity(); + m_glScopeMatrix2.translate ( + -1.0f + ((float) 2*m_leftMargin / (float) width()), + 1.0f - ((float) 2*(m_botMargin + m_topMargin + scopeHeight) / (float) height()) + ); + m_glScopeMatrix2.scale ( + (float) 2*scopeWidth / (float) width(), + (float) -2*scopeHeight / (float) height() + ); + + m_glBot2ScaleMatrix.setToIdentity(); + m_glBot2ScaleMatrix.translate ( + -1.0f + ((float) 2*m_leftMargin / (float) width()), + 1.0f - ((float) 2*(scopeHeight + m_topMargin + scopeHeight + m_botMargin + 1) / (float) height()) + ); + m_glBot2ScaleMatrix.scale ( + (float) 2*scopeWidth / (float) width(), + (float) -2*(m_botMargin - 1) / (float) height() + ); + + m_glLeft2ScaleMatrix.setToIdentity(); + m_glLeft2ScaleMatrix.translate ( + -1.0f, + 1.0f - ((float) 2*(m_topMargin + scopeHeight + m_botMargin) / (float) height()) + ); + m_glLeft2ScaleMatrix.scale ( + (float) 2*(m_leftMargin-1) / (float) width(), + (float) -2*scopeHeight / (float) height() + ); + + { // X horizontal scale (X1) + m_x1Scale.setSize(scopeWidth); + + m_bot1ScalePixmap = QPixmap( + scopeWidth, + m_botMargin - 1 + ); + + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + m_bot1ScalePixmap.fill(Qt::black); + QPainter painter(&m_bot1ScalePixmap); + painter.setPen(QColor(0xf0, 0xf0, 0xff)); + painter.setFont(font()); + tickList = &m_x1Scale.getTickList(); + + for(int i = 0; i < tickList->count(); i++) { + tick = &(*tickList)[i]; + if(tick->major) { + if(tick->textSize > 0) { + painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text); + } + } + } + + m_glShaderBottom1Scale.initTexture(m_bot1ScalePixmap.toImage()); + } // X horizontal scale (X1) + + { // Y horizontal scale (X2) + m_x2Scale.setSize(scopeWidth); + m_bot2ScalePixmap = QPixmap( + scopeWidth, + m_botMargin - 1 + ); + + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + m_bot2ScalePixmap.fill(Qt::black); + QPainter painter(&m_bot2ScalePixmap); + painter.setPen(QColor(0xf0, 0xf0, 0xff)); + painter.setFont(font()); + tickList = &m_x2Scale.getTickList(); + + for(int i = 0; i < tickList->count(); i++) { + tick = &(*tickList)[i]; + if(tick->major) { + if(tick->textSize > 0) { + painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text); + } + } + } + + m_glShaderBottom2Scale.initTexture(m_bot2ScalePixmap.toImage()); + } // Y horizontal scale (X2) + + { // X vertical scale (Y1) + m_y1Scale.setSize(scopeHeight); + + m_left1ScalePixmap = QPixmap( + m_leftMargin - 1, + scopeHeight + ); + + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + m_left1ScalePixmap.fill(Qt::black); + QPainter painter(&m_left1ScalePixmap); + painter.setPen(QColor(0xf0, 0xf0, 0xff)); + painter.setFont(font()); + tickList = &m_y1Scale.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 + scopeHeight - tick->textPos - fm.ascent()/2), tick->text); + } + } + } + + m_glShaderLeft1Scale.initTexture(m_left1ScalePixmap.toImage()); + } // X vertical scale (Y1) + + { // Y vertical scale (Y2) + m_y2Scale.setSize(scopeHeight); + + m_left2ScalePixmap = QPixmap( + m_leftMargin - 1, + scopeHeight + ); + + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + m_left2ScalePixmap.fill(Qt::black); + QPainter painter(&m_left2ScalePixmap); + painter.setPen(QColor(0xf0, 0xf0, 0xff)); + painter.setFont(font()); + tickList = &m_y2Scale.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 + scopeHeight - tick->textPos - fm.ascent()/2), tick->text); + } + } + } + + m_glShaderLeft2Scale.initTexture(m_left2ScalePixmap.toImage()); + } // Y vertical scale (Y2) +} + +void GLScopeMulti::setHorizontalDisplays() +{ + QFontMetrics fm(font()); + int M = fm.width("-"); + int scopeHeight = height() - m_topMargin - m_botMargin; + int scopeWidth = (width() - m_rightMargin)/2 - m_leftMargin; + + // X display + + m_glScopeRect1 = QRectF( + (float) m_leftMargin / (float) width(), + (float) m_topMargin / (float) height(), + (float) scopeWidth / (float) width(), + (float) scopeHeight / (float) height() + ); + m_glScopeMatrix1.setToIdentity(); + m_glScopeMatrix1.translate ( + -1.0f + ((float) 2*m_leftMargin / (float) width()), + 1.0f - ((float) 2*m_topMargin / (float) height()) + ); + m_glScopeMatrix1.scale ( + (float) 2*scopeWidth / (float) width(), + (float) -2*scopeHeight / (float) height() + ); + + m_glBot1ScaleMatrix.setToIdentity(); + m_glBot1ScaleMatrix.translate ( + -1.0f + ((float) 2*m_leftMargin / (float) width()), + 1.0f - ((float) 2*(scopeHeight + m_topMargin + 1) / (float) height()) + ); + m_glBot1ScaleMatrix.scale ( + (float) 2*scopeWidth / (float) width(), + (float) -2*(m_botMargin - 1) / (float) height() + ); + + m_glLeft1ScaleMatrix.setToIdentity(); + m_glLeft1ScaleMatrix.translate ( + -1.0f, + 1.0f - ((float) 2*m_topMargin / (float) height()) + ); + m_glLeft1ScaleMatrix.scale ( + (float) 2*(m_leftMargin-1) / (float) width(), + (float) -2*scopeHeight / (float) height() + ); + + // Y display + + m_glScopeRect2 = QRectF( + (float)(m_leftMargin + m_leftMargin + ((width() - m_leftMargin - m_leftMargin - m_rightMargin) / 2)) / (float)width(), + (float)m_topMargin / (float)height(), + (float)((width() - m_leftMargin - m_leftMargin - m_rightMargin) / 2) / (float)width(), + (float)(height() - m_topMargin - m_botMargin) / (float)height() + ); + m_glScopeMatrix2.setToIdentity(); + m_glScopeMatrix2.translate ( + -1.0f + ((float) 2*(m_leftMargin + m_leftMargin + ((width() - m_leftMargin - m_leftMargin - m_rightMargin) / 2)) / (float) width()), + 1.0f - ((float) 2*m_topMargin / (float) height()) + ); + m_glScopeMatrix2.scale ( + (float) 2*((width() - m_leftMargin - m_leftMargin - m_rightMargin) / 2) / (float) width(), + (float) -2*(height() - m_topMargin - m_botMargin) / (float) height() + ); + + m_glBot2ScaleMatrix.setToIdentity(); + m_glBot2ScaleMatrix.translate ( + -1.0f + ((float) 2*(m_leftMargin + m_leftMargin + scopeWidth) / (float) width()), + 1.0f - ((float) 2*(scopeHeight + m_topMargin + 1) / (float) height()) + ); + m_glBot2ScaleMatrix.scale ( + (float) 2*scopeWidth / (float) width(), + (float) -2*(m_botMargin - 1) / (float) height() + ); + + m_glLeft2ScaleMatrix.setToIdentity(); + m_glLeft2ScaleMatrix.translate ( + -1.0f + (float) 2*(m_leftMargin + scopeWidth) / (float) width(), + 1.0f - ((float) 2*m_topMargin / (float) height()) + ); + m_glLeft2ScaleMatrix.scale ( + (float) 2*(m_leftMargin-1) / (float) width(), + (float) -2*scopeHeight / (float) height() + ); + + { // X horizontal scale (X1) + m_x1Scale.setSize(scopeWidth); + + m_bot1ScalePixmap = QPixmap( + scopeWidth, + m_botMargin - 1 + ); + + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + m_bot1ScalePixmap.fill(Qt::black); + QPainter painter(&m_bot1ScalePixmap); + painter.setPen(QColor(0xf0, 0xf0, 0xff)); + painter.setFont(font()); + tickList = &m_x1Scale.getTickList(); + + for(int i = 0; i < tickList->count(); i++) { + tick = &(*tickList)[i]; + if(tick->major) { + if(tick->textSize > 0) { + painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text); + } + } + } + + m_glShaderBottom1Scale.initTexture(m_bot1ScalePixmap.toImage()); + + } // X horizontal scale (X1) + + { // Y horizontal scale (X2) + m_x2Scale.setSize(scopeWidth); + m_bot2ScalePixmap = QPixmap( + scopeWidth, + m_botMargin - 1 + ); + + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + m_bot2ScalePixmap.fill(Qt::black); + QPainter painter(&m_bot2ScalePixmap); + painter.setPen(QColor(0xf0, 0xf0, 0xff)); + painter.setFont(font()); + tickList = &m_x2Scale.getTickList(); + + for(int i = 0; i < tickList->count(); i++) { + tick = &(*tickList)[i]; + if(tick->major) { + if(tick->textSize > 0) { + painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text); + } + } + } + + m_glShaderBottom2Scale.initTexture(m_bot2ScalePixmap.toImage()); + } // Y horizontal scale (X2) + + { // X vertical scale (Y1) + m_y1Scale.setSize(scopeHeight); + + m_left1ScalePixmap = QPixmap( + m_leftMargin - 1, + scopeHeight + ); + + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + m_left1ScalePixmap.fill(Qt::black); + QPainter painter(&m_left1ScalePixmap); + painter.setPen(QColor(0xf0, 0xf0, 0xff)); + painter.setFont(font()); + tickList = &m_y1Scale.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 + scopeHeight - tick->textPos - fm.ascent()/2), tick->text); + } + } + } + + m_glShaderLeft1Scale.initTexture(m_left1ScalePixmap.toImage()); + } // X vertical scale (Y1) + + { // Y vertical scale (Y2) + m_y2Scale.setSize(scopeHeight); + + m_left2ScalePixmap = QPixmap( + m_leftMargin - 1, + scopeHeight + ); + + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + m_left2ScalePixmap.fill(Qt::black); + QPainter painter(&m_left2ScalePixmap); + painter.setPen(QColor(0xf0, 0xf0, 0xff)); + painter.setFont(font()); + tickList = &m_y2Scale.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 + scopeHeight - tick->textPos - fm.ascent()/2), tick->text); + } + } + } + + m_glShaderLeft2Scale.initTexture(m_left2ScalePixmap.toImage()); + } // Y vertical scale (Y2) +} + +void GLScopeMulti::setPolarDisplays() +{ + QFontMetrics fm(font()); + int M = fm.width("-"); + int scopeHeight = height() - m_topMargin - m_botMargin; + int scopeWidth = (width() - m_rightMargin)/2 - m_leftMargin; + int scopeDim = std::min(scopeWidth, scopeHeight); + scopeWidth += scopeWidth - scopeDim; + + // Mixed XY display (left) + + m_glScopeRect1 = QRectF( + (float) m_leftMargin / (float) width(), + (float) m_topMargin / (float) height(), + (float) (scopeWidth-m_leftMargin) / (float) width(), + (float) scopeHeight / (float) height() + ); + m_glScopeMatrix1.setToIdentity(); + m_glScopeMatrix1.translate ( + -1.0f + ((float) 2*m_leftMargin / (float) width()), + 1.0f - ((float) 2*m_topMargin / (float) height()) + ); + m_glScopeMatrix1.scale ( + (float) 2*(scopeWidth-m_leftMargin) / (float) width(), + (float) -2*scopeHeight / (float) height() + ); + + m_glBot1ScaleMatrix.setToIdentity(); + m_glBot1ScaleMatrix.translate ( + -1.0f + ((float) 2*m_leftMargin / (float) width()), + 1.0f - ((float) 2*(scopeHeight + m_topMargin + 1) / (float) height()) + ); + m_glBot1ScaleMatrix.scale ( + (float) 2*(scopeWidth-m_leftMargin) / (float) width(), + (float) -2*(m_botMargin - 1) / (float) height() + ); + + m_glLeft1ScaleMatrix.setToIdentity(); + m_glLeft1ScaleMatrix.translate ( + -1.0f, + 1.0f - ((float) 2*m_topMargin / (float) height()) + ); + m_glLeft1ScaleMatrix.scale ( + (float) 2*(m_leftMargin-1) / (float) width(), + (float) -2*scopeHeight / (float) height() + ); + + m_glRight1ScaleMatrix.setToIdentity(); + m_glRight1ScaleMatrix.translate ( + -1.0f + ((float) 2*scopeWidth / (float) width()), + 1.0f - ((float) 2*m_topMargin / (float) height()) + ); + m_glRight1ScaleMatrix.scale ( + (float) 2*(m_leftMargin-1) / (float) width(), + (float) -2*scopeHeight / (float) height() + ); + + // Polar XY display (right) + + m_glScopeRect2 = QRectF( + (float)(m_leftMargin + scopeWidth + m_leftMargin) / (float)width(), + (float)m_topMargin / (float)height(), + (float) scopeDim / (float)width(), + (float)(height() - m_topMargin - m_botMargin) / (float)height() + ); + m_glScopeMatrix2.setToIdentity(); + m_glScopeMatrix2.translate ( + -1.0f + ((float) 2*(m_leftMargin + scopeWidth + m_leftMargin) / (float) width()), + 1.0f - ((float) 2*m_topMargin / (float) height()) + ); + m_glScopeMatrix2.scale ( + (float) 2*scopeDim / (float) width(), + (float) -2*(height() - m_topMargin - m_botMargin) / (float) height() + ); + + m_glBot2ScaleMatrix.setToIdentity(); + m_glBot2ScaleMatrix.translate ( + -1.0f + ((float) 2*(m_leftMargin + m_leftMargin + scopeWidth) / (float) width()), + 1.0f - ((float) 2*(scopeHeight + m_topMargin + 1) / (float) height()) + ); + m_glBot2ScaleMatrix.scale ( + (float) 2*scopeDim / (float) width(), + (float) -2*(m_botMargin - 1) / (float) height() + ); + + m_glLeft2ScaleMatrix.setToIdentity(); + m_glLeft2ScaleMatrix.translate ( + -1.0f + (float) 2*(m_leftMargin + scopeWidth) / (float) width(), + 1.0f - ((float) 2*m_topMargin / (float) height()) + ); + m_glLeft2ScaleMatrix.scale ( + (float) 2*(m_leftMargin-1) / (float) width(), + (float) -2*scopeHeight / (float) height() + ); + + { // Mixed XY horizontal scale (X1) + m_x1Scale.setSize(scopeWidth); + + m_bot1ScalePixmap = QPixmap( + scopeWidth, + m_botMargin - 1 + ); + + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + m_bot1ScalePixmap.fill(Qt::black); + QPainter painter(&m_bot1ScalePixmap); + painter.setPen(QColor(0xf0, 0xf0, 0xff)); + painter.setFont(font()); + tickList = &m_x1Scale.getTickList(); + + for(int i = 0; i < tickList->count(); i++) { + tick = &(*tickList)[i]; + if(tick->major) { + if(tick->textSize > 0) { + painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text); + } + } + } + + m_glShaderBottom1Scale.initTexture(m_bot1ScalePixmap.toImage()); + } // Mixed XY horizontal scale (X1) + + { // Polar XY horizontal scale (X2) + m_x2Scale.setSize(scopeDim); + m_bot2ScalePixmap = QPixmap( + scopeDim, + m_botMargin - 1 + ); + + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + m_bot2ScalePixmap.fill(Qt::black); + QPainter painter(&m_bot2ScalePixmap); + painter.setPen(QColor(0xf0, 0xf0, 0xff)); + painter.setFont(font()); + tickList = &m_x2Scale.getTickList(); + + for(int i = 0; i < tickList->count(); i++) { + tick = &(*tickList)[i]; + if(tick->major) { + if(tick->textSize > 0) { + painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text); + } + } + } + + m_glShaderBottom2Scale.initTexture(m_bot2ScalePixmap.toImage()); + } // Polar XY horizontal scale (X2) + + { // Mixed XY vertical scale (Y1) + m_y1Scale.setSize(scopeHeight); + + m_left1ScalePixmap = QPixmap( + m_leftMargin - 1, + scopeHeight + ); + + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + m_left1ScalePixmap.fill(Qt::black); + QPainter painter(&m_left1ScalePixmap); + painter.setPen(QColor(0xf0, 0xf0, 0xff)); + painter.setFont(font()); + tickList = &m_y1Scale.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 + scopeHeight - tick->textPos - fm.ascent()/2), tick->text); + } + } + } + + m_glShaderLeft1Scale.initTexture(m_left1ScalePixmap.toImage()); + + } // Mixed XY vertical scale (Y1) + + { // Polar XY vertical scale (Y2) + m_y2Scale.setSize(scopeHeight); + + m_left2ScalePixmap = QPixmap( + m_leftMargin - 1, + scopeHeight + ); + + const ScaleEngine::TickList* tickList; + const ScaleEngine::Tick* tick; + + m_left2ScalePixmap.fill(Qt::black); + QPainter painter(&m_left2ScalePixmap); + painter.setPen(QColor(0xf0, 0xf0, 0xff)); + painter.setFont(font()); + tickList = &m_y2Scale.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 + scopeHeight - tick->textPos - fm.ascent()/2), tick->text); + } + } + } + + m_glShaderLeft2Scale.initTexture(m_left2ScalePixmap.toImage()); + } // Polar XY vertical scale (Y2) +} + +void GLScopeMulti::setYScale(ScaleEngine& scale, uint32_t highlightedTraceIndex) +{ + ScopeVisMulti::TraceData& traceData = (*m_tracesData)[highlightedTraceIndex]; + float amp_range = 2.0 / traceData.m_amp; + float amp_ofs = traceData.m_ofs; + float pow_floor = -100.0 + traceData.m_ofs * 100.0; + float pow_range = 100.0 / traceData.m_amp; + + switch (traceData.m_projectionType) + { + case ScopeVisMulti::ProjectionMagDB: // dB scale + scale.setRange(Unit::Decibel, pow_floor, pow_floor + pow_range); + break; + case ScopeVisMulti::ProjectionMagLin: + if (amp_range < 2.0) { + scale.setRange(Unit::None, amp_ofs * 1000.0, amp_range * 1000.0 + amp_ofs * 1000.0); + } else { + scale.setRange(Unit::None, amp_ofs, amp_range + amp_ofs); + } + break; + case ScopeVisMulti::ProjectionPhase: // Phase or frequency + case ScopeVisMulti::ProjectionDPhase: + scale.setRange(Unit::None, -1.0/traceData.m_amp + amp_ofs, 1.0/traceData.m_amp + amp_ofs); + break; + case ScopeVisMulti::ProjectionReal: // Linear generic + case ScopeVisMulti::ProjectionImag: + default: + if (amp_range < 2.0) { + scale.setRange(Unit::None, - amp_range * 500.0 + amp_ofs * 1000.0, amp_range * 500.0 + amp_ofs * 1000.0); + } else { + scale.setRange(Unit::None, - amp_range * 0.5 + amp_ofs, amp_range * 0.5 + amp_ofs); + } + break; + } +} + +void GLScopeMulti::drawChannelOverlay( + const QString& text, + const QColor& color, + QPixmap& channelOverlayPixmap, + const QRectF& glScopeRect) +{ + if (text.isEmpty()) { + return; + } + + QFontMetricsF metrics(m_channelOverlayFont); + QRectF rect = metrics.boundingRect(text); + channelOverlayPixmap = QPixmap(rect.width() + 4.0f, rect.height()); + channelOverlayPixmap.fill(Qt::transparent); + QPainter painter(&channelOverlayPixmap); + painter.setRenderHints(QPainter::Antialiasing|QPainter::TextAntialiasing, false); + painter.fillRect(rect, QColor(0, 0, 0, 0x80)); + QColor textColor(color); + textColor.setAlpha(0xC0); + painter.setPen(textColor); + painter.setFont(m_channelOverlayFont); + painter.drawText(QPointF(0, rect.height() - 2.0f), text); + painter.end(); + + m_glShaderPowerOverlay.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 = glScopeRect.width() - ((rect.width() + 4.0f) / width()); + float rectX = glScopeRect.x() + shiftX; + float rectY = glScopeRect.y(); + float rectW = rect.width() / (float) width(); + float rectH = rect.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_glShaderPowerOverlay.drawSurface(mat, tex1, vtx1, 4); + } +} + +void GLScopeMulti::tick() +{ + if(m_dataChanged) { + update(); + } +} + +void GLScopeMulti::connectTimer(const QTimer& timer) +{ + qDebug() << "GLScopeMulti::connectTimer"; + disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); + connect(&timer, SIGNAL(timeout()), this, SLOT(tick())); + m_timer.stop(); +} + +void GLScopeMulti::cleanup() +{ + //makeCurrent(); + m_glShaderSimple.cleanup(); + m_glShaderBottom1Scale.cleanup(); + m_glShaderBottom2Scale.cleanup(); + m_glShaderLeft1Scale.cleanup(); + m_glShaderPowerOverlay.cleanup(); + //doneCurrent(); +} + diff --git a/sdrbase/gui/glscopemulti.h b/sdrbase/gui/glscopemulti.h new file mode 100644 index 000000000..577008586 --- /dev/null +++ b/sdrbase/gui/glscopemulti.h @@ -0,0 +1,162 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 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 // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_GUI_GLSCOPEMULTI_H_ +#define SDRBASE_GUI_GLSCOPEMULTI_H_ + +#include +#include +#include +#include +#include +#include +#include "dsp/dsptypes.h" +#include "dsp/scopevismulti.h" +#include "gui/scaleengine.h" +#include "gui/glshadersimple.h" +#include "gui/glshadertextured.h" +#include "util/export.h" +#include "util/bitfieldindex.h" + +class QPainter; + +class SDRANGEL_API GLScopeMulti: public QGLWidget { + Q_OBJECT + +public: + enum DisplayMode { + DisplayXYH, + DisplayXYV, + DisplayX, + DisplayY, + DisplayPol + }; + + GLScopeMulti(QWidget* parent = 0); + virtual ~GLScopeMulti(); + + void connectTimer(const QTimer& timer); + + void setTraces(std::vector* tracesData, std::vector* traces); + void newTraces(std::vector* traces); + + int getSampleRate() const { return m_sampleRate; } + int getTraceSize() const { return m_traceSize; } + + void setTriggerPre(uint32_t triggerPre); //!< number of samples + void setTimeOfsProMill(int timeOfsProMill); + void setSampleRate(int sampleRate); + void setTimeBase(int timeBase); + void setFocusedTraceIndex(uint32_t traceIndex); + void setDisplayMode(DisplayMode displayMode); + void setTraceSize(int trceSize); + void updateDisplay(); + void setDisplayGridIntensity(int intensity); + void setDisplayTraceIntensity(int intensity); + void setFocusedTriggerData(ScopeVisMulti::TriggerData& triggerData) { m_focusedTriggerData = triggerData; } + void setConfigChanged() { m_configChanged = true; } + //void incrementTraceCounter() { m_traceCounter++; } + + bool getDataChanged() const { return m_dataChanged; } + DisplayMode getDisplayMode() const { return m_displayMode; } + +signals: + void sampleRateChanged(int); + +private: + std::vector *m_tracesData; + std::vector *m_traces; + ScopeVisMulti::TriggerData m_focusedTriggerData; + //int m_traceCounter; + uint32_t m_bufferIndex; + DisplayMode m_displayMode; + QTimer m_timer; + QMutex m_mutex; + bool m_dataChanged; + bool m_configChanged; + int m_sampleRate; + int m_timeOfsProMill; + uint32_t m_triggerPre; + int m_traceSize; + int m_timeBase; + int m_timeOffset; + uint32_t m_focusedTraceIndex; + + // graphics stuff + QRectF m_glScopeRect1; + QRectF m_glScopeRect2; + QMatrix4x4 m_glScopeMatrix1; + QMatrix4x4 m_glScopeMatrix2; + QMatrix4x4 m_glLeft1ScaleMatrix; + QMatrix4x4 m_glRight1ScaleMatrix; + QMatrix4x4 m_glLeft2ScaleMatrix; + QMatrix4x4 m_glBot1ScaleMatrix; + QMatrix4x4 m_glBot2ScaleMatrix; + + QPixmap m_left1ScalePixmap; + QPixmap m_left2ScalePixmap; + QPixmap m_bot1ScalePixmap; + QPixmap m_bot2ScalePixmap; + QPixmap m_channelOverlayPixmap1; + QPixmap m_channelOverlayPixmap2; + + int m_displayGridIntensity; + int m_displayTraceIntensity; + + ScaleEngine m_x1Scale; //!< Display #1 X scale. Time scale + ScaleEngine m_x2Scale; //!< Display #2 X scale. Time scale + ScaleEngine m_y1Scale; //!< Display #1 Y scale. Always connected to trace #0 (X trace) + ScaleEngine m_y2Scale; //!< Display #2 Y scale. Connected to highlighted Y trace (#1..n) + + QFont m_channelOverlayFont; + + GLShaderSimple m_glShaderSimple; + GLShaderTextured m_glShaderLeft1Scale; + GLShaderTextured m_glShaderBottom1Scale; + GLShaderTextured m_glShaderLeft2Scale; + GLShaderTextured m_glShaderBottom2Scale; + GLShaderTextured m_glShaderPowerOverlay; + + static const int m_topMargin = 5; + static const int m_botMargin = 20; + static const int m_leftMargin = 35; + static const int m_rightMargin = 5; + + void initializeGL(); + void resizeGL(int width, int height); + void paintGL(); + + void applyConfig(); + void setYScale(ScaleEngine& scale, uint32_t highlightedTraceIndex); + void setUniqueDisplays(); //!< Arrange displays when X and Y are unique on screen + void setVerticalDisplays(); //!< Arrange displays when X and Y are stacked vertically + void setHorizontalDisplays(); //!< Arrange displays when X and Y are stacked horizontally + void setPolarDisplays(); //!< Arrange displays when X and Y are stacked over on the left and polar display is on the right + + void drawChannelOverlay( //!< Draws a text overlay + const QString& text, + const QColor& color, + QPixmap& channelOverlayPixmap, + const QRectF& glScopeRect); + +protected slots: + void cleanup(); + void tick(); + +}; + +#endif /* SDRBASE_GUI_GLSCOPENG_H_ */ diff --git a/sdrbase/gui/glscopemultigui.cpp b/sdrbase/gui/glscopemultigui.cpp new file mode 100644 index 000000000..02d49e1c1 --- /dev/null +++ b/sdrbase/gui/glscopemultigui.cpp @@ -0,0 +1,1543 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 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 // +// // +// 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 "glscopemultigui.h" +#include "glscopemulti.h" +#include "ui_glscopemultigui.h" +#include "util/simpleserializer.h" + +const double GLScopeMultiGUI::amps[11] = { 0.2, 0.1, 0.05, 0.02, 0.01, 0.005, 0.002, 0.001, 0.0005, 0.0002, 0.0001 }; + +GLScopeMultiGUI::GLScopeMultiGUI(QWidget* parent) : + QWidget(parent), + ui(new Ui::GLScopeMultiGUI), + m_messageQueue(0), + m_glScope(0), + m_scopeVis(0), + m_sampleRate(0), + m_traceLenMult(1), + m_timeBase(1), + m_timeOffset(0) +{ + qDebug("GLScopeMultiGUI::GLScopeMultiGUI"); + setEnabled(false); + ui->setupUi(this); + ui->trigDelayFine->setMaximum(ScopeVisMulti::m_traceChunkSize / 10.0); + ui->traceColor->setStyleSheet("QLabel { background-color : rgb(255,255,64); }"); + m_focusedTraceColor.setRgb(255,255,64); + ui->trigColor->setStyleSheet("QLabel { background-color : rgb(0,255,0); }"); + m_focusedTriggerColor.setRgb(0,255,0); + ui->traceText->setText("X"); + ui->mem->setMaximum(ScopeVisMulti::m_nbTraceMemories - 1); +} + +GLScopeMultiGUI::~GLScopeMultiGUI() +{ + delete ui; +} + +void GLScopeMultiGUI::setBuddies(MessageQueue* messageQueue, ScopeVisMulti* scopeVis, GLScopeMulti* glScope) +{ + qDebug("GLScopeMultiGUI::setBuddies"); + + m_messageQueue = messageQueue; + m_scopeVis = scopeVis; + m_glScope = glScope; + + // initialize display combo + ui->onlyX->setChecked(true); + ui->onlyY->setChecked(false); + ui->horizontalXY->setChecked(false); + ui->verticalXY->setChecked(false); + ui->polar->setChecked(false); + ui->onlyY->setEnabled(false); + ui->horizontalXY->setEnabled(false); + ui->verticalXY->setEnabled(false); + ui->polar->setEnabled(false); + m_glScope->setDisplayMode(GLScopeMulti::DisplayX); + + // initialize trigger combo + ui->trigPos->setChecked(true); + ui->trigNeg->setChecked(false); + ui->trigBoth->setChecked(false); + ui->trigOneShot->setChecked(false); + ui->trigOneShot->setEnabled(false); + ui->freerun->setChecked(true); + + // Add a trigger + ScopeVisMulti::TriggerData triggerData; + fillTriggerData(triggerData); + m_scopeVis->addTrigger(triggerData); + + // Add a trace + ScopeVisMulti::TraceData traceData; + fillTraceData(traceData); + m_scopeVis->addTrace(traceData); + + setEnabled(true); + connect(m_glScope, SIGNAL(sampleRateChanged(int)), this, SLOT(on_scope_sampleRateChanged(int))); + + ui->traceMode->clear(); + fillProjectionCombo(ui->traceMode); + + ui->trigMode->clear(); + fillProjectionCombo(ui->trigMode); + + m_scopeVis->configure( + ui->sourcePort->maximum() + 1, + 2*m_traceLenMult*ScopeVisMulti::m_traceChunkSize, + m_timeBase, + m_timeOffset*10, + (uint32_t) (m_glScope->getTraceSize() * (ui->trigPre->value()/100.0f)), + ui->freerun->isChecked()); + + m_scopeVis->configure( + ui->sourcePort->maximum() + 1, + m_traceLenMult*ScopeVisMulti::m_traceChunkSize, + m_timeBase, + m_timeOffset*10, + (uint32_t) (m_glScope->getTraceSize() * (ui->trigPre->value()/100.0f)), + ui->freerun->isChecked()); + + setTraceLenDisplay(); + setTimeScaleDisplay(); + setTimeOfsDisplay(); + setAmpScaleDisplay(); + setAmpOfsDisplay(); + setTraceDelayDisplay(); +} + +void GLScopeMultiGUI::setSampleRate(int sampleRate) +{ + m_sampleRate = sampleRate; +} + +void GLScopeMultiGUI::on_scope_sampleRateChanged(int sampleRate) +{ + //m_sampleRate = m_glScope->getSampleRate(); + m_sampleRate = sampleRate; + ui->sampleRateText->setText(tr("%1\nkS/s").arg(m_sampleRate / 1000.0f, 0, 'f', 2)); + setTraceLenDisplay(); + setTimeScaleDisplay(); + setTimeOfsDisplay(); + setTraceDelayDisplay(); + setTrigPreDisplay(); + setTrigDelayDisplay(); +} + +void GLScopeMultiGUI::resetToDefaults() +{ +} + + +QByteArray GLScopeMultiGUI::serialize() const +{ + SimpleSerializer s(1); + + // first row + s.writeS32(1, (int) m_glScope->getDisplayMode()); + s.writeS32(2, ui->traceIntensity->value()); + s.writeS32(3, ui->gridIntensity->value()); + s.writeS32(4, ui->time->value()); + s.writeS32(5, ui->timeOfs->value()); + s.writeS32(6, ui->traceLen->value()); + + // second row - by trace + const std::vector& tracesData = m_scopeVis->getTracesData(); + std::vector::const_iterator traceDataIt = tracesData.begin(); + s.writeU32(10, (uint32_t) tracesData.size()); + int i = 0; + + for (; traceDataIt != tracesData.end(); ++traceDataIt, i++) + { + s.writeS32(20 + 16*i, (int) traceDataIt->m_projectionType); + s.writeU32(21 + 16*i, traceDataIt->m_ampIndex); + s.writeS32(22 + 16*i, traceDataIt->m_ofsCoarse); + s.writeS32(23 + 16*i, traceDataIt->m_ofsFine); + s.writeS32(24 + 16*i, traceDataIt->m_traceDelayCoarse); + s.writeS32(25 + 16*i, traceDataIt->m_traceDelayFine); + s.writeFloat(26 + 16*i, traceDataIt->m_traceColorR); + s.writeFloat(27 + 16*i, traceDataIt->m_traceColorG); + s.writeFloat(28 + 16*i, traceDataIt->m_traceColorB); + } + + // third row - by trigger + s.writeU32(200, (uint32_t) m_scopeVis->getNbTriggers()); + s.writeS32(201, ui->trigPre->value()); + + for (int i = 0; i < m_scopeVis->getNbTriggers(); i++) + { + const ScopeVisMulti::TriggerData& triggerData = m_scopeVis->getTriggerData(i); + s.writeS32(210 + 16*i, (int) triggerData.m_projectionType); + s.writeS32(211 + 16*i, triggerData.m_triggerRepeat); + s.writeBool(212 + 16*i, triggerData.m_triggerPositiveEdge); + s.writeBool(213 + 16*i, triggerData.m_triggerBothEdges); + s.writeS32(214 + 16*i, triggerData.m_triggerLevelCoarse); + s.writeS32(215 + 16*i, triggerData.m_triggerLevelFine); + s.writeS32(216 + 16*i, triggerData.m_triggerDelayCoarse); + s.writeS32(217 + 16*i, triggerData.m_triggerDelayFine); + s.writeFloat(218 + 16*i, triggerData.m_triggerColorR); + s.writeFloat(219 + 16*i, triggerData.m_triggerColorG); + s.writeFloat(220 + 16*i, triggerData.m_triggerColorB); + } + + return s.final(); +} + +bool GLScopeMultiGUI::deserialize(const QByteArray& data) +{ + qDebug("GLScopeMultiGUI::deserialize"); + SimpleDeserializer d(data); + + if(!d.isValid()) { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + TraceUIBlocker traceUIBlocker(ui); + TrigUIBlocker trigUIBlocker(ui); + int intValue; + uint32_t uintValue; + bool boolValue; + + ui->onlyX->setEnabled(false); + ui->onlyY->setEnabled(false); + ui->horizontalXY->setEnabled(false); + ui->verticalXY->setEnabled(false); + ui->polar->setEnabled(false); + + ui->traceMode->setCurrentIndex(intValue); + d.readS32(1, &intValue, (int) GLScopeMulti::DisplayX); + m_glScope->setDisplayMode((GLScopeMulti::DisplayMode) intValue); + + ui->onlyX->setChecked(false); + ui->onlyY->setChecked(false); + ui->horizontalXY->setChecked(false); + ui->verticalXY->setChecked(false); + ui->polar->setChecked(false); + + switch (m_glScope->getDisplayMode()) + { + case GLScopeMulti::DisplayY: + ui->onlyY->setChecked(true); + break; + case GLScopeMulti::DisplayXYH: + ui->horizontalXY->setChecked(true); + break; + case GLScopeMulti::DisplayXYV: + ui->verticalXY->setChecked(true); + break; + case GLScopeMulti::DisplayPol: + ui->polar->setChecked(true); + break; + case GLScopeMulti::DisplayX: + default: + ui->onlyX->setChecked(true); + break; + } + + d.readS32(2, &intValue, 50); + ui->traceIntensity->setValue(intValue); + d.readS32(3, &intValue, 10); + ui->gridIntensity->setValue(intValue); + d.readS32(4, &intValue, 1); + ui->time->setValue(intValue); + d.readS32(5, &intValue, 0); + ui->timeOfs->setValue(intValue); + d.readS32(6, &intValue, 1); + ui->traceLen->setValue(intValue); + + // trace stuff + + uint32_t nbTracesSaved; + d.readU32(10, &nbTracesSaved, 1); + const std::vector& tracesData = m_scopeVis->getTracesData(); + int iTrace = tracesData.size(); + + qDebug("GLScopeMultiGUI::deserialize: nbTracesSaved: %u tracesData.size(): %lu", nbTracesSaved, tracesData.size()); + + while (iTrace > nbTracesSaved) // remove possible traces in excess + { + m_scopeVis->removeTrace(iTrace - 1); + iTrace--; + } + + for (iTrace = 0; iTrace < nbTracesSaved; iTrace++) + { + ScopeVisMulti::TraceData traceData; + float r, g, b; + + d.readS32(20 + 16*iTrace, &intValue, 0); + ui->traceMode->setCurrentIndex(intValue); + d.readU32(21 + 16*iTrace, &uintValue, 0); + ui->amp->setValue(uintValue); + d.readS32(22 + 16*iTrace, &intValue, 0); + ui->ofsCoarse->setValue(intValue); + d.readS32(23 + 16*iTrace, &intValue, 0); + ui->ofsFine->setValue(intValue); + d.readS32(24 + 16*iTrace, &intValue, 0); + ui->traceDelayCoarse->setValue(intValue); + d.readS32(25 + 16*iTrace, &intValue, 0); + ui->traceDelayFine->setValue(intValue); + d.readFloat(26 + 16*iTrace, &r, 1.0f); + d.readFloat(27 + 16*iTrace, &g, 1.0f); + d.readFloat(28 + 16*iTrace, &b, 1.0f); + m_focusedTraceColor.setRgbF(r, g, b); + + fillTraceData(traceData); + + if (iTrace < tracesData.size()) // change existing traces + { + m_scopeVis->changeTrace(traceData, iTrace); + } + else // add new traces + { + m_scopeVis->addTrace(traceData); + } + } + + + ui->trace->setMaximum(nbTracesSaved-1); + ui->trace->setValue(nbTracesSaved-1); + m_glScope->setFocusedTraceIndex(nbTracesSaved-1); + + int r,g,b,a; + m_focusedTraceColor.getRgb(&r, &g, &b, &a); + ui->traceColor->setStyleSheet(tr("QLabel { background-color : rgb(%1,%2,%3); }").arg(r).arg(g).arg(b)); + + setTraceIndexDisplay(); + setAmpScaleDisplay(); + setAmpOfsDisplay(); + setTraceDelayDisplay(); + + ui->onlyX->setEnabled(true); + ui->onlyY->setEnabled(nbTracesSaved > 1); + ui->horizontalXY->setEnabled(nbTracesSaved > 1); + ui->verticalXY->setEnabled(nbTracesSaved > 1); + ui->polar->setEnabled(nbTracesSaved > 1); + + // trigger stuff + + uint32_t nbTriggersSaved; + d.readU32(200, &nbTriggersSaved, 1); + uint32_t nbTriggers = m_scopeVis->getNbTriggers(); + int iTrigger = nbTriggers; + + d.readS32(201, &intValue, 0); + ui->trigPre->setValue(intValue); + + qDebug("GLScopeMultiGUI::deserialize: nbTriggersSaved: %u nbTriggers: %u", nbTriggersSaved, nbTriggers); + + while (iTrigger > nbTriggersSaved) // remove possible triggers in excess + { + m_scopeVis->removeTrigger(iTrigger - 1); + iTrigger--; + } + + for (iTrigger = 0; iTrigger < nbTriggersSaved; iTrigger++) + { + ScopeVisMulti::TriggerData triggerData = m_scopeVis->getTriggerData(iTrigger); + float r, g, b; + + d.readS32(210 + 16*iTrigger, &intValue, 0); + ui->trigMode->setCurrentIndex(intValue); + d.readS32(211 + 16*iTrigger, &intValue, 1); + ui->trigCount->setValue(intValue); + d.readBool(212 + 16*iTrigger, &boolValue, true); + ui->trigPos->setChecked(boolValue); + d.readBool(213 + 16*iTrigger, &boolValue, false); + ui->trigBoth->setChecked(boolValue); + d.readS32(214 + 16*iTrigger, &intValue, 1); + ui->trigLevelCoarse->setValue(intValue); + d.readS32(215 + 16*iTrigger, &intValue, 1); + ui->trigLevelFine->setValue(intValue); + d.readS32(216 + 16*iTrigger, &intValue, 1); + ui->trigDelayCoarse->setValue(intValue); + d.readS32(217 + 16*iTrigger, &intValue, 1); + ui->trigDelayFine->setValue(intValue); + d.readFloat(218 + 16*iTrigger, &r, 1.0f); + d.readFloat(219 + 16*iTrigger, &g, 1.0f); + d.readFloat(220 + 16*iTrigger, &b, 1.0f); + m_focusedTriggerColor.setRgbF(r, g, b); + + fillTriggerData(triggerData); + + if (iTrigger < nbTriggers) // change existing triggers + { + m_scopeVis->changeTrigger(triggerData, iTrigger); + } + else // add new trigers + { + m_scopeVis->addTrigger(triggerData); + } + + if (iTrigger == nbTriggersSaved-1) + { + m_glScope->setFocusedTriggerData(triggerData); + } + } + + ui->trig->setMaximum(nbTriggersSaved-1); + ui->trig->setValue(nbTriggersSaved-1); + + m_focusedTriggerColor.getRgb(&r, &g, &b, &a); + ui->trigColor->setStyleSheet(tr("QLabel { background-color : rgb(%1,%2,%3); }").arg(r).arg(g).arg(b)); + + setTrigCountDisplay(); + setTrigDelayDisplay(); + setTrigIndexDisplay(); + setTrigLevelDisplay(); + setTrigPreDisplay(); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +void GLScopeMultiGUI::on_onlyX_toggled(bool checked) +{ + if (checked) + { + ui->onlyY->setChecked(false); + ui->horizontalXY->setChecked(false); + ui->verticalXY->setChecked(false); + ui->polar->setChecked(false); + m_glScope->setDisplayMode(GLScopeMulti::DisplayX); + } +} + +void GLScopeMultiGUI::on_onlyY_toggled(bool checked) +{ + if (checked) + { + ui->onlyX->setChecked(false); + ui->horizontalXY->setChecked(false); + ui->verticalXY->setChecked(false); + ui->polar->setChecked(false); + m_glScope->setDisplayMode(GLScopeMulti::DisplayY); + } +} + +void GLScopeMultiGUI::on_horizontalXY_toggled(bool checked) +{ + if (checked) + { + ui->onlyX->setChecked(false); + ui->onlyY->setChecked(false); + ui->verticalXY->setChecked(false); + ui->polar->setChecked(false); + m_glScope->setDisplayMode(GLScopeMulti::DisplayXYH); + } +} + +void GLScopeMultiGUI::on_verticalXY_toggled(bool checked) +{ + if (checked) + { + ui->onlyX->setChecked(false); + ui->onlyY->setChecked(false); + ui->horizontalXY->setChecked(false); + ui->polar->setChecked(false); + m_glScope->setDisplayMode(GLScopeMulti::DisplayXYV); + } +} + +void GLScopeMultiGUI::on_polar_toggled(bool checked) +{ + if (checked) + { + ui->onlyX->setChecked(false); + ui->onlyY->setChecked(false); + ui->horizontalXY->setChecked(false); + ui->verticalXY->setChecked(false); + m_glScope->setDisplayMode(GLScopeMulti::DisplayPol); + } +} + +void GLScopeMultiGUI::on_traceIntensity_valueChanged(int value) +{ + ui->traceIntensity->setToolTip(QString("Trace intensity: %1").arg(value)); + m_glScope->setDisplayTraceIntensity(value); +} + +void GLScopeMultiGUI::on_gridIntensity_valueChanged(int value) +{ + ui->gridIntensity->setToolTip(QString("Grid intensity: %1").arg(value)); + m_glScope->setDisplayGridIntensity(value); +} + +void GLScopeMultiGUI::on_time_valueChanged(int value) +{ + m_timeBase = value; + setTimeScaleDisplay(); + setTraceDelayDisplay(); + m_scopeVis->configure( + ui->sourcePort->maximum() + 1, + m_traceLenMult*ScopeVisMulti::m_traceChunkSize, + m_timeBase, + m_timeOffset*10, + (uint32_t) (m_glScope->getTraceSize() * (ui->trigPre->value()/100.0f)), + ui->freerun->isChecked()); +} + +void GLScopeMultiGUI::on_timeOfs_valueChanged(int value) +{ + if ((value < 0) || (value > 100)) { + return; + } + + m_timeOffset = value; + setTimeOfsDisplay(); + m_scopeVis->configure( + ui->sourcePort->maximum() + 1, + m_traceLenMult*ScopeVisMulti::m_traceChunkSize, + m_timeBase, + m_timeOffset*10, + (uint32_t) (m_glScope->getTraceSize() * (ui->trigPre->value()/100.0f)), + ui->freerun->isChecked()); +} + +void GLScopeMultiGUI::on_traceLen_valueChanged(int value) +{ + if ((value < 1) || (value > 100)) { + return; + } + + m_traceLenMult = value; + m_scopeVis->configure( + ui->sourcePort->maximum() + 1, + m_traceLenMult*ScopeVisMulti::m_traceChunkSize, + m_timeBase, + m_timeOffset*10, + (uint32_t) (m_glScope->getTraceSize() * (ui->trigPre->value()/100.0f)), + ui->freerun->isChecked()); + setTraceLenDisplay(); + setTimeScaleDisplay(); + setTimeOfsDisplay(); + setTrigDelayDisplay(); + setTrigPreDisplay(); +} + +void GLScopeMultiGUI::on_trace_valueChanged(int value) +{ + ui->traceText->setText(value == 0 ? "X" : QString("Y%1").arg(ui->trace->value())); + + ScopeVisMulti::TraceData traceData; + m_scopeVis->getTraceData(traceData, value); + + qDebug() << "GLScopeMultiGUI::on_trace_valueChanged:" + << " m_projectionType: " << (int) traceData.m_projectionType + << " m_amp" << traceData.m_amp + << " m_ofs" << traceData.m_ofs + << " m_traceDelay" << traceData.m_traceDelay; + + setTraceUI(traceData); + + m_scopeVis->focusOnTrace(value); +} + +void GLScopeMultiGUI::on_traceAdd_clicked(bool checked) +{ + ScopeVisMulti::TraceData traceData; + fillTraceData(traceData); + addTrace(traceData); +} + +void GLScopeMultiGUI::on_traceDel_clicked(bool checked) +{ + if (ui->trace->value() > 0) // not the X trace + { + ui->trace->setMaximum(ui->trace->maximum() - 1); + + if (ui->trace->value() == 0) + { + ui->onlyX->setChecked(true); + ui->onlyY->setEnabled(false); + ui->horizontalXY->setEnabled(false); + ui->verticalXY->setEnabled(false); + ui->polar->setEnabled(false); + m_glScope->setDisplayMode(GLScopeMulti::DisplayX); + } + + m_scopeVis->removeTrace(ui->trace->value()); + changeCurrentTrace(); + } +} + +void GLScopeMultiGUI::on_traceUp_clicked(bool checked) +{ + if (ui->trace->maximum() > 0) // more than one trace + { + int newTraceIndex = (ui->trace->value() + 1) % (ui->trace->maximum()+1); + m_scopeVis->moveTrace(ui->trace->value(), true); + ui->trace->setValue(newTraceIndex); // follow trace + ScopeVisMulti::TraceData traceData; + m_scopeVis->getTraceData(traceData, ui->trace->value()); + setTraceUI(traceData); + m_scopeVis->focusOnTrace(ui->trace->value()); + } +} + +void GLScopeMultiGUI::on_traceDown_clicked(bool checked) +{ + if (ui->trace->value() > 0) // not the X (lowest) trace + { + int newTraceIndex = (ui->trace->value() - 1) % (ui->trace->maximum()+1); + m_scopeVis->moveTrace(ui->trace->value(), false); + ui->trace->setValue(newTraceIndex); // follow trace + ScopeVisMulti::TraceData traceData; + m_scopeVis->getTraceData(traceData, ui->trace->value()); + setTraceUI(traceData); + m_scopeVis->focusOnTrace(ui->trace->value()); + } +} + +void GLScopeMultiGUI::on_trig_valueChanged(int value) +{ + ui->trigText->setText(tr("%1").arg(value)); + + ScopeVisMulti::TriggerData triggerData; + m_scopeVis->getTriggerData(triggerData, value); + + qDebug() << "GLScopeMultiGUI::on_trig_valueChanged:" + << " m_projectionType: " << (int) triggerData.m_projectionType + << " m_triggerRepeat" << triggerData.m_triggerRepeat + << " m_triggerPositiveEdge" << triggerData.m_triggerPositiveEdge + << " m_triggerBothEdges" << triggerData.m_triggerBothEdges + << " m_triggerLevel" << triggerData.m_triggerLevel; + + setTriggerUI(triggerData); + + m_scopeVis->focusOnTrigger(value); +} + +void GLScopeMultiGUI::on_trigAdd_clicked(bool checked) +{ + ScopeVisMulti::TriggerData triggerData; + fillTriggerData(triggerData); + addTrigger(triggerData); +} + +void GLScopeMultiGUI::on_trigDel_clicked(bool checked) +{ + if (ui->trig->value() > 0) + { + m_scopeVis->removeTrigger(ui->trig->value()); + ui->trig->setMaximum(ui->trig->maximum() - 1); + } +} + +void GLScopeMultiGUI::on_trigUp_clicked(bool checked) +{ + if (ui->trig->maximum() > 0) // more than one trigger + { + int newTriggerIndex = (ui->trig->value() + 1) % (ui->trig->maximum()+1); + m_scopeVis->moveTrigger(ui->trace->value(), true); + ui->trig->setValue(newTriggerIndex); // follow trigger + ScopeVisMulti::TriggerData triggerData; + m_scopeVis->getTriggerData(triggerData, ui->trig->value()); + setTriggerUI(triggerData); + m_scopeVis->focusOnTrigger(ui->trig->value()); + } +} + +void GLScopeMultiGUI::on_trigDown_clicked(bool checked) +{ + if (ui->trig->value() > 0) // not the 0 (lowest) trigger + { + int newTriggerIndex = (ui->trig->value() - 1) % (ui->trig->maximum()+1); + m_scopeVis->moveTrigger(ui->trace->value(), false); + ui->trig->setValue(newTriggerIndex); // follow trigger + ScopeVisMulti::TriggerData triggerData; + m_scopeVis->getTriggerData(triggerData, ui->trig->value()); + setTriggerUI(triggerData); + m_scopeVis->focusOnTrigger(ui->trig->value()); + } +} + +void GLScopeMultiGUI::on_traceMode_currentIndexChanged(int index) +{ + setAmpScaleDisplay(); + setAmpOfsDisplay(); + changeCurrentTrace(); +} + +void GLScopeMultiGUI::on_amp_valueChanged(int value) +{ + setAmpScaleDisplay(); + changeCurrentTrace(); +} + +void GLScopeMultiGUI::on_ofsCoarse_valueChanged(int value) +{ + setAmpOfsDisplay(); + changeCurrentTrace(); +} + +void GLScopeMultiGUI::on_ofsFine_valueChanged(int value) +{ + setAmpOfsDisplay(); + changeCurrentTrace(); +} + +void GLScopeMultiGUI::on_traceDelayCoarse_valueChanged(int value) +{ + setTraceDelayDisplay(); + changeCurrentTrace(); +} + +void GLScopeMultiGUI::on_traceDelayFine_valueChanged(int value) +{ + setTraceDelayDisplay(); + changeCurrentTrace(); +} + +void GLScopeMultiGUI::on_traceView_toggled(bool checked) +{ + changeCurrentTrace(); +} + +void GLScopeMultiGUI::on_traceColor_clicked() +{ + QColor newColor = QColorDialog::getColor(m_focusedTraceColor); + + if (newColor.isValid()) // user clicked OK and selected a color + { + m_focusedTraceColor = newColor; + int r,g,b,a; + m_focusedTraceColor.getRgb(&r, &g, &b, &a); + ui->traceColor->setStyleSheet(tr("QLabel { background-color : rgb(%1,%2,%3); }").arg(r).arg(g).arg(b)); + changeCurrentTrace(); + } +} + +void GLScopeMultiGUI::on_mem_valueChanged(int value) +{ + QString text; + text.sprintf("%02d", value); + ui->memText->setText(text); + disableLiveMode(value > 0); // block trigger UI line if memory is active + m_scopeVis->setMemoryIndex(value); +} + +void GLScopeMultiGUI::on_trigMode_currentIndexChanged(int index) +{ + setTrigLevelDisplay(); + changeCurrentTrigger(); +} + +void GLScopeMultiGUI::on_trigCount_valueChanged(int value) +{ + setTrigCountDisplay(); + changeCurrentTrigger(); +} + +void GLScopeMultiGUI::on_trigPos_toggled(bool checked) +{ + if (checked) + { + ui->trigNeg->setChecked(false); + ui->trigBoth->setChecked(false); + } + changeCurrentTrigger(); +} + +void GLScopeMultiGUI::on_trigNeg_toggled(bool checked) +{ + if (checked) + { + ui->trigPos->setChecked(false); + ui->trigBoth->setChecked(false); + } + changeCurrentTrigger(); +} + +void GLScopeMultiGUI::on_trigBoth_toggled(bool checked) +{ + if (checked) + { + ui->trigNeg->setChecked(false); + ui->trigPos->setChecked(false); + } + changeCurrentTrigger(); +} + +void GLScopeMultiGUI::on_trigLevelCoarse_valueChanged(int value) +{ + setTrigLevelDisplay(); + changeCurrentTrigger(); +} + +void GLScopeMultiGUI::on_trigLevelFine_valueChanged(int value) +{ + setTrigLevelDisplay(); + changeCurrentTrigger(); +} + +void GLScopeMultiGUI::on_trigDelayCoarse_valueChanged(int value) +{ + setTrigDelayDisplay(); + changeCurrentTrigger(); +} + +void GLScopeMultiGUI::on_trigDelayFine_valueChanged(int value) +{ + setTrigDelayDisplay(); + changeCurrentTrigger(); +} + +void GLScopeMultiGUI::on_trigPre_valueChanged(int value) +{ + setTrigPreDisplay(); + m_scopeVis->configure( + ui->sourcePort->maximum() + 1, + m_traceLenMult*ScopeVisMulti::m_traceChunkSize, + m_timeBase, + m_timeOffset*10, + (uint32_t) (m_glScope->getTraceSize() * (ui->trigPre->value()/100.0f)), + ui->freerun->isChecked()); +} + +void GLScopeMultiGUI::on_trigColor_clicked() +{ + QColor newColor = QColorDialog::getColor(m_focusedTriggerColor); + + if (newColor.isValid()) // user clicked "OK" + { + m_focusedTriggerColor = newColor; + int r,g,b,a; + m_focusedTriggerColor.getRgb(&r, &g, &b, &a); + ui->trigColor->setStyleSheet(tr("QLabel { background-color : rgb(%1,%2,%3); }").arg(r).arg(g).arg(b)); + changeCurrentTrigger(); + } +} + +void GLScopeMultiGUI::on_trigOneShot_toggled(bool checked) +{ + m_scopeVis->setOneShot(checked); +} + +void GLScopeMultiGUI::on_freerun_toggled(bool checked) +{ + if (checked) + { + ui->trigOneShot->setChecked(false); + ui->trigOneShot->setEnabled(false); + } + else + { + ui->trigOneShot->setEnabled(true); + } + + m_scopeVis->configure( + ui->sourcePort->maximum() + 1, + m_traceLenMult*ScopeVisMulti::m_traceChunkSize, + m_timeBase, + m_timeOffset*10, + (uint32_t) (m_glScope->getTraceSize() * (ui->trigPre->value()/100.0f)), + ui->freerun->isChecked()); +} + +void GLScopeMultiGUI::setTraceIndexDisplay() +{ + ui->traceText->setText(ui->trace->value() == 0 ? "X" : QString("Y%1").arg(ui->trace->value())); +} + +void GLScopeMultiGUI::setTrigCountDisplay() +{ + QString text; + text.sprintf("%02d", ui->trigCount->value()); + ui->trigCountText->setText(text); +} + +void GLScopeMultiGUI::setTimeScaleDisplay() +{ + m_sampleRate = m_glScope->getSampleRate(); + unsigned int n_samples = (m_glScope->getTraceSize() * 1.0) / (double) m_timeBase; + double t = (m_glScope->getTraceSize() * 1.0 / m_sampleRate) / (double) m_timeBase; + + if (n_samples < 1000) { + ui->timeText->setToolTip(tr("%1 S").arg(n_samples)); + } else if (n_samples < 1000000) { + ui->timeText->setToolTip(tr("%1 kS").arg(n_samples/1000.0)); + } else { + ui->timeText->setToolTip(tr("%1 MS").arg(n_samples/1000000.0)); + } + + if(t < 0.000001) + { + t = round(t * 100000000000.0) / 100.0; + ui->timeText->setText(tr("%1\nns").arg(t)); + } + else if(t < 0.001) + { + t = round(t * 100000000.0) / 100.0; + ui->timeText->setText(tr("%1\nµs").arg(t)); + } + else if(t < 1.0) + { + t = round(t * 100000.0) / 100.0; + ui->timeText->setText(tr("%1\nms").arg(t)); + } + else + { + t = round(t * 100.0) / 100.0; + ui->timeText->setText(tr("%1\ns").arg(t)); + } +} + +void GLScopeMultiGUI::setTraceLenDisplay() +{ + unsigned int n_samples = m_traceLenMult * ScopeVisMulti::m_traceChunkSize; + + if (n_samples < 1000) { + ui->traceLenText->setToolTip(tr("%1 S").arg(n_samples)); + } else if (n_samples < 1000000) { + ui->traceLenText->setToolTip(tr("%1 kS").arg(n_samples/1000.0)); + } else { + ui->traceLenText->setToolTip(tr("%1 MS").arg(n_samples/1000000.0)); + } + + m_sampleRate = m_glScope->getSampleRate(); + double t = (m_glScope->getTraceSize() * 1.0 / m_sampleRate); + + if(t < 0.000001) + ui->traceLenText->setText(tr("%1\nns").arg(t * 1000000000.0, 0, 'f', 2)); + else if(t < 0.001) + ui->traceLenText->setText(tr("%1\nµs").arg(t * 1000000.0, 0, 'f', 2)); + else if(t < 1.0) + ui->traceLenText->setText(tr("%1\nms").arg(t * 1000.0, 0, 'f', 2)); + else + ui->traceLenText->setText(tr("%1\ns").arg(t * 1.0, 0, 'f', 2)); +} + +void GLScopeMultiGUI::setTimeOfsDisplay() +{ + unsigned int n_samples = m_glScope->getTraceSize() * (m_timeOffset/100.0); + double dt = m_glScope->getTraceSize() * (m_timeOffset/100.0) / m_sampleRate; + + if (n_samples < 1000) { + ui->timeOfsText->setToolTip(tr("%1 S").arg(n_samples)); + } else if (n_samples < 1000000) { + ui->timeOfsText->setToolTip(tr("%1 kS").arg(n_samples/1000.0)); + } else { + ui->timeOfsText->setToolTip(tr("%1 MS").arg(n_samples/1000000.0)); + } + + if(dt < 0.000001) + ui->timeOfsText->setText(tr("%1\nns").arg(dt * 1000000000.0, 0, 'f', 2)); + else if(dt < 0.001) + ui->timeOfsText->setText(tr("%1\nµs").arg(dt * 1000000.0, 0, 'f', 2)); + else if(dt < 1.0) + ui->timeOfsText->setText(tr("%1\nms").arg(dt * 1000.0, 0, 'f', 2)); + else + ui->timeOfsText->setText(tr("%1\ns").arg(dt * 1.0, 0, 'f', 2)); +} + +void GLScopeMultiGUI::setAmpScaleDisplay() +{ + ScopeVisMulti::ProjectionType projectionType = (ScopeVisMulti::ProjectionType) ui->traceMode->currentIndex(); + double ampValue = amps[ui->amp->value()]; + + if (projectionType == ScopeVisMulti::ProjectionMagDB) + { + double displayValue = ampValue*500.0f; + + if (displayValue < 10.0f) { + ui->ampText->setText(tr("%1\ndB").arg(displayValue, 0, 'f', 2)); + } + else { + ui->ampText->setText(tr("%1\ndB").arg(displayValue, 0, 'f', 1)); + } + } + else + { + double a = ampValue*10.0f; + + if(a < 0.000001) + ui->ampText->setText(tr("%1\nn").arg(a * 1000000000.0)); + else if(a < 0.001) + ui->ampText->setText(tr("%1\nµ").arg(a * 1000000.0)); + else if(a < 1.0) + ui->ampText->setText(tr("%1\nm").arg(a * 1000.0)); + else + ui->ampText->setText(tr("%1").arg(a * 1.0)); + } +} + +void GLScopeMultiGUI::setAmpOfsDisplay() +{ + ScopeVisMulti::ProjectionType projectionType = (ScopeVisMulti::ProjectionType) ui->traceMode->currentIndex(); + double o = (ui->ofsCoarse->value() * 10.0f) + (ui->ofsFine->value() / 20.0f); + + if (projectionType == ScopeVisMulti::ProjectionMagDB) + { + ui->ofsText->setText(tr("%1\ndB").arg(o/10.0f - 100.0f, 0, 'f', 1)); + } + else + { + double a; + + if (projectionType == ScopeVisMulti::ProjectionMagLin) + { + a = o/2000.0f; + } + else + { + a = o/1000.0f; + } + + if(fabs(a) < 0.000001f) + ui->ofsText->setText(tr("%1\nn").arg(a * 1000000000.0)); + else if(fabs(a) < 0.001f) + ui->ofsText->setText(tr("%1\nµ").arg(a * 1000000.0)); + else if(fabs(a) < 1.0f) + ui->ofsText->setText(tr("%1\nm").arg(a * 1000.0)); + else + ui->ofsText->setText(tr("%1").arg(a * 1.0)); + } +} + +void GLScopeMultiGUI::setTraceDelayDisplay() +{ + if (m_sampleRate > 0) + { + int n_samples = ui->traceDelayCoarse->value()*100 + ui->traceDelayFine->value(); + double t = ((double) n_samples) / m_sampleRate; + + if (n_samples < 1000) { + ui->traceDelayText->setToolTip(tr("%1 S").arg(n_samples)); + } else if (n_samples < 1000000) { + ui->traceDelayText->setToolTip(tr("%1 kS").arg(n_samples/1000.0)); + } else { + ui->traceDelayText->setToolTip(tr("%1 MS").arg(n_samples/1000000.0)); + } + + if(t < 0.000001) + ui->traceDelayText->setText(tr("%1\nns").arg(t * 1000000000.0, 0, 'f', 2)); + else if(t < 0.001) + ui->traceDelayText->setText(tr("%1\nµs").arg(t * 1000000.0, 0, 'f', 2)); + else if(t < 1.0) + ui->traceDelayText->setText(tr("%1\nms").arg(t * 1000.0, 0, 'f', 2)); + else + ui->traceDelayText->setText(tr("%1\ns").arg(t * 1.0, 0, 'f', 2)); + } +} + +void GLScopeMultiGUI::setTrigIndexDisplay() +{ + ui->trigText->setText(tr("%1").arg(ui->trig->value())); +} + +void GLScopeMultiGUI::setTrigLevelDisplay() +{ + double t = (ui->trigLevelCoarse->value() / 100.0f) + (ui->trigLevelFine->value() / 20000.0f); + ScopeVisMulti::ProjectionType projectionType = (ScopeVisMulti::ProjectionType) ui->trigMode->currentIndex(); + + ui->trigLevelCoarse->setToolTip(QString("Trigger level coarse: %1 %").arg(ui->trigLevelCoarse->value() / 100.0f)); + ui->trigLevelFine->setToolTip(QString("Trigger level fine: %1 ppm").arg(ui->trigLevelFine->value() * 50)); + + if (projectionType == ScopeVisMulti::ProjectionMagDB) { + ui->trigLevelText->setText(tr("%1\ndB").arg(100.0 * (t - 1.0), 0, 'f', 1)); + } + else + { + double a; + + if (projectionType == ScopeVisMulti::ProjectionMagLin) { + a = 1.0 + t; + } else { + a = t; + } + + if(fabs(a) < 0.000001) + ui->trigLevelText->setText(tr("%1\nn").arg(a * 1000000000.0f, 0, 'f', 2)); + else if(fabs(a) < 0.001) + ui->trigLevelText->setText(tr("%1\nµ").arg(a * 1000000.0f, 0, 'f', 2)); + else if(fabs(a) < 1.0) + ui->trigLevelText->setText(tr("%1\nm").arg(a * 1000.0f, 0, 'f', 2)); + else + ui->trigLevelText->setText(tr("%1").arg(a * 1.0f, 0, 'f', 2)); + } +} + +void GLScopeMultiGUI::setTrigDelayDisplay() +{ + if (m_sampleRate > 0) + { + double delayMult = ui->trigDelayCoarse->value() + ui->trigDelayFine->value() / (ScopeVisMulti::m_traceChunkSize / 10.0); + unsigned int n_samples_delay = m_traceLenMult * ScopeVisMulti::m_traceChunkSize * delayMult; + + if (n_samples_delay < 1000) { + ui->trigDelayText->setToolTip(tr("%1 S").arg(n_samples_delay)); + } else if (n_samples_delay < 1000000) { + ui->trigDelayText->setToolTip(tr("%1 kS").arg(n_samples_delay/1000.0)); + } else if (n_samples_delay < 1000000000) { + ui->trigDelayText->setToolTip(tr("%1 MS").arg(n_samples_delay/1000000.0)); + } else { + ui->trigDelayText->setToolTip(tr("%1 GS").arg(n_samples_delay/1000000000.0)); + } + + m_sampleRate = m_glScope->getSampleRate(); + double t = (n_samples_delay * 1.0f / m_sampleRate); + + if(t < 0.000001) + ui->trigDelayText->setText(tr("%1\nns").arg(t * 1000000000.0, 0, 'f', 2)); + else if(t < 0.001) + ui->trigDelayText->setText(tr("%1\nµs").arg(t * 1000000.0, 0, 'f', 2)); + else if(t < 1.0) + ui->trigDelayText->setText(tr("%1\nms").arg(t * 1000.0, 0, 'f', 2)); + else + ui->trigDelayText->setText(tr("%1\ns").arg(t * 1.0, 0, 'f', 2)); + } +} + +void GLScopeMultiGUI::setTrigPreDisplay() +{ + if (m_sampleRate > 0) + { + unsigned int n_samples_delay = m_glScope->getTraceSize() * (ui->trigPre->value()/100.0f); + double dt = m_glScope->getTraceSize() * (ui->trigPre->value()/100.0f) / m_sampleRate; + + if (n_samples_delay < 1000) { + ui->trigPreText->setToolTip(tr("%1 S").arg(n_samples_delay)); + } else if (n_samples_delay < 1000000) { + ui->trigPreText->setToolTip(tr("%1 kS").arg(n_samples_delay/1000.0)); + } else if (n_samples_delay < 1000000000) { + ui->trigPreText->setToolTip(tr("%1 MS").arg(n_samples_delay/1000000.0)); + } else { + ui->trigPreText->setToolTip(tr("%1 GS").arg(n_samples_delay/1000000000.0)); + } + + if(dt < 0.000001) + ui->trigPreText->setText(tr("%1\nns").arg(dt * 1000000000.0f, 0, 'f', 2)); + else if(dt < 0.001) + ui->trigPreText->setText(tr("%1\nµs").arg(dt * 1000000.0f, 0, 'f', 2)); + else if(dt < 1.0) + ui->trigPreText->setText(tr("%1\nms").arg(dt * 1000.0f, 0, 'f', 2)); + else + ui->trigPreText->setText(tr("%1\ns").arg(dt * 1.0f, 0, 'f', 2)); + } +} + +void GLScopeMultiGUI::changeCurrentTrace() +{ + ScopeVisMulti::TraceData traceData; + fillTraceData(traceData); + uint32_t currentTraceIndex = ui->trace->value(); + m_scopeVis->changeTrace(traceData, currentTraceIndex); +} + +void GLScopeMultiGUI::changeCurrentTrigger() +{ + ScopeVisMulti::TriggerData triggerData; + fillTriggerData(triggerData); + uint32_t currentTriggerIndex = ui->trig->value(); + m_scopeVis->changeTrigger(triggerData, currentTriggerIndex); +} + +void GLScopeMultiGUI::fillProjectionCombo(QComboBox* comboBox) +{ + comboBox->addItem("Real", ScopeVisMulti::ProjectionReal); + comboBox->addItem("Imag", ScopeVisMulti::ProjectionImag); + comboBox->addItem("Mag", ScopeVisMulti::ProjectionMagLin); + comboBox->addItem("MagdB", ScopeVisMulti::ProjectionMagDB); + comboBox->addItem("Phi", ScopeVisMulti::ProjectionPhase); + comboBox->addItem("dPhi", ScopeVisMulti::ProjectionDPhase); +} + +void GLScopeMultiGUI::disableLiveMode(bool disable) +{ + ui->traceLen->setEnabled(!disable); + ui->trig->setEnabled(!disable); + ui->trigAdd->setEnabled(!disable); + ui->trigDel->setEnabled(!disable); + ui->trigMode->setEnabled(!disable); + ui->trigCount->setEnabled(!disable); + ui->trigPos->setEnabled(!disable); + ui->trigNeg->setEnabled(!disable); + ui->trigBoth->setEnabled(!disable); + ui->trigLevelCoarse->setEnabled(!disable); + ui->trigLevelFine->setEnabled(!disable); + ui->trigDelayCoarse->setEnabled(!disable); + ui->trigDelayFine->setEnabled(!disable); + ui->trigPre->setEnabled(!disable); + ui->trigOneShot->setEnabled(!disable); + ui->freerun->setEnabled(!disable); +} + +void GLScopeMultiGUI::fillTraceData(ScopeVisMulti::TraceData& traceData) +{ + traceData.m_projectionType = (ScopeVisMulti::ProjectionType) ui->traceMode->currentIndex(); + traceData.m_hasTextOverlay = (traceData.m_projectionType == ScopeVisMulti::ProjectionMagDB); + traceData.m_textOverlay.clear(); + traceData.m_inputIndex = 0; + traceData.m_amp = 0.2 / amps[ui->amp->value()]; + traceData.m_ampIndex = ui->amp->value(); + + traceData.m_ofsCoarse = ui->ofsCoarse->value(); + traceData.m_ofsFine = ui->ofsFine->value(); + + if (traceData.m_projectionType == ScopeVisMulti::ProjectionMagLin) { + traceData.m_ofs = ((10.0 * ui->ofsCoarse->value()) + (ui->ofsFine->value() / 20.0)) / 2000.0f; + } else { + traceData.m_ofs = ((10.0 * ui->ofsCoarse->value()) + (ui->ofsFine->value() / 20.0)) / 1000.0f; + } + + traceData.m_traceDelayCoarse = ui->traceDelayCoarse->value(); + traceData.m_traceDelayFine = ui->traceDelayFine->value(); + traceData.m_traceDelay = traceData.m_traceDelayCoarse * 100 + traceData.m_traceDelayFine; + traceData.setColor(m_focusedTraceColor); + traceData.m_viewTrace = ui->traceView->isChecked(); +} + +void GLScopeMultiGUI::fillTriggerData(ScopeVisMulti::TriggerData& triggerData) +{ + triggerData.m_projectionType = (ScopeVisMulti::ProjectionType) ui->trigMode->currentIndex(); + triggerData.m_inputIndex = 0; + triggerData.m_triggerLevel = (ui->trigLevelCoarse->value() / 100.0) + (ui->trigLevelFine->value() / 20000.0); + triggerData.m_triggerLevelCoarse = ui->trigLevelCoarse->value(); + triggerData.m_triggerLevelFine = ui->trigLevelFine->value(); + triggerData.m_triggerPositiveEdge = ui->trigPos->isChecked(); + triggerData.m_triggerBothEdges = ui->trigBoth->isChecked(); + triggerData.m_triggerRepeat = ui->trigCount->value(); + triggerData.m_triggerDelayMult = ui->trigDelayCoarse->value() + ui->trigDelayFine->value() / (ScopeVisMulti::m_traceChunkSize / 10.0); + triggerData.m_triggerDelay = (int) (m_traceLenMult * ScopeVisMulti::m_traceChunkSize * triggerData.m_triggerDelayMult); + triggerData.m_triggerDelayCoarse = ui->trigDelayCoarse->value(); + triggerData.m_triggerDelayFine = ui->trigDelayFine->value(); + triggerData.setColor(m_focusedTriggerColor); +} + +void GLScopeMultiGUI::setTraceUI(const ScopeVisMulti::TraceData& traceData) +{ + TraceUIBlocker traceUIBlocker(ui); + + ui->traceMode->setCurrentIndex((int) traceData.m_projectionType); + ui->amp->setValue(traceData.m_ampIndex); + setAmpScaleDisplay(); + + ui->ofsCoarse->setValue(traceData.m_ofsCoarse); + ui->ofsFine->setValue(traceData.m_ofsFine); + setAmpOfsDisplay(); + + ui->traceDelayCoarse->setValue(traceData.m_traceDelayCoarse); + ui->traceDelayFine->setValue(traceData.m_traceDelayFine); + setTraceDelayDisplay(); + + m_focusedTraceColor = traceData.m_traceColor; + int r, g, b, a; + m_focusedTraceColor.getRgb(&r, &g, &b, &a); + ui->traceColor->setStyleSheet(tr("QLabel { background-color : rgb(%1,%2,%3); }").arg(r).arg(g).arg(b)); + + ui->traceView->setChecked(traceData.m_viewTrace); +} + +void GLScopeMultiGUI::setTriggerUI(const ScopeVisMulti::TriggerData& triggerData) +{ + TrigUIBlocker trigUIBlocker(ui); + + ui->trigMode->setCurrentIndex((int) triggerData.m_projectionType); + ui->trigCount->setValue(triggerData.m_triggerRepeat); + setTrigCountDisplay(); + + ui->trigPos->setChecked(false); + ui->trigNeg->setChecked(false); + ui->trigPos->doToggle(false); + ui->trigNeg->doToggle(false); + + if (triggerData.m_triggerBothEdges) + { + ui->trigBoth->setChecked(true); + ui->trigBoth->doToggle(true); + } + else + { + ui->trigBoth->setChecked(false); + ui->trigBoth->doToggle(false); + + if (triggerData.m_triggerPositiveEdge) + { + ui->trigPos->setChecked(true); + ui->trigPos->doToggle(true); + } + else + { + ui->trigNeg->setChecked(true); + ui->trigNeg->doToggle(true); + } + } + + ui->trigLevelCoarse->setValue(triggerData.m_triggerLevelCoarse); + ui->trigLevelFine->setValue(triggerData.m_triggerLevelFine); + setTrigLevelDisplay(); + + ui->trigDelayCoarse->setValue(triggerData.m_triggerDelayCoarse); + ui->trigDelayFine->setValue(triggerData.m_triggerDelayFine); + setTrigDelayDisplay(); + + m_focusedTriggerColor = triggerData.m_triggerColor; + int r, g, b, a; + m_focusedTriggerColor.getRgb(&r, &g, &b, &a); + ui->trigColor->setStyleSheet(tr("QLabel { background-color : rgb(%1,%2,%3); }").arg(r).arg(g).arg(b)); +} + +void GLScopeMultiGUI::applySettings() +{ +} + +bool GLScopeMultiGUI::handleMessage(Message* message) +{ + return false; +} + +GLScopeMultiGUI::TrigUIBlocker::TrigUIBlocker(Ui::GLScopeMultiGUI *ui) : + m_ui(ui) +{ + m_oldStateTrigMode = ui->trigMode->blockSignals(true); + m_oldStateTrigCount = ui->trigCount->blockSignals(true); + m_oldStateTrigPos = ui->trigPos->blockSignals(true); + m_oldStateTrigNeg = ui->trigNeg->blockSignals(true); + m_oldStateTrigBoth = ui->trigBoth->blockSignals(true); + m_oldStateTrigLevelCoarse = ui->trigLevelCoarse->blockSignals(true); + m_oldStateTrigLevelFine = ui->trigLevelFine->blockSignals(true); + m_oldStateTrigDelayCoarse = ui->trigDelayCoarse->blockSignals(true); + m_oldStateTrigDelayFine = ui->trigDelayFine->blockSignals(true); +} + +GLScopeMultiGUI::TrigUIBlocker::~TrigUIBlocker() +{ + unBlock(); +} + +void GLScopeMultiGUI::TrigUIBlocker::unBlock() +{ + m_ui->trigMode->blockSignals(m_oldStateTrigMode); + m_ui->trigCount->blockSignals(m_oldStateTrigCount); + m_ui->trigPos->blockSignals(m_oldStateTrigPos); + m_ui->trigNeg->blockSignals(m_oldStateTrigNeg); + m_ui->trigBoth->blockSignals(m_oldStateTrigBoth); + m_ui->trigLevelCoarse->blockSignals(m_oldStateTrigLevelCoarse); + m_ui->trigLevelFine->blockSignals(m_oldStateTrigLevelFine); + m_ui->trigDelayCoarse->blockSignals(m_oldStateTrigDelayCoarse); + m_ui->trigDelayFine->blockSignals(m_oldStateTrigDelayFine); +} + +GLScopeMultiGUI::TraceUIBlocker::TraceUIBlocker(Ui::GLScopeMultiGUI* ui) : + m_ui(ui) +{ + m_oldStateTrace = m_ui->trace->blockSignals(true); + m_oldStateTraceAdd = m_ui->traceAdd->blockSignals(true); + m_oldStateTraceDel = m_ui->traceDel->blockSignals(true); + m_oldStateTraceMode = m_ui->traceMode->blockSignals(true); + m_oldStateAmp = m_ui->amp->blockSignals(true); + m_oldStateOfsCoarse = m_ui->ofsCoarse->blockSignals(true); + m_oldStateOfsFine = m_ui->ofsFine->blockSignals(true); + m_oldStateTraceDelayCoarse = m_ui->traceDelayCoarse->blockSignals(true); + m_oldStateTraceDelayFine = m_ui->traceDelayFine->blockSignals(true); + m_oldStateTraceColor = m_ui->traceColor->blockSignals(true); +} + +GLScopeMultiGUI::TraceUIBlocker::~TraceUIBlocker() +{ + unBlock(); +} + +void GLScopeMultiGUI::TraceUIBlocker::unBlock() +{ + m_ui->trace->blockSignals(m_oldStateTrace); + m_ui->traceAdd->blockSignals(m_oldStateTraceAdd); + m_ui->traceDel->blockSignals(m_oldStateTraceDel); + m_ui->traceMode->blockSignals(m_oldStateTraceMode); + m_ui->amp->blockSignals(m_oldStateAmp); + m_ui->ofsCoarse->blockSignals(m_oldStateOfsCoarse); + m_ui->ofsFine->blockSignals(m_oldStateOfsFine); + m_ui->traceDelayCoarse->blockSignals(m_oldStateTraceDelayCoarse); + m_ui->traceDelayFine->blockSignals(m_oldStateTraceDelayFine); + m_ui->traceColor->blockSignals(m_oldStateTraceColor); +} + +GLScopeMultiGUI::MainUIBlocker::MainUIBlocker(Ui::GLScopeMultiGUI* ui) : + m_ui(ui) +{ + m_oldStateOnlyX = m_ui->onlyX->blockSignals(true); + m_oldStateOnlyY = m_ui->onlyY->blockSignals(true); + m_oldStateHorizontalXY = m_ui->horizontalXY->blockSignals(true); + m_oldStateVerticalXY = m_ui->verticalXY->blockSignals(true); + m_oldStatePolar = m_ui->polar->blockSignals(true); +// m_oldStateTime = m_ui->time->blockSignals(true); +// m_oldStateTimeOfs = m_ui->timeOfs->blockSignals(true); +// m_oldStateTraceLen = m_ui->traceLen->blockSignals(true); +} + +GLScopeMultiGUI::MainUIBlocker::~MainUIBlocker() +{ + unBlock(); +} + +void GLScopeMultiGUI::MainUIBlocker::unBlock() +{ + m_ui->onlyX->blockSignals(m_oldStateOnlyX); + m_ui->onlyY->blockSignals(m_oldStateOnlyY); + m_ui->horizontalXY->blockSignals(m_oldStateHorizontalXY); + m_ui->verticalXY->blockSignals(m_oldStateVerticalXY); + m_ui->polar->blockSignals(m_oldStatePolar); +// m_ui->time->blockSignals(m_oldStateTime); +// m_ui->timeOfs->blockSignals(m_oldStateTimeOfs); +// m_ui->traceLen->blockSignals(m_oldStateTraceLen); +} + +void GLScopeMultiGUI::setDisplayMode(DisplayMode displayMode) +{ + if (ui->trace->maximum() == 0) + { + ui->onlyX->setChecked(true); + } + else + { + switch (displayMode) + { + case DisplayX: + ui->onlyX->setChecked(true); + break; + case DisplayY: + ui->onlyY->setChecked(true); + break; + case DisplayXYH: + ui->horizontalXY->setChecked(true); + break; + case DisplayXYV: + ui->verticalXY->setChecked(true); + break; + case DisplayPol: + ui->polar->setChecked(true); + break; + default: + ui->onlyX->setChecked(true); + break; + } + } +} + +void GLScopeMultiGUI::setTraceIntensity(int value) +{ + if ((value < ui->traceIntensity->minimum()) || (value > ui->traceIntensity->maximum())) { + return; + } + + ui->traceIntensity->setValue(value); +} + +void GLScopeMultiGUI::setGridIntensity(int value) +{ + if ((value < ui->gridIntensity->minimum()) || (value > ui->gridIntensity->maximum())) { + return; + } + + ui->gridIntensity->setValue(value); +} + +void GLScopeMultiGUI::setTimeBase(int step) +{ + if ((step < ui->time->minimum()) || (step > ui->time->maximum())) { + return; + } + + ui->time->setValue(step); +} + +void GLScopeMultiGUI::setTimeOffset(int step) +{ + if ((step < ui->timeOfs->minimum()) || (step > ui->timeOfs->maximum())) { + return; + } + + ui->timeOfs->setValue(step); +} + +void GLScopeMultiGUI::setTraceLength(int step) +{ + if ((step < ui->traceLen->minimum()) || (step > ui->traceLen->maximum())) { + return; + } + + ui->traceLen->setValue(step); +} + +void GLScopeMultiGUI::setPreTrigger(int step) +{ + if ((step < ui->trigPre->minimum()) || (step > ui->trigPre->maximum())) { + return; + } + + ui->trigPre->setValue(step); +} + +void GLScopeMultiGUI::changeTrace(int traceIndex, const ScopeVisMulti::TraceData& traceData) +{ + m_scopeVis->changeTrace(traceData, traceIndex); +} + +void GLScopeMultiGUI::addTrace(const ScopeVisMulti::TraceData& traceData) +{ + if (ui->trace->maximum() < 3) + { + if (ui->trace->value() == 0) + { + ui->onlyY->setEnabled(true); + ui->horizontalXY->setEnabled(true); + ui->verticalXY->setEnabled(true); + ui->polar->setEnabled(true); + } + + m_scopeVis->addTrace(traceData); + ui->trace->setMaximum(ui->trace->maximum() + 1); + } +} + +void GLScopeMultiGUI::focusOnTrace(int traceIndex) +{ + on_trace_valueChanged(traceIndex); +} + +void GLScopeMultiGUI::changeTrigger(int triggerIndex, const ScopeVisMulti::TriggerData& triggerData) +{ + m_scopeVis->changeTrigger(triggerData, triggerIndex); +} + +void GLScopeMultiGUI::addTrigger(const ScopeVisMulti::TriggerData& triggerData) +{ + if (ui->trig->maximum() < 9) + { + m_scopeVis->addTrigger(triggerData); + ui->trig->setMaximum(ui->trig->maximum() + 1); + } +} + +void GLScopeMultiGUI::focusOnTrigger(int triggerIndex) +{ + on_trig_valueChanged(triggerIndex); +} + + diff --git a/sdrbase/gui/glscopemultigui.h b/sdrbase/gui/glscopemultigui.h new file mode 100644 index 000000000..0c04df3dc --- /dev/null +++ b/sdrbase/gui/glscopemultigui.h @@ -0,0 +1,235 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 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 // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_GUI_GLSCOPEMULTIGUI_H_ +#define SDRBASE_GUI_GLSCOPEMULTIGUI_H_ + +#include +#include + +#include "dsp/dsptypes.h" +#include "util/export.h" +#include "util/message.h" +#include "dsp/scopevismulti.h" + +namespace Ui { + class GLScopeMultiGUI; +} + +class MessageQueue; +class GLScopeMulti; + +class SDRANGEL_API GLScopeMultiGUI : public QWidget { + Q_OBJECT + +public: + enum DisplayMode { + DisplayXYH, + DisplayXYV, + DisplayX, + DisplayY, + DisplayPol + }; + + explicit GLScopeMultiGUI(QWidget* parent = 0); + ~GLScopeMultiGUI(); + + void setBuddies(MessageQueue* messageQueue, ScopeVisMulti* scopeVis, GLScopeMulti* glScope); + + void setSampleRate(int sampleRate); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + + bool handleMessage(Message* message); + + // preconfiguration methods + // global (first line): + void setDisplayMode(DisplayMode displayMode); + void setTraceIntensity(int value); + void setGridIntensity(int value); + void setTimeBase(int step); + void setTimeOffset(int step); + void setTraceLength(int step); + void setPreTrigger(int step); + // trace (second line): + void changeTrace(int traceIndex, const ScopeVisMulti::TraceData& traceData); + void addTrace(const ScopeVisMulti::TraceData& traceData); + void focusOnTrace(int traceIndex); + // trigger (third line): + void changeTrigger(int triggerIndex, const ScopeVisMulti::TriggerData& triggerData); + void addTrigger(const ScopeVisMulti::TriggerData& triggerData); + void focusOnTrigger(int triggerIndex); + +private: + class TrigUIBlocker + { + public: + TrigUIBlocker(Ui::GLScopeMultiGUI *ui); + ~TrigUIBlocker(); + + void unBlock(); + + private: + Ui::GLScopeMultiGUI *m_ui; + bool m_oldStateTrigMode; + bool m_oldStateTrigCount; + bool m_oldStateTrigPos; + bool m_oldStateTrigNeg; + bool m_oldStateTrigBoth; + bool m_oldStateTrigLevelCoarse; + bool m_oldStateTrigLevelFine; + bool m_oldStateTrigDelayCoarse; + bool m_oldStateTrigDelayFine; + }; + + class TraceUIBlocker + { + public: + TraceUIBlocker(Ui::GLScopeMultiGUI *ui); + ~TraceUIBlocker(); + + void unBlock(); + + private: + Ui::GLScopeMultiGUI *m_ui; + bool m_oldStateTrace; + bool m_oldStateTraceAdd; + bool m_oldStateTraceDel; + bool m_oldStateTraceMode; + bool m_oldStateAmp; + bool m_oldStateOfsCoarse; + bool m_oldStateOfsFine; + bool m_oldStateTraceDelayCoarse; + bool m_oldStateTraceDelayFine; + bool m_oldStateTraceColor; + }; + + class MainUIBlocker + { + public: + MainUIBlocker(Ui::GLScopeMultiGUI *ui); + ~MainUIBlocker(); + + void unBlock(); + + private: + Ui::GLScopeMultiGUI *m_ui; + bool m_oldStateOnlyX; + bool m_oldStateOnlyY; + bool m_oldStateHorizontalXY; + bool m_oldStateVerticalXY; + bool m_oldStatePolar; +// bool m_oldStateTime; +// bool m_oldStateTimeOfs; +// bool m_oldStateTraceLen; + }; + + Ui::GLScopeMultiGUI* ui; + + MessageQueue* m_messageQueue; + ScopeVisMulti* m_scopeVis; + GLScopeMulti* m_glScope; + + int m_sampleRate; + int m_timeBase; + int m_timeOffset; + int m_traceLenMult; + QColor m_focusedTraceColor; + QColor m_focusedTriggerColor; + + static const double amps[11]; + + void applySettings(); + // First row + void setTraceIndexDisplay(); + void setTimeScaleDisplay(); + void setTraceLenDisplay(); + void setTimeOfsDisplay(); + // Second row + void setAmpScaleDisplay(); + void setAmpOfsDisplay(); + void setTraceDelayDisplay(); + // Third row + void setTrigIndexDisplay(); + void setTrigCountDisplay(); + void setTrigLevelDisplay(); + void setTrigDelayDisplay(); + void setTrigPreDisplay(); + + void changeCurrentTrace(); + void changeCurrentTrigger(); + + void fillTraceData(ScopeVisMulti::TraceData& traceData); + void fillTriggerData(ScopeVisMulti::TriggerData& triggerData); + void setTriggerUI(const ScopeVisMulti::TriggerData& triggerData); + void setTraceUI(const ScopeVisMulti::TraceData& traceData); + + void fillProjectionCombo(QComboBox* comboBox); + void disableLiveMode(bool disable); + +private slots: + void on_scope_sampleRateChanged(int value); + // First row + void on_onlyX_toggled(bool checked); + void on_onlyY_toggled(bool checked); + void on_horizontalXY_toggled(bool checked); + void on_verticalXY_toggled(bool checked); + void on_polar_toggled(bool checked); + void on_traceIntensity_valueChanged(int value); + void on_gridIntensity_valueChanged(int value); + void on_time_valueChanged(int value); + void on_timeOfs_valueChanged(int value); + void on_traceLen_valueChanged(int value); + // Second row + void on_trace_valueChanged(int value); + void on_traceAdd_clicked(bool checked); + void on_traceDel_clicked(bool checked); + void on_traceUp_clicked(bool checked); + void on_traceDown_clicked(bool checked); + void on_traceMode_currentIndexChanged(int index); + void on_amp_valueChanged(int value); + void on_ofsCoarse_valueChanged(int value); + void on_ofsFine_valueChanged(int value); + void on_traceDelayCoarse_valueChanged(int value); + void on_traceDelayFine_valueChanged(int value); + void on_traceView_toggled(bool checked); + void on_traceColor_clicked(); + void on_mem_valueChanged(int value); + // Third row + void on_trig_valueChanged(int value); + void on_trigAdd_clicked(bool checked); + void on_trigDel_clicked(bool checked); + void on_trigUp_clicked(bool checked); + void on_trigDown_clicked(bool checked); + void on_trigMode_currentIndexChanged(int index); + void on_trigCount_valueChanged(int value); + void on_trigPos_toggled(bool checked); + void on_trigNeg_toggled(bool checked); + void on_trigBoth_toggled(bool checked); + void on_trigLevelCoarse_valueChanged(int value); + void on_trigLevelFine_valueChanged(int value); + void on_trigDelayCoarse_valueChanged(int value); + void on_trigDelayFine_valueChanged(int value); + void on_trigPre_valueChanged(int value); + void on_trigColor_clicked(); + void on_trigOneShot_toggled(bool checked); + void on_freerun_toggled(bool checked); +}; + + +#endif /* SDRBASE_GUI_GLSCOPEMULTIGUI_H_ */ diff --git a/sdrbase/gui/glscopemultigui.ui b/sdrbase/gui/glscopemultigui.ui new file mode 100644 index 000000000..253784e37 --- /dev/null +++ b/sdrbase/gui/glscopemultigui.ui @@ -0,0 +1,1907 @@ + + + GLScopeMultiGUI + + + + 0 + 0 + 750 + 120 + + + + + 750 + 0 + + + + + 8 + + + + Oscilloscope + + + + 1 + + + 2 + + + 1 + + + 2 + + + 1 + + + + + 2 + + + 0 + + + 0 + + + + + + 24 + 24 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 190 + 190 + 190 + + + + + + + + Show only X trace (trace #0) + + + X + + + true + + + false + + + + + + + + 24 + 24 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 190 + 190 + 190 + + + + + + + + Show only Y traces + + + Y + + + + + + + + 24 + 24 + + + + Display X and Y traces side by side + + + ... + + + + :/horizontal_w.png:/horizontal_w.png + + + + + + + + 24 + 24 + + + + Display X and Y trances on top of each other + + + ... + + + + :/vertical_w.png:/vertical_w.png + + + + + + + + 24 + 24 + + + + Display XY traces and polar trace + + + ... + + + + :/constellation.png:/constellation.png + + + + + + + + 0 + 0 + + + + + 24 + 24 + + + + Trace intensity + + + 100 + + + 1 + + + 50 + + + + + + + + 24 + 24 + + + + Grid intensity + + + 100 + + + 1 + + + 10 + + + + + + + Qt::Vertical + + + + + + + Time range + + + T: + + + + + + + + 40 + 0 + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Time range + + + 1 + + + 100 + + + 1 + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + + + + Time offset + + + O: + + + + + + + + 40 + 0 + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Time offset + + + 100 + + + 1 + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + + + + Trace length + + + L: + + + + + + + + 40 + 0 + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Trace length + + + 1 + + + 20 + + + 1 + + + 1 + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + + + + + 52 + 0 + + + + Currently displayed trace sample rate (kS/s) + + + 00000.00 +kS/s + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + + + + 2 + + + + + + 24 + 0 + + + + Tra + + + + + + + + 15 + 0 + + + + 0 + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 24 + 24 + + + + Trace index (0 is X trace) + + + 0 + + + 1 + + + + + + + 0 + + + + + + 18 + 18 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 190 + 190 + 190 + + + + + + + + + 10 + + + + Add a new Y trace + + + + + + + + + + + + 18 + 18 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 190 + 190 + 190 + + + + + + + + + 10 + + + + Remove current Y trace + + + - + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 18 + 18 + + + + Move trace up in trace list (wraps around) + + + + + + + :/arrow_up.png:/arrow_up.png + + + + + + + + 18 + 18 + + + + Move trace down in trace list + + + + + + + :/arrow_down.png:/arrow_down.png + + + + + + + + + Qt::Vertical + + + + + + + + 65 + 16777215 + + + + Trace mode + + + + Real + + + + + Imag + + + + + Mag + + + + + MagdB + + + + + Phi + + + + + dPhi + + + + + + + + + 24 + 24 + + + + Source port selection + + + 0 + + + 1 + + + + + + + + 12 + 0 + + + + Source port number + + + 0 + + + + + + + Qt::Vertical + + + + + + + Vertical range + + + A: + + + + + + + + 36 + 0 + + + + Vertical range value + + + 000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Vertical range + + + 10 + + + 1 + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + + + + Vertical offset + + + O: + + + + + + + + 36 + 0 + + + + Vertical offset value + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 2 + + + + + + 16777215 + 14 + + + + Vertical offset coarse + + + -100 + + + 1 + + + Qt::Horizontal + + + + + + + + 16777215 + 14 + + + + Vertical offset fine + + + 200 + + + 1 + + + Qt::Horizontal + + + + + + + + + Qt::Vertical + + + + + + + Trace delay + + + D: + + + + + + + + 40 + 0 + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 2 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Trace delay coarse + + + 48 + + + 1 + + + Qt::Horizontal + + + + + + + Trace delay fine + + + 100 + + + 1 + + + Qt::Horizontal + + + + + + + + + Qt::Vertical + + + + + + + View trace toggle + + + + + + true + + + + + + + + 16 + 16 + + + + + 16 + 16 + + + + Current trace color (click to change) + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + + + + M: + + + + + + + Trace memory index (greater is older) + + + 00 + + + + + + + + 24 + 24 + + + + Select trace memory (0 is live) + + + 15 + + + 1 + + + + + + + + + Qt::Horizontal + + + + + + + 2 + + + + + + 24 + 0 + + + + Trig + + + + + + + + 15 + 0 + + + + 0 + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 24 + 24 + + + + Sellect triggger in trigger chain + + + 0 + + + 1 + + + + + + + 0 + + + + + + 18 + 18 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 190 + 190 + 190 + + + + + + + + + 10 + + + + Add a new trigger to the trigger chain + + + + + + + + + + + + 18 + 18 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 190 + 190 + 190 + + + + + + + + + 10 + + + + Remove current trigger from the chain + + + - + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 18 + 18 + + + + Move trigger up in trigger chain (wraps around) + + + + + + + :/arrow_up.png:/arrow_up.png + + + + + + + + 18 + 18 + + + + Move trigger down in trigger chain + + + + + + + :/arrow_down.png:/arrow_down.png + + + + + + + + + Qt::Vertical + + + + + + + + 65 + 16777215 + + + + Trigger mode + + + + Real + + + + + Imag + + + + + Mag + + + + + MagdB + + + + + Phi + + + + + dPhi + + + + + + + + + 24 + 24 + + + + 0 + + + 1 + + + + + + + + 12 + 0 + + + + 0 + + + + + + + Qt::Vertical + + + + + + + + 24 + 24 + + + + Number of trigger condition before trigger is actuated + + + 100 + + + 1 + + + + + + + 00 + + + + + + + + 24 + 24 + + + + Trigger on rising edge + + + ... + + + + :/slopep_icon.png:/slopep_icon.png + + + + + + + + 24 + 24 + + + + Trigger on falling edge + + + ... + + + + :/slopen_icon.png:/slopen_icon.png + + + + + + + + 24 + 24 + + + + Trigger on both edges + + + ... + + + + :/slopeb_icon.png:/slopeb_icon.png + + + + + + + Qt::Vertical + + + + + + + Triger level + + + L: + + + + + + + + 40 + 0 + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 2 + + + + + + 16777215 + 14 + + + + Trigger level coarse + + + -100 + + + 1 + + + Qt::Horizontal + + + + + + + + 16777215 + 14 + + + + Trigger level fine + + + 200 + + + 1 + + + Qt::Horizontal + + + + + + + + + Qt::Vertical + + + + + + + Trigger delay + + + D: + + + + + + + + 40 + 0 + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 2 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Trigger delay coarse (trace length multiplier) + + + 100 + + + 1 + + + Qt::Horizontal + + + + + + + Trigger delay fine (trace length divider) + + + 480 + + + 1 + + + Qt::Horizontal + + + + + + + + + Qt::Vertical + + + + + + + Pre-trigger delay + + + P: + + + + + + + + 40 + 0 + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Pre-trigger delay + + + 1 + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 16 + 16 + + + + + 16 + 16 + + + + Current trigger color (click to change) + + + + + + + + + + + 24 + 24 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 190 + 190 + 190 + + + + + + + + One shot trigger + + + 1 + + + + + + + + 24 + 24 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 190 + 190 + 190 + + + + + + + + Freerun (de-activate triggers) + + + F + + + + + + + + + + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+ + ClickableLabel + QLabel +
gui/clickablelabel.h
+
+
+ + + + +