git clone git://git.osmocom.org/sdrangelove.git
@@ -0,0 +1,28 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "audio/audiodeviceinfo.h"
|
||||
|
||||
AudioDeviceInfo::AudioDeviceInfo()
|
||||
{
|
||||
}
|
||||
|
||||
int AudioDeviceInfo::match(const QString& api, const QString device) const
|
||||
{
|
||||
// nothing found - fall back to default
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <string.h>
|
||||
#include <QTime>
|
||||
#include "audio/audiofifo.h"
|
||||
|
||||
#define MIN(x, y) ((x) < (y) ? (x) : (y))
|
||||
|
||||
AudioFifo::AudioFifo() :
|
||||
m_fifo(NULL)
|
||||
{
|
||||
m_size = 0;
|
||||
m_fill = 0;
|
||||
m_head = 0;
|
||||
m_tail = 0;
|
||||
}
|
||||
|
||||
AudioFifo::AudioFifo(uint sampleSize, uint numSamples) :
|
||||
m_fifo(NULL)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
create(sampleSize, numSamples);
|
||||
}
|
||||
|
||||
AudioFifo::~AudioFifo()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
if(m_fifo != NULL) {
|
||||
delete[] m_fifo;
|
||||
m_fifo = NULL;
|
||||
}
|
||||
|
||||
m_writeWaitCondition.wakeOne();
|
||||
m_readWaitCondition.wakeOne();
|
||||
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
bool AudioFifo::setSize(uint sampleSize, uint numSamples)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
return create(sampleSize, numSamples);
|
||||
}
|
||||
|
||||
uint AudioFifo::write(const quint8* data, uint numSamples, int timeout)
|
||||
{
|
||||
QTime time;
|
||||
uint total;
|
||||
uint remaining;
|
||||
uint copyLen;
|
||||
|
||||
if(m_fifo == NULL)
|
||||
return 0;
|
||||
|
||||
time.start();
|
||||
m_mutex.lock();
|
||||
|
||||
if(timeout == 0)
|
||||
total = MIN(numSamples, m_size - m_fill);
|
||||
else total = numSamples;
|
||||
|
||||
remaining = total;
|
||||
while(remaining > 0) {
|
||||
if(isFull()) {
|
||||
if(time.elapsed() < timeout) {
|
||||
m_writeWaitLock.lock();
|
||||
m_mutex.unlock();
|
||||
int ms = timeout - time.elapsed();
|
||||
if(ms < 1)
|
||||
ms = 1;
|
||||
bool ok = m_writeWaitCondition.wait(&m_writeWaitLock, ms);
|
||||
m_writeWaitLock.unlock();
|
||||
if(!ok)
|
||||
return total - remaining;
|
||||
m_mutex.lock();
|
||||
if(m_fifo == NULL) {
|
||||
m_mutex.unlock();
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
m_mutex.unlock();
|
||||
return total - remaining;
|
||||
}
|
||||
}
|
||||
copyLen = MIN(remaining, m_size - m_fill);
|
||||
copyLen = MIN(copyLen, m_size - m_tail);
|
||||
memcpy(m_fifo + (m_tail * m_sampleSize), data, copyLen * m_sampleSize);
|
||||
m_tail += copyLen;
|
||||
m_tail %= m_size;
|
||||
m_fill += copyLen;
|
||||
data += copyLen * m_sampleSize;
|
||||
remaining -= copyLen;
|
||||
m_readWaitCondition.wakeOne();
|
||||
}
|
||||
|
||||
m_mutex.unlock();
|
||||
return total;
|
||||
}
|
||||
|
||||
uint AudioFifo::read(quint8* data, uint numSamples, int timeout)
|
||||
{
|
||||
QTime time;
|
||||
uint total;
|
||||
uint remaining;
|
||||
uint copyLen;
|
||||
|
||||
if(m_fifo == NULL)
|
||||
return 0;
|
||||
|
||||
time.start();
|
||||
m_mutex.lock();
|
||||
|
||||
if(timeout == 0)
|
||||
total = MIN(numSamples, m_fill);
|
||||
else total = numSamples;
|
||||
|
||||
remaining = total;
|
||||
while(remaining > 0) {
|
||||
if(isEmpty()) {
|
||||
if(time.elapsed() < timeout) {
|
||||
m_readWaitLock.lock();
|
||||
m_mutex.unlock();
|
||||
int ms = timeout - time.elapsed();
|
||||
if(ms < 1)
|
||||
ms = 1;
|
||||
bool ok = m_readWaitCondition.wait(&m_readWaitLock, ms);
|
||||
m_readWaitLock.unlock();
|
||||
if(!ok)
|
||||
return total - remaining;
|
||||
m_mutex.lock();
|
||||
if(m_fifo == NULL) {
|
||||
m_mutex.unlock();
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
m_mutex.unlock();
|
||||
return total - remaining;
|
||||
}
|
||||
}
|
||||
|
||||
copyLen = MIN(remaining, m_fill);
|
||||
copyLen = MIN(copyLen, m_size - m_head);
|
||||
memcpy(data, m_fifo + (m_head * m_sampleSize), copyLen * m_sampleSize);
|
||||
m_head += copyLen;
|
||||
m_head %= m_size;
|
||||
m_fill -= copyLen;
|
||||
data += copyLen * m_sampleSize;
|
||||
remaining -= copyLen;
|
||||
m_writeWaitCondition.wakeOne();
|
||||
}
|
||||
|
||||
m_mutex.unlock();
|
||||
return total;
|
||||
}
|
||||
|
||||
uint AudioFifo::drain(uint numSamples)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
if(numSamples > m_fill)
|
||||
numSamples = m_fill;
|
||||
m_head = (m_head + numSamples) % m_size;
|
||||
m_fill -= numSamples;
|
||||
|
||||
m_writeWaitCondition.wakeOne();
|
||||
return numSamples;
|
||||
}
|
||||
|
||||
void AudioFifo::clear()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
m_fill = 0;
|
||||
m_head = 0;
|
||||
m_tail = 0;
|
||||
|
||||
m_writeWaitCondition.wakeOne();
|
||||
}
|
||||
|
||||
bool AudioFifo::create(uint sampleSize, uint numSamples)
|
||||
{
|
||||
if(m_fifo != NULL) {
|
||||
delete[] m_fifo;
|
||||
m_fifo = NULL;
|
||||
}
|
||||
|
||||
m_sampleSize = sampleSize;
|
||||
m_size = 0;
|
||||
m_fill = 0;
|
||||
m_head = 0;
|
||||
m_tail = 0;
|
||||
|
||||
if((m_fifo = new qint8[numSamples * m_sampleSize]) == NULL) {
|
||||
qDebug("out of memory");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_size = numSamples;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <string.h>
|
||||
#include <QAudioFormat>
|
||||
#include <QAudioDeviceInfo>
|
||||
#include <QAudioOutput>
|
||||
#include "audio/audiooutput.h"
|
||||
#include "audio/audiofifo.h"
|
||||
|
||||
AudioOutput::AudioOutput() :
|
||||
m_mutex(),
|
||||
m_audioOutput(NULL),
|
||||
m_audioFifos()
|
||||
{
|
||||
}
|
||||
|
||||
AudioOutput::~AudioOutput()
|
||||
{
|
||||
stop();
|
||||
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
for(AudioFifos::iterator it = m_audioFifos.begin(); it != m_audioFifos.end(); ++it)
|
||||
delete *it;
|
||||
m_audioFifos.clear();
|
||||
}
|
||||
|
||||
bool AudioOutput::start(int device, int rate)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
Q_UNUSED(device);
|
||||
Q_UNUSED(rate);
|
||||
|
||||
QAudioFormat format;
|
||||
QAudioDeviceInfo devInfo(QAudioDeviceInfo::defaultOutputDevice());
|
||||
|
||||
format.setSampleRate(41000);
|
||||
format.setChannelCount(2);
|
||||
format.setSampleSize(16);
|
||||
format.setCodec("audio/pcm");
|
||||
format.setByteOrder(QAudioFormat::LittleEndian);
|
||||
format.setSampleType(QAudioFormat::SignedInt);
|
||||
|
||||
if(!devInfo.isFormatSupported(format)) {
|
||||
qDebug("default format not supported - try to use nearest");
|
||||
format = devInfo.nearestFormat(format);
|
||||
}
|
||||
|
||||
if(format.sampleSize() != 16) {
|
||||
qDebug("audio device doesn't support 16 bit samples (%s)", qPrintable(devInfo.defaultOutputDevice().deviceName()));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_audioOutput = new QAudioOutput(format);
|
||||
|
||||
QIODevice::open(QIODevice::ReadOnly);
|
||||
|
||||
m_audioOutput->start(this);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
void AudioOutput::stop()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
if(m_audioOutput != NULL) {
|
||||
m_audioOutput->stop();
|
||||
delete m_audioOutput;
|
||||
m_audioOutput = NULL;
|
||||
}
|
||||
QIODevice::close();
|
||||
}
|
||||
|
||||
void AudioOutput::addFifo(AudioFifo* audioFifo)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
m_audioFifos.push_back(audioFifo);
|
||||
}
|
||||
|
||||
void AudioOutput::removeFifo(AudioFifo* audioFifo)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
m_audioFifos.remove(audioFifo);
|
||||
}
|
||||
|
||||
bool AudioOutput::open(OpenMode mode)
|
||||
{
|
||||
Q_UNUSED(mode);
|
||||
return false;
|
||||
}
|
||||
|
||||
qint64 AudioOutput::readData(char* data, qint64 maxLen)
|
||||
{
|
||||
if(maxLen == 0)
|
||||
return 0;
|
||||
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
maxLen -= maxLen % 4;
|
||||
int framesPerBuffer = maxLen / 4;
|
||||
|
||||
if((int)m_mixBuffer.size() < framesPerBuffer * 2) {
|
||||
m_mixBuffer.resize(framesPerBuffer * 2); // allocate 2 qint32 per frame (stereo)
|
||||
if(m_mixBuffer.size() != framesPerBuffer * 2)
|
||||
return 0;
|
||||
}
|
||||
memset(&m_mixBuffer[0], 0x00, 2 * framesPerBuffer * sizeof(m_mixBuffer[0])); // start with silence
|
||||
|
||||
// sum up a block from all fifos
|
||||
for(AudioFifos::iterator it = m_audioFifos.begin(); it != m_audioFifos.end(); ++it) {
|
||||
// use outputBuffer as temp - yes, one memcpy could be saved
|
||||
uint samples = (*it)->read((quint8*)data, framesPerBuffer, 1);
|
||||
const qint16* src = (const qint16*)data;
|
||||
std::vector<qint32>::iterator dst = m_mixBuffer.begin();
|
||||
for(uint i = 0; i < samples; i++) {
|
||||
*dst += *src;
|
||||
++src;
|
||||
++dst;
|
||||
*dst += *src;
|
||||
++src;
|
||||
++dst;
|
||||
}
|
||||
}
|
||||
|
||||
// convert to int16
|
||||
std::vector<qint32>::const_iterator src = m_mixBuffer.begin();
|
||||
qint16* dst = (qint16*)data;
|
||||
for(int i = 0; i < framesPerBuffer; ++i) {
|
||||
// left channel
|
||||
qint32 s = *src++;
|
||||
if(s < -32768)
|
||||
s = -32768;
|
||||
else if(s > 32767)
|
||||
s = 32767;
|
||||
*dst++ = s;
|
||||
// right channel
|
||||
s = *src++;
|
||||
if(s < -32768)
|
||||
s = -32768;
|
||||
else if(s > 32767)
|
||||
s = 32767;
|
||||
*dst++ = s;
|
||||
}
|
||||
|
||||
return framesPerBuffer * 4;
|
||||
}
|
||||
|
||||
qint64 AudioOutput::writeData(const char* data, qint64 len)
|
||||
{
|
||||
Q_UNUSED(data);
|
||||
Q_UNUSED(len);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
#include "dsp/channelizer.h"
|
||||
#include "dsp/inthalfbandfilter.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
|
||||
Channelizer::Channelizer(SampleSink* sampleSink) :
|
||||
m_sampleSink(sampleSink),
|
||||
m_inputSampleRate(100000),
|
||||
m_requestedOutputSampleRate(100000),
|
||||
m_requestedCenterFrequency(0),
|
||||
m_currentOutputSampleRate(100000),
|
||||
m_currentCenterFrequency(0)
|
||||
{
|
||||
}
|
||||
|
||||
Channelizer::~Channelizer()
|
||||
{
|
||||
freeFilterChain();
|
||||
}
|
||||
|
||||
void Channelizer::configure(MessageQueue* messageQueue, int sampleRate, int centerFrequency)
|
||||
{
|
||||
Message* cmd = DSPConfigureChannelizer::create(sampleRate, centerFrequency);
|
||||
cmd->submit(messageQueue, this);
|
||||
}
|
||||
|
||||
void Channelizer::feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool firstOfBurst)
|
||||
{
|
||||
for(SampleVector::const_iterator sample = begin; sample != end; ++sample) {
|
||||
Sample s(*sample);
|
||||
FilterStages::iterator stage = m_filterStages.begin();
|
||||
while(stage != m_filterStages.end()) {
|
||||
if(!(*stage)->work(&s))
|
||||
break;
|
||||
++stage;
|
||||
}
|
||||
if(stage == m_filterStages.end())
|
||||
m_sampleBuffer.push_back(s);
|
||||
}
|
||||
|
||||
if(m_sampleSink != NULL)
|
||||
m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), firstOfBurst);
|
||||
|
||||
m_sampleBuffer.clear();
|
||||
}
|
||||
|
||||
void Channelizer::start()
|
||||
{
|
||||
if(m_sampleSink != NULL)
|
||||
m_sampleSink->start();
|
||||
}
|
||||
|
||||
void Channelizer::stop()
|
||||
{
|
||||
if(m_sampleSink != NULL)
|
||||
m_sampleSink->stop();
|
||||
}
|
||||
|
||||
bool Channelizer::handleMessage(Message* cmd)
|
||||
{
|
||||
if(DSPSignalNotification::match(cmd)) {
|
||||
DSPSignalNotification* signal = (DSPSignalNotification*)cmd;
|
||||
m_inputSampleRate = signal->getSampleRate();
|
||||
applyConfiguration();
|
||||
cmd->completed();
|
||||
if(m_sampleSink != NULL) {
|
||||
signal = DSPSignalNotification::create(m_currentOutputSampleRate, m_currentCenterFrequency);
|
||||
if(!m_sampleSink->handleMessage(signal))
|
||||
signal->completed();
|
||||
}
|
||||
return true;
|
||||
} else if(DSPConfigureChannelizer::match(cmd)) {
|
||||
DSPConfigureChannelizer* chan = (DSPConfigureChannelizer*)cmd;
|
||||
m_requestedOutputSampleRate = chan->getSampleRate();
|
||||
m_requestedCenterFrequency = chan->getCenterFrequency();
|
||||
applyConfiguration();
|
||||
cmd->completed();
|
||||
if(m_sampleSink != NULL) {
|
||||
DSPSignalNotification* signal = DSPSignalNotification::create(m_currentOutputSampleRate, m_currentCenterFrequency);
|
||||
if(!m_sampleSink->handleMessage(signal))
|
||||
signal->completed();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if(m_sampleSink != NULL)
|
||||
return m_sampleSink->handleMessage(cmd);
|
||||
else return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Channelizer::applyConfiguration()
|
||||
{
|
||||
freeFilterChain();
|
||||
m_currentCenterFrequency = createFilterChain(
|
||||
m_inputSampleRate / -2, m_inputSampleRate / 2,
|
||||
m_requestedCenterFrequency - m_requestedOutputSampleRate / 2, m_requestedCenterFrequency + m_requestedOutputSampleRate / 2);
|
||||
m_currentOutputSampleRate = m_inputSampleRate / (1 << m_filterStages.size());
|
||||
}
|
||||
|
||||
Channelizer::FilterStage::FilterStage(Mode mode) :
|
||||
m_filter(new IntHalfbandFilter),
|
||||
m_workFunction(NULL)
|
||||
{
|
||||
switch(mode) {
|
||||
case ModeCenter:
|
||||
m_workFunction = &IntHalfbandFilter::workDecimateCenter;
|
||||
break;
|
||||
|
||||
case ModeLowerHalf:
|
||||
m_workFunction = &IntHalfbandFilter::workDecimateLowerHalf;
|
||||
break;
|
||||
|
||||
case ModeUpperHalf:
|
||||
m_workFunction = &IntHalfbandFilter::workDecimateUpperHalf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Channelizer::FilterStage::~FilterStage()
|
||||
{
|
||||
delete m_filter;
|
||||
}
|
||||
|
||||
bool Channelizer::signalContainsChannel(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd) const
|
||||
{
|
||||
//qDebug(" testing signal [%f, %f], channel [%f, %f]", sigStart, sigEnd, chanStart, chanEnd);
|
||||
if(sigEnd <= sigStart)
|
||||
return false;
|
||||
if(chanEnd <= chanStart)
|
||||
return false;
|
||||
return (sigStart <= chanStart) && (sigEnd >= chanEnd);
|
||||
}
|
||||
|
||||
Real Channelizer::createFilterChain(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd)
|
||||
{
|
||||
Real sigBw = sigEnd - sigStart;
|
||||
Real safetyMargin = sigBw / 20;
|
||||
Real rot = sigBw / 4;
|
||||
|
||||
safetyMargin = 0;
|
||||
|
||||
//qDebug("Signal [%f, %f] (BW %f), Channel [%f, %f], Rot %f, Safety %f", sigStart, sigEnd, sigBw, chanStart, chanEnd, rot, safetyMargin);
|
||||
#if 1
|
||||
// check if it fits into the left half
|
||||
if(signalContainsChannel(sigStart + safetyMargin, sigStart + sigBw / 2.0 - safetyMargin, chanStart, chanEnd)) {
|
||||
//qDebug("-> take left half (rotate by +1/4 and decimate by 2)");
|
||||
m_filterStages.push_back(new FilterStage(FilterStage::ModeLowerHalf));
|
||||
return createFilterChain(sigStart, sigStart + sigBw / 2.0, chanStart, chanEnd);
|
||||
}
|
||||
|
||||
// check if it fits into the right half
|
||||
if(signalContainsChannel(sigEnd - sigBw / 2.0f + safetyMargin, sigEnd - safetyMargin, chanStart, chanEnd)) {
|
||||
//qDebug("-> take right half (rotate by -1/4 and decimate by 2)");
|
||||
m_filterStages.push_back(new FilterStage(FilterStage::ModeUpperHalf));
|
||||
return createFilterChain(sigEnd - sigBw / 2.0f, sigEnd, chanStart, chanEnd);
|
||||
}
|
||||
|
||||
// check if it fits into the center
|
||||
if(signalContainsChannel(sigStart + rot + safetyMargin, sigStart + rot + sigBw / 2.0f - safetyMargin, chanStart, chanEnd)) {
|
||||
//qDebug("-> take center half (decimate by 2)");
|
||||
m_filterStages.push_back(new FilterStage(FilterStage::ModeCenter));
|
||||
return createFilterChain(sigStart + rot, sigStart + sigBw / 2.0f + rot, chanStart, chanEnd);
|
||||
}
|
||||
#endif
|
||||
Real ofs = ((chanEnd - chanStart) / 2.0 + chanStart) - ((sigEnd - sigStart) / 2.0 + sigStart);
|
||||
qDebug("-> complete (final BW %f, frequency offset %f)", sigBw, ofs);
|
||||
return ofs;
|
||||
}
|
||||
|
||||
void Channelizer::freeFilterChain()
|
||||
{
|
||||
for(FilterStages::iterator it = m_filterStages.begin(); it != m_filterStages.end(); ++it)
|
||||
delete *it;
|
||||
m_filterStages.clear();
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QTime>
|
||||
#include <stdio.h>
|
||||
#include "channelizer.h"
|
||||
#include "hardware/audiooutput.h"
|
||||
|
||||
Channelizer::Channelizer()
|
||||
{
|
||||
#if 0
|
||||
m_spectrum.configure(128 , 25, FFTWindow::Bartlett);
|
||||
m_buffer.resize(2048);
|
||||
m_bufferFill = 0;
|
||||
m_nco.setFreq(-100000, 500000);
|
||||
m_interpolator.create(51, 32, 32 * 500000, 150000 / 2);
|
||||
m_distance = 500000.0 / 176400.0;
|
||||
|
||||
m_interpolator2.create(19, 8, 8 * 176400, 15000 / 2);
|
||||
m_distance2 = 4;
|
||||
|
||||
m_audioFifo.setSize(4, 44100 / 2 * 4);
|
||||
m_audioOutput = new AudioOutput;
|
||||
m_audioOutput->start(0, 44100, &m_audioFifo);
|
||||
m_resampler = 1.0;
|
||||
|
||||
m_resamplerCtrl.setup(0.00001, 0, -0.00001);
|
||||
#endif
|
||||
}
|
||||
|
||||
Channelizer::~Channelizer()
|
||||
{
|
||||
#if 0
|
||||
m_audioOutput->stop();
|
||||
delete m_audioOutput;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if 0
|
||||
void Channelizer::setGLSpectrum(GLSpectrum* glSpectrum)
|
||||
{
|
||||
m_spectrum.setGLSpectrum(glSpectrum);
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t Channelizer::workUnitSize()
|
||||
{
|
||||
#if 0
|
||||
return m_buffer.size();
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t Channelizer::work(SampleVector::const_iterator begin, SampleVector::const_iterator end)
|
||||
{
|
||||
#if 0
|
||||
int buffered = m_audioOutput->bufferedSamples();
|
||||
|
||||
if(m_audioFifo.fill() < (m_audioFifo.size() / 6)) {
|
||||
while(m_audioFifo.fill() < (m_audioFifo.size() / 2)) {
|
||||
quint32 d = 0;
|
||||
m_audioFifo.write((quint8*)&d, 4);
|
||||
}
|
||||
qDebug("underflow - fill %d (vs %d)", m_audioFifo.fill(), m_audioFifo.size() / 4 / 2);
|
||||
}
|
||||
|
||||
buffered = m_audioOutput->bufferedSamples();
|
||||
int fill = m_audioFifo.fill() / 4 + buffered;
|
||||
float err = (float)fill / ((m_audioFifo.size() / 4) / 2);
|
||||
|
||||
float ctrl = m_resamplerCtrl.feed(err);
|
||||
//float resamplerRate = (ctrl / 1.0);
|
||||
float resamplerRate = err;
|
||||
|
||||
if(resamplerRate < 0.9999)
|
||||
resamplerRate = 0.9999;
|
||||
else if(resamplerRate > 1.0001)
|
||||
resamplerRate = 1.0001;
|
||||
m_resampler = m_resampler * 0.99 + resamplerRate * 0.01;
|
||||
//m_resampler = resamplerRate;
|
||||
|
||||
if(m_resampler < 0.995)
|
||||
m_resampler = 0.995;
|
||||
else if(m_resampler > 1.005)
|
||||
m_resampler = 1.005;
|
||||
|
||||
//qDebug("%lld %5d %f %f %f", QDateTime::currentMSecsSinceEpoch(), fill, ctrl, m_resampler, err);
|
||||
|
||||
struct AudioSample {
|
||||
qint16 l;
|
||||
qint16 r;
|
||||
};
|
||||
|
||||
size_t count = end - begin;
|
||||
Complex ci;
|
||||
bool consumed;
|
||||
bool consumed2;
|
||||
|
||||
for(SampleVector::const_iterator it = begin; it < end; it++) {
|
||||
Complex c(it->real() / 32768.0, it->imag() / 32768.0);
|
||||
c *= m_nco.nextIQ();
|
||||
|
||||
consumed = false;
|
||||
if(m_interpolator.interpolate(&m_distance, c, &consumed, &ci)) {
|
||||
|
||||
Complex d = ci * conj(m_lastSample);
|
||||
m_lastSample = ci;
|
||||
//Complex demod(atan2(d.imag(), d.real()) * 0.5, 0);
|
||||
Real demod = atan2(d.imag(), d.real()) / M_PI;
|
||||
|
||||
consumed2 = false;
|
||||
c = Complex(demod, 0);
|
||||
while(!consumed2) {
|
||||
if(m_interpolator2.interpolate(&m_distance2, c, &consumed2, &ci)) {
|
||||
m_buffer[m_bufferFill++] = Sample(ci.real() * 32767.0, 0.0);
|
||||
|
||||
AudioSample s;
|
||||
s.l = ci.real() * 32767.0;
|
||||
s.r = s.l;
|
||||
m_audioFifo.write((quint8*)&s, 4, 1);
|
||||
|
||||
if(m_bufferFill >= m_buffer.size()) {
|
||||
m_spectrum.feed(m_buffer.begin(), m_buffer.end());
|
||||
m_bufferFill = 0;
|
||||
}
|
||||
m_distance2 += 4.0 * m_resampler;
|
||||
}
|
||||
}
|
||||
m_distance += 500000 / 176400.0;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "dsp/channelmarker.h"
|
||||
|
||||
QRgb ChannelMarker::m_colorTable[] = {
|
||||
qRgb(0xc0, 0x00, 0x00),
|
||||
qRgb(0x00, 0xc0, 0x00),
|
||||
qRgb(0x00, 0x00, 0xc0),
|
||||
|
||||
qRgb(0xc0, 0xc0, 0x00),
|
||||
qRgb(0xc0, 0x00, 0xc0),
|
||||
qRgb(0x00, 0xc0, 0xc0),
|
||||
|
||||
qRgb(0xc0, 0x60, 0x00),
|
||||
qRgb(0xc0, 0x00, 0x60),
|
||||
qRgb(0x60, 0x00, 0xc0),
|
||||
|
||||
qRgb(0x60, 0x00, 0x00),
|
||||
qRgb(0x00, 0x60, 0x00),
|
||||
qRgb(0x00, 0x00, 0x60),
|
||||
|
||||
qRgb(0x60, 0x60, 0x00),
|
||||
qRgb(0x60, 0x00, 0x60),
|
||||
qRgb(0x00, 0x60, 0x60),
|
||||
|
||||
0
|
||||
};
|
||||
int ChannelMarker::m_nextColor = 0;
|
||||
|
||||
ChannelMarker::ChannelMarker(QObject* parent) :
|
||||
QObject(parent),
|
||||
m_centerFrequency(0),
|
||||
m_bandwidth(0),
|
||||
m_visible(false),
|
||||
m_color(m_colorTable[m_nextColor])
|
||||
{
|
||||
++m_nextColor;
|
||||
if(m_colorTable[m_nextColor] == 0)
|
||||
m_nextColor = 0;
|
||||
}
|
||||
|
||||
void ChannelMarker::setTitle(const QString& title)
|
||||
{
|
||||
m_title = title;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
void ChannelMarker::setCenterFrequency(int centerFrequency)
|
||||
{
|
||||
m_centerFrequency = centerFrequency;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
void ChannelMarker::setBandwidth(int bandwidth)
|
||||
{
|
||||
m_bandwidth = bandwidth;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
void ChannelMarker::setVisible(bool visible)
|
||||
{
|
||||
m_visible = visible;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
void ChannelMarker::setColor(const QColor& color)
|
||||
{
|
||||
m_color = color;
|
||||
emit changed();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "dsp/dspcommands.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(DSPPing, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPExit, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPAcquisitionStart, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPAcquisitionStop, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPGetDeviceDescription, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPGetErrorMessage, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPSetSource, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPAddSink, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPRemoveSink, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPAddAudioSource, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPRemoveAudioSource, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPConfigureSpectrumVis, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPConfigureCorrection, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPEngineReport, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPConfigureScopeVis, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPSignalNotification, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPConfigureChannelizer, Message)
|
||||
@@ -0,0 +1,517 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <stdio.h>
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/channelizer.h"
|
||||
#include "dsp/samplefifo.h"
|
||||
#include "dsp/samplesink.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/samplesource/samplesource.h"
|
||||
|
||||
DSPEngine::DSPEngine(MessageQueue* reportQueue, QObject* parent) :
|
||||
QThread(parent),
|
||||
m_messageQueue(),
|
||||
m_reportQueue(reportQueue),
|
||||
m_state(StNotStarted),
|
||||
m_sampleSource(NULL),
|
||||
m_sampleSinks(),
|
||||
m_sampleRate(0),
|
||||
m_centerFrequency(0),
|
||||
m_dcOffsetCorrection(false),
|
||||
m_iqImbalanceCorrection(false),
|
||||
m_iOffset(0),
|
||||
m_qOffset(0),
|
||||
m_iRange(1 << 16),
|
||||
m_qRange(1 << 16),
|
||||
m_imbalance(65536)
|
||||
{
|
||||
moveToThread(this);
|
||||
}
|
||||
|
||||
DSPEngine::~DSPEngine()
|
||||
{
|
||||
wait();
|
||||
}
|
||||
|
||||
void DSPEngine::start()
|
||||
{
|
||||
DSPPing cmd;
|
||||
QThread::start();
|
||||
cmd.execute(&m_messageQueue);
|
||||
}
|
||||
|
||||
void DSPEngine::stop()
|
||||
{
|
||||
DSPExit cmd;
|
||||
cmd.execute(&m_messageQueue);
|
||||
}
|
||||
|
||||
bool DSPEngine::startAcquisition()
|
||||
{
|
||||
DSPAcquisitionStart cmd;
|
||||
return cmd.execute(&m_messageQueue) == StRunning;
|
||||
}
|
||||
|
||||
void DSPEngine::stopAcquistion()
|
||||
{
|
||||
DSPAcquisitionStop cmd;
|
||||
cmd.execute(&m_messageQueue);
|
||||
}
|
||||
|
||||
void DSPEngine::setSource(SampleSource* source)
|
||||
{
|
||||
DSPSetSource cmd(source);
|
||||
cmd.execute(&m_messageQueue);
|
||||
}
|
||||
|
||||
void DSPEngine::addSink(SampleSink* sink)
|
||||
{
|
||||
DSPAddSink cmd(sink);
|
||||
cmd.execute(&m_messageQueue);
|
||||
}
|
||||
|
||||
void DSPEngine::removeSink(SampleSink* sink)
|
||||
{
|
||||
DSPRemoveSink cmd(sink);
|
||||
cmd.execute(&m_messageQueue);
|
||||
}
|
||||
|
||||
void DSPEngine::addAudioSource(AudioFifo* audioFifo)
|
||||
{
|
||||
DSPAddAudioSource cmd(audioFifo);
|
||||
cmd.execute(&m_messageQueue);
|
||||
}
|
||||
|
||||
void DSPEngine::removeAudioSource(AudioFifo* audioFifo)
|
||||
{
|
||||
DSPRemoveAudioSource cmd(audioFifo);
|
||||
cmd.execute(&m_messageQueue);
|
||||
}
|
||||
|
||||
void DSPEngine::configureCorrections(bool dcOffsetCorrection, bool iqImbalanceCorrection)
|
||||
{
|
||||
Message* cmd = DSPConfigureCorrection::create(dcOffsetCorrection, iqImbalanceCorrection);
|
||||
cmd->submit(&m_messageQueue);
|
||||
}
|
||||
|
||||
QString DSPEngine::errorMessage()
|
||||
{
|
||||
DSPGetErrorMessage cmd;
|
||||
cmd.execute(&m_messageQueue);
|
||||
return cmd.getErrorMessage();
|
||||
}
|
||||
|
||||
QString DSPEngine::deviceDescription()
|
||||
{
|
||||
DSPGetDeviceDescription cmd;
|
||||
cmd.execute(&m_messageQueue);
|
||||
return cmd.getDeviceDescription();
|
||||
}
|
||||
|
||||
void DSPEngine::run()
|
||||
{
|
||||
connect(&m_messageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleMessages()), Qt::QueuedConnection);
|
||||
|
||||
m_state = StIdle;
|
||||
|
||||
handleMessages();
|
||||
exec();
|
||||
}
|
||||
|
||||
void DSPEngine::dcOffset(SampleVector::iterator begin, SampleVector::iterator end)
|
||||
{
|
||||
int count = end - begin;
|
||||
int io = 0;
|
||||
int qo = 0;
|
||||
|
||||
// sum all sample components
|
||||
for(SampleVector::iterator it = begin; it < end; it++) {
|
||||
io += it->real();
|
||||
qo += it->imag();
|
||||
}
|
||||
|
||||
// build a sliding average (el cheapo style)
|
||||
m_iOffset = (m_iOffset * 3 + io / count) >> 2;
|
||||
m_qOffset = (m_qOffset * 3 + qo / count) >> 2;
|
||||
|
||||
// correct samples
|
||||
Sample corr(m_iOffset, m_qOffset);
|
||||
for(SampleVector::iterator it = begin; it < end; it++)
|
||||
*it -= corr;
|
||||
}
|
||||
|
||||
void DSPEngine::imbalance(SampleVector::iterator begin, SampleVector::iterator end)
|
||||
{
|
||||
int iMin = 0;
|
||||
int iMax = 0;
|
||||
int qMin = 0;
|
||||
int qMax = 0;
|
||||
|
||||
// find value ranges for both I and Q
|
||||
// both intervals should be same same size (for a perfect circle)
|
||||
for(SampleVector::iterator it = begin; it < end; it++) {
|
||||
if(it != begin) {
|
||||
if(it->real() < iMin)
|
||||
iMin = it->real();
|
||||
else if(it->real() > iMax)
|
||||
iMax = it->real();
|
||||
if(it->imag() < qMin)
|
||||
qMin = it->imag();
|
||||
else if(it->imag() > qMax)
|
||||
qMax = it->imag();
|
||||
|
||||
} else {
|
||||
iMin = it->real();
|
||||
iMax = it->real();
|
||||
qMin = it->imag();
|
||||
qMax = it->imag();
|
||||
}
|
||||
}
|
||||
|
||||
// sliding average (el cheapo again)
|
||||
m_iRange = (m_iRange * 15 + (iMax - iMin)) >> 4;
|
||||
m_qRange = (m_qRange * 15 + (qMax - qMin)) >> 4;
|
||||
|
||||
// calculate imbalance as Q15.16
|
||||
if(m_qRange != 0)
|
||||
m_imbalance = ((uint)m_iRange << 16) / (uint)m_qRange;
|
||||
|
||||
// correct imbalance and convert back to signed int 16
|
||||
for(SampleVector::iterator it = begin; it < end; it++)
|
||||
it->m_imag = (it->m_imag * m_imbalance) >> 16;
|
||||
}
|
||||
|
||||
void DSPEngine::work()
|
||||
{
|
||||
SampleFifo* sampleFifo = m_sampleSource->getSampleFifo();
|
||||
size_t samplesDone = 0;
|
||||
bool firstOfBurst = true;
|
||||
|
||||
while((sampleFifo->fill() > 0) && (m_messageQueue.countPending() == 0) && (samplesDone < m_sampleRate)) {
|
||||
SampleVector::iterator part1begin;
|
||||
SampleVector::iterator part1end;
|
||||
SampleVector::iterator part2begin;
|
||||
SampleVector::iterator part2end;
|
||||
|
||||
size_t count = sampleFifo->readBegin(sampleFifo->fill(), &part1begin, &part1end, &part2begin, &part2end);
|
||||
|
||||
// first part of FIFO data
|
||||
if(part1begin != part1end) {
|
||||
// correct stuff
|
||||
if(m_dcOffsetCorrection)
|
||||
dcOffset(part1begin, part1end);
|
||||
if(m_iqImbalanceCorrection)
|
||||
imbalance(part1begin, part1end);
|
||||
// feed data to handlers
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++)
|
||||
(*it)->feed(part1begin, part1end, firstOfBurst);
|
||||
firstOfBurst = false;
|
||||
}
|
||||
// second part of FIFO data (used when block wraps around)
|
||||
if(part2begin != part2end) {
|
||||
// correct stuff
|
||||
if(m_dcOffsetCorrection)
|
||||
dcOffset(part2begin, part2end);
|
||||
if(m_iqImbalanceCorrection)
|
||||
imbalance(part2begin, part2end);
|
||||
// feed data to handlers
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++)
|
||||
(*it)->feed(part2begin, part2end, firstOfBurst);
|
||||
firstOfBurst = false;
|
||||
}
|
||||
|
||||
// adjust FIFO pointers
|
||||
sampleFifo->readCommit(count);
|
||||
samplesDone += count;
|
||||
}
|
||||
|
||||
#if 0
|
||||
size_t wus;
|
||||
size_t maxWorkUnitSize = 0;
|
||||
size_t samplesDone = 0;
|
||||
|
||||
wus = m_spectrum.workUnitSize();
|
||||
if(wus > maxWorkUnitSize)
|
||||
maxWorkUnitSize = wus;
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++) {
|
||||
wus = (*it)->workUnitSize();
|
||||
if(wus > maxWorkUnitSize)
|
||||
maxWorkUnitSize = wus;
|
||||
}
|
||||
|
||||
while((m_sampleFifo.fill() > maxWorkUnitSize) && (m_commandQueue.countPending() == 0) && (samplesDone < m_sampleRate)) {
|
||||
SampleVector::iterator part1begin;
|
||||
SampleVector::iterator part1end;
|
||||
SampleVector::iterator part2begin;
|
||||
SampleVector::iterator part2end;
|
||||
|
||||
size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end);
|
||||
|
||||
// first part of FIFO data
|
||||
if(part1begin != part1end) {
|
||||
// correct stuff
|
||||
if(m_settings.dcOffsetCorrection())
|
||||
dcOffset(part1begin, part1end);
|
||||
if(m_settings.iqImbalanceCorrection())
|
||||
imbalance(part1begin, part1end);
|
||||
// feed data to handlers
|
||||
m_spectrum.feed(part1begin, part1end);
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++)
|
||||
(*it)->feed(part1begin, part1end);
|
||||
}
|
||||
// second part of FIFO data (used when block wraps around)
|
||||
if(part2begin != part2end) {
|
||||
// correct stuff
|
||||
if(m_settings.dcOffsetCorrection())
|
||||
dcOffset(part2begin, part2end);
|
||||
if(m_settings.iqImbalanceCorrection())
|
||||
imbalance(part2begin, part2end);
|
||||
// feed data to handlers
|
||||
m_spectrum.feed(part2begin, part2end);
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++)
|
||||
(*it)->feed(part1begin, part1end);
|
||||
}
|
||||
|
||||
// adjust FIFO pointers
|
||||
m_sampleFifo.readCommit(count);
|
||||
samplesDone += count;
|
||||
}
|
||||
|
||||
// check if the center frequency has changed (has to be responsive)
|
||||
if(m_settings.isModifiedCenterFreq())
|
||||
m_sampleSource->setCenterFrequency(m_settings.centerFreq());
|
||||
// check if decimation has changed (needed to be done here, because to high a sample rate can clog the switch)
|
||||
if(m_settings.isModifiedDecimation()) {
|
||||
m_sampleSource->setDecimation(m_settings.decimation());
|
||||
m_sampleRate = 4000000 / (1 << m_settings.decimation());
|
||||
qDebug("New rate: %d", m_sampleRate);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
DSPEngine::State DSPEngine::gotoIdle()
|
||||
{
|
||||
switch(m_state) {
|
||||
case StNotStarted:
|
||||
return StNotStarted;
|
||||
|
||||
case StIdle:
|
||||
case StError:
|
||||
return StIdle;
|
||||
|
||||
case StRunning:
|
||||
break;
|
||||
}
|
||||
|
||||
if(m_sampleSource == NULL)
|
||||
return StIdle;
|
||||
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++)
|
||||
(*it)->stop();
|
||||
m_sampleSource->stopInput();
|
||||
m_deviceDescription.clear();
|
||||
m_audioOutput.stop();
|
||||
m_sampleRate = 0;
|
||||
|
||||
return StIdle;
|
||||
}
|
||||
|
||||
DSPEngine::State DSPEngine::gotoRunning()
|
||||
{
|
||||
switch(m_state) {
|
||||
case StNotStarted:
|
||||
return StNotStarted;
|
||||
|
||||
case StRunning:
|
||||
return StRunning;
|
||||
|
||||
case StIdle:
|
||||
case StError:
|
||||
break;
|
||||
}
|
||||
|
||||
if(m_sampleSource == NULL)
|
||||
return gotoError("No sample source configured");
|
||||
|
||||
m_iOffset = 0;
|
||||
m_qOffset = 0;
|
||||
m_iRange = 1 << 16;
|
||||
m_qRange = 1 << 16;
|
||||
|
||||
if(!m_sampleSource->startInput(0))
|
||||
return gotoError("Could not start sample source");
|
||||
m_deviceDescription = m_sampleSource->getDeviceDescription();
|
||||
|
||||
m_audioOutput.start(0, 44100);
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++)
|
||||
(*it)->start();
|
||||
m_sampleRate = 0; // make sure, report is sent
|
||||
generateReport();
|
||||
|
||||
return StRunning;
|
||||
}
|
||||
|
||||
DSPEngine::State DSPEngine::gotoError(const QString& errorMessage)
|
||||
{
|
||||
m_errorMessage = errorMessage;
|
||||
m_deviceDescription.clear();
|
||||
m_state = StError;
|
||||
return StError;
|
||||
}
|
||||
|
||||
void DSPEngine::handleSetSource(SampleSource* source)
|
||||
{
|
||||
gotoIdle();
|
||||
if(m_sampleSource != NULL)
|
||||
disconnect(m_sampleSource->getSampleFifo(), SIGNAL(dataReady()), this, SLOT(handleData()));
|
||||
m_sampleSource = source;
|
||||
if(m_sampleSource != NULL)
|
||||
connect(m_sampleSource->getSampleFifo(), SIGNAL(dataReady()), this, SLOT(handleData()), Qt::QueuedConnection);
|
||||
generateReport();
|
||||
}
|
||||
|
||||
void DSPEngine::generateReport()
|
||||
{
|
||||
bool needReport = false;
|
||||
int sampleRate;
|
||||
quint64 centerFrequency;
|
||||
|
||||
if(m_sampleSource != NULL) {
|
||||
sampleRate = m_sampleSource->getSampleRate();
|
||||
centerFrequency = m_sampleSource->getCenterFrequency();
|
||||
} else {
|
||||
sampleRate = 100000;
|
||||
centerFrequency = 100000000;
|
||||
}
|
||||
|
||||
if(sampleRate != m_sampleRate) {
|
||||
m_sampleRate = sampleRate;
|
||||
needReport = true;
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++) {
|
||||
DSPSignalNotification* signal = DSPSignalNotification::create(m_sampleRate, 0);
|
||||
signal->submit(&m_messageQueue, *it);
|
||||
}
|
||||
}
|
||||
if(centerFrequency != m_centerFrequency) {
|
||||
m_centerFrequency = centerFrequency;
|
||||
needReport = true;
|
||||
}
|
||||
|
||||
if(needReport) {
|
||||
Message* rep = DSPEngineReport::create(m_sampleRate, m_centerFrequency);
|
||||
rep->submit(m_reportQueue);
|
||||
}
|
||||
}
|
||||
|
||||
bool DSPEngine::distributeMessage(Message* message)
|
||||
{
|
||||
if(m_sampleSource != NULL) {
|
||||
if((message->getDestination() == NULL) || (message->getDestination() == m_sampleSource)) {
|
||||
if(m_sampleSource->handleMessage(message)) {
|
||||
generateReport();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++) {
|
||||
if((message->getDestination() == NULL) || (message->getDestination() == *it)) {
|
||||
if((*it)->handleMessage(message))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DSPEngine::handleData()
|
||||
{
|
||||
if(m_state == StRunning)
|
||||
work();
|
||||
}
|
||||
|
||||
void DSPEngine::handleMessages()
|
||||
{
|
||||
Message* message;
|
||||
while((message = m_messageQueue.accept()) != NULL) {
|
||||
qDebug("Message: %s", message->getIdentifier());
|
||||
|
||||
if(DSPPing::match(message)) {
|
||||
message->completed(m_state);
|
||||
} else if(DSPExit::match(message)) {
|
||||
gotoIdle();
|
||||
m_state = StNotStarted;
|
||||
exit();
|
||||
message->completed(m_state);
|
||||
} else if(DSPAcquisitionStart::match(message)) {
|
||||
m_state = gotoIdle();
|
||||
if(m_state == StIdle)
|
||||
m_state = gotoRunning();
|
||||
message->completed(m_state);
|
||||
} else if(DSPAcquisitionStop::match(message)) {
|
||||
m_state = gotoIdle();
|
||||
message->completed(m_state);
|
||||
} else if(DSPGetDeviceDescription::match(message)) {
|
||||
((DSPGetDeviceDescription*)message)->setDeviceDescription(m_deviceDescription);
|
||||
message->completed();
|
||||
} else if(DSPGetErrorMessage::match(message)) {
|
||||
((DSPGetErrorMessage*)message)->setErrorMessage(m_errorMessage);
|
||||
message->completed();
|
||||
} else if(DSPSetSource::match(message)) {
|
||||
handleSetSource(((DSPSetSource*)message)->getSampleSource());
|
||||
message->completed();
|
||||
} else if(DSPAddSink::match(message)) {
|
||||
SampleSink* sink = ((DSPAddSink*)message)->getSampleSink();
|
||||
if(m_state == StRunning) {
|
||||
DSPSignalNotification* signal = DSPSignalNotification::create(m_sampleRate, 0);
|
||||
signal->submit(&m_messageQueue, sink);
|
||||
sink->start();
|
||||
}
|
||||
m_sampleSinks.push_back(sink);
|
||||
message->completed();
|
||||
} else if(DSPRemoveSink::match(message)) {
|
||||
SampleSink* sink = ((DSPAddSink*)message)->getSampleSink();
|
||||
if(m_state == StRunning)
|
||||
sink->stop();
|
||||
m_sampleSinks.remove(sink);
|
||||
message->completed();
|
||||
} else if(DSPAddAudioSource::match(message)) {
|
||||
m_audioOutput.addFifo(((DSPAddAudioSource*)message)->getAudioFifo());
|
||||
message->completed();
|
||||
} else if(DSPRemoveAudioSource::match(message)) {
|
||||
m_audioOutput.removeFifo(((DSPAddAudioSource*)message)->getAudioFifo());
|
||||
message->completed();
|
||||
} else if(DSPConfigureCorrection::match(message)) {
|
||||
DSPConfigureCorrection* conf = (DSPConfigureCorrection*)message;
|
||||
m_iqImbalanceCorrection = conf->getIQImbalanceCorrection();
|
||||
if(m_dcOffsetCorrection != conf->getDCOffsetCorrection()) {
|
||||
m_dcOffsetCorrection = conf->getDCOffsetCorrection();
|
||||
m_iOffset = 0;
|
||||
m_qOffset = 0;
|
||||
}
|
||||
if(m_iqImbalanceCorrection != conf->getIQImbalanceCorrection()) {
|
||||
m_iqImbalanceCorrection = conf->getIQImbalanceCorrection();
|
||||
m_iRange = 1 << 16;
|
||||
m_qRange = 1 << 16;
|
||||
m_imbalance = 65536;
|
||||
}
|
||||
message->completed();
|
||||
} else {
|
||||
if(!distributeMessage(message))
|
||||
message->completed();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "dsp/fftengine.h"
|
||||
#ifdef USE_KISSFFT
|
||||
#include "dsp/kissengine.h"
|
||||
#endif
|
||||
#ifdef USE_FFTW
|
||||
#include "dsp/fftwengine.h"
|
||||
#endif // USE_FFTW
|
||||
|
||||
FFTEngine::~FFTEngine()
|
||||
{
|
||||
}
|
||||
|
||||
FFTEngine* FFTEngine::create()
|
||||
{
|
||||
#ifdef USE_FFTW
|
||||
qDebug("FFT: using FFTW engine");
|
||||
return new FFTWEngine;
|
||||
#endif // USE_FFTW
|
||||
#ifdef USE_KISSFFT
|
||||
qDebug("FFT: using KissFFT engine");
|
||||
return new KissEngine;
|
||||
#endif // USE_KISSFFT
|
||||
|
||||
qCritical("FFT: no engine built");
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#include <QTime>
|
||||
#include "dsp/fftwengine.h"
|
||||
|
||||
FFTWEngine::FFTWEngine() :
|
||||
m_plans(),
|
||||
m_currentPlan(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
FFTWEngine::~FFTWEngine()
|
||||
{
|
||||
freeAll();
|
||||
}
|
||||
|
||||
void FFTWEngine::configure(int n, bool inverse)
|
||||
{
|
||||
for(Plans::const_iterator it = m_plans.begin(); it != m_plans.end(); ++it) {
|
||||
if(((*it)->n == n) && ((*it)->inverse == inverse)) {
|
||||
m_currentPlan = *it;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_currentPlan = new Plan;
|
||||
m_currentPlan->n = n;
|
||||
m_currentPlan->inverse = inverse;
|
||||
m_currentPlan->in = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * n);
|
||||
m_currentPlan->out = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * n);
|
||||
QTime t;
|
||||
t.start();
|
||||
m_globalPlanMutex.lock();
|
||||
m_currentPlan->plan = fftwf_plan_dft_1d(n, m_currentPlan->in, m_currentPlan->out, inverse ? FFTW_BACKWARD : FFTW_FORWARD, FFTW_PATIENT);
|
||||
m_globalPlanMutex.unlock();
|
||||
qDebug("FFT: creating FFTW plan (n=%d,%s) took %dms", n, inverse ? "inverse" : "forward", t.elapsed());
|
||||
m_plans.push_back(m_currentPlan);
|
||||
}
|
||||
|
||||
void FFTWEngine::transform()
|
||||
{
|
||||
if(m_currentPlan != NULL)
|
||||
fftwf_execute(m_currentPlan->plan);
|
||||
}
|
||||
|
||||
Complex* FFTWEngine::in()
|
||||
{
|
||||
if(m_currentPlan != NULL)
|
||||
return reinterpret_cast<Complex*>(m_currentPlan->in);
|
||||
else return NULL;
|
||||
}
|
||||
|
||||
Complex* FFTWEngine::out()
|
||||
{
|
||||
if(m_currentPlan != NULL)
|
||||
return reinterpret_cast<Complex*>(m_currentPlan->out);
|
||||
else return NULL;
|
||||
}
|
||||
|
||||
QMutex FFTWEngine::m_globalPlanMutex;
|
||||
|
||||
void FFTWEngine::freeAll()
|
||||
{
|
||||
for(Plans::iterator it = m_plans.begin(); it != m_plans.end(); ++it) {
|
||||
fftwf_destroy_plan((*it)->plan);
|
||||
fftwf_free((*it)->in);
|
||||
fftwf_free((*it)->out);
|
||||
delete *it;
|
||||
}
|
||||
m_plans.clear();
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "dsp/fftwindow.h"
|
||||
|
||||
void FFTWindow::create(Function function, int n)
|
||||
{
|
||||
Real (*wFunc)(Real n, Real i);
|
||||
|
||||
m_window.clear();
|
||||
|
||||
switch(function) {
|
||||
case Flattop:
|
||||
wFunc = flatTop;
|
||||
break;
|
||||
|
||||
case Bartlett:
|
||||
wFunc = bartlett;
|
||||
break;
|
||||
|
||||
case BlackmanHarris:
|
||||
wFunc = blackmanHarris;
|
||||
break;
|
||||
|
||||
case Hamming:
|
||||
wFunc = hamming;
|
||||
break;
|
||||
|
||||
case Hanning:
|
||||
wFunc = hanning;
|
||||
break;
|
||||
|
||||
case Rectangle:
|
||||
default:
|
||||
wFunc = rectangle;
|
||||
break;
|
||||
}
|
||||
|
||||
for(int i = 0; i < n; i++)
|
||||
m_window.push_back(wFunc(n, i));
|
||||
}
|
||||
|
||||
void FFTWindow::apply(const std::vector<Real>& in, std::vector<Real>* out)
|
||||
{
|
||||
for(size_t i = 0; i < m_window.size(); i++)
|
||||
(*out)[i] = in[i] * m_window[i];
|
||||
}
|
||||
|
||||
void FFTWindow::apply(const std::vector<Complex>& in, std::vector<Complex>* out)
|
||||
{
|
||||
for(size_t i = 0; i < m_window.size(); i++)
|
||||
(*out)[i] = in[i] * m_window[i];
|
||||
}
|
||||
|
||||
void FFTWindow::apply(const Complex* in, Complex* out)
|
||||
{
|
||||
for(size_t i = 0; i < m_window.size(); i++)
|
||||
out[i] = in[i] * m_window[i];
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
#include "dsp/interpolator.h"
|
||||
|
||||
static std::vector<Real> createPolyphaseLowPass(
|
||||
int phaseSteps,
|
||||
double gain,
|
||||
double sampleRateHz,
|
||||
double cutoffFreqHz,
|
||||
double transitionWidthHz,
|
||||
double oobAttenuationdB)
|
||||
{
|
||||
int ntaps = (int)(oobAttenuationdB * sampleRateHz / (22.0 * transitionWidthHz));
|
||||
if((ntaps % 2) != 0)
|
||||
ntaps++;
|
||||
ntaps *= phaseSteps;
|
||||
|
||||
std::vector<float> taps(ntaps);
|
||||
std::vector<float> window(ntaps);
|
||||
|
||||
for(int n = 0; n < ntaps; n++)
|
||||
window[n] = 0.54 - 0.46 * cos ((2 * M_PI * n) / (ntaps - 1));
|
||||
|
||||
int M = (ntaps - 1) / 2;
|
||||
double fwT0 = 2 * M_PI * cutoffFreqHz / sampleRateHz;
|
||||
for(int n = -M; n <= M; n++) {
|
||||
if(n == 0) taps[n + M] = fwT0 / M_PI * window[n + M];
|
||||
else taps[n + M] = sin (n * fwT0) / (n * M_PI) * window[n + M];
|
||||
}
|
||||
|
||||
double max = taps[0 + M];
|
||||
for(int n = 1; n <= M; n++)
|
||||
max += 2.0 * taps[n + M];
|
||||
|
||||
gain /= max;
|
||||
|
||||
for(int i = 0; i < ntaps; i++)
|
||||
taps[i] *= gain;
|
||||
|
||||
return taps;
|
||||
}
|
||||
|
||||
Interpolator::Interpolator() :
|
||||
m_taps(NULL),
|
||||
m_alignedTaps(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
Interpolator::~Interpolator()
|
||||
{
|
||||
free();
|
||||
}
|
||||
|
||||
void Interpolator::create(int phaseSteps, double sampleRate, double cutoff)
|
||||
{
|
||||
free();
|
||||
|
||||
std::vector<Real> taps = createPolyphaseLowPass(
|
||||
phaseSteps, // number of polyphases
|
||||
1.0, // gain
|
||||
phaseSteps * sampleRate, // sampling frequency
|
||||
cutoff, // hz beginning of transition band
|
||||
sampleRate / 5.0, // hz width of transition band
|
||||
20.0); // out of band attenuation
|
||||
|
||||
// init state
|
||||
m_ptr = 0;
|
||||
m_nTaps = taps.size() / phaseSteps;
|
||||
m_phaseSteps = phaseSteps;
|
||||
m_samples.resize(m_nTaps + 2);
|
||||
for(int i = 0; i < m_nTaps + 2; i++)
|
||||
m_samples[i] = 0;
|
||||
|
||||
// reorder into polyphase
|
||||
std::vector<Real> polyphase(taps.size());
|
||||
for(int phase = 0; phase < phaseSteps; phase++) {
|
||||
for(int i = 0; i < m_nTaps; i++)
|
||||
polyphase[phase * m_nTaps + i] = taps[i * phaseSteps + phase];
|
||||
}
|
||||
|
||||
// normalize phase filters
|
||||
for(int phase = 0; phase < phaseSteps; phase++) {
|
||||
Real sum = 0;
|
||||
for(int i = phase * m_nTaps; i < phase * m_nTaps + m_nTaps; i++)
|
||||
sum += polyphase[i];
|
||||
for(int i = phase * m_nTaps; i < phase * m_nTaps + m_nTaps; i++)
|
||||
polyphase[i] /= sum;
|
||||
}
|
||||
|
||||
// move taps around to match sse storage requirements
|
||||
m_taps = new float[2 * taps.size() + 8];
|
||||
for(int i = 0; i < 2 * taps.size() + 8; ++i)
|
||||
m_taps[i] = 0;
|
||||
m_alignedTaps = (float*)((((quint64)m_taps) + 15) & ~15);
|
||||
for(int i = 0; i < taps.size(); ++i) {
|
||||
m_alignedTaps[2 * i + 0] = polyphase[i];
|
||||
m_alignedTaps[2 * i + 1] = polyphase[i];
|
||||
}
|
||||
m_taps2 = new float[2 * taps.size() + 8];
|
||||
for(int i = 0; i < 2 * taps.size() + 8; ++i)
|
||||
m_taps2[i] = 0;
|
||||
m_alignedTaps2 = (float*)((((quint64)m_taps2) + 15) & ~15);
|
||||
for(int i = 1; i < taps.size(); ++i) {
|
||||
m_alignedTaps2[2 * (i - 1) + 0] = polyphase[i];
|
||||
m_alignedTaps2[2 * (i - 1) + 1] = polyphase[i];
|
||||
}
|
||||
}
|
||||
|
||||
void Interpolator::free()
|
||||
{
|
||||
if(m_taps != NULL) {
|
||||
delete[] m_taps;
|
||||
m_taps = NULL;
|
||||
m_alignedTaps = NULL;
|
||||
delete[] m_taps2;
|
||||
m_taps2 = NULL;
|
||||
m_alignedTaps2 = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "dsp/inthalfbandfilter.h"
|
||||
|
||||
IntHalfbandFilter::IntHalfbandFilter()
|
||||
{
|
||||
for(int i = 0; i < HB_FILTERORDER + 1; i++) {
|
||||
m_samples[i][0] = 0;
|
||||
m_samples[i][1] = 0;
|
||||
}
|
||||
m_ptr = 0;
|
||||
m_state = 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "dsp/kissengine.h"
|
||||
|
||||
void KissEngine::configure(int n, bool inverse)
|
||||
{
|
||||
m_fft.configure(n, inverse);
|
||||
if(n > m_in.size())
|
||||
m_in.resize(n);
|
||||
if(n > m_out.size())
|
||||
m_out.resize(n);
|
||||
}
|
||||
|
||||
void KissEngine::transform()
|
||||
{
|
||||
m_fft.transform(&m_in[0], &m_out[0]);
|
||||
}
|
||||
|
||||
Complex* KissEngine::in()
|
||||
{
|
||||
return &m_in[0];
|
||||
}
|
||||
|
||||
Complex* KissEngine::out()
|
||||
{
|
||||
return &m_out[0];
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <stdio.h>
|
||||
#include <QtGlobal>
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
#include "dsp/lowpass.h"
|
||||
@@ -0,0 +1 @@
|
||||
#include "dsp/movingaverage.h"
|
||||
@@ -0,0 +1,70 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <stdio.h>
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include "dsp/nco.h"
|
||||
|
||||
Real NCO::m_table[NCO::TableSize];
|
||||
bool NCO::m_tableInitialized = false;
|
||||
|
||||
void NCO::initTable()
|
||||
{
|
||||
if(m_tableInitialized)
|
||||
return;
|
||||
|
||||
for(int i = 0; i < TableSize; i++)
|
||||
m_table[i] = cos((2.0 * M_PI * i) / TableSize);
|
||||
|
||||
m_tableInitialized = true;
|
||||
}
|
||||
|
||||
NCO::NCO()
|
||||
{
|
||||
initTable();
|
||||
m_phase = 0;
|
||||
}
|
||||
|
||||
void NCO::setFreq(Real freq, Real sampleRate)
|
||||
{
|
||||
m_phaseIncrement = (freq * TableSize) / sampleRate;
|
||||
qDebug("NCO phase inc %d", m_phaseIncrement);
|
||||
}
|
||||
|
||||
float NCO::next()
|
||||
{
|
||||
m_phase += m_phaseIncrement;
|
||||
while(m_phase >= TableSize)
|
||||
m_phase -= TableSize;
|
||||
while(m_phase < 0)
|
||||
m_phase += TableSize;
|
||||
|
||||
return m_table[m_phase];
|
||||
}
|
||||
|
||||
Complex NCO::nextIQ()
|
||||
{
|
||||
m_phase += m_phaseIncrement;
|
||||
while(m_phase >= TableSize)
|
||||
m_phase -= TableSize;
|
||||
while(m_phase < 0)
|
||||
m_phase += TableSize;
|
||||
|
||||
return Complex(m_table[m_phase], -m_table[(m_phase + TableSize / 4) % TableSize]);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#include "pidcontroller.h"
|
||||
|
||||
PIDController::PIDController() :
|
||||
m_p(0.0),
|
||||
m_i(0.0),
|
||||
m_d(0.0),
|
||||
m_int(0.0),
|
||||
m_diff(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
void PIDController::setup(Real p, Real i, Real d)
|
||||
{
|
||||
m_p = p;
|
||||
m_i = i;
|
||||
m_d = d;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef INCLUDE_PIDCONTROLLER_H
|
||||
#define INCLUDE_PIDCONTROLLER_H
|
||||
|
||||
#include "dsp/dsptypes.h"
|
||||
|
||||
class PIDController {
|
||||
private:
|
||||
Real m_p;
|
||||
Real m_i;
|
||||
Real m_d;
|
||||
Real m_int;
|
||||
Real m_diff;
|
||||
|
||||
public:
|
||||
PIDController();
|
||||
|
||||
void setup(Real p, Real i, Real d);
|
||||
|
||||
Real feed(Real v)
|
||||
{
|
||||
m_int += v * m_i;
|
||||
Real d = m_d * (m_diff - v);
|
||||
m_diff = v;
|
||||
return (v * m_p) + m_int + d;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // INCLUDE_PIDCONTROLLER_H
|
||||
@@ -0,0 +1,231 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "dsp/samplefifo.h"
|
||||
|
||||
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
|
||||
|
||||
void SampleFifo::create(uint s)
|
||||
{
|
||||
m_size = 0;
|
||||
m_fill = 0;
|
||||
m_head = 0;
|
||||
m_tail = 0;
|
||||
|
||||
m_data.resize(s);
|
||||
m_size = m_data.size();
|
||||
|
||||
if(m_size != s)
|
||||
qCritical("SampleFifo: out of memory");
|
||||
}
|
||||
|
||||
SampleFifo::SampleFifo(QObject* parent) :
|
||||
QObject(parent),
|
||||
m_data()
|
||||
{
|
||||
m_suppressed = -1;
|
||||
m_size = 0;
|
||||
m_fill = 0;
|
||||
m_head = 0;
|
||||
m_tail = 0;
|
||||
}
|
||||
|
||||
SampleFifo::SampleFifo(int size, QObject* parent) :
|
||||
QObject(parent),
|
||||
m_data()
|
||||
{
|
||||
m_suppressed = -1;
|
||||
|
||||
create(size);
|
||||
}
|
||||
|
||||
SampleFifo::~SampleFifo()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
bool SampleFifo::setSize(int size)
|
||||
{
|
||||
create(size);
|
||||
|
||||
return m_data.size() == (uint)size;
|
||||
}
|
||||
|
||||
uint SampleFifo::write(const quint8* data, uint count)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
uint total;
|
||||
uint remaining;
|
||||
uint len;
|
||||
const Sample* begin = (const Sample*)data;
|
||||
count /= 4;
|
||||
|
||||
total = MIN(count, m_size - m_fill);
|
||||
if(total < count) {
|
||||
if(m_suppressed < 0) {
|
||||
m_suppressed = 0;
|
||||
m_msgRateTimer.start();
|
||||
qCritical("SampleFifo: overflow - dropping %u samples", count - total);
|
||||
} else {
|
||||
if(m_msgRateTimer.elapsed() > 2500) {
|
||||
qCritical("SampleFifo: %u messages dropped", m_suppressed);
|
||||
qCritical("SampleFifo: overflow - dropping %u samples", count - total);
|
||||
m_suppressed = -1;
|
||||
} else {
|
||||
m_suppressed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remaining = total;
|
||||
while(remaining > 0) {
|
||||
len = MIN(remaining, m_size - m_tail);
|
||||
std::copy(begin, begin + len, m_data.begin() + m_tail);
|
||||
m_tail += len;
|
||||
m_tail %= m_size;
|
||||
m_fill += len;
|
||||
begin += len;
|
||||
remaining -= len;
|
||||
}
|
||||
|
||||
if(m_fill > 0)
|
||||
emit dataReady();
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
uint SampleFifo::write(SampleVector::const_iterator begin, SampleVector::const_iterator end)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
uint count = end - begin;
|
||||
uint total;
|
||||
uint remaining;
|
||||
uint len;
|
||||
|
||||
total = MIN(count, m_size - m_fill);
|
||||
if(total < count) {
|
||||
if(m_suppressed < 0) {
|
||||
m_suppressed = 0;
|
||||
m_msgRateTimer.start();
|
||||
qCritical("SampleFifo: overflow - dropping %u samples", count - total);
|
||||
} else {
|
||||
if(m_msgRateTimer.elapsed() > 2500) {
|
||||
qCritical("SampleFifo: %u messages dropped", m_suppressed);
|
||||
qCritical("SampleFifo: overflow - dropping %u samples", count - total);
|
||||
m_suppressed = -1;
|
||||
} else {
|
||||
m_suppressed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remaining = total;
|
||||
while(remaining > 0) {
|
||||
len = MIN(remaining, m_size - m_tail);
|
||||
std::copy(begin, begin + len, m_data.begin() + m_tail);
|
||||
m_tail += len;
|
||||
m_tail %= m_size;
|
||||
m_fill += len;
|
||||
begin += len;
|
||||
remaining -= len;
|
||||
}
|
||||
|
||||
if(m_fill > 0)
|
||||
emit dataReady();
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
uint SampleFifo::read(SampleVector::iterator begin, SampleVector::iterator end)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
uint count = end - begin;
|
||||
uint total;
|
||||
uint remaining;
|
||||
uint len;
|
||||
|
||||
total = MIN(count, m_fill);
|
||||
if(total < count)
|
||||
qCritical("SampleFifo: underflow - missing %u samples", count - total);
|
||||
|
||||
remaining = total;
|
||||
while(remaining > 0) {
|
||||
len = MIN(remaining, m_size - m_head);
|
||||
std::copy(m_data.begin() + m_head, m_data.begin() + m_head + len, begin);
|
||||
m_head += len;
|
||||
m_head %= m_size;
|
||||
m_fill -= len;
|
||||
begin += len;
|
||||
remaining -= len;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
uint SampleFifo::readBegin(uint count,
|
||||
SampleVector::iterator* part1Begin, SampleVector::iterator* part1End,
|
||||
SampleVector::iterator* part2Begin, SampleVector::iterator* part2End)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
uint total;
|
||||
uint remaining;
|
||||
uint len;
|
||||
uint head = m_head;
|
||||
|
||||
total = MIN(count, m_fill);
|
||||
if(total < count)
|
||||
qCritical("SampleFifo: underflow - missing %u samples", count - total);
|
||||
|
||||
remaining = total;
|
||||
if(remaining > 0) {
|
||||
len = MIN(remaining, m_size - head);
|
||||
*part1Begin = m_data.begin() + head;
|
||||
*part1End = m_data.begin() + head + len;
|
||||
head += len;
|
||||
head %= m_size;
|
||||
remaining -= len;
|
||||
} else {
|
||||
*part1Begin = m_data.end();
|
||||
*part1End = m_data.end();
|
||||
}
|
||||
if(remaining > 0) {
|
||||
len = MIN(remaining, m_size - head);
|
||||
*part2Begin = m_data.begin() + head;
|
||||
*part2End = m_data.begin() + head + len;
|
||||
} else {
|
||||
*part2Begin = m_data.end();
|
||||
*part2End = m_data.end();
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
uint SampleFifo::readCommit(uint count)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
if(count > m_fill) {
|
||||
qCritical("SampleFifo: cannot commit more than available samples");
|
||||
count = m_fill;
|
||||
}
|
||||
m_head = (m_head + count) % m_size;
|
||||
m_fill -= count;
|
||||
|
||||
return count;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "dsp/samplesink.h"
|
||||
|
||||
SampleSink::SampleSink()
|
||||
{
|
||||
}
|
||||
|
||||
SampleSink::~SampleSink()
|
||||
{
|
||||
}
|
||||
|
||||
#if 0
|
||||
#include "samplesink.h"
|
||||
|
||||
SampleSink::SampleSink() :
|
||||
m_sinkBuffer(),
|
||||
m_sinkBufferFill(0)
|
||||
{
|
||||
}
|
||||
|
||||
void SampleSink::feed(SampleVector::const_iterator begin, SampleVector::const_iterator end)
|
||||
{
|
||||
size_t wus = workUnitSize();
|
||||
|
||||
// make sure our buffer is big enough for at least one work unit
|
||||
if(m_sinkBuffer.size() < wus)
|
||||
m_sinkBuffer.resize(wus);
|
||||
|
||||
while(begin < end) {
|
||||
// if the buffer contains something, keep filling it until it contains one complete work unit
|
||||
if((m_sinkBufferFill > 0) && (m_sinkBufferFill < wus)) {
|
||||
// check number if missing samples, but don't copy more than we have
|
||||
size_t len = wus - m_sinkBufferFill;
|
||||
if(len > (size_t)(end - begin))
|
||||
len = end - begin;
|
||||
// copy
|
||||
std::copy(begin, begin + len, m_sinkBuffer.begin() + m_sinkBufferFill);
|
||||
// adjust pointers
|
||||
m_sinkBufferFill += len;
|
||||
begin += len;
|
||||
}
|
||||
|
||||
// if one complete work unit is in the buffer, feed it to the worker
|
||||
if(m_sinkBufferFill >= wus) {
|
||||
size_t done = 0;
|
||||
while(m_sinkBufferFill - done >= wus)
|
||||
done += work(m_sinkBuffer.begin() + done, m_sinkBuffer.begin() + done + wus);
|
||||
// now copy the remaining data to the front of the buffer (should be zero)
|
||||
if(m_sinkBufferFill - done > 0) {
|
||||
qDebug("error in SampleSink buffer management");
|
||||
std::copy(m_sinkBuffer.begin() + done, m_sinkBuffer.begin() + m_sinkBufferFill, m_sinkBuffer.begin());
|
||||
}
|
||||
m_sinkBufferFill -= done;
|
||||
}
|
||||
|
||||
// if no remainder from last run is buffered and we have at least one work unit left, feed the data to the worker
|
||||
if(m_sinkBufferFill == 0) {
|
||||
while((size_t)(end - begin) > wus)
|
||||
begin += work(begin, begin + wus);
|
||||
// copy any remaining data to the buffer
|
||||
std::copy(begin, end, m_sinkBuffer.begin());
|
||||
m_sinkBufferFill = end - begin;
|
||||
begin += m_sinkBufferFill;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,64 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "dsp/samplesource/samplesource.h"
|
||||
#include "util/simpleserializer.h"
|
||||
|
||||
SampleSource::GeneralSettings::GeneralSettings() :
|
||||
m_centerFrequency(100000000)
|
||||
{
|
||||
}
|
||||
|
||||
void SampleSource::GeneralSettings::resetToDefaults()
|
||||
{
|
||||
m_centerFrequency = 100000000;
|
||||
}
|
||||
|
||||
QByteArray SampleSource::GeneralSettings::serialize() const
|
||||
{
|
||||
SimpleSerializer s(1);
|
||||
s.writeU64(1, m_centerFrequency);
|
||||
return s.final();
|
||||
}
|
||||
|
||||
bool SampleSource::GeneralSettings::deserialize(const QByteArray& data)
|
||||
{
|
||||
SimpleDeserializer d(data);
|
||||
|
||||
if(!d.isValid()) {
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(d.getVersion() == 1) {
|
||||
d.readU64(1, &m_centerFrequency, 100000000);
|
||||
return true;
|
||||
} else {
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SampleSource::SampleSource(MessageQueue* guiMessageQueue) :
|
||||
m_sampleFifo(),
|
||||
m_guiMessageQueue(guiMessageQueue)
|
||||
{
|
||||
}
|
||||
|
||||
SampleSource::~SampleSource()
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
#include "dsp/scopevis.h"
|
||||
#include "gui/glscope.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
ScopeVis::ScopeVis(GLScope* glScope) :
|
||||
m_glScope(glScope),
|
||||
m_trace(100000),
|
||||
m_fill(0),
|
||||
m_triggerState(Untriggered),
|
||||
m_triggerChannel(TriggerFreeRun),
|
||||
m_triggerLevelHigh(0.01 * 32768),
|
||||
m_triggerLevelLow(0.01 * 32768 - 1024),
|
||||
m_sampleRate(0)
|
||||
{
|
||||
}
|
||||
|
||||
void ScopeVis::configure(MessageQueue* msgQueue, TriggerChannel triggerChannel, Real triggerLevelHigh, Real triggerLevelLow)
|
||||
{
|
||||
Message* cmd = DSPConfigureScopeVis::create(triggerChannel, triggerLevelHigh, triggerLevelLow);
|
||||
cmd->submit(msgQueue, this);
|
||||
}
|
||||
|
||||
void ScopeVis::feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool firstOfBurst)
|
||||
{
|
||||
while(begin < end) {
|
||||
if(m_triggerChannel == TriggerChannelI) {
|
||||
if(m_triggerState == Untriggered) {
|
||||
while(begin < end) {
|
||||
if(begin->real() >= m_triggerLevelHigh) {
|
||||
m_triggerState = Triggered;
|
||||
break;
|
||||
}
|
||||
++begin;
|
||||
}
|
||||
}
|
||||
if(m_triggerState == Triggered) {
|
||||
int count = end - begin;
|
||||
if(count > (int)(m_trace.size() - m_fill))
|
||||
count = m_trace.size() - m_fill;
|
||||
std::vector<Complex>::iterator it = m_trace.begin() + m_fill;
|
||||
for(int i = 0; i < count; ++i) {
|
||||
*it++ = Complex(begin->real() / 32768.0, begin->imag() / 32768.0);
|
||||
++begin;
|
||||
}
|
||||
m_fill += count;
|
||||
if(m_fill >= m_trace.size()) {
|
||||
m_glScope->newTrace(m_trace, m_sampleRate);
|
||||
m_fill = 0;
|
||||
m_triggerState = WaitForReset;
|
||||
}
|
||||
}
|
||||
if(m_triggerState == WaitForReset) {
|
||||
while(begin < end) {
|
||||
if(begin->real() < m_triggerLevelLow) {
|
||||
m_triggerState = Untriggered;
|
||||
break;
|
||||
}
|
||||
++begin;
|
||||
}
|
||||
}
|
||||
} else if(m_triggerChannel == TriggerChannelQ) {
|
||||
if(m_triggerState == Untriggered) {
|
||||
while(begin < end) {
|
||||
if(begin->imag() >= m_triggerLevelHigh) {
|
||||
m_triggerState = Triggered;
|
||||
break;
|
||||
}
|
||||
++begin;
|
||||
}
|
||||
}
|
||||
if(m_triggerState == Triggered) {
|
||||
int count = end - begin;
|
||||
if(count > (int)(m_trace.size() - m_fill))
|
||||
count = m_trace.size() - m_fill;
|
||||
std::vector<Complex>::iterator it = m_trace.begin() + m_fill;
|
||||
for(int i = 0; i < count; ++i) {
|
||||
*it++ = Complex(begin->real() / 32768.0, begin->imag() / 32768.0);
|
||||
++begin;
|
||||
}
|
||||
m_fill += count;
|
||||
if(m_fill >= m_trace.size()) {
|
||||
m_glScope->newTrace(m_trace, m_sampleRate);
|
||||
m_fill = 0;
|
||||
m_triggerState = WaitForReset;
|
||||
}
|
||||
}
|
||||
if(m_triggerState == WaitForReset) {
|
||||
while(begin < end) {
|
||||
if(begin->imag() < m_triggerLevelLow) {
|
||||
m_triggerState = Untriggered;
|
||||
break;
|
||||
}
|
||||
++begin;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int count = end - begin;
|
||||
if(count > (int)(m_trace.size() - m_fill))
|
||||
count = m_trace.size() - m_fill;
|
||||
std::vector<Complex>::iterator it = m_trace.begin() + m_fill;
|
||||
for(int i = 0; i < count; ++i) {
|
||||
*it++ = Complex(begin->real() / 32768.0, begin->imag() / 32768.0);
|
||||
++begin;
|
||||
}
|
||||
m_fill += count;
|
||||
if(m_fill >= m_trace.size()) {
|
||||
m_glScope->newTrace(m_trace, m_sampleRate);
|
||||
m_fill = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScopeVis::start()
|
||||
{
|
||||
}
|
||||
|
||||
void ScopeVis::stop()
|
||||
{
|
||||
}
|
||||
|
||||
bool ScopeVis::handleMessage(Message* message)
|
||||
{
|
||||
if(DSPSignalNotification::match(message)) {
|
||||
DSPSignalNotification* signal = (DSPSignalNotification*)message;
|
||||
m_sampleRate = signal->getSampleRate();
|
||||
message->completed();
|
||||
return true;
|
||||
} else if(DSPConfigureScopeVis::match(message)) {
|
||||
DSPConfigureScopeVis* conf = (DSPConfigureScopeVis*)message;
|
||||
m_triggerState = Untriggered;
|
||||
m_triggerChannel = (TriggerChannel)conf->getTriggerChannel();
|
||||
m_triggerLevelHigh = conf->getTriggerLevelHigh() * 32767;
|
||||
m_triggerLevelLow = conf->getTriggerLevelLow() * 32767;
|
||||
message->completed();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
#include "dsp/spectrumvis.h"
|
||||
#include "gui/glspectrum.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
#define MAX_FFT_SIZE 4096
|
||||
|
||||
#ifdef _WIN32
|
||||
double log2f(double n)
|
||||
{
|
||||
return log(n) / log(2.0);
|
||||
}
|
||||
#endif
|
||||
|
||||
SpectrumVis::SpectrumVis(GLSpectrum* glSpectrum) :
|
||||
SampleSink(),
|
||||
m_fft(FFTEngine::create()),
|
||||
m_fftBuffer(MAX_FFT_SIZE),
|
||||
m_logPowerSpectrum(MAX_FFT_SIZE),
|
||||
m_fftBufferFill(0),
|
||||
m_glSpectrum(glSpectrum)
|
||||
{
|
||||
handleConfigure(1024, 10, FFTWindow::BlackmanHarris);
|
||||
}
|
||||
|
||||
SpectrumVis::~SpectrumVis()
|
||||
{
|
||||
delete m_fft;
|
||||
}
|
||||
|
||||
void SpectrumVis::configure(MessageQueue* msgQueue, int fftSize, int overlapPercent, FFTWindow::Function window)
|
||||
{
|
||||
Message* cmd = DSPConfigureSpectrumVis::create(fftSize, overlapPercent, window);
|
||||
cmd->submit(msgQueue, this);
|
||||
}
|
||||
|
||||
void SpectrumVis::feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool firstOfBurst)
|
||||
{
|
||||
// if no visualisation is set, send the samples to /dev/null
|
||||
if(m_glSpectrum == NULL)
|
||||
return;
|
||||
|
||||
while(begin < end) {
|
||||
size_t todo = end - begin;
|
||||
size_t samplesNeeded = m_refillSize - m_fftBufferFill;
|
||||
|
||||
if(todo >= samplesNeeded) {
|
||||
// fill up the buffer
|
||||
std::vector<Complex>::iterator it = m_fftBuffer.begin() + m_fftBufferFill;
|
||||
for(size_t i = 0; i < samplesNeeded; ++i, ++begin)
|
||||
*it++ = Complex(begin->real() / 32768.0, begin->imag() / 32768.0);
|
||||
|
||||
// apply fft window (and copy from m_fftBuffer to m_fftIn)
|
||||
m_window.apply(&m_fftBuffer[0], m_fft->in());
|
||||
|
||||
// calculate FFT
|
||||
m_fft->transform();
|
||||
|
||||
// extract power spectrum and reorder buckets
|
||||
Real ofs = 20.0f * log10f(1.0f / m_fftSize);
|
||||
Real mult = (10.0f / log2f(10.0f));
|
||||
const Complex* fftOut = m_fft->out();
|
||||
for(size_t i = 0; i < m_fftSize; i++) {
|
||||
Complex c = fftOut[((i + (m_fftSize >> 1)) & (m_fftSize - 1))];
|
||||
Real v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v = mult * log2f(v) + ofs;
|
||||
m_logPowerSpectrum[i] = v;
|
||||
}
|
||||
|
||||
// send new data to visualisation
|
||||
m_glSpectrum->newSpectrum(m_logPowerSpectrum, m_fftSize);
|
||||
|
||||
// advance buffer respecting the fft overlap factor
|
||||
std::copy(m_fftBuffer.begin() + m_refillSize, m_fftBuffer.end(), m_fftBuffer.begin());
|
||||
|
||||
// start over
|
||||
m_fftBufferFill = m_overlapSize;
|
||||
} else {
|
||||
// not enough samples for FFT - just fill in new data and return
|
||||
for(std::vector<Complex>::iterator it = m_fftBuffer.begin() + m_fftBufferFill; begin < end; ++begin)
|
||||
*it++ = Complex(begin->real() / 32768.0, begin->imag() / 32768.0);
|
||||
m_fftBufferFill += todo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumVis::start()
|
||||
{
|
||||
}
|
||||
|
||||
void SpectrumVis::stop()
|
||||
{
|
||||
}
|
||||
|
||||
bool SpectrumVis::handleMessage(Message* message)
|
||||
{
|
||||
if(DSPConfigureSpectrumVis::match(message)) {
|
||||
DSPConfigureSpectrumVis* conf = (DSPConfigureSpectrumVis*)message;
|
||||
handleConfigure(conf->getFFTSize(), conf->getOverlapPercent(), conf->getWindow());
|
||||
message->completed();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumVis::handleConfigure(int fftSize, int overlapPercent, FFTWindow::Function window)
|
||||
{
|
||||
if(fftSize > MAX_FFT_SIZE)
|
||||
fftSize = MAX_FFT_SIZE;
|
||||
else if(fftSize < 64)
|
||||
fftSize = 64;
|
||||
if(overlapPercent > 100)
|
||||
m_overlapPercent = 100;
|
||||
else if(overlapPercent < 0)
|
||||
m_overlapPercent = 0;
|
||||
|
||||
m_fftSize = fftSize;
|
||||
m_overlapPercent = overlapPercent;
|
||||
m_fft->configure(m_fftSize, false);
|
||||
m_window.create(window, m_fftSize);
|
||||
m_overlapSize = (m_fftSize * m_overlapPercent) / 100;
|
||||
m_refillSize = m_fftSize - m_overlapSize;
|
||||
m_fftBufferFill = m_overlapSize;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
#include <QThread>
|
||||
#include "dsp/threadedsamplesink.h"
|
||||
#include "util/message.h"
|
||||
|
||||
ThreadedSampleSink::ThreadedSampleSink(SampleSink* sampleSink) :
|
||||
m_thread(new QThread),
|
||||
m_sampleSink(sampleSink)
|
||||
{
|
||||
moveToThread(m_thread);
|
||||
connect(m_thread, SIGNAL(started()), this, SLOT(threadStarted()));
|
||||
connect(m_thread, SIGNAL(finished()), this, SLOT(threadFinished()));
|
||||
|
||||
m_messageQueue.moveToThread(m_thread);
|
||||
connect(&m_messageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleMessages()));
|
||||
|
||||
m_sampleFifo.moveToThread(m_thread);
|
||||
connect(&m_sampleFifo, SIGNAL(dataReady()), this, SLOT(handleData()));
|
||||
m_sampleFifo.setSize(262144);
|
||||
|
||||
sampleSink->moveToThread(m_thread);
|
||||
}
|
||||
|
||||
ThreadedSampleSink::~ThreadedSampleSink()
|
||||
{
|
||||
m_thread->exit();
|
||||
m_thread->wait();
|
||||
delete m_thread;
|
||||
}
|
||||
|
||||
void ThreadedSampleSink::feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool firstOfBurst)
|
||||
{
|
||||
Q_UNUSED(firstOfBurst);
|
||||
m_sampleFifo.write(begin, end);
|
||||
}
|
||||
|
||||
void ThreadedSampleSink::start()
|
||||
{
|
||||
m_thread->start();
|
||||
}
|
||||
|
||||
void ThreadedSampleSink::stop()
|
||||
{
|
||||
m_thread->exit();
|
||||
m_thread->wait();
|
||||
m_sampleFifo.readCommit(m_sampleFifo.fill());
|
||||
}
|
||||
|
||||
bool ThreadedSampleSink::handleMessage(Message* cmd)
|
||||
{
|
||||
// called from other thread
|
||||
m_messageQueue.submit(cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ThreadedSampleSink::handleData()
|
||||
{
|
||||
bool firstOfBurst = true;
|
||||
|
||||
while((m_sampleFifo.fill() > 0) && (m_messageQueue.countPending() == 0)) {
|
||||
SampleVector::iterator part1begin;
|
||||
SampleVector::iterator part1end;
|
||||
SampleVector::iterator part2begin;
|
||||
SampleVector::iterator part2end;
|
||||
|
||||
size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end);
|
||||
|
||||
// first part of FIFO data
|
||||
if(part1begin != part1end) {
|
||||
// handle data
|
||||
if(m_sampleSink != NULL)
|
||||
m_sampleSink->feed(part1begin, part1end, firstOfBurst);
|
||||
firstOfBurst = false;
|
||||
}
|
||||
// second part of FIFO data (used when block wraps around)
|
||||
if(part2begin != part2end) {
|
||||
// handle data
|
||||
if(m_sampleSink != NULL)
|
||||
m_sampleSink->feed(part1begin, part1end, firstOfBurst);
|
||||
firstOfBurst = false;
|
||||
}
|
||||
|
||||
// adjust FIFO pointers
|
||||
m_sampleFifo.readCommit(count);
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadedSampleSink::handleMessages()
|
||||
{
|
||||
Message* message;
|
||||
while((message = m_messageQueue.accept()) != NULL) {
|
||||
qDebug("CMD: %s", message->getIdentifier());
|
||||
if(m_sampleSink != NULL) {
|
||||
if(!m_sampleSink->handleMessage(message))
|
||||
message->completed();
|
||||
} else {
|
||||
message->completed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadedSampleSink::threadStarted()
|
||||
{
|
||||
if(m_sampleSink != NULL)
|
||||
m_sampleSink->start();
|
||||
}
|
||||
|
||||
void ThreadedSampleSink::threadFinished()
|
||||
{
|
||||
if(m_sampleSink != NULL)
|
||||
m_sampleSink->stop();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "gui/aboutdialog.h"
|
||||
#include "ui_aboutdialog.h"
|
||||
|
||||
AboutDialog::AboutDialog(QWidget* parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::AboutDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
}
|
||||
|
||||
AboutDialog::~AboutDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AboutDialog</class>
|
||||
<widget class="QDialog" name="AboutDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>484</width>
|
||||
<height>503</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>About SDRangelove</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Panel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="../resources/res.qrc">:/logo.png</pixmap>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string><p>Copyright (C) 2013 maintech GmbH, Otto-Hahn-Str. 15, 97204 Höchberg, Germany<br>
|
||||
Written by Christian Daniel.</p>
|
||||
<p>Many thanks to the osmocom developer team - especially horizon, Hoernchen &amp; tnt.</p>
|
||||
<p>SDRangelove itself is licensed as "GPL2+" with the added exception, that plugins using only header files from the "include"-subdirectory and not from the "include-gpl"-subdirectory do not count as derived works.</p>
|
||||
<p>The following rules apply to the SDRangelove main application and libsdrbase:<br>
|
||||
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; either version 2 of the License, or (at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
You should have received a copy of the GNU General Public License along with this program. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p>
|
||||
<p>For the license of installed plugins, look into the plugin list.</p></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../resources/res.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>AboutDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>AboutDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "gui/addpresetdialog.h"
|
||||
#include "ui_addpresetdialog.h"
|
||||
|
||||
AddPresetDialog::AddPresetDialog(const QStringList& groups, const QString& group, QWidget* parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::AddPresetDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->group->addItems(groups);
|
||||
ui->group->lineEdit()->setText(group);
|
||||
}
|
||||
|
||||
AddPresetDialog::~AddPresetDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
QString AddPresetDialog::group() const
|
||||
{
|
||||
return ui->group->lineEdit()->text();
|
||||
}
|
||||
|
||||
QString AddPresetDialog::description() const
|
||||
{
|
||||
return ui->description->text();
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AddPresetDialog</class>
|
||||
<widget class="QDialog" name="AddPresetDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>324</width>
|
||||
<height>204</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Preset Description</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>&Group</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>group</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="group">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>&Description</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>description</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="description"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>description</tabstop>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
<tabstop>group</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>AddPresetDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>257</x>
|
||||
<y>194</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>203</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>AddPresetDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>314</x>
|
||||
<y>194</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>203</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -0,0 +1,77 @@
|
||||
#include <QPainter>
|
||||
#include <QColorDialog>
|
||||
#include "gui/basicchannelsettingswidget.h"
|
||||
#include "dsp/channelmarker.h"
|
||||
#include "ui_basicchannelsettingswidget.h"
|
||||
|
||||
BasicChannelSettingsWidget::BasicChannelSettingsWidget(ChannelMarker* marker, QWidget* parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::BasicChannelSettingsWidget),
|
||||
m_channelMarker(marker)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->title->setText(m_channelMarker->getTitle());
|
||||
paintColor();
|
||||
ui->red->setValue(m_channelMarker->getColor().red());
|
||||
ui->green->setValue(m_channelMarker->getColor().green());
|
||||
ui->blue->setValue(m_channelMarker->getColor().blue());
|
||||
}
|
||||
|
||||
BasicChannelSettingsWidget::~BasicChannelSettingsWidget()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void BasicChannelSettingsWidget::on_title_textChanged(const QString& text)
|
||||
{
|
||||
m_channelMarker->setTitle(text);
|
||||
}
|
||||
|
||||
void BasicChannelSettingsWidget::on_colorBtn_clicked()
|
||||
{
|
||||
QColor c = m_channelMarker->getColor();
|
||||
c = QColorDialog::getColor(c, this, tr("Select Color for Channel"));
|
||||
if(c.isValid()) {
|
||||
m_channelMarker->setColor(c);
|
||||
paintColor();
|
||||
ui->red->setValue(m_channelMarker->getColor().red());
|
||||
ui->green->setValue(m_channelMarker->getColor().green());
|
||||
ui->blue->setValue(m_channelMarker->getColor().blue());
|
||||
}
|
||||
}
|
||||
|
||||
void BasicChannelSettingsWidget::paintColor()
|
||||
{
|
||||
QColor c(m_channelMarker->getColor());
|
||||
QPixmap pm(24, 24);
|
||||
pm.fill(c);
|
||||
ui->colorBtn->setIcon(pm);
|
||||
ui->color->setText(tr("#%1%2%3")
|
||||
.arg(c.red(), 2, 16, QChar('0'))
|
||||
.arg(c.green(), 2, 16, QChar('0'))
|
||||
.arg(c.blue(), 2, 16, QChar('0')));
|
||||
}
|
||||
|
||||
void BasicChannelSettingsWidget::on_red_valueChanged(int value)
|
||||
{
|
||||
QColor c(m_channelMarker->getColor());
|
||||
c.setRed(value);
|
||||
m_channelMarker->setColor(c);
|
||||
paintColor();
|
||||
}
|
||||
|
||||
void BasicChannelSettingsWidget::on_green_valueChanged(int value)
|
||||
{
|
||||
QColor c(m_channelMarker->getColor());
|
||||
c.setGreen(value);
|
||||
m_channelMarker->setColor(c);
|
||||
paintColor();
|
||||
}
|
||||
|
||||
void BasicChannelSettingsWidget::on_blue_valueChanged(int value)
|
||||
{
|
||||
QColor c(m_channelMarker->getColor());
|
||||
c.setBlue(value);
|
||||
m_channelMarker->setColor(c);
|
||||
paintColor();
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>BasicChannelSettingsWidget</class>
|
||||
<widget class="QWidget" name="BasicChannelSettingsWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>149</width>
|
||||
<height>116</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Title & Color</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="margin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Title</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="title"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Color</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QToolButton" name="colorBtn">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="color">
|
||||
<property name="text">
|
||||
<string>#ff0000</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="2">
|
||||
<widget class="QSlider" name="red">
|
||||
<property name="maximum">
|
||||
<number>255</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" colspan="2">
|
||||
<widget class="QSlider" name="green">
|
||||
<property name="maximum">
|
||||
<number>255</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" colspan="2">
|
||||
<widget class="QSlider" name="blue">
|
||||
<property name="maximum">
|
||||
<number>255</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>title</tabstop>
|
||||
<tabstop>colorBtn</tabstop>
|
||||
<tabstop>red</tabstop>
|
||||
<tabstop>green</tabstop>
|
||||
<tabstop>blue</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <QPainter>
|
||||
#include "gui/buttonswitch.h"
|
||||
|
||||
ButtonSwitch::ButtonSwitch(QWidget* parent) :
|
||||
QToolButton(parent)
|
||||
{
|
||||
setCheckable(true);
|
||||
m_originalPalette = palette();
|
||||
connect(this, SIGNAL(toggled(bool)), this, SLOT(onToggled(bool)));
|
||||
}
|
||||
|
||||
void ButtonSwitch::onToggled(bool checked)
|
||||
{
|
||||
if(checked) {
|
||||
QPalette p = m_originalPalette;
|
||||
p.setColor(QPalette::Button, QColor(0x80, 0x46, 0x00));
|
||||
setPalette(p);
|
||||
} else {
|
||||
setPalette(m_originalPalette);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#include <QBoxLayout>
|
||||
#include <QSpacerItem>
|
||||
#include <QPainter>
|
||||
#include <QResizeEvent>
|
||||
#include "gui/channelwindow.h"
|
||||
#include "gui/rollupwidget.h"
|
||||
|
||||
ChannelWindow::ChannelWindow(QWidget* parent) :
|
||||
QScrollArea(parent)
|
||||
{
|
||||
m_container = new QWidget(this);
|
||||
m_layout = new QBoxLayout(QBoxLayout::TopToBottom, m_container);
|
||||
setWidget(m_container);
|
||||
setWidgetResizable(true);
|
||||
setBackgroundRole(QPalette::Base);
|
||||
m_layout->setMargin(3);
|
||||
m_layout->setSpacing(3);
|
||||
}
|
||||
|
||||
void ChannelWindow::addRollupWidget(QWidget* rollupWidget)
|
||||
{
|
||||
rollupWidget->setParent(m_container);
|
||||
m_container->layout()->addWidget(rollupWidget);
|
||||
}
|
||||
|
||||
void ChannelWindow::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
if(event->size().height() > event->size().width()) {
|
||||
m_layout->setDirection(QBoxLayout::TopToBottom);
|
||||
m_layout->setAlignment(Qt::AlignTop);
|
||||
} else {
|
||||
m_layout->setDirection(QBoxLayout::LeftToRight);
|
||||
m_layout->setAlignment(Qt::AlignLeft);
|
||||
}
|
||||
QScrollArea::resizeEvent(event);
|
||||
}
|
||||
@@ -0,0 +1,478 @@
|
||||
#include <QPainter>
|
||||
#include <QMouseEvent>
|
||||
#include "gui/glscope.h"
|
||||
#include "dsp/dspengine.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
static double log2f(double n)
|
||||
{
|
||||
return log(n) / log(2.0);
|
||||
}
|
||||
#endif
|
||||
|
||||
GLScope::GLScope(QWidget* parent) :
|
||||
QGLWidget(parent),
|
||||
m_dataChanged(false),
|
||||
m_configChanged(true),
|
||||
m_mode(ModeIQ),
|
||||
m_orientation(Qt::Horizontal),
|
||||
m_displayTrace(&m_rawTrace),
|
||||
m_oldTraceSize(-1),
|
||||
m_sampleRate(0),
|
||||
m_dspEngine(NULL),
|
||||
m_scopeVis(NULL),
|
||||
m_amp(1.0),
|
||||
m_timeBase(1),
|
||||
m_timeOfsProMill(0),
|
||||
m_triggerChannel(ScopeVis::TriggerFreeRun)
|
||||
{
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick()));
|
||||
m_timer.start(50);
|
||||
}
|
||||
|
||||
GLScope::~GLScope()
|
||||
{
|
||||
if(m_dspEngine != NULL) {
|
||||
m_dspEngine->removeSink(m_scopeVis);
|
||||
delete m_scopeVis;
|
||||
}
|
||||
}
|
||||
|
||||
void GLScope::setDSPEngine(DSPEngine* dspEngine)
|
||||
{
|
||||
if((m_dspEngine == NULL) && (dspEngine != NULL)) {
|
||||
m_dspEngine = dspEngine;
|
||||
m_scopeVis = new ScopeVis(this);
|
||||
m_dspEngine->addSink(m_scopeVis);
|
||||
}
|
||||
}
|
||||
|
||||
void GLScope::setAmp(Real amp)
|
||||
{
|
||||
m_amp = amp;
|
||||
update();
|
||||
}
|
||||
|
||||
void GLScope::setTimeBase(int timeBase)
|
||||
{
|
||||
m_timeBase = timeBase;
|
||||
update();
|
||||
}
|
||||
|
||||
void GLScope::setTimeOfsProMill(int timeOfsProMill)
|
||||
{
|
||||
m_timeOfsProMill = timeOfsProMill;
|
||||
update();
|
||||
}
|
||||
|
||||
void GLScope::setMode(Mode mode)
|
||||
{
|
||||
m_mode = mode;
|
||||
m_dataChanged = true;
|
||||
update();
|
||||
}
|
||||
|
||||
void GLScope::setOrientation(Qt::Orientation orientation)
|
||||
{
|
||||
m_orientation = orientation;
|
||||
m_configChanged = true;
|
||||
update();
|
||||
}
|
||||
|
||||
void GLScope::newTrace(const std::vector<Complex>& trace, int sampleRate)
|
||||
{
|
||||
if(!m_mutex.tryLock(2))
|
||||
return;
|
||||
if(m_dataChanged) {
|
||||
m_mutex.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
m_rawTrace = trace;
|
||||
|
||||
m_sampleRate = sampleRate;
|
||||
m_dataChanged = true;
|
||||
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
void GLScope::initializeGL()
|
||||
{
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
void GLScope::resizeGL(int width, int height)
|
||||
{
|
||||
glViewport(0, 0, width, height);
|
||||
m_configChanged = true;
|
||||
}
|
||||
|
||||
void GLScope::paintGL()
|
||||
{
|
||||
if(!m_mutex.tryLock(2))
|
||||
return;
|
||||
|
||||
if(m_configChanged)
|
||||
applyConfig();
|
||||
|
||||
handleMode();
|
||||
|
||||
if(m_displayTrace->size() != m_oldTraceSize) {
|
||||
m_oldTraceSize = m_displayTrace->size();
|
||||
emit traceSizeChanged(m_displayTrace->size());
|
||||
}
|
||||
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
glPushMatrix();
|
||||
glScalef(2.0, -2.0, 1.0);
|
||||
glTranslatef(-0.50, -0.5, 0);
|
||||
|
||||
// I
|
||||
|
||||
// draw rect around
|
||||
glPushMatrix();
|
||||
glTranslatef(m_glScopeRect1.x(), m_glScopeRect1.y(), 0);
|
||||
glScalef(m_glScopeRect1.width(), m_glScopeRect1.height(), 1);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glLineWidth(1.0f);
|
||||
glColor4f(1, 1, 1, 0.5);
|
||||
glBegin(GL_LINE_LOOP);
|
||||
glVertex2f(1, 1);
|
||||
glVertex2f(0, 1);
|
||||
glVertex2f(0, 0);
|
||||
glVertex2f(1, 0);
|
||||
glEnd();
|
||||
glDisable(GL_BLEND);
|
||||
// paint grid
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glLineWidth(1.0f);
|
||||
glColor4f(1, 1, 1, 0.05f);
|
||||
for(int i = 1; i < 10; i++) {
|
||||
glBegin(GL_LINE_LOOP);
|
||||
glVertex2f(0, i * 0.1);
|
||||
glVertex2f(1, i * 0.1);
|
||||
glEnd();
|
||||
}
|
||||
for(int i = 1; i < 10; i++) {
|
||||
glBegin(GL_LINE_LOOP);
|
||||
glVertex2f(i * 0.1, 0);
|
||||
glVertex2f(i * 0.1, 1);
|
||||
glEnd();
|
||||
}
|
||||
glPopMatrix();
|
||||
|
||||
if(m_triggerChannel == ScopeVis::TriggerChannelI) {
|
||||
glPushMatrix();
|
||||
glTranslatef(m_glScopeRect1.x(), m_glScopeRect1.y() + m_glScopeRect1.height() / 2.0, 0);
|
||||
glScalef(m_glScopeRect1.width(), -(m_glScopeRect1.height() / 2) * m_amp1, 1);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glEnable(GL_LINE_SMOOTH);
|
||||
glLineWidth(1.0f);
|
||||
glColor4f(0, 1, 0, 0.3f);
|
||||
glBegin(GL_LINE_LOOP);
|
||||
glVertex2f(0, m_triggerLevelHigh);
|
||||
glVertex2f(1, m_triggerLevelHigh);
|
||||
glEnd();
|
||||
glColor4f(0, 0.8f, 0.0, 0.3f);
|
||||
glBegin(GL_LINE_LOOP);
|
||||
glVertex2f(0, m_triggerLevelLow);
|
||||
glVertex2f(1, m_triggerLevelLow);
|
||||
glEnd();
|
||||
glDisable(GL_LINE_SMOOTH);
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
if(m_displayTrace->size() > 0) {
|
||||
glPushMatrix();
|
||||
glTranslatef(m_glScopeRect1.x(), m_glScopeRect1.y() + m_glScopeRect1.height() / 2.0, 0);
|
||||
glScalef(m_glScopeRect1.width() * (float)m_timeBase / (float)(m_displayTrace->size() - 1), -(m_glScopeRect1.height() / 2) * m_amp1, 1);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glEnable(GL_LINE_SMOOTH);
|
||||
glLineWidth(1.0f);
|
||||
glColor4f(1, 1, 0, 0.4f);
|
||||
int start = m_timeOfsProMill * (m_displayTrace->size() - (m_displayTrace->size() / m_timeBase)) / 1000;
|
||||
int end = start + m_displayTrace->size() / m_timeBase;
|
||||
if(end - start < 2)
|
||||
start--;
|
||||
float posLimit = 1.0 / m_amp1;
|
||||
float negLimit = -1.0 / m_amp1;
|
||||
glBegin(GL_LINE_STRIP);
|
||||
for(int i = start; i < end; i++) {
|
||||
float v = (*m_displayTrace)[i].real() + m_ofs1;
|
||||
if(v > posLimit)
|
||||
v = posLimit;
|
||||
else if(v < negLimit)
|
||||
v = negLimit;
|
||||
glVertex2f(i - start, v);
|
||||
}
|
||||
glEnd();
|
||||
glDisable(GL_LINE_SMOOTH);
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
// Q
|
||||
|
||||
// draw rect around
|
||||
glPushMatrix();
|
||||
glTranslatef(m_glScopeRect2.x(), m_glScopeRect2.y(), 0);
|
||||
glScalef(m_glScopeRect2.width(), m_glScopeRect2.height(), 1);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glLineWidth(1.0f);
|
||||
glColor4f(1, 1, 1, 0.5);
|
||||
glBegin(GL_LINE_LOOP);
|
||||
glVertex2f(1, 1);
|
||||
glVertex2f(0, 1);
|
||||
glVertex2f(0, 0);
|
||||
glVertex2f(1, 0);
|
||||
glEnd();
|
||||
glDisable(GL_BLEND);
|
||||
// paint grid
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glLineWidth(1.0f);
|
||||
glColor4f(1, 1, 1, 0.05f);
|
||||
for(int i = 1; i < 10; i++) {
|
||||
glBegin(GL_LINE_LOOP);
|
||||
glVertex2f(0, i * 0.1);
|
||||
glVertex2f(1, i * 0.1);
|
||||
glEnd();
|
||||
}
|
||||
for(int i = 1; i < 10; i++) {
|
||||
glBegin(GL_LINE_LOOP);
|
||||
glVertex2f(i * 0.1, 0);
|
||||
glVertex2f(i * 0.1, 1);
|
||||
glEnd();
|
||||
}
|
||||
glPopMatrix();
|
||||
|
||||
if(m_triggerChannel == ScopeVis::TriggerChannelQ) {
|
||||
glPushMatrix();
|
||||
glTranslatef(m_glScopeRect2.x(), m_glScopeRect2.y() + m_glScopeRect2.height() / 2.0, 0);
|
||||
glScalef(m_glScopeRect2.width(), -(m_glScopeRect2.height() / 2) * m_amp2, 1);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glEnable(GL_LINE_SMOOTH);
|
||||
glLineWidth(1.0f);
|
||||
glColor4f(0, 1, 0, 0.3f);
|
||||
glBegin(GL_LINE_LOOP);
|
||||
glVertex2f(0, m_triggerLevelHigh);
|
||||
glVertex2f(1, m_triggerLevelHigh);
|
||||
glEnd();
|
||||
glColor4f(0, 0.8f, 0.0, 0.3f);
|
||||
glBegin(GL_LINE_LOOP);
|
||||
glVertex2f(0, m_triggerLevelLow);
|
||||
glVertex2f(1, m_triggerLevelLow);
|
||||
glEnd();
|
||||
glDisable(GL_LINE_SMOOTH);
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
if(m_displayTrace->size() > 0) {
|
||||
glPushMatrix();
|
||||
glTranslatef(m_glScopeRect2.x(), m_glScopeRect2.y() + m_glScopeRect2.height() / 2.0, 0);
|
||||
glScalef(m_glScopeRect2.width() * (float)m_timeBase / (float)(m_displayTrace->size() - 1), -(m_glScopeRect2.height() / 2) * m_amp2, 1);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glEnable(GL_LINE_SMOOTH);
|
||||
glLineWidth(1.0f);
|
||||
glColor4f(1, 1, 0, 0.4f);
|
||||
int start = m_timeOfsProMill * (m_displayTrace->size() - (m_displayTrace->size() / m_timeBase)) / 1000;
|
||||
int end = start + m_displayTrace->size() / m_timeBase;
|
||||
if(end - start < 2)
|
||||
start--;
|
||||
float posLimit = 1.0 / m_amp2;
|
||||
float negLimit = -1.0 / m_amp2;
|
||||
glBegin(GL_LINE_STRIP);
|
||||
for(int i = start; i < end; i++) {
|
||||
float v = (*m_displayTrace)[i].imag();
|
||||
if(v > posLimit)
|
||||
v = posLimit;
|
||||
else if(v < negLimit)
|
||||
v = negLimit;
|
||||
glVertex2f(i - start, v);
|
||||
}
|
||||
glEnd();
|
||||
glDisable(GL_LINE_SMOOTH);
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
glPopMatrix();
|
||||
m_dataChanged = false;
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
void GLScope::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
#if 0
|
||||
int x = event->x() - 10;
|
||||
int y = event->y() - 10;
|
||||
Real time;
|
||||
Real amplitude;
|
||||
ScopeVis::TriggerChannel channel;
|
||||
if((x < 0) || (x >= width() - 20))
|
||||
return;
|
||||
if((y < 0) || (y >= height() - 20))
|
||||
return;
|
||||
|
||||
if((m_sampleRate != 0) && (m_timeBase != 0) && (width() > 20))
|
||||
time = ((Real)x * (Real)m_displayTrace->size()) / ((Real)m_sampleRate * (Real)m_timeBase * (Real)(width() - 20));
|
||||
else time = -1.0;
|
||||
|
||||
if(y < (height() - 30) / 2) {
|
||||
channel = ScopeVis::TriggerChannelI;
|
||||
if((m_amp != 0) && (height() > 30))
|
||||
amplitude = 2.0 * ((height() - 30) * 0.25 - (Real)y) / (m_amp * (height() - 30) / 2.0);
|
||||
else amplitude = -1;
|
||||
} else if(y > (height() - 30) / 2 + 10) {
|
||||
y -= 10 + (height() - 30) / 2;
|
||||
channel = ScopeVis::TriggerChannelQ;
|
||||
if((m_amp != 0) && (height() > 30))
|
||||
amplitude = 2.0 * ((height() - 30) * 0.25 - (Real)y) / (m_amp * (height() - 30) / 2.0);
|
||||
else amplitude = -1;
|
||||
} else {
|
||||
channel = ScopeVis::TriggerFreeRun;
|
||||
}
|
||||
|
||||
if(m_dspEngine != NULL) {
|
||||
qDebug("amp %f", amplitude);
|
||||
m_triggerLevelHigh = amplitude + 0.01 / m_amp;
|
||||
m_triggerLevelLow = amplitude - 0.01 / m_amp;
|
||||
if(m_triggerLevelHigh > 1.0)
|
||||
m_triggerLevelHigh = 1.0;
|
||||
else if(m_triggerLevelHigh < -1.0)
|
||||
m_triggerLevelHigh = -1.0;
|
||||
if(m_triggerLevelLow > 1.0)
|
||||
m_triggerLevelLow = 1.0;
|
||||
else if(m_triggerLevelLow < -1.0)
|
||||
m_triggerLevelLow = -1.0;
|
||||
m_scopeVis->configure(m_dspEngine->getMessageQueue(), channel, m_triggerLevelHigh, m_triggerLevelLow);
|
||||
m_triggerChannel = channel;
|
||||
m_changed = true;
|
||||
update();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void GLScope::handleMode()
|
||||
{
|
||||
switch(m_mode) {
|
||||
case ModeIQ:
|
||||
m_displayTrace = &m_rawTrace;
|
||||
m_amp1 = m_amp;
|
||||
m_amp2 = m_amp;
|
||||
m_ofs1 = 0.0;
|
||||
m_ofs2 = 0.0;
|
||||
break;
|
||||
|
||||
case ModeMagLinPha: {
|
||||
m_mathTrace.resize(m_rawTrace.size());
|
||||
std::vector<Complex>::iterator dst = m_mathTrace.begin();
|
||||
for(std::vector<Complex>::const_iterator src = m_rawTrace.begin(); src != m_rawTrace.end(); ++src)
|
||||
*dst++ = Complex(abs(*src), arg(*src) / M_PI);
|
||||
m_displayTrace = &m_mathTrace;
|
||||
m_amp1 = m_amp;
|
||||
m_amp2 = 1.0;
|
||||
m_ofs1 = -1.0 / m_amp1;
|
||||
m_ofs2 = 0.0;
|
||||
break;
|
||||
}
|
||||
|
||||
case ModeMagdBPha: {
|
||||
m_mathTrace.resize(m_rawTrace.size());
|
||||
std::vector<Complex>::iterator dst = m_mathTrace.begin();
|
||||
Real mult = (10.0f / log2f(10.0f));
|
||||
for(std::vector<Complex>::const_iterator src = m_rawTrace.begin(); src != m_rawTrace.end(); ++src) {
|
||||
Real v = src->real() * src->real() + src->imag() * src->imag();
|
||||
v = (96.0 + (mult * log2f(v))) / 96.0;
|
||||
*dst++ = Complex(v, arg(*src) / M_PI);
|
||||
}
|
||||
m_displayTrace = &m_mathTrace;
|
||||
m_amp1 = 2.0 * m_amp;
|
||||
m_amp2 = 1.0;
|
||||
m_ofs1 = -1.0 / m_amp1;
|
||||
m_ofs2 = 0.0;
|
||||
break;
|
||||
}
|
||||
|
||||
case ModeDerived12: {
|
||||
if(m_rawTrace.size() > 3) {
|
||||
m_mathTrace.resize(m_rawTrace.size() - 3);
|
||||
std::vector<Complex>::iterator dst = m_mathTrace.begin();
|
||||
for(uint i = 3; i < m_rawTrace.size() ; i++) {
|
||||
*dst++ = Complex(
|
||||
abs(m_rawTrace[i] - m_rawTrace[i - 1]),
|
||||
abs(m_rawTrace[i] - m_rawTrace[i - 1]) - abs(m_rawTrace[i - 2] - m_rawTrace[i - 3]));
|
||||
}
|
||||
m_displayTrace = &m_mathTrace;
|
||||
m_amp1 = m_amp;
|
||||
m_amp2 = m_amp;
|
||||
m_ofs1 = -1.0 / m_amp1;
|
||||
m_ofs2 = 0.0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ModeCyclostationary: {
|
||||
if(m_rawTrace.size() > 2) {
|
||||
m_mathTrace.resize(m_rawTrace.size() - 2);
|
||||
std::vector<Complex>::iterator dst = m_mathTrace.begin();
|
||||
for(uint i = 2; i < m_rawTrace.size() ; i++)
|
||||
*dst++ = Complex(abs(m_rawTrace[i] - conj(m_rawTrace[i - 1])), 0);
|
||||
m_displayTrace = &m_mathTrace;
|
||||
m_amp1 = m_amp;
|
||||
m_amp2 = m_amp;
|
||||
m_ofs1 = -1.0 / m_amp1;
|
||||
m_ofs2 = 0.0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void GLScope::applyConfig()
|
||||
{
|
||||
m_configChanged = false;
|
||||
|
||||
if(m_orientation == Qt::Vertical) {
|
||||
m_glScopeRect1 = QRectF(
|
||||
(float)10 / (float)width(),
|
||||
(float)10 / (float)height(),
|
||||
(float)(width() - 10 - 10) / (float)width(),
|
||||
(float)((height() - 10 - 10 - 10) / 2) / (float)height()
|
||||
);
|
||||
m_glScopeRect2 = QRectF(
|
||||
(float)10 / (float)width(),
|
||||
(float)(10 + 10 + (height() - 10 - 10 - 10) / 2) / (float)height(),
|
||||
(float)(width() - 10 - 10) / (float)width(),
|
||||
(float)((height() - 10 - 10 - 10) / 2) / (float)height()
|
||||
);
|
||||
} else {
|
||||
m_glScopeRect1 = QRectF(
|
||||
(float)10 / (float)width(),
|
||||
(float)10 / (float)height(),
|
||||
(float)((width() - 10 - 10 - 10) / 2) / (float)width(),
|
||||
(float)(height() - 10 - 10) / (float)height()
|
||||
);
|
||||
m_glScopeRect2 = QRectF(
|
||||
(float)(10 + 10 + ((width() - 10 - 10 - 10) / 2)) / (float)width(),
|
||||
(float)10 / (float)height(),
|
||||
(float)((width() - 10 - 10 - 10) / 2) / (float)width(),
|
||||
(float)(height() - 10 - 10) / (float)height()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void GLScope::tick()
|
||||
{
|
||||
if(m_dataChanged)
|
||||
update();
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
#include "gui/glspectrumgui.h"
|
||||
#include "dsp/fftwindow.h"
|
||||
#include "dsp/spectrumvis.h"
|
||||
#include "gui/glspectrum.h"
|
||||
#include "util/simpleserializer.h"
|
||||
#include "ui_glspectrumgui.h"
|
||||
|
||||
GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::GLSpectrumGUI),
|
||||
m_messageQueue(NULL),
|
||||
m_spectrumVis(NULL),
|
||||
m_glSpectrum(NULL),
|
||||
m_fftSize(1024),
|
||||
m_fftOverlap(10),
|
||||
m_fftWindow(FFTWindow::Hamming),
|
||||
m_refLevel(0),
|
||||
m_powerRange(100),
|
||||
m_decay(0),
|
||||
m_displayWaterfall(false),
|
||||
m_invertedWaterfall(false),
|
||||
m_displayMaxHold(true),
|
||||
m_displayHistogram(true),
|
||||
m_displayGrid(true),
|
||||
m_invert(false)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
for(int ref = 0; ref >= -95; ref -= 5)
|
||||
ui->refLevel->addItem(QString("%1").arg(ref));
|
||||
for(int range = 100; range >= 5; range -= 5)
|
||||
ui->levelRange->addItem(QString("%1").arg(range));
|
||||
}
|
||||
|
||||
GLSpectrumGUI::~GLSpectrumGUI()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::setBuddies(MessageQueue* messageQueue, SpectrumVis* spectrumVis, GLSpectrum* glSpectrum)
|
||||
{
|
||||
m_messageQueue = messageQueue;
|
||||
m_spectrumVis = spectrumVis;
|
||||
m_glSpectrum = glSpectrum;
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::resetToDefaults()
|
||||
{
|
||||
m_fftSize = 1024;
|
||||
m_fftOverlap = 10;
|
||||
m_fftWindow = FFTWindow::Hamming;
|
||||
m_refLevel = 0;
|
||||
m_powerRange = 100;
|
||||
m_decay = 0;
|
||||
m_displayWaterfall = false;
|
||||
m_invertedWaterfall = false;
|
||||
m_displayMaxHold = true;
|
||||
m_displayHistogram = true;
|
||||
m_displayGrid = true;
|
||||
m_invert = false;
|
||||
applySettings();
|
||||
}
|
||||
|
||||
QByteArray GLSpectrumGUI::serialize() const
|
||||
{
|
||||
SimpleSerializer s(1);
|
||||
s.writeS32(1, m_fftSize);
|
||||
s.writeS32(2, m_fftOverlap);
|
||||
s.writeS32(3, m_fftWindow);
|
||||
s.writeReal(4, m_refLevel);
|
||||
s.writeReal(5, m_powerRange);
|
||||
s.writeBool(6, m_displayWaterfall);
|
||||
s.writeBool(7, m_invertedWaterfall);
|
||||
s.writeBool(8, m_displayMaxHold);
|
||||
s.writeBool(9, m_displayHistogram);
|
||||
s.writeS32(10, m_decay);
|
||||
s.writeBool(11, m_displayGrid);
|
||||
s.writeBool(12, m_invert);
|
||||
return s.final();
|
||||
}
|
||||
|
||||
bool GLSpectrumGUI::deserialize(const QByteArray& data)
|
||||
{
|
||||
SimpleDeserializer d(data);
|
||||
|
||||
if(!d.isValid()) {
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(d.getVersion() == 1) {
|
||||
d.readS32(1, &m_fftSize, 1024);
|
||||
d.readS32(2, &m_fftOverlap, 10);
|
||||
d.readS32(3, &m_fftWindow, FFTWindow::Hamming);
|
||||
d.readReal(4, &m_refLevel, 0);
|
||||
d.readReal(5, &m_powerRange, 100);
|
||||
d.readBool(6, &m_displayWaterfall, true);
|
||||
d.readBool(7, &m_invertedWaterfall, false);
|
||||
d.readBool(8, &m_displayMaxHold, false);
|
||||
d.readBool(9, &m_displayHistogram, true);
|
||||
d.readS32(10, &m_decay, 0);
|
||||
d.readBool(11, &m_displayGrid, true);
|
||||
d.readBool(12, &m_invert, false);
|
||||
applySettings();
|
||||
return true;
|
||||
} else {
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::applySettings()
|
||||
{
|
||||
ui->fftWindow->setCurrentIndex(m_fftWindow);
|
||||
for(int i = 0; i < 6; i++) {
|
||||
if(m_fftSize == (1 << (i + 7))) {
|
||||
ui->fftSize->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ui->refLevel->setCurrentIndex(-m_refLevel / 5);
|
||||
ui->levelRange->setCurrentIndex((100 - m_powerRange) / 5);
|
||||
ui->decay->setCurrentIndex(m_decay + 2);
|
||||
ui->waterfall->setChecked(m_displayWaterfall);
|
||||
ui->maxHold->setChecked(m_displayMaxHold);
|
||||
ui->histogram->setChecked(m_displayHistogram);
|
||||
ui->invert->setChecked(m_invert);
|
||||
ui->grid->setChecked(m_displayGrid);
|
||||
|
||||
m_glSpectrum->setDisplayWaterfall(m_displayWaterfall);
|
||||
m_glSpectrum->setInvertedWaterfall(m_invertedWaterfall);
|
||||
m_glSpectrum->setDisplayMaxHold(m_displayMaxHold);
|
||||
m_glSpectrum->setDisplayHistogram(m_displayHistogram);
|
||||
m_glSpectrum->setDecay(m_decay);
|
||||
m_glSpectrum->setInvertedWaterfall(m_invert);
|
||||
m_glSpectrum->setDisplayGrid(m_displayGrid);
|
||||
|
||||
m_spectrumVis->configure(m_messageQueue, m_fftSize, m_fftOverlap, (FFTWindow::Function)m_fftWindow);
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_fftWindow_currentIndexChanged(int index)
|
||||
{
|
||||
m_fftWindow = index;
|
||||
if(m_spectrumVis == NULL)
|
||||
return;
|
||||
m_spectrumVis->configure(m_messageQueue, m_fftSize, m_fftOverlap, (FFTWindow::Function)m_fftWindow);
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_fftSize_currentIndexChanged(int index)
|
||||
{
|
||||
m_fftSize = 1 << (7 + index);
|
||||
if(m_spectrumVis != NULL)
|
||||
m_spectrumVis->configure(m_messageQueue, m_fftSize, m_fftOverlap, (FFTWindow::Function)m_fftWindow);
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_refLevel_currentIndexChanged(int index)
|
||||
{
|
||||
m_refLevel = 0 - index * 5;
|
||||
if(m_glSpectrum != NULL)
|
||||
m_glSpectrum->setReferenceLevel(m_refLevel);
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_levelRange_currentIndexChanged(int index)
|
||||
{
|
||||
m_powerRange = 100 - index * 5;
|
||||
if(m_glSpectrum != NULL)
|
||||
m_glSpectrum->setPowerRange(m_powerRange);
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_decay_currentIndexChanged(int index)
|
||||
{
|
||||
m_decay = index - 2;
|
||||
if(m_glSpectrum != NULL)
|
||||
m_glSpectrum->setDecay(m_decay);
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_waterfall_toggled(bool checked)
|
||||
{
|
||||
m_displayWaterfall = checked;
|
||||
if(m_glSpectrum != NULL)
|
||||
m_glSpectrum->setDisplayWaterfall(m_displayWaterfall);
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_histogram_toggled(bool checked)
|
||||
{
|
||||
m_displayHistogram = checked;
|
||||
if(m_glSpectrum != NULL)
|
||||
m_glSpectrum->setDisplayHistogram(m_displayHistogram);
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_maxHold_toggled(bool checked)
|
||||
{
|
||||
m_displayMaxHold = checked;
|
||||
if(m_glSpectrum != NULL)
|
||||
m_glSpectrum->setDisplayMaxHold(m_displayMaxHold);
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_invert_toggled(bool checked)
|
||||
{
|
||||
m_invert = checked;
|
||||
if(m_glSpectrum != NULL)
|
||||
m_glSpectrum->setInvertedWaterfall(m_invert);
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_grid_toggled(bool checked)
|
||||
{
|
||||
m_displayGrid = checked;
|
||||
if(m_glSpectrum != NULL)
|
||||
m_glSpectrum->setDisplayGrid(m_displayGrid);
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>GLSpectrumGUI</class>
|
||||
<widget class="QWidget" name="GLSpectrumGUI">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>215</width>
|
||||
<height>94</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Oscilloscope</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout" columnstretch="1,1,1,1">
|
||||
<property name="margin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_17">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Window</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label_18">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>FFT Size</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="label_19">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Ref (dB)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QLabel" name="label_20">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Range (dB)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QComboBox" name="fftWindow">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Ignored" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>FFT window function</string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Bartlett</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Blackman-Harris</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Flat Top</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Hamming</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Hanning</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Rectangle</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="fftSize">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Ignored" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>FFT window function</string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>128</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>256</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>512</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1024</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>2048</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>4096</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QComboBox" name="refLevel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Ignored" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>FFT window function</string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QComboBox" name="levelRange">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Ignored" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>FFT window function</string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Decay</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QComboBox" name="decay">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Ignored" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>FFT window function</string>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>-2</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>-1</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>normal</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>+1</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>+2</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" colspan="3">
|
||||
<layout class="QHBoxLayout" name="controlBtns">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="waterfall">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Display waterfall</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Waterfall</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/res.qrc">
|
||||
<normaloff>:/waterfall.png</normaloff>:/waterfall.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="histogram">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Display phosphor effect spectrum</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Histogram</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/res.qrc">
|
||||
<normaloff>:/histogram.png</normaloff>:/histogram.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="maxHold">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Display live spectrum</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Max Hold</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/res.qrc">
|
||||
<normaloff>:/maxhold.png</normaloff>:/maxhold.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="invert">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Exchange waterfall and histogram</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Inv</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/res.qrc">
|
||||
<normaloff>:/invertspectrum.png</normaloff>:/invertspectrum.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="grid">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Toggle the scale grid</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Grid</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/res.qrc">
|
||||
<normaloff>:/grid.png</normaloff>:/grid.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ButtonSwitch</class>
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/buttonswitch.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>fftWindow</tabstop>
|
||||
<tabstop>fftSize</tabstop>
|
||||
<tabstop>refLevel</tabstop>
|
||||
<tabstop>levelRange</tabstop>
|
||||
<tabstop>decay</tabstop>
|
||||
<tabstop>waterfall</tabstop>
|
||||
<tabstop>histogram</tabstop>
|
||||
<tabstop>maxHold</tabstop>
|
||||
<tabstop>invert</tabstop>
|
||||
<tabstop>grid</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../resources/res.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -0,0 +1,56 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QPainter>
|
||||
#include "gui/indicator.h"
|
||||
|
||||
void Indicator::paintEvent(QPaintEvent*)
|
||||
{
|
||||
QPainter painter(this);
|
||||
|
||||
painter.setPen(Qt::black);
|
||||
painter.setBrush(m_color);
|
||||
|
||||
painter.drawRect(0, 0, 18, 15);
|
||||
painter.drawText(0, 0, 19, 16, Qt::AlignCenter | Qt::AlignHCenter, m_text);
|
||||
}
|
||||
|
||||
QSize Indicator::sizeHint() const
|
||||
{
|
||||
return QSize(20, 16);
|
||||
}
|
||||
|
||||
Indicator::Indicator(const QString& text, QWidget* parent) :
|
||||
QWidget(parent),
|
||||
m_color(Qt::gray),
|
||||
m_text(text)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
|
||||
QFont f = font();
|
||||
f.setBold(true);
|
||||
f.setPixelSize(8);
|
||||
setFont(f);
|
||||
}
|
||||
|
||||
void Indicator::setColor(const QColor& color)
|
||||
{
|
||||
if(m_color != color) {
|
||||
m_color = color;
|
||||
update();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "gui/pluginsdialog.h"
|
||||
#include "mainwindow.h"
|
||||
#include "ui_pluginsdialog.h"
|
||||
|
||||
PluginsDialog::PluginsDialog(PluginManager* pluginManager, QWidget* parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::PluginsDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
const PluginManager::Plugins& plugins = pluginManager->getPlugins();
|
||||
for(PluginManager::Plugins::const_iterator it = plugins.constBegin(); it != plugins.constEnd(); ++it) {
|
||||
QStringList sl;
|
||||
const PluginDescriptor& desc = it->plugin->getPluginDescriptor();
|
||||
sl.append(desc.displayedName);
|
||||
sl.append(desc.version);
|
||||
if(desc.licenseIsGPL)
|
||||
sl.append(tr("YES"));
|
||||
else sl.append("no");
|
||||
QTreeWidgetItem* pluginItem = new QTreeWidgetItem(ui->tree, sl);
|
||||
sl.clear();
|
||||
sl.append(tr("Copyright: %1").arg(desc.copyright));
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem(pluginItem, sl);
|
||||
item->setFirstColumnSpanned(true);
|
||||
sl.clear();
|
||||
sl.append(tr("Website: %1").arg(desc.website));
|
||||
item = new QTreeWidgetItem(pluginItem, sl);
|
||||
item->setFirstColumnSpanned(true);
|
||||
sl.clear();
|
||||
sl.append(tr("Source Code: %1").arg(desc.sourceCodeURL));
|
||||
item = new QTreeWidgetItem(pluginItem, sl);
|
||||
item->setFirstColumnSpanned(true);
|
||||
}
|
||||
ui->tree->resizeColumnToContents(0);
|
||||
ui->tree->resizeColumnToContents(1);
|
||||
ui->tree->resizeColumnToContents(2);
|
||||
}
|
||||
|
||||
PluginsDialog::~PluginsDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PluginsDialog</class>
|
||||
<widget class="QDialog" name="PluginsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Loaded Plugins</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="tree">
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Version</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>GPL</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>PluginsDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -0,0 +1,55 @@
|
||||
#include <QTreeWidgetItem>
|
||||
#include "gui/preferencesdialog.h"
|
||||
#include "ui_preferencesdialog.h"
|
||||
#include "audio/audiodeviceinfo.h"
|
||||
|
||||
PreferencesDialog::PreferencesDialog(AudioDeviceInfo* audioDeviceInfo, QWidget* parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::PreferencesDialog),
|
||||
m_audioDeviceInfo(audioDeviceInfo)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
const AudioDeviceInfo::Devices& devices = audioDeviceInfo->getDevices();
|
||||
|
||||
QTreeWidgetItem* api;
|
||||
QStringList sl;
|
||||
sl.append(tr("Default (use first suitable device)"));
|
||||
api = new QTreeWidgetItem(ui->audioTree, sl, ATDefault);
|
||||
api->setFirstColumnSpanned(true);
|
||||
for(AudioDeviceInfo::Devices::const_iterator it = devices.begin(); it != devices.end(); ++it) {
|
||||
int apiIndex;
|
||||
sl.clear();
|
||||
|
||||
for(apiIndex = 0; apiIndex < ui->audioTree->topLevelItemCount(); ++apiIndex) {
|
||||
if(ui->audioTree->topLevelItem(apiIndex)->text(0) == it->api)
|
||||
break;
|
||||
}
|
||||
if(apiIndex >= ui->audioTree->topLevelItemCount()) {
|
||||
sl.append(it->api);
|
||||
api = new QTreeWidgetItem(ui->audioTree, sl, ATInterface);
|
||||
api->setExpanded(true);
|
||||
api->setFirstColumnSpanned(true);
|
||||
sl.clear();
|
||||
} else {
|
||||
api = ui->audioTree->topLevelItem(apiIndex);
|
||||
}
|
||||
|
||||
sl.append(it->name);
|
||||
new QTreeWidgetItem(api, sl, ATDevice);
|
||||
}
|
||||
if(ui->audioTree->currentItem() == NULL)
|
||||
ui->audioTree->setCurrentItem(ui->audioTree->topLevelItem(0));
|
||||
|
||||
ui->tabWidget->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
PreferencesDialog::~PreferencesDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void PreferencesDialog::accept()
|
||||
{
|
||||
QDialog::accept();
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PreferencesDialog</class>
|
||||
<widget class="QDialog" name="PreferencesDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Receiver Hardware</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="treeWidget">
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Audio Output</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="audioTree">
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">Device</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>treeWidget</tabstop>
|
||||
<tabstop>audioTree</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>PreferencesDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>PreferencesDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -0,0 +1,29 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "gui/presetitem.h"
|
||||
|
||||
PresetItem::PresetItem(QTreeWidgetItem* parent, const QStringList& strings, quint64 frequency, int type) :
|
||||
QTreeWidgetItem(parent, strings, type),
|
||||
m_frequency(frequency)
|
||||
{
|
||||
}
|
||||
|
||||
bool PresetItem::operator<(const QTreeWidgetItem& other) const
|
||||
{
|
||||
return m_frequency < ((const PresetItem&)other).m_frequency;
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
#include <QEvent>
|
||||
#include <QPainter>
|
||||
#include <QMouseEvent>
|
||||
#include "gui/rollupwidget.h"
|
||||
#include "ui_glspectrumgui.h"
|
||||
|
||||
RollupWidget::RollupWidget(QWidget* parent) :
|
||||
QWidget(parent)
|
||||
{
|
||||
setMinimumSize(250, 150);
|
||||
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||
setBackgroundRole(QPalette::Window);
|
||||
|
||||
setAutoFillBackground(false);
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, true);
|
||||
|
||||
m_titleColor = palette().highlight().color();
|
||||
}
|
||||
|
||||
QByteArray RollupWidget::saveState(int version) const
|
||||
{
|
||||
QByteArray state;
|
||||
QDataStream stream(&state, QIODevice::WriteOnly);
|
||||
int count = 0;
|
||||
|
||||
for(int i = 0; i < children().count(); ++i) {
|
||||
QWidget* r = qobject_cast<QWidget*>(children()[i]);
|
||||
if(r != NULL)
|
||||
count++;
|
||||
}
|
||||
|
||||
stream << VersionMarker;
|
||||
stream << version;
|
||||
stream << count;
|
||||
|
||||
for(int i = 0; i < children().count(); ++i) {
|
||||
QWidget* r = qobject_cast<QWidget*>(children()[i]);
|
||||
if(r != NULL) {
|
||||
stream << r->objectName();
|
||||
if(r->isHidden())
|
||||
stream << (int)0;
|
||||
else stream << (int)1;
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
bool RollupWidget::restoreState(const QByteArray& state, int version)
|
||||
{
|
||||
if(state.isEmpty())
|
||||
return false;
|
||||
QByteArray sd = state;
|
||||
QDataStream stream(&sd, QIODevice::ReadOnly);
|
||||
int marker, v;
|
||||
stream >> marker;
|
||||
stream >> v;
|
||||
if((stream.status() != QDataStream::Ok) || (marker != VersionMarker) || (v != version))
|
||||
return false;
|
||||
|
||||
int count;
|
||||
stream >> count;
|
||||
|
||||
if(stream.status() != QDataStream::Ok)
|
||||
return false;
|
||||
for(int i = 0; i < count; ++i) {
|
||||
QString name;
|
||||
int visible;
|
||||
|
||||
stream >> name;
|
||||
stream >> visible;
|
||||
|
||||
if(stream.status() != QDataStream::Ok)
|
||||
return false;
|
||||
|
||||
for(int j = 0; j < children().count(); ++j) {
|
||||
QWidget* r = qobject_cast<QWidget*>(children()[j]);
|
||||
if(r != NULL) {
|
||||
if(r->objectName() == name) {
|
||||
if(visible)
|
||||
r->show();
|
||||
else r->hide();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RollupWidget::setTitleColor(const QColor& c)
|
||||
{
|
||||
m_titleColor = c;
|
||||
update();
|
||||
}
|
||||
|
||||
int RollupWidget::arrangeRollups()
|
||||
{
|
||||
QFontMetrics fm(font());
|
||||
int pos = fm.height() + 4;
|
||||
|
||||
for(int i = 0; i < children().count(); ++i) {
|
||||
QWidget* r = qobject_cast<QWidget*>(children()[i]);
|
||||
if(r != NULL) {
|
||||
pos += fm.height() + 2;
|
||||
if(!r->isHidden()) {
|
||||
r->move(2, pos + 3);
|
||||
int h = 0;
|
||||
if(r->hasHeightForWidth())
|
||||
h = r->heightForWidth(width() - 4);
|
||||
else h = r->sizeHint().height();
|
||||
r->resize(width() - 4, h);
|
||||
pos += r->height() + 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
setMinimumHeight(pos);
|
||||
setMaximumHeight(pos);
|
||||
return pos;
|
||||
}
|
||||
|
||||
void RollupWidget::paintEvent(QPaintEvent*)
|
||||
{
|
||||
QPainter p(this);
|
||||
QColor frame = palette().highlight().color();
|
||||
|
||||
// Eigenbau
|
||||
QFontMetrics fm(font());
|
||||
|
||||
p.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
// Ecken
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(palette().base());
|
||||
p.drawRect(0, 0, 5, 5);
|
||||
p.drawRect(width() - 5, 0, 5, 5);
|
||||
p.drawRect(0, height() - 5, 5, 5);
|
||||
p.drawRect(width() - 5, height() - 5, 5, 5);
|
||||
|
||||
// Rahmen
|
||||
p.setPen(frame);
|
||||
p.setBrush(palette().window());
|
||||
QRectF r(rect());
|
||||
r.adjust(0.5, 0.5, -0.5, -0.5);
|
||||
p.drawRoundedRect(r, 3.0, 3.0, Qt::AbsoluteSize);
|
||||
|
||||
// Titel-Hintergrund
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(m_titleColor);
|
||||
QPainterPath path;
|
||||
path.moveTo(1.5, fm.height() + 2.5);
|
||||
path.lineTo(width() - 1.5, fm.height() + 2.5);
|
||||
path.lineTo(width() - 1.5, 3.5);
|
||||
path.arcTo(QRectF(width() - 3.5, 0, 2.5, 2.5), 270, -90);
|
||||
path.lineTo(3.5, 1.5);
|
||||
path.arcTo(QRectF(1.5, 2.5, 2.5, 2.5), 90, 90);
|
||||
p.drawPath(path);
|
||||
|
||||
// Titel-Abschlusslinie
|
||||
p.setPen(frame);
|
||||
p.drawLine(QPointF(0.5, 2 + fm.height() + 1.5), QPointF(width() - 1.5, 2 + fm.height() + 1.5));
|
||||
|
||||
// Aktiv-Button links
|
||||
p.setPen(QPen(palette().windowText().color(), 1.0));
|
||||
p.setBrush(palette().light());
|
||||
p.drawRoundedRect(QRectF(3.5, 3.5, fm.ascent(), fm.ascent()), 2.0, 2.0, Qt::AbsoluteSize);
|
||||
|
||||
// Schließen-Button rechts
|
||||
p.setRenderHint(QPainter::Antialiasing, true);
|
||||
p.setPen(QPen(palette().windowText().color(), 1.0));
|
||||
p.setBrush(palette().light());
|
||||
r = QRectF(width() - 3.5 - fm.ascent(), 3.5, fm.ascent(), fm.ascent());
|
||||
p.drawRoundedRect(r, 2.0, 2.0, Qt::AbsoluteSize);
|
||||
p.setPen(QPen(palette().windowText().color(), 1.5));
|
||||
p.drawLine(r.topLeft() + QPointF(1, 1), r.bottomRight() + QPointF(-1, -1));
|
||||
p.drawLine(r.bottomLeft() + QPointF(1, -1), r.topRight() + QPointF(-1, 1));
|
||||
|
||||
// Titel
|
||||
p.setPen(palette().highlightedText().color());
|
||||
p.drawText(QRect(2 + fm.height(), 2, width() - 4 - 2 * fm.height(), fm.height()),
|
||||
fm.elidedText(windowTitle(), Qt::ElideMiddle, width() - 4 - 2 * fm.height(), 0));
|
||||
|
||||
// Rollups
|
||||
int pos = fm.height() + 4;
|
||||
|
||||
const QObjectList& c = children();
|
||||
QObjectList::ConstIterator w = c.begin();
|
||||
QObjectList::ConstIterator n = c.begin();
|
||||
|
||||
for(n = c.begin(); n != c.end(); ++n) {
|
||||
if(qobject_cast<QWidget*>(*n) != NULL)
|
||||
break;
|
||||
}
|
||||
for(w = n; w != c.end(); w = n) {
|
||||
if(n != c.end())
|
||||
++n;
|
||||
for(; n != c.end(); ++n) {
|
||||
if(qobject_cast<QWidget*>(*n) != NULL)
|
||||
break;
|
||||
}
|
||||
pos += paintRollup(qobject_cast<QWidget*>(*w), pos, &p, n == c.end(), frame);
|
||||
}
|
||||
}
|
||||
|
||||
int RollupWidget::paintRollup(QWidget* rollup, int pos, QPainter* p, bool last, const QColor& frame)
|
||||
{
|
||||
QFontMetrics fm(font());
|
||||
int height = 1;
|
||||
|
||||
// Titel-Abschlusslinie
|
||||
if(!rollup->isHidden()) {
|
||||
p->setPen(palette().dark().color());
|
||||
p->drawLine(QPointF(1.5, pos + fm.height() + 1.5), QPointF(width() - 1.5, pos + fm.height() + 1.5));
|
||||
p->setPen(palette().light().color());
|
||||
p->drawLine(QPointF(1.5, pos + fm.height() + 2.5), QPointF(width() - 1.5, pos + fm.height() + 2.5));
|
||||
height += 2;
|
||||
} else {
|
||||
if(!last) {
|
||||
p->setPen(frame);
|
||||
p->drawLine(QPointF(1.5, pos + fm.height() + 1.5), QPointF(width() - 1.5, pos + fm.height() + 1.5));
|
||||
height++;
|
||||
}
|
||||
}
|
||||
|
||||
// Titel
|
||||
p->setPen(palette().windowText().color());
|
||||
p->drawText(QRect(2 + fm.height(), pos, width() - 4 - fm.height(), fm.height()),
|
||||
fm.elidedText(rollup->windowTitle(), Qt::ElideMiddle, width() - 4 - fm.height(), 0));
|
||||
height += fm.height();
|
||||
|
||||
// Ausklapp-Icon
|
||||
p->setPen(palette().windowText().color());
|
||||
p->setBrush(palette().windowText());
|
||||
if(!rollup->isHidden()) {
|
||||
QPolygonF a;
|
||||
a.append(QPointF(3.5, pos + 2));
|
||||
a.append(QPointF(3.5 + fm.ascent(), pos + 2));
|
||||
a.append(QPointF(3.5 + fm.ascent() / 2.0, pos + fm.height() - 2));
|
||||
p->drawPolygon(a);
|
||||
} else {
|
||||
QPolygonF a;
|
||||
a.append(QPointF(3.5, pos + 2));
|
||||
a.append(QPointF(3.5, pos + fm.height() - 2));
|
||||
a.append(QPointF(3.5 + fm.ascent(), pos + fm.height() / 2));
|
||||
p->drawPolygon(a);
|
||||
}
|
||||
|
||||
// Inhalt
|
||||
if(!rollup->isHidden() && (!last)) {
|
||||
// Rollup-Abschlusslinie
|
||||
p->setPen(frame);
|
||||
p->drawLine(QPointF(1.5, pos + fm.height() + rollup->height() + 6.5),
|
||||
QPointF(width() - 1.5, pos + fm.height() + rollup->height() + 6.5));
|
||||
height += rollup->height() + 4;
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
void RollupWidget::resizeEvent(QResizeEvent* size)
|
||||
{
|
||||
arrangeRollups();
|
||||
QWidget::resizeEvent(size);
|
||||
}
|
||||
|
||||
void RollupWidget::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
QFontMetrics fm(font());
|
||||
|
||||
// menu box left
|
||||
if(QRectF(3.5, 3.5, fm.ascent(), fm.ascent()).contains(event->pos())) {
|
||||
emit customContextMenuRequested(event->pos());
|
||||
return;
|
||||
}
|
||||
|
||||
// close button right
|
||||
if(QRectF(width() - 3.5 - fm.ascent(), 3.5, fm.ascent(), fm.ascent()).contains(event->pos())) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
// check if we need to change a rollup widget
|
||||
int pos = fm.height() + 4;
|
||||
for(int i = 0; i < children().count(); ++i) {
|
||||
QWidget* r = qobject_cast<QWidget*>(children()[i]);
|
||||
if(r != NULL) {
|
||||
if((event->y() >= pos) && (event->y() < (pos + fm.height() + 3))) {
|
||||
if(r->isHidden()) {
|
||||
r->show();
|
||||
//emit widgetRolled(r, true);
|
||||
} else {
|
||||
r->hide();
|
||||
//emit widgetRolled(r, false);
|
||||
}
|
||||
arrangeRollups();
|
||||
repaint();
|
||||
return;
|
||||
} else {
|
||||
pos += fm.height() + 2;
|
||||
if(!r->isHidden())
|
||||
pos += r->height() + 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RollupWidget::mouseDoubleClickEvent(QMouseEvent* event)
|
||||
{
|
||||
QFontMetrics fm(font());
|
||||
|
||||
// menu box left
|
||||
if(QRectF(3.5, 3.5, fm.ascent(), fm.ascent()).contains(event->pos())) {
|
||||
emit menuDoubleClickEvent();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool RollupWidget::event(QEvent* event)
|
||||
{
|
||||
if(event->type() == QEvent::ChildAdded) {
|
||||
((QChildEvent*)event)->child()->installEventFilter(this);
|
||||
arrangeRollups();
|
||||
} else if(event->type() == QEvent::ChildRemoved) {
|
||||
((QChildEvent*)event)->child()->removeEventFilter(this);
|
||||
arrangeRollups();
|
||||
}
|
||||
return QWidget::event(event);
|
||||
}
|
||||
|
||||
bool RollupWidget::eventFilter(QObject* object, QEvent* event)
|
||||
{
|
||||
if(event->type() == QEvent::Show) {
|
||||
if(children().contains(object)) {
|
||||
arrangeRollups();
|
||||
emit widgetRolled(qobject_cast<QWidget*>(object), true);
|
||||
}
|
||||
} else if(event->type() == QEvent::Hide) {
|
||||
if(children().contains(object)) {
|
||||
arrangeRollups();
|
||||
emit widgetRolled(qobject_cast<QWidget*>(object), false);
|
||||
}
|
||||
} else if(event->type() == QEvent::WindowTitleChange) {
|
||||
if(children().contains(object))
|
||||
repaint();
|
||||
}
|
||||
return QWidget::eventFilter(object, event);
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QPainter>
|
||||
#include "gui/scale.h"
|
||||
|
||||
Scale::Scale(QWidget* parent) :
|
||||
QWidget(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void Scale::setOrientation(Qt::Orientation orientation)
|
||||
{
|
||||
m_orientation = orientation;
|
||||
m_scaleEngine.setOrientation(orientation);
|
||||
m_scaleEngine.setFont(font());
|
||||
QFontMetrics fm(font());
|
||||
switch(m_orientation) {
|
||||
case Qt::Horizontal:
|
||||
m_scaleEngine.setSize(width());
|
||||
setMinimumWidth(0);
|
||||
setMaximumWidth(QWIDGETSIZE_MAX);
|
||||
setMinimumHeight(3 + fontMetrics().ascent());
|
||||
setMaximumHeight(3 + fontMetrics().ascent());
|
||||
break;
|
||||
case Qt::Vertical:
|
||||
m_scaleEngine.setSize(height());
|
||||
setMinimumWidth(30);
|
||||
setMaximumWidth(30);
|
||||
setMinimumHeight(0);
|
||||
setMaximumHeight(QWIDGETSIZE_MAX);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Scale::setRange(Unit::Physical physicalUnit, float rangeMin, float rangeMax)
|
||||
{
|
||||
m_scaleEngine.setRange(physicalUnit, rangeMin, rangeMax);
|
||||
update();
|
||||
}
|
||||
|
||||
void Scale::paintEvent(QPaintEvent*)
|
||||
{
|
||||
QPainter painter(this);
|
||||
const ScaleEngine::TickList& tickList = m_scaleEngine.getTickList();
|
||||
QFontMetricsF fontMetrics(font());
|
||||
const ScaleEngine::Tick* tick;
|
||||
int i;
|
||||
float bottomLine;
|
||||
|
||||
switch(m_orientation) {
|
||||
case Qt::Horizontal: {
|
||||
painter.setPen(Qt::black);
|
||||
|
||||
// Zwischenlinien für x-Achse zeichnen
|
||||
for(i = 0; i < tickList.count(); i++) {
|
||||
tick = &tickList[i];
|
||||
if(!tick->major)
|
||||
painter.drawLine(QLineF(tick->pos, 0, tick->pos, 1));
|
||||
}
|
||||
|
||||
// Skala am Rand zeichnen
|
||||
painter.drawLine(QLineF(0, 0, width() - 1, 0));
|
||||
|
||||
// Hauptlinien und Beschriftung für x-Achse zeichnen
|
||||
for(i = 0; i < tickList.count(); i++) {
|
||||
tick = &tickList[i];
|
||||
if(tick->major) {
|
||||
painter.drawLine(QLineF(tick->pos - 1, 0, tick->pos - 1, 3));
|
||||
if(tick->textSize > 0) {
|
||||
painter.drawText(QPointF(tick->textPos, 3 + fontMetrics.ascent()), tick->text);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Qt::Vertical: {
|
||||
bottomLine = height() - 1;
|
||||
painter.setPen(Qt::black);
|
||||
|
||||
// Zwischenlinien für y-Achse zeichnen
|
||||
for(i = 0; i < tickList.count(); i++) {
|
||||
tick = &tickList[i];
|
||||
if(!tick->major)
|
||||
painter.drawLine(QLineF(width() - 2, bottomLine - tick->pos, width() - 1, bottomLine - tick->pos));
|
||||
}
|
||||
|
||||
// Skala am Rand zeichnen
|
||||
painter.drawLine(QLineF(width() - 1, 0, width() - 1, height() - 1));
|
||||
|
||||
// Hauptlinien und Beschriftung für y-Achse zeichnen
|
||||
for(i = 0; i < tickList.count(); i++) {
|
||||
tick = &tickList[i];
|
||||
if(tick->major) {
|
||||
painter.drawLine(QLineF(width() - 4, bottomLine - tick->pos, width() - 1, bottomLine - tick->pos));
|
||||
if(tick->textSize > 0)
|
||||
painter.drawText(QPointF(width() - 4 - tick->textSize, bottomLine - tick->textPos), tick->text);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Scale::resizeEvent(QResizeEvent*)
|
||||
{
|
||||
switch(m_orientation) {
|
||||
case Qt::Horizontal:
|
||||
m_scaleEngine.setSize(width());
|
||||
break;
|
||||
case Qt::Vertical:
|
||||
m_scaleEngine.setSize(height());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,569 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <math.h>
|
||||
#include <QFontMetrics>
|
||||
#include <QDataStream>
|
||||
#include "gui/scaleengine.h"
|
||||
|
||||
/*
|
||||
static double trunc(double d)
|
||||
{
|
||||
return (d > 0) ? floor(d) : ceil(d);
|
||||
}
|
||||
*/
|
||||
|
||||
QString ScaleEngine::formatTick(double value, int decimalPlaces, bool fancyTime)
|
||||
{
|
||||
if((m_physicalUnit != Unit::Time) || (!fancyTime) || 1) {
|
||||
return QString("%1").arg(value, 0, 'f', decimalPlaces);
|
||||
} else {
|
||||
QString str;
|
||||
double orig = fabs(value);
|
||||
double tmp;
|
||||
|
||||
if(orig >= 86400.0) {
|
||||
tmp = floor(value / 86400.0);
|
||||
str = QString("%1.").arg(tmp, 0, 'f', 0);
|
||||
value -= tmp * 86400.0;
|
||||
if(value < 0.0)
|
||||
value *= -1.0;
|
||||
}
|
||||
|
||||
if(orig >= 3600.0) {
|
||||
tmp = floor(value / 3600.0);
|
||||
str += QString("%1:").arg(tmp, 2, 'f', 0, QChar('0'));
|
||||
value -= tmp * 3600.0;
|
||||
if(value < 0.0)
|
||||
value *= -1.0;
|
||||
}
|
||||
|
||||
if(orig >= 60.0) {
|
||||
tmp = floor(value / 60.0);
|
||||
str += QString("%1:").arg(tmp, 2, 'f', 0, QChar('0'));
|
||||
value -= tmp * 60.0;
|
||||
if(value < 0.0)
|
||||
value *= -1.0;
|
||||
}
|
||||
|
||||
tmp = value;
|
||||
str += QString("%1").arg(tmp, 2, 'f', decimalPlaces, QChar('0'));
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
void ScaleEngine::calcCharSize()
|
||||
{
|
||||
QFontMetricsF fontMetrics(m_font);
|
||||
|
||||
if(m_orientation == Qt::Vertical) {
|
||||
m_charSize = fontMetrics.height();
|
||||
} else {
|
||||
QString str("012345679.,-");
|
||||
int i;
|
||||
float size;
|
||||
float max = 0.0f;
|
||||
for(i = 0; i < str.length(); i++) {
|
||||
size = fontMetrics.width(QString(str[i]));
|
||||
if(size > max)
|
||||
max = size;
|
||||
}
|
||||
m_charSize = max;
|
||||
}
|
||||
}
|
||||
|
||||
void ScaleEngine::calcScaleFactor()
|
||||
{
|
||||
double median;
|
||||
|
||||
median = ((m_rangeMax - m_rangeMin) / 2.0) + m_rangeMin;
|
||||
m_scale = 1.0;
|
||||
|
||||
switch(m_physicalUnit) {
|
||||
case Unit::None:
|
||||
m_unitStr.clear();
|
||||
break;
|
||||
|
||||
case Unit::Frequency:
|
||||
if(median < 1000.0) {
|
||||
m_unitStr = QObject::tr("Hz");
|
||||
} else if(median < 1000000.0) {
|
||||
m_unitStr = QObject::tr("kHz");
|
||||
m_scale = 1000.0;
|
||||
} else if(median < 1000000000.0) {
|
||||
m_unitStr = QObject::tr("MHz");
|
||||
m_scale = 1000000.0;
|
||||
} else if(median < 1000000000000.0){
|
||||
m_unitStr = QObject::tr("GHz");
|
||||
m_scale = 1000000000.0;
|
||||
} else {
|
||||
m_unitStr = QObject::tr("THz");
|
||||
m_scale = 1000000000000.0;
|
||||
}
|
||||
break;
|
||||
|
||||
case Unit::Information:
|
||||
if(median < 1024.0) {
|
||||
m_unitStr = QObject::tr("Bytes");
|
||||
} else if(median < 1048576.0) {
|
||||
m_unitStr = QObject::tr("KiBytes");
|
||||
m_scale = 1024.0;
|
||||
} else if(median < 1073741824.0) {
|
||||
m_unitStr = QObject::tr("MiBytes");
|
||||
m_scale = 1048576.0;
|
||||
} else if(median < 1099511627776.0) {
|
||||
m_unitStr = QObject::tr("GiBytes");
|
||||
m_scale = 1073741824.0;
|
||||
} else if(median < 1125899906842624.0) {
|
||||
m_unitStr = QObject::tr("TiBytes");
|
||||
m_scale = 1099511627776.0;
|
||||
} else {
|
||||
m_unitStr = QObject::tr("PiBytes");
|
||||
m_scale = 1125899906842624.0;
|
||||
}
|
||||
break;
|
||||
|
||||
case Unit::Percent:
|
||||
m_unitStr = QString("%");
|
||||
break;
|
||||
|
||||
case Unit::Decibel:
|
||||
m_unitStr = QString("dB");
|
||||
break;
|
||||
|
||||
case Unit::DecibelMilliWatt:
|
||||
m_unitStr = QString("dBm");
|
||||
break;
|
||||
|
||||
case Unit::DecibelMicroVolt:
|
||||
m_unitStr = QString("dBµV");
|
||||
break;
|
||||
|
||||
case Unit::AngleDegrees:
|
||||
m_unitStr = QString("°");
|
||||
|
||||
case Unit::Time:
|
||||
if(median < 0.001) {
|
||||
m_unitStr = QString("µs");
|
||||
m_scale = 0.000001;
|
||||
} else if(median < 1.0) {
|
||||
m_unitStr = QString("ms");
|
||||
m_scale = 0.001;
|
||||
} else {
|
||||
m_unitStr = QString("s");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
double ScaleEngine::calcMajorTickUnits(double distance, int* retDecimalPlaces)
|
||||
{
|
||||
double sign;
|
||||
double log10x;
|
||||
double exponent;
|
||||
double base;
|
||||
int decimalPlaces;
|
||||
|
||||
if(distance == 0.0)
|
||||
return 0.0;
|
||||
|
||||
sign = (distance > 0.0) ? 1.0 : -1.0;
|
||||
log10x = log10(fabs(distance));
|
||||
exponent = floor(log10x);
|
||||
base = pow(10.0, log10x - exponent);
|
||||
decimalPlaces = (int)(-exponent);
|
||||
/*
|
||||
if((m_physicalUnit == Unit::Time) && (distance >= 1.0)) {
|
||||
if(retDecimalPlaces != NULL)
|
||||
*retDecimalPlaces = 0;
|
||||
if(distance < 1.0)
|
||||
return 1.0;
|
||||
else if(distance < 5.0)
|
||||
return 5.0;
|
||||
else if(distance < 10.0)
|
||||
return 10.0;
|
||||
else if(distance < 15.0)
|
||||
return 15.0;
|
||||
else if(distance < 30.0)
|
||||
return 30.0;
|
||||
else if(distance < 60.0)
|
||||
return 60.0;
|
||||
else if(distance < 5.0 * 60.0)
|
||||
return 5.0 * 60.0;
|
||||
else if(distance < 10.0 * 60.0)
|
||||
return 10.0 * 60.0;
|
||||
else if(distance < 15.0 * 60.0)
|
||||
return 15.0 * 60.0;
|
||||
else if(distance < 30.0 * 60.0)
|
||||
return 30.0 * 60.0;
|
||||
else if(distance < 3600.0)
|
||||
return 3600.0;
|
||||
else if(distance < 2.0 * 3600.0)
|
||||
return 2.0 * 3600.0;
|
||||
else if(distance < 3.0 * 3600.0)
|
||||
return 3.0 * 3600.0;
|
||||
else if(distance < 6.0 * 3600.0)
|
||||
return 6.0 * 3600.0;
|
||||
else if(distance < 12.0 * 3600.0)
|
||||
return 12.0 * 3600.0;
|
||||
else if(distance < 86000.0)
|
||||
return 86000.0;
|
||||
else if(distance < 2.0 * 86000.0)
|
||||
return 2.0 * 86000.0;
|
||||
else if(distance < 7.0 * 86000.0)
|
||||
return 7.0 * 86000.0;
|
||||
else if(distance < 10.0 * 86000.0)
|
||||
return 10.0 * 86000.0;
|
||||
else if(distance < 30.0 * 86000.0)
|
||||
return 30.0 * 86000.0;
|
||||
else return 90.0 * 86000.0;
|
||||
} else {*/
|
||||
if(base <= 1.0) {
|
||||
base = 1.0;
|
||||
} else if(base <= 2.0) {
|
||||
base = 2.0;
|
||||
} else if(base <= 2.5) {
|
||||
base = 2.5;
|
||||
if(decimalPlaces >= 0)
|
||||
decimalPlaces++;
|
||||
} else if(base <= 5.0) {
|
||||
base = 5.0;
|
||||
} else {
|
||||
base = 10.0;
|
||||
}/*
|
||||
}*/
|
||||
|
||||
if(retDecimalPlaces != NULL) {
|
||||
if(decimalPlaces < 0)
|
||||
decimalPlaces = 0;
|
||||
*retDecimalPlaces = decimalPlaces;
|
||||
}
|
||||
|
||||
return sign * base * pow(10.0, exponent);
|
||||
}
|
||||
|
||||
int ScaleEngine::calcTickTextSize()
|
||||
{
|
||||
int tmp;
|
||||
int tickLen;
|
||||
int decimalPlaces;
|
||||
|
||||
tickLen = 1;
|
||||
tmp = formatTick(m_rangeMin / m_scale, 0).length();
|
||||
if(tmp > tickLen)
|
||||
tickLen = tmp;
|
||||
tmp = formatTick(m_rangeMax / m_scale, 0).length();
|
||||
if(tmp > tickLen)
|
||||
tickLen = tmp;
|
||||
|
||||
calcMajorTickUnits((m_rangeMax - m_rangeMin) / m_scale, &decimalPlaces);
|
||||
|
||||
return tickLen + decimalPlaces + 1;
|
||||
}
|
||||
|
||||
void ScaleEngine::forceTwoTicks()
|
||||
{
|
||||
Tick tick;
|
||||
QFontMetricsF fontMetrics(m_font);
|
||||
|
||||
m_tickList.clear();
|
||||
tick.major = true;
|
||||
|
||||
tick.pos = getPosFromValue(m_rangeMin);
|
||||
tick.text = formatTick(m_rangeMin / m_scale, m_decimalPlaces);
|
||||
tick.textSize = fontMetrics.boundingRect(tick.text).width();
|
||||
if(m_orientation == Qt::Vertical)
|
||||
tick.textPos = tick.pos - fontMetrics.ascent() / 2;
|
||||
else tick.textPos = tick.pos - fontMetrics.boundingRect(tick.text).width() / 2;
|
||||
m_tickList.append(tick);
|
||||
|
||||
tick.pos = getPosFromValue(m_rangeMax);
|
||||
tick.text = formatTick(m_rangeMax / m_scale, m_decimalPlaces);
|
||||
tick.textSize = fontMetrics.boundingRect(tick.text).width();
|
||||
if(m_orientation == Qt::Vertical)
|
||||
tick.textPos = tick.pos - fontMetrics.ascent() / 2;
|
||||
else tick.textPos = tick.pos - fontMetrics.boundingRect(tick.text).width() / 2;
|
||||
m_tickList.append(tick);
|
||||
}
|
||||
|
||||
void ScaleEngine::reCalc()
|
||||
{
|
||||
float majorTickSize;
|
||||
double rangeMinScaled;
|
||||
double rangeMaxScaled;
|
||||
int maxNumMajorTicks;
|
||||
int numMajorTicks;
|
||||
int step;
|
||||
int skip;
|
||||
double value;
|
||||
double value2;
|
||||
int i;
|
||||
int j;
|
||||
Tick tick;
|
||||
float pos;
|
||||
QString str;
|
||||
QFontMetricsF fontMetrics(m_font);
|
||||
float endPos;
|
||||
float lastEndPos;
|
||||
bool done;
|
||||
|
||||
if(!m_recalc)
|
||||
return;
|
||||
m_recalc = false;
|
||||
|
||||
m_tickList.clear();
|
||||
|
||||
calcScaleFactor();
|
||||
rangeMinScaled = m_rangeMin / m_scale;
|
||||
rangeMaxScaled = m_rangeMax / m_scale;
|
||||
|
||||
if(m_orientation == Qt::Vertical) {
|
||||
maxNumMajorTicks = (int)(m_size / (fontMetrics.lineSpacing() * 1.3f));
|
||||
} else {
|
||||
majorTickSize = (calcTickTextSize() + 2) * m_charSize;
|
||||
if(majorTickSize != 0.0)
|
||||
maxNumMajorTicks = (int)(m_size / majorTickSize);
|
||||
else maxNumMajorTicks = 20;
|
||||
}
|
||||
|
||||
m_majorTickValueDistance = calcMajorTickUnits((rangeMaxScaled - rangeMinScaled) / maxNumMajorTicks, &m_decimalPlaces);
|
||||
numMajorTicks = (int)((rangeMaxScaled - rangeMinScaled) / m_majorTickValueDistance);
|
||||
|
||||
if(numMajorTicks == 0) {
|
||||
forceTwoTicks();
|
||||
return;
|
||||
}
|
||||
|
||||
if(maxNumMajorTicks > 0)
|
||||
m_numMinorTicks = (int)(m_size / (maxNumMajorTicks * fontMetrics.height()));
|
||||
else m_numMinorTicks = 0;
|
||||
if(m_numMinorTicks < 1)
|
||||
m_numMinorTicks = 0;
|
||||
else if(m_numMinorTicks < 2)
|
||||
m_numMinorTicks = 1;
|
||||
else if(m_numMinorTicks < 5)
|
||||
m_numMinorTicks = 2;
|
||||
else if(m_numMinorTicks < 10)
|
||||
m_numMinorTicks = 5;
|
||||
else m_numMinorTicks = 10;
|
||||
|
||||
m_firstMajorTickValue = floor(rangeMinScaled / m_majorTickValueDistance) * m_majorTickValueDistance;
|
||||
|
||||
skip = 0;
|
||||
|
||||
if(rangeMinScaled == rangeMaxScaled)
|
||||
return;
|
||||
|
||||
while(true) {
|
||||
m_tickList.clear();
|
||||
|
||||
step = 0;
|
||||
lastEndPos = -100000000;
|
||||
done = true;
|
||||
|
||||
for(i = 0; true; i++) {
|
||||
value = majorTickValue(i);
|
||||
|
||||
for(j = 1; j < m_numMinorTicks; j++) {
|
||||
value2 = value + minorTickValue(j);
|
||||
if(value2 < rangeMinScaled)
|
||||
continue;
|
||||
if(value2 > rangeMaxScaled)
|
||||
break;
|
||||
pos = getPosFromValue((value + minorTickValue(j)) * m_scale);
|
||||
if((pos >= 0) && (pos < m_size)) {
|
||||
tick.pos = pos;
|
||||
tick.major = false;
|
||||
tick.textPos = -1;
|
||||
tick.textSize = -1;
|
||||
tick.text.clear();
|
||||
}
|
||||
m_tickList.append(tick);
|
||||
}
|
||||
|
||||
pos = getPosFromValue(value * m_scale);
|
||||
if(pos < 0.0)
|
||||
continue;
|
||||
if(pos >= m_size)
|
||||
break;
|
||||
|
||||
tick.pos = pos;
|
||||
tick.major = true;
|
||||
tick.textPos = -1;
|
||||
tick.textSize = -1;
|
||||
tick.text.clear();
|
||||
|
||||
if(step % (skip + 1) != 0) {
|
||||
m_tickList.append(tick);
|
||||
step++;
|
||||
continue;
|
||||
}
|
||||
step++;
|
||||
|
||||
str = formatTick(value, m_decimalPlaces);
|
||||
tick.text = str;
|
||||
tick.textSize = fontMetrics.boundingRect(tick.text).width();
|
||||
if(m_orientation == Qt::Vertical) {
|
||||
tick.textPos = pos - fontMetrics.ascent() / 2;
|
||||
endPos = tick.textPos + fontMetrics.ascent();
|
||||
} else {
|
||||
tick.textPos = pos - fontMetrics.boundingRect(tick.text).width() / 2;
|
||||
endPos = tick.textPos + tick.textSize;
|
||||
}
|
||||
|
||||
if(lastEndPos >= tick.textPos) {
|
||||
done = false;
|
||||
break;
|
||||
} else {
|
||||
lastEndPos = endPos;
|
||||
}
|
||||
|
||||
m_tickList.append(tick);
|
||||
}
|
||||
if(done)
|
||||
break;
|
||||
skip++;
|
||||
}
|
||||
|
||||
// make sure we have at least two major ticks with numbers
|
||||
numMajorTicks = 0;
|
||||
for(i = 0; i < m_tickList.count(); i++) {
|
||||
tick = m_tickList.at(i);
|
||||
if(tick.major)
|
||||
numMajorTicks++;
|
||||
}
|
||||
if(numMajorTicks < 2)
|
||||
forceTwoTicks();
|
||||
}
|
||||
|
||||
double ScaleEngine::majorTickValue(int tick)
|
||||
{
|
||||
return m_firstMajorTickValue + (tick * m_majorTickValueDistance);
|
||||
}
|
||||
|
||||
double ScaleEngine::minorTickValue(int tick)
|
||||
{
|
||||
if(m_numMinorTicks < 1)
|
||||
return 0.0;
|
||||
return (m_majorTickValueDistance * tick) / m_numMinorTicks;
|
||||
}
|
||||
|
||||
ScaleEngine::ScaleEngine() :
|
||||
m_orientation(Qt::Horizontal),
|
||||
m_physicalUnit(Unit::None),
|
||||
m_rangeMin(-1.0),
|
||||
m_rangeMax(1.0),
|
||||
m_recalc(true)
|
||||
{
|
||||
}
|
||||
|
||||
void ScaleEngine::setOrientation(Qt::Orientation orientation)
|
||||
{
|
||||
m_orientation = orientation;
|
||||
m_recalc = true;
|
||||
}
|
||||
|
||||
void ScaleEngine::setFont(const QFont& font)
|
||||
{
|
||||
m_font = font;
|
||||
m_recalc = true;
|
||||
calcCharSize();
|
||||
}
|
||||
|
||||
void ScaleEngine::setSize(float size)
|
||||
{
|
||||
if(size > 0.0f) {
|
||||
m_size = size;
|
||||
} else {
|
||||
m_size = 1.0f;
|
||||
}
|
||||
m_recalc = true;
|
||||
}
|
||||
|
||||
void ScaleEngine::setRange(Unit::Physical physicalUnit, float rangeMin, float rangeMax)
|
||||
{
|
||||
double tmpRangeMin;
|
||||
double tmpRangeMax;
|
||||
/*
|
||||
if(rangeMin < rangeMax) {
|
||||
tmpRangeMin = rangeMin;
|
||||
tmpRangeMax = rangeMax;
|
||||
} else if(rangeMin > rangeMax) {
|
||||
tmpRangeMin = rangeMax;
|
||||
tmpRangeMax = rangeMin;
|
||||
} else {
|
||||
tmpRangeMin = rangeMin * 0.99;
|
||||
tmpRangeMax = rangeMin * 1.01 + 0.01;
|
||||
}
|
||||
*/
|
||||
tmpRangeMin = rangeMin;
|
||||
tmpRangeMax = rangeMax;
|
||||
|
||||
if((tmpRangeMin != m_rangeMin) || (tmpRangeMax != m_rangeMax) || (m_physicalUnit != physicalUnit)) {
|
||||
m_physicalUnit = physicalUnit;
|
||||
m_rangeMin = tmpRangeMin;
|
||||
m_rangeMax = tmpRangeMax;
|
||||
m_recalc = true;
|
||||
}
|
||||
}
|
||||
|
||||
float ScaleEngine::getPosFromValue(double value)
|
||||
{
|
||||
return ((value - m_rangeMin) / (m_rangeMax - m_rangeMin)) * (m_size - 1.0);
|
||||
}
|
||||
|
||||
float ScaleEngine::getValueFromPos(double pos)
|
||||
{
|
||||
return ((pos * (m_rangeMax - m_rangeMin)) / (m_size - 1.0)) + m_rangeMin;
|
||||
}
|
||||
|
||||
const ScaleEngine::TickList& ScaleEngine::getTickList()
|
||||
{
|
||||
reCalc();
|
||||
return m_tickList;
|
||||
}
|
||||
|
||||
QString ScaleEngine::getRangeMinStr()
|
||||
{
|
||||
if(m_unitStr.length() > 0)
|
||||
return QString("%1 %2").arg(formatTick(m_rangeMin / m_scale, m_decimalPlaces, false)).arg(m_unitStr);
|
||||
else return QString("%1").arg(formatTick(m_rangeMin / m_scale, m_decimalPlaces, false));
|
||||
}
|
||||
|
||||
QString ScaleEngine::getRangeMaxStr()
|
||||
{
|
||||
if(m_unitStr.length() > 0)
|
||||
return QString("%1 %2").arg(formatTick(m_rangeMax / m_scale, m_decimalPlaces, false)).arg(m_unitStr);
|
||||
else return QString("%1").arg(formatTick(m_rangeMax / m_scale, m_decimalPlaces, false));
|
||||
}
|
||||
|
||||
float ScaleEngine::getScaleWidth()
|
||||
{
|
||||
float max;
|
||||
float len;
|
||||
int i;
|
||||
|
||||
reCalc();
|
||||
max = 0.0f;
|
||||
for(i = 0; i < m_tickList.count(); i++) {
|
||||
len = m_tickList[i].textSize;
|
||||
if(len > max)
|
||||
max = len;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "gui/scopewindow.h"
|
||||
#include "ui_scopewindow.h"
|
||||
#include "util/simpleserializer.h"
|
||||
|
||||
ScopeWindow::ScopeWindow(DSPEngine* dspEngine, QWidget* parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::ScopeWindow),
|
||||
m_sampleRate(0),
|
||||
m_timeBase(1)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->scope->setDSPEngine(dspEngine);
|
||||
}
|
||||
|
||||
ScopeWindow::~ScopeWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ScopeWindow::setSampleRate(int sampleRate)
|
||||
{
|
||||
m_sampleRate = sampleRate;
|
||||
on_scope_traceSizeChanged(0);
|
||||
}
|
||||
|
||||
void ScopeWindow::resetToDefaults()
|
||||
{
|
||||
m_displayData = GLScope::ModeIQ;
|
||||
m_displayOrientation = Qt::Horizontal;
|
||||
m_timeBase = 1;
|
||||
m_timeOffset = 0;
|
||||
m_amplification = 0;
|
||||
applySettings();
|
||||
}
|
||||
|
||||
QByteArray ScopeWindow::serialize() const
|
||||
{
|
||||
SimpleSerializer s(1);
|
||||
s.writeS32(1, m_displayData);
|
||||
s.writeS32(2, m_displayOrientation);
|
||||
s.writeS32(3, m_timeBase);
|
||||
s.writeS32(4, m_timeOffset);
|
||||
s.writeS32(5, m_amplification);
|
||||
return s.final();
|
||||
}
|
||||
|
||||
bool ScopeWindow::deserialize(const QByteArray& data)
|
||||
{
|
||||
SimpleDeserializer d(data);
|
||||
|
||||
if(!d.isValid()) {
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(d.getVersion() == 1) {
|
||||
d.readS32(1, &m_displayData, GLScope::ModeIQ);
|
||||
d.readS32(2, &m_displayOrientation, Qt::Horizontal);
|
||||
d.readS32(3, &m_timeBase, 1);
|
||||
d.readS32(4, &m_timeOffset, 0);
|
||||
d.readS32(5, &m_amplification, 0);
|
||||
if(m_timeBase < 0)
|
||||
m_timeBase = 1;
|
||||
applySettings();
|
||||
return true;
|
||||
} else {
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ScopeWindow::on_amp_valueChanged(int value)
|
||||
{
|
||||
static qreal amps[11] = { 0.2, 0.1, 0.05, 0.02, 0.01, 0.005, 0.002, 0.001, 0.0005, 0.0002, 0.0001 };
|
||||
ui->ampText->setText(tr("%1\n/div").arg(amps[value], 0, 'f', 4));
|
||||
ui->scope->setAmp(0.2 / amps[value]);
|
||||
m_amplification = value;
|
||||
}
|
||||
|
||||
void ScopeWindow::on_scope_traceSizeChanged(int)
|
||||
{
|
||||
qreal t = (ui->scope->getTraceSize() * 0.1 / m_sampleRate) / (qreal)m_timeBase;
|
||||
if(t < 0.000001)
|
||||
ui->timeText->setText(tr("%1\nns/div").arg(t * 1000000000.0));
|
||||
else if(t < 0.001)
|
||||
ui->timeText->setText(tr("%1\nµs/div").arg(t * 1000000.0));
|
||||
else if(t < 1.0)
|
||||
ui->timeText->setText(tr("%1\nms/div").arg(t * 1000.0));
|
||||
else ui->timeText->setText(tr("%1\ns/div").arg(t * 1.0));
|
||||
}
|
||||
|
||||
void ScopeWindow::on_time_valueChanged(int value)
|
||||
{
|
||||
m_timeBase = value;
|
||||
on_scope_traceSizeChanged(0);
|
||||
ui->scope->setTimeBase(m_timeBase);
|
||||
}
|
||||
|
||||
void ScopeWindow::on_timeOfs_valueChanged(int value)
|
||||
{
|
||||
m_timeOffset = value;
|
||||
ui->scope->setTimeOfsProMill(value);
|
||||
}
|
||||
|
||||
void ScopeWindow::on_displayMode_currentIndexChanged(int index)
|
||||
{
|
||||
m_displayData = index;
|
||||
switch(index) {
|
||||
case 0: // i+q
|
||||
ui->scope->setMode(GLScope::ModeIQ);
|
||||
break;
|
||||
case 1: // mag(lin)+pha
|
||||
ui->scope->setMode(GLScope::ModeMagLinPha);
|
||||
break;
|
||||
case 2: // mag(dB)+pha
|
||||
ui->scope->setMode(GLScope::ModeMagdBPha);
|
||||
break;
|
||||
case 3: // derived1+derived2
|
||||
ui->scope->setMode(GLScope::ModeDerived12);
|
||||
break;
|
||||
case 4: // clostationary
|
||||
ui->scope->setMode(GLScope::ModeCyclostationary);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ScopeWindow::on_horizView_clicked()
|
||||
{
|
||||
m_displayOrientation = Qt::Horizontal;
|
||||
if(ui->horizView->isChecked()) {
|
||||
ui->vertView->setChecked(false);
|
||||
ui->scope->setOrientation(Qt::Horizontal);
|
||||
} else {
|
||||
ui->horizView->setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
void ScopeWindow::on_vertView_clicked()
|
||||
{
|
||||
m_displayOrientation = Qt::Vertical;
|
||||
if(ui->vertView->isChecked()) {
|
||||
ui->horizView->setChecked(false);
|
||||
ui->scope->setOrientation(Qt::Vertical);
|
||||
} else {
|
||||
ui->vertView->setChecked(true);
|
||||
ui->scope->setOrientation(Qt::Vertical);
|
||||
}
|
||||
}
|
||||
|
||||
void ScopeWindow::applySettings()
|
||||
{
|
||||
ui->displayMode->setCurrentIndex(m_displayData);
|
||||
if(m_displayOrientation == Qt::Horizontal) {
|
||||
ui->scope->setOrientation(Qt::Horizontal);
|
||||
ui->horizView->setChecked(true);
|
||||
ui->vertView->setChecked(false);
|
||||
} else {
|
||||
ui->scope->setOrientation(Qt::Vertical);
|
||||
ui->horizView->setChecked(false);
|
||||
ui->vertView->setChecked(true);
|
||||
}
|
||||
ui->time->setValue(m_timeBase);
|
||||
ui->timeOfs->setValue(m_timeOffset);
|
||||
ui->amp->setValue(m_amplification);
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ScopeWindow</class>
|
||||
<widget class="QWidget" name="ScopeWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>798</width>
|
||||
<height>149</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Oscilloscope</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="GLScope" name="scope" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Data</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="displayMode">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContentsOnFirstShow</enum>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>I+Q Level (linear)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Magnitude (linear) + Phase</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Magnitude (dB) + Phase</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Derived 1st + 2nd order</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Cyclostationary</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QToolButton" name="horizView">
|
||||
<property name="toolTip">
|
||||
<string>Horizontal display arrangement</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/res.qrc">
|
||||
<normaloff>:/horizontal.png</normaloff>:/horizontal.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="vertView">
|
||||
<property name="toolTip">
|
||||
<string>Vertical display arrangement</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/res.qrc">
|
||||
<normaloff>:/vertical.png</normaloff>:/vertical.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Time</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="time">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="tickPosition">
|
||||
<enum>QSlider::TicksBelow</enum>
|
||||
</property>
|
||||
<property name="tickInterval">
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="timeText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.1000
|
||||
/div</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="timeOfs">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="tickPosition">
|
||||
<enum>QSlider::NoTicks</enum>
|
||||
</property>
|
||||
<property name="tickInterval">
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Amp</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="amp">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="tickPosition">
|
||||
<enum>QSlider::TicksBelow</enum>
|
||||
</property>
|
||||
<property name="tickInterval">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="ampText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.2000
|
||||
/div</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>GLScope</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/glscope.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../resources/res.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -0,0 +1,427 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QPainter>
|
||||
#include <QMouseEvent>
|
||||
#include <QWheelEvent>
|
||||
#include <QKeyEvent>
|
||||
#include "gui/valuedial.h"
|
||||
|
||||
ValueDial::ValueDial(QWidget* parent) :
|
||||
QWidget(parent),
|
||||
m_animationState(0)
|
||||
{
|
||||
setAutoFillBackground(false);
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, true);
|
||||
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||
setMouseTracking(true);
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
|
||||
m_background.setStart(0, 0);
|
||||
m_background.setFinalStop(0, 1);
|
||||
m_background.setCoordinateMode(QGradient::ObjectBoundingMode);
|
||||
|
||||
m_background.setColorAt(0.0, QColor(0x40, 0x40, 0x40));
|
||||
m_background.setColorAt(0.1, QColor(0xc0, 0xc0, 0xc0));
|
||||
m_background.setColorAt(0.2, QColor(0xf0, 0xf0, 0xf0));
|
||||
m_background.setColorAt(0.5, QColor(0xff, 0xff, 0xff));
|
||||
m_background.setColorAt(0.8, QColor(0xd0, 0xd0, 0xd0));
|
||||
m_background.setColorAt(0.9, QColor(0xa0, 0xa0, 0xa0));
|
||||
m_background.setColorAt(1.0, QColor(0x40, 0x40, 0x40));
|
||||
|
||||
m_value = 0;
|
||||
m_valueMin = 0;
|
||||
m_valueMax = 2200000;
|
||||
m_numDigits = 7;
|
||||
m_numDecimalPoints = m_numDigits / 3;
|
||||
m_cursor = -1;
|
||||
|
||||
m_hightlightedDigit = -1;
|
||||
m_text = formatText(m_value);
|
||||
m_cursorState = false;
|
||||
|
||||
connect(&m_animationTimer, SIGNAL(timeout()), this, SLOT(animate()));
|
||||
connect(&m_blinkTimer, SIGNAL(timeout()), this, SLOT(blink()));
|
||||
}
|
||||
|
||||
void ValueDial::setFont(const QFont& font)
|
||||
{
|
||||
QWidget::setFont(font);
|
||||
|
||||
QFontMetrics fm(font);
|
||||
m_digitWidth = fm.width('0');
|
||||
m_digitHeight = fm.ascent();
|
||||
if(m_digitWidth < m_digitHeight)
|
||||
m_digitWidth = m_digitHeight;
|
||||
setFixedWidth((m_numDigits + m_numDecimalPoints) * m_digitWidth + 2);
|
||||
setFixedHeight(m_digitHeight * 2 + 2);
|
||||
}
|
||||
|
||||
void ValueDial::setValue(quint64 value)
|
||||
{
|
||||
m_valueNew = value;
|
||||
if(m_valueNew < m_valueMin)
|
||||
m_valueNew = m_valueMin;
|
||||
else if(m_valueNew > m_valueMax)
|
||||
m_valueNew = m_valueMax;
|
||||
if(m_valueNew < m_value)
|
||||
m_animationState = 1;
|
||||
else if(m_valueNew > m_value)
|
||||
m_animationState = -1;
|
||||
else return;
|
||||
m_animationTimer.start(20);
|
||||
m_textNew = formatText(m_valueNew);
|
||||
}
|
||||
|
||||
void ValueDial::setValueRange(uint numDigits, quint64 min, quint64 max)
|
||||
{
|
||||
m_numDigits = numDigits;
|
||||
m_numDecimalPoints = m_numDigits / 3;
|
||||
|
||||
m_valueMin = min;
|
||||
m_valueMax = max;
|
||||
|
||||
if(m_value < min)
|
||||
setValue(min);
|
||||
else if(m_value > max)
|
||||
setValue(max);
|
||||
setFixedWidth((m_numDigits + m_numDecimalPoints) * m_digitWidth + 2);
|
||||
}
|
||||
|
||||
quint64 ValueDial::findExponent(int digit)
|
||||
{
|
||||
quint64 e = 1;
|
||||
int d = (m_numDigits + m_numDecimalPoints) - digit;
|
||||
d = d - (d / 4) - 1;
|
||||
for(int i = 0; i < d; i++)
|
||||
e *= 10;
|
||||
return e;
|
||||
}
|
||||
|
||||
QChar ValueDial::digitNeigh(QChar c, bool dir)
|
||||
{
|
||||
if(dir) {
|
||||
if(c == QChar('0'))
|
||||
return QChar('9');
|
||||
else return QChar::fromLatin1(c.toLatin1() - 1);
|
||||
} else {
|
||||
if(c == QChar('9'))
|
||||
return QChar('0');
|
||||
else return QChar::fromLatin1(c.toLatin1() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
QString ValueDial::formatText(quint64 value)
|
||||
{
|
||||
QString str = QString("%1").arg(value, m_numDigits, 10, QChar('0'));
|
||||
for(int i = 0; i < m_numDecimalPoints; i++)
|
||||
str.insert(m_numDigits - 3 - 3 * i, ".");
|
||||
return str;
|
||||
}
|
||||
|
||||
void ValueDial::paintEvent(QPaintEvent*)
|
||||
{
|
||||
QPainter painter(this);
|
||||
|
||||
painter.setPen(Qt::black);
|
||||
painter.setBrush(m_background);
|
||||
|
||||
painter.drawRect(0, 0, width() - 1, height() - 1);
|
||||
|
||||
painter.setPen(QColor(0x20, 0x20, 0x20));
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
for(int i = 1; i < m_numDigits + m_numDecimalPoints; i++) {
|
||||
painter.setPen(QColor(0x20, 0x20, 0x20));
|
||||
painter.drawLine(1 + i * m_digitWidth, 1, 1 + i * m_digitWidth, height() - 1);
|
||||
painter.setPen(QColor(0x00, 0x00, 0x00, 0x20));
|
||||
painter.drawLine(0 + i * m_digitWidth, 1, 0 + i * m_digitWidth, height() - 1);
|
||||
painter.drawLine(2 + i * m_digitWidth, 1, 2 + i * m_digitWidth, height() - 1);
|
||||
}
|
||||
painter.setPen(QColor(0x00, 0x00, 0x00, 0x20));
|
||||
painter.drawLine(1, 1, 1, height() - 1);
|
||||
painter.drawLine(width() - 2, 1, width() - 2, height() - 1);
|
||||
|
||||
if(m_hightlightedDigit >= 0) {
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(QColor(0xff, 0x00, 0x00, 0x20));
|
||||
painter.drawRect(2 + m_hightlightedDigit * m_digitWidth, 1, m_digitWidth - 1, height() - 1);
|
||||
}
|
||||
|
||||
if(m_animationState == 0) {
|
||||
for(int i = 0; i < m_text.length(); i++) {
|
||||
painter.setClipRect(1 + i * m_digitWidth, 1, m_digitWidth, m_digitHeight * 2);
|
||||
painter.setPen(QColor(0x10, 0x10, 0x10));
|
||||
painter.drawText(QRect(1 + i * m_digitWidth, m_digitHeight * 0.6, m_digitWidth, m_digitHeight), Qt::AlignCenter, m_text.mid(i, 1));
|
||||
if(m_text[i] != QChar('.')) {
|
||||
painter.setPen(QColor(0x00, 0x00, 0x00));
|
||||
painter.drawText(QRect(1 + i * m_digitWidth, m_digitHeight * -0.7, m_digitWidth, m_digitHeight), Qt::AlignCenter, digitNeigh(m_text[i], true));
|
||||
painter.drawText(QRect(1 + i * m_digitWidth, m_digitHeight * 1.9, m_digitWidth, m_digitHeight), Qt::AlignCenter, digitNeigh(m_text[i], false));
|
||||
}
|
||||
}
|
||||
painter.setClipping(false);
|
||||
if((m_cursor >= 0) && (m_cursorState)) {
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(QColor(0x10, 0x10, 0x10));
|
||||
painter.drawRect(4 + m_cursor * m_digitWidth, 1 + m_digitHeight * 1.5, m_digitWidth - 5, m_digitHeight / 6);
|
||||
}
|
||||
} else {
|
||||
if(m_animationState != 0) {
|
||||
for(int i = 0; i < m_text.length(); i++) {
|
||||
if(m_text[i] == m_textNew[i]) {
|
||||
painter.setClipRect(1 + i * m_digitWidth, 1, m_digitWidth, m_digitHeight * 2);
|
||||
painter.setPen(QColor(0x10, 0x10, 0x10));
|
||||
painter.drawText(QRect(1 + i * m_digitWidth, m_digitHeight * 0.6, m_digitWidth, m_digitHeight), Qt::AlignCenter, m_text.mid(i, 1));
|
||||
if(m_text[i] != QChar('.')) {
|
||||
painter.setPen(QColor(0x00, 0x00, 0x00));
|
||||
painter.drawText(QRect(1 + i * m_digitWidth, m_digitHeight * -0.7, m_digitWidth, m_digitHeight), Qt::AlignCenter, digitNeigh(m_text[i], true));
|
||||
painter.drawText(QRect(1 + i * m_digitWidth, m_digitHeight * 1.9, m_digitWidth, m_digitHeight), Qt::AlignCenter, digitNeigh(m_text[i], false));
|
||||
}
|
||||
} else {
|
||||
int h = m_digitHeight * 0.6 + m_digitHeight * m_animationState / 2.0;
|
||||
painter.setClipRect(1 + i * m_digitWidth, 1, m_digitWidth, m_digitHeight * 2);
|
||||
painter.setPen(QColor(0x10, 0x10, 0x10));
|
||||
painter.drawText(QRect(1 + i * m_digitWidth, h, m_digitWidth, m_digitHeight), Qt::AlignCenter, m_text.mid(i, 1));
|
||||
if(m_text[i] != QChar('.')) {
|
||||
painter.setPen(QColor(0x00, 0x00, 0x00));
|
||||
painter.drawText(QRect(1 + i * m_digitWidth, h + m_digitHeight * -0.7, m_digitWidth, m_digitHeight), Qt::AlignCenter, digitNeigh(m_text[i], true));
|
||||
painter.drawText(QRect(1 + i * m_digitWidth, h + m_digitHeight * 1.9, m_digitWidth, m_digitHeight), Qt::AlignCenter, digitNeigh(m_text[i], false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ValueDial::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
int i;
|
||||
|
||||
i = (event->x() - 1) / m_digitWidth;
|
||||
if(m_text[i] == QChar('.')) {
|
||||
i++;
|
||||
if(i > m_numDigits + m_numDecimalPoints)
|
||||
return;
|
||||
}
|
||||
m_cursor = i;
|
||||
m_cursorState = true;
|
||||
m_blinkTimer.start(400);
|
||||
update();
|
||||
}
|
||||
|
||||
void ValueDial::mouseMoveEvent(QMouseEvent* event)
|
||||
{
|
||||
int i;
|
||||
|
||||
i = (event->x() - 1) / m_digitWidth;
|
||||
if(m_text[i] == QChar('.'))
|
||||
i = -1;
|
||||
|
||||
if(i != m_hightlightedDigit) {
|
||||
m_hightlightedDigit = i;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void ValueDial::wheelEvent(QWheelEvent* event)
|
||||
{
|
||||
int i;
|
||||
|
||||
i = (event->x() - 1) / m_digitWidth;
|
||||
if(m_text[i] != QChar('.'))
|
||||
m_hightlightedDigit = i;
|
||||
else
|
||||
return;
|
||||
|
||||
if(m_cursor >= 0) {
|
||||
m_cursor = -1;
|
||||
m_blinkTimer.stop();
|
||||
update();
|
||||
}
|
||||
quint64 e = findExponent(m_hightlightedDigit);
|
||||
|
||||
if(m_animationState == 0) {
|
||||
if(event->delta() < 0) {
|
||||
if(event->modifiers() & Qt::ShiftModifier)
|
||||
e *= 5;
|
||||
if(m_value < e)
|
||||
m_valueNew = m_valueMin;
|
||||
else m_valueNew = m_value - e;
|
||||
} else {
|
||||
if(event->modifiers() & Qt::ShiftModifier)
|
||||
e *= 5;
|
||||
if(m_valueMax - m_value < e)
|
||||
m_valueNew = m_valueMax;
|
||||
else m_valueNew = m_value + e;
|
||||
}
|
||||
setValue(m_valueNew);
|
||||
emit changed(m_valueNew);
|
||||
}
|
||||
}
|
||||
|
||||
void ValueDial::leaveEvent(QEvent*)
|
||||
{
|
||||
if(m_hightlightedDigit != -1) {
|
||||
m_hightlightedDigit = -1;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void ValueDial::keyPressEvent(QKeyEvent* value)
|
||||
{
|
||||
if(m_cursor >= 0) {
|
||||
if((value->key() == Qt::Key_Return) || (value->key() == Qt::Key_Enter) || (value->key() == Qt::Key_Escape)) {
|
||||
m_cursor = -1;
|
||||
m_cursorState = false;
|
||||
m_blinkTimer.stop();
|
||||
update();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if((m_cursor < 0) && (m_hightlightedDigit >= 0)) {
|
||||
m_cursor = m_hightlightedDigit;
|
||||
if(m_text[m_cursor] == QChar('.'))
|
||||
m_cursor++;
|
||||
if(m_cursor >= m_numDigits + m_numDecimalPoints)
|
||||
return;
|
||||
m_cursorState = true;
|
||||
m_blinkTimer.start(400);
|
||||
update();
|
||||
}
|
||||
|
||||
if(m_cursor < 0)
|
||||
return;
|
||||
|
||||
if((value->key() == Qt::Key_Left) || (value->key() == Qt::Key_Backspace)) {
|
||||
if(m_cursor > 0) {
|
||||
m_cursor--;
|
||||
if(m_text[m_cursor] == QChar('.'))
|
||||
m_cursor--;
|
||||
if(m_cursor < 0)
|
||||
m_cursor++;
|
||||
m_cursorState = true;
|
||||
update();
|
||||
return;
|
||||
}
|
||||
} else if(value->key() == Qt::Key_Right) {
|
||||
if(m_cursor < m_numDecimalPoints + m_numDigits) {
|
||||
m_cursor++;
|
||||
if(m_text[m_cursor] == QChar('.'))
|
||||
m_cursor++;
|
||||
if(m_cursor >= m_numDecimalPoints + m_numDigits)
|
||||
m_cursor--;
|
||||
m_cursorState = true;
|
||||
update();
|
||||
return;
|
||||
}
|
||||
} else if(value->key() == Qt::Key_Up) {
|
||||
quint64 e = findExponent(m_cursor);
|
||||
if(value->modifiers() & Qt::ShiftModifier)
|
||||
e *= 5;
|
||||
if(m_animationState != 0)
|
||||
m_value = m_valueNew;
|
||||
if(m_valueMax - m_value < e)
|
||||
m_valueNew = m_valueMax;
|
||||
else m_valueNew = m_value + e;
|
||||
setValue(m_valueNew);
|
||||
emit changed(m_valueNew);
|
||||
} else if(value->key() == Qt::Key_Down) {
|
||||
quint64 e = findExponent(m_cursor);
|
||||
if(value->modifiers() & Qt::ShiftModifier)
|
||||
e *= 5;
|
||||
if(m_animationState != 0)
|
||||
m_value = m_valueNew;
|
||||
if(m_value < e)
|
||||
m_valueNew = m_valueMin;
|
||||
else m_valueNew = m_value - e;
|
||||
setValue(m_valueNew);
|
||||
emit changed(m_valueNew);
|
||||
}
|
||||
|
||||
if(value->text().length() != 1)
|
||||
return;
|
||||
QChar c = value->text()[0];
|
||||
|
||||
if(c >= QChar('0') && (c <= QChar('9'))) {
|
||||
int d = c.toLatin1() - '0';
|
||||
quint64 e = findExponent(m_cursor);
|
||||
quint64 v = (m_value / e) % 10;
|
||||
if(m_animationState != 0)
|
||||
m_value = m_valueNew;
|
||||
v = m_value - v * e;
|
||||
v += d * e;
|
||||
setValue(v);
|
||||
emit changed(m_valueNew);
|
||||
m_cursor++;
|
||||
if(m_text[m_cursor] == QChar('.'))
|
||||
m_cursor++;
|
||||
if(m_cursor >= m_numDigits + m_numDecimalPoints) {
|
||||
m_cursor = -1;
|
||||
m_blinkTimer.stop();
|
||||
} else {
|
||||
m_cursorState = true;
|
||||
}
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void ValueDial::focusInEvent(QFocusEvent*)
|
||||
{
|
||||
if(m_cursor == -1) {
|
||||
m_cursor = 0;
|
||||
m_cursorState = true;
|
||||
m_blinkTimer.start(400);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void ValueDial::focusOutEvent(QFocusEvent*)
|
||||
{
|
||||
m_cursor = -1;
|
||||
m_blinkTimer.stop();
|
||||
update();
|
||||
}
|
||||
|
||||
void ValueDial::animate()
|
||||
{
|
||||
update();
|
||||
|
||||
if(m_animationState > 0)
|
||||
m_animationState++;
|
||||
else if(m_animationState < 0)
|
||||
m_animationState--;
|
||||
else {
|
||||
m_animationTimer.stop();
|
||||
m_animationState = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if(abs(m_animationState) >= 4) {
|
||||
m_animationState = 0;
|
||||
m_animationTimer.stop();
|
||||
m_value = m_valueNew;
|
||||
m_text = m_textNew;
|
||||
}
|
||||
}
|
||||
|
||||
void ValueDial::blink()
|
||||
{
|
||||
if(m_cursor >= 0) {
|
||||
m_cursorState = !m_cursorState;
|
||||
update();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,526 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QLabel>
|
||||
#include "mainwindow.h"
|
||||
#include "ui_mainwindow.h"
|
||||
#include "audio/audiodeviceinfo.h"
|
||||
#include "gui/indicator.h"
|
||||
#include "gui/presetitem.h"
|
||||
#include "gui/scopewindow.h"
|
||||
#include "gui/addpresetdialog.h"
|
||||
#include "gui/pluginsdialog.h"
|
||||
#include "gui/preferencesdialog.h"
|
||||
#include "gui/aboutdialog.h"
|
||||
#include "gui/rollupwidget.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/spectrumvis.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "plugin/plugingui.h"
|
||||
#include "plugin/pluginapi.h"
|
||||
#include "plugin/plugingui.h"
|
||||
|
||||
MainWindow::MainWindow(QWidget* parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::MainWindow),
|
||||
m_audioDeviceInfo(new AudioDeviceInfo),
|
||||
m_messageQueue(new MessageQueue),
|
||||
m_settings(),
|
||||
m_dspEngine(new DSPEngine(m_messageQueue)),
|
||||
m_lastEngineState((DSPEngine::State)-1),
|
||||
m_startOsmoSDRUpdateAfterStop(false),
|
||||
m_scopeWindow(NULL),
|
||||
m_inputGUI(NULL),
|
||||
m_sampleRate(0),
|
||||
m_centerFrequency(0),
|
||||
m_pluginManager(new PluginManager(this, m_dspEngine))
|
||||
{
|
||||
ui->setupUi(this);
|
||||
delete ui->mainToolBar;
|
||||
createStatusBar();
|
||||
|
||||
setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
|
||||
setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
|
||||
setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
|
||||
setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
|
||||
|
||||
// work around broken Qt dock widget ordering
|
||||
removeDockWidget(ui->inputDock);
|
||||
removeDockWidget(ui->processingDock);
|
||||
removeDockWidget(ui->presetDock);
|
||||
removeDockWidget(ui->channelDock);
|
||||
addDockWidget(Qt::LeftDockWidgetArea, ui->inputDock);
|
||||
addDockWidget(Qt::LeftDockWidgetArea, ui->processingDock);
|
||||
addDockWidget(Qt::LeftDockWidgetArea, ui->presetDock);
|
||||
addDockWidget(Qt::RightDockWidgetArea, ui->channelDock);
|
||||
ui->inputDock->show();
|
||||
ui->processingDock->show();
|
||||
ui->presetDock->show();
|
||||
ui->channelDock->show();
|
||||
|
||||
ui->menu_Window->addAction(ui->inputDock->toggleViewAction());
|
||||
ui->menu_Window->addAction(ui->processingDock->toggleViewAction());
|
||||
ui->menu_Window->addAction(ui->presetDock->toggleViewAction());
|
||||
ui->menu_Window->addAction(ui->channelDock->toggleViewAction());
|
||||
|
||||
connect(m_messageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleMessages()), Qt::QueuedConnection);
|
||||
|
||||
connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
|
||||
m_statusTimer.start(500);
|
||||
|
||||
m_pluginManager->loadPlugins();
|
||||
bool sampleSourceSignalsBlocked = ui->sampleSource->blockSignals(true);
|
||||
m_pluginManager->fillSampleSourceSelector(ui->sampleSource);
|
||||
ui->sampleSource->blockSignals(sampleSourceSignalsBlocked);
|
||||
|
||||
m_dspEngine->start();
|
||||
|
||||
m_spectrumVis = new SpectrumVis(ui->glSpectrum);
|
||||
m_dspEngine->addSink(m_spectrumVis);
|
||||
|
||||
ui->glSpectrumGUI->setBuddies(m_dspEngine->getMessageQueue(), m_spectrumVis, ui->glSpectrum);
|
||||
|
||||
loadSettings();
|
||||
|
||||
int sampleSourceIndex = m_pluginManager->selectSampleSource(m_settings.getCurrent()->getSource()); // select SampleSource from settings
|
||||
if(sampleSourceIndex >= 0) {
|
||||
bool sampleSourceSignalsBlocked = ui->sampleSource->blockSignals(true);
|
||||
ui->sampleSource->setCurrentIndex(sampleSourceIndex);
|
||||
ui->sampleSource->blockSignals(sampleSourceSignalsBlocked);
|
||||
}
|
||||
|
||||
loadSettings(m_settings.getCurrent());
|
||||
|
||||
applySettings();
|
||||
updatePresets();
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
m_dspEngine->stopAcquistion();
|
||||
|
||||
saveSettings();
|
||||
|
||||
m_pluginManager->freeAll();
|
||||
|
||||
m_dspEngine->removeSink(m_spectrumVis);
|
||||
delete m_spectrumVis;
|
||||
|
||||
if(m_scopeWindow != NULL) {
|
||||
delete m_scopeWindow;
|
||||
m_scopeWindow = NULL;
|
||||
}
|
||||
|
||||
delete m_pluginManager;
|
||||
|
||||
m_dspEngine->stop();
|
||||
|
||||
delete m_dspEngine;
|
||||
delete m_messageQueue;
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void MainWindow::addChannelCreateAction(QAction* action)
|
||||
{
|
||||
ui->menu_Channels->addAction(action);
|
||||
}
|
||||
|
||||
void MainWindow::addChannelRollup(QWidget* widget)
|
||||
{
|
||||
((ChannelWindow*)ui->channelDock->widget())->addRollupWidget(widget);
|
||||
ui->channelDock->show();
|
||||
ui->channelDock->raise();
|
||||
}
|
||||
|
||||
void MainWindow::addViewAction(QAction* action)
|
||||
{
|
||||
ui->menu_Window->addAction(action);
|
||||
}
|
||||
|
||||
void MainWindow::addChannelMarker(ChannelMarker* channelMarker)
|
||||
{
|
||||
ui->glSpectrum->addChannelMarker(channelMarker);
|
||||
}
|
||||
|
||||
void MainWindow::removeChannelMarker(ChannelMarker* channelMarker)
|
||||
{
|
||||
ui->glSpectrum->removeChannelMarker(channelMarker);
|
||||
}
|
||||
|
||||
void MainWindow::setInputGUI(QWidget* gui)
|
||||
{
|
||||
if(m_inputGUI != NULL)
|
||||
ui->inputDock->widget()->layout()->removeWidget(m_inputGUI);
|
||||
if(gui != NULL)
|
||||
ui->inputDock->widget()->layout()->addWidget(gui);
|
||||
m_inputGUI = gui;
|
||||
}
|
||||
|
||||
void MainWindow::loadSettings()
|
||||
{
|
||||
m_settings.load();
|
||||
|
||||
for(int i = 0; i < m_settings.getPresetCount(); ++i)
|
||||
addPresetToTree(m_settings.getPreset(i));
|
||||
|
||||
Preset* current = m_settings.getCurrent();
|
||||
|
||||
loadSettings(current);
|
||||
}
|
||||
|
||||
void MainWindow::loadSettings(const Preset* preset)
|
||||
{
|
||||
if(preset->getShowScope()) {
|
||||
on_action_Oscilloscope_triggered();
|
||||
m_scopeWindow->deserialize(preset->getScopeConfig());
|
||||
}
|
||||
ui->glSpectrumGUI->deserialize(preset->getSpectrumConfig());
|
||||
ui->dcOffset->setChecked(preset->getDCOffsetCorrection());
|
||||
ui->iqImbalance->setChecked(preset->getIQImbalanceCorrection());
|
||||
|
||||
m_pluginManager->loadSettings(preset);
|
||||
|
||||
// has to be last step
|
||||
restoreState(preset->getLayout());
|
||||
}
|
||||
|
||||
void MainWindow::saveSettings()
|
||||
{
|
||||
saveSettings(m_settings.getCurrent());
|
||||
m_settings.save();
|
||||
}
|
||||
|
||||
void MainWindow::saveSettings(Preset* preset)
|
||||
{
|
||||
preset->setSpectrumConfig(ui->glSpectrumGUI->serialize());
|
||||
if(preset->getShowScope())
|
||||
preset->setScopeConfig(m_scopeWindow->serialize());
|
||||
else preset->setScopeConfig(QByteArray());
|
||||
|
||||
preset->clearChannels();
|
||||
m_pluginManager->saveSettings(preset);
|
||||
|
||||
preset->setLayout(saveState());
|
||||
}
|
||||
|
||||
void MainWindow::createStatusBar()
|
||||
{
|
||||
m_sampleRateWidget = new QLabel(tr("Rate: 0 kHz"), this);
|
||||
m_sampleRateWidget->setToolTip(tr("Sample Rate"));
|
||||
statusBar()->addPermanentWidget(m_sampleRateWidget);
|
||||
|
||||
m_engineIdle = new Indicator(tr("Idle"), this);
|
||||
m_engineIdle->setToolTip(tr("DSP engine is idle"));
|
||||
statusBar()->addPermanentWidget(m_engineIdle);
|
||||
|
||||
m_engineRunning = new Indicator(tr("Run"), this);
|
||||
m_engineRunning->setToolTip(tr("DSP engine is running"));
|
||||
statusBar()->addPermanentWidget(m_engineRunning);
|
||||
|
||||
m_engineError = new Indicator(tr("Err"), this);
|
||||
m_engineError->setToolTip(tr("DSP engine failed"));
|
||||
statusBar()->addPermanentWidget(m_engineError);
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent*)
|
||||
{
|
||||
}
|
||||
|
||||
void MainWindow::updateCenterFreqDisplay()
|
||||
{
|
||||
ui->glSpectrum->setCenterFrequency(m_centerFrequency);
|
||||
}
|
||||
|
||||
void MainWindow::updateSampleRate()
|
||||
{
|
||||
ui->glSpectrum->setSampleRate(m_sampleRate);
|
||||
m_sampleRateWidget->setText(tr("Rate: %1 kHz").arg((float)m_sampleRate / 1000));
|
||||
if(m_scopeWindow != NULL)
|
||||
m_scopeWindow->setSampleRate(m_sampleRate);
|
||||
}
|
||||
|
||||
void MainWindow::updatePresets()
|
||||
{
|
||||
ui->presetTree->resizeColumnToContents(0);
|
||||
if(ui->presetTree->currentItem() != NULL) {
|
||||
ui->presetDelete->setEnabled(true);
|
||||
ui->presetLoad->setEnabled(true);
|
||||
} else {
|
||||
ui->presetDelete->setEnabled(false);
|
||||
ui->presetLoad->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
QTreeWidgetItem* MainWindow::addPresetToTree(const Preset* preset)
|
||||
{
|
||||
QTreeWidgetItem* group = NULL;
|
||||
for(int i = 0; i < ui->presetTree->topLevelItemCount(); i++) {
|
||||
if(ui->presetTree->topLevelItem(i)->text(0) == preset->getGroup()) {
|
||||
group = ui->presetTree->topLevelItem(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(group == NULL) {
|
||||
QStringList sl;
|
||||
sl.append(preset->getGroup());
|
||||
group = new QTreeWidgetItem(ui->presetTree, sl, PGroup);
|
||||
group->setFirstColumnSpanned(true);
|
||||
group->setExpanded(true);
|
||||
ui->presetTree->sortByColumn(0, Qt::AscendingOrder);
|
||||
}
|
||||
QStringList sl;
|
||||
sl.append(QString("%1 kHz").arg(preset->getCenterFrequency() / 1000));
|
||||
sl.append(preset->getDescription());
|
||||
PresetItem* item = new PresetItem(group, sl, preset->getCenterFrequency(), PItem);
|
||||
item->setTextAlignment(0, Qt::AlignRight);
|
||||
item->setData(0, Qt::UserRole, qVariantFromValue(preset));
|
||||
ui->presetTree->resizeColumnToContents(0);
|
||||
|
||||
updatePresets();
|
||||
return item;
|
||||
}
|
||||
|
||||
void MainWindow::applySettings()
|
||||
{
|
||||
updateCenterFreqDisplay();
|
||||
updateSampleRate();
|
||||
}
|
||||
|
||||
void MainWindow::handleMessages()
|
||||
{
|
||||
Message* message;
|
||||
while((message = m_messageQueue->accept()) != NULL) {
|
||||
qDebug("Message: %s", message->getIdentifier());
|
||||
if(DSPEngineReport::match(message)) {
|
||||
DSPEngineReport* rep = (DSPEngineReport*)message;
|
||||
m_sampleRate = rep->getSampleRate();
|
||||
m_centerFrequency = rep->getCenterFrequency();
|
||||
qDebug("SampleRate:%d, CenterFrequency:%llu", rep->getSampleRate(), rep->getCenterFrequency());
|
||||
updateCenterFreqDisplay();
|
||||
updateSampleRate();
|
||||
message->completed();
|
||||
} else {
|
||||
if(!m_pluginManager->handleMessage(message))
|
||||
message->completed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::updateStatus()
|
||||
{
|
||||
int state = m_dspEngine->state();
|
||||
if(m_lastEngineState != state) {
|
||||
switch(state) {
|
||||
case DSPEngine::StNotStarted:
|
||||
m_engineIdle->setColor(Qt::gray);
|
||||
m_engineRunning->setColor(Qt::gray);
|
||||
m_engineError->setColor(Qt::gray);
|
||||
statusBar()->clearMessage();
|
||||
break;
|
||||
|
||||
case DSPEngine::StIdle:
|
||||
m_engineIdle->setColor(Qt::cyan);
|
||||
m_engineRunning->setColor(Qt::gray);
|
||||
m_engineError->setColor(Qt::gray);
|
||||
statusBar()->clearMessage();
|
||||
if(m_startOsmoSDRUpdateAfterStop)
|
||||
on_actionOsmoSDR_Firmware_Upgrade_triggered();
|
||||
break;
|
||||
|
||||
case DSPEngine::StRunning:
|
||||
m_engineIdle->setColor(Qt::gray);
|
||||
m_engineRunning->setColor(Qt::green);
|
||||
m_engineError->setColor(Qt::gray);
|
||||
statusBar()->showMessage(tr("Sampling from %1").arg(m_dspEngine->deviceDescription()));
|
||||
break;
|
||||
|
||||
case DSPEngine::StError:
|
||||
m_engineIdle->setColor(Qt::gray);
|
||||
m_engineRunning->setColor(Qt::gray);
|
||||
m_engineError->setColor(Qt::red);
|
||||
statusBar()->showMessage(tr("Error: %1").arg(m_dspEngine->errorMessage()));
|
||||
if(m_startOsmoSDRUpdateAfterStop)
|
||||
on_actionOsmoSDR_Firmware_Upgrade_triggered();
|
||||
break;
|
||||
}
|
||||
m_lastEngineState = state;
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::scopeWindowDestroyed()
|
||||
{
|
||||
ui->action_Oscilloscope->setChecked(false);
|
||||
m_settings.getCurrent()->setShowScope(false);
|
||||
m_scopeWindow = NULL;
|
||||
}
|
||||
|
||||
void MainWindow::on_action_Start_triggered()
|
||||
{
|
||||
m_dspEngine->startAcquisition();
|
||||
}
|
||||
|
||||
void MainWindow::on_action_Stop_triggered()
|
||||
{
|
||||
m_dspEngine->stopAcquistion();
|
||||
}
|
||||
|
||||
void MainWindow::on_dcOffset_toggled(bool checked)
|
||||
{
|
||||
m_settings.getCurrent()->setDCOffsetCorrection(checked);
|
||||
m_dspEngine->configureCorrections(m_settings.getCurrent()->getDCOffsetCorrection(), m_settings.getCurrent()->getIQImbalanceCorrection());
|
||||
}
|
||||
|
||||
void MainWindow::on_iqImbalance_toggled(bool checked)
|
||||
{
|
||||
m_settings.getCurrent()->setIQImbalanceCorrection(checked);
|
||||
m_dspEngine->configureCorrections(m_settings.getCurrent()->getDCOffsetCorrection(), m_settings.getCurrent()->getIQImbalanceCorrection());
|
||||
}
|
||||
|
||||
void MainWindow::on_action_View_Fullscreen_toggled(bool checked)
|
||||
{
|
||||
if(checked)
|
||||
showFullScreen();
|
||||
else showNormal();
|
||||
}
|
||||
|
||||
void MainWindow::on_actionOsmoSDR_Firmware_Upgrade_triggered()
|
||||
{
|
||||
DSPEngine::State engineState = m_dspEngine->state();
|
||||
if((engineState != DSPEngine::StIdle) && (engineState != DSPEngine::StError)) {
|
||||
m_startOsmoSDRUpdateAfterStop = true;
|
||||
m_dspEngine->stopAcquistion();
|
||||
return;
|
||||
}
|
||||
m_startOsmoSDRUpdateAfterStop = false;
|
||||
/* TODO: osmosdr upgrade
|
||||
OsmoSDRUpgrade osmoSDRUpgrade;
|
||||
osmoSDRUpgrade.exec();
|
||||
*/
|
||||
}
|
||||
|
||||
void MainWindow::on_presetSave_clicked()
|
||||
{
|
||||
QStringList groups;
|
||||
QString group;
|
||||
for(int i = 0; i < ui->presetTree->topLevelItemCount(); i++)
|
||||
groups.append(ui->presetTree->topLevelItem(i)->text(0));
|
||||
|
||||
QTreeWidgetItem* item = ui->presetTree->currentItem();
|
||||
if(item != NULL) {
|
||||
if(item->type() == PGroup)
|
||||
group = item->text(0);
|
||||
else if(item->type() == PItem)
|
||||
group = item->parent()->text(0);
|
||||
}
|
||||
|
||||
AddPresetDialog dlg(groups, group, this);
|
||||
|
||||
if(dlg.exec() == QDialog::Accepted) {
|
||||
Preset* preset = m_settings.newPreset(dlg.group(), dlg.description());
|
||||
saveSettings(preset);
|
||||
|
||||
ui->presetTree->setCurrentItem(addPresetToTree(preset));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_presetLoad_clicked()
|
||||
{
|
||||
QTreeWidgetItem* item = ui->presetTree->currentItem();
|
||||
if(item == NULL) {
|
||||
updatePresets();
|
||||
return;
|
||||
}
|
||||
const Preset* preset = qvariant_cast<const Preset*>(item->data(0, Qt::UserRole));
|
||||
if(preset == NULL)
|
||||
return;
|
||||
|
||||
loadSettings(preset);
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void MainWindow::on_presetDelete_clicked()
|
||||
{
|
||||
QTreeWidgetItem* item = ui->presetTree->currentItem();
|
||||
if(item == NULL) {
|
||||
updatePresets();
|
||||
return;
|
||||
}
|
||||
const Preset* preset = qvariant_cast<const Preset*>(item->data(0, Qt::UserRole));
|
||||
if(preset == NULL)
|
||||
return;
|
||||
|
||||
if(QMessageBox::question(this, tr("Delete Preset"), tr("Do you want to delete preset '%1'?").arg(preset->getDescription()), QMessageBox::No | QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) {
|
||||
delete item;
|
||||
m_settings.deletePreset(preset);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_presetTree_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous)
|
||||
{
|
||||
updatePresets();
|
||||
}
|
||||
|
||||
void MainWindow::on_presetTree_itemActivated(QTreeWidgetItem *item, int column)
|
||||
{
|
||||
on_presetLoad_clicked();
|
||||
}
|
||||
|
||||
void MainWindow::on_action_Oscilloscope_triggered()
|
||||
{
|
||||
if(m_scopeWindow != NULL) {
|
||||
((QWidget*)m_scopeWindow->parent())->raise();
|
||||
return;
|
||||
}
|
||||
|
||||
QDockWidget* dock = new QDockWidget(tr("Signalscope"), this);
|
||||
dock->setObjectName(QString::fromUtf8("scopeDock"));
|
||||
m_scopeWindow = new ScopeWindow(m_dspEngine);
|
||||
connect(m_scopeWindow, SIGNAL(destroyed()), this, SLOT(scopeWindowDestroyed()));
|
||||
m_scopeWindow->setSampleRate(m_sampleRate);
|
||||
dock->setWidget(m_scopeWindow);
|
||||
dock->setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||
addDockWidget(Qt::BottomDockWidgetArea, dock);
|
||||
dock->setAttribute(Qt::WA_DeleteOnClose);
|
||||
m_settings.getCurrent()->setShowScope(true);
|
||||
}
|
||||
|
||||
void MainWindow::on_action_Loaded_Plugins_triggered()
|
||||
{
|
||||
PluginsDialog pluginsDialog(m_pluginManager, this);
|
||||
|
||||
pluginsDialog.exec();
|
||||
}
|
||||
|
||||
void MainWindow::on_action_Preferences_triggered()
|
||||
{
|
||||
PreferencesDialog preferencesDialog(m_audioDeviceInfo, this);
|
||||
|
||||
preferencesDialog.exec();
|
||||
}
|
||||
|
||||
void MainWindow::on_sampleSource_currentIndexChanged(int index)
|
||||
{
|
||||
m_pluginManager->selectSampleSource(ui->sampleSource->currentIndex());
|
||||
}
|
||||
|
||||
void MainWindow::on_action_About_triggered()
|
||||
{
|
||||
AboutDialog dlg(this);
|
||||
dlg.exec();
|
||||
}
|
||||
@@ -0,0 +1,483 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1012</width>
|
||||
<height>706</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>SDRangelove</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="resources/res.qrc">
|
||||
<normaloff>:/appicon.png</normaloff>:/appicon.png</iconset>
|
||||
</property>
|
||||
<property name="animated">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="GLSpectrum" name="glSpectrum" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menuBar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1012</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menu_File">
|
||||
<property name="title">
|
||||
<string>&File</string>
|
||||
</property>
|
||||
<addaction name="action_Exit"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_Options">
|
||||
<property name="title">
|
||||
<string>&Options</string>
|
||||
</property>
|
||||
<addaction name="actionOsmoSDR_Firmware_Upgrade"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_Preferences"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_Acquisition">
|
||||
<property name="title">
|
||||
<string>&Acquisition</string>
|
||||
</property>
|
||||
<addaction name="action_Start"/>
|
||||
<addaction name="action_Stop"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_View">
|
||||
<property name="title">
|
||||
<string>&View</string>
|
||||
</property>
|
||||
<addaction name="action_View_Fullscreen"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_Oscilloscope"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_Help">
|
||||
<property name="title">
|
||||
<string>&Help</string>
|
||||
</property>
|
||||
<addaction name="action_Open_Website"/>
|
||||
<addaction name="action_Check_for_Update"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_Loaded_Plugins"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_About"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_Channels">
|
||||
<property name="title">
|
||||
<string>&Channels</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_Window">
|
||||
<property name="title">
|
||||
<string>&Window</string>
|
||||
</property>
|
||||
</widget>
|
||||
<addaction name="menu_File"/>
|
||||
<addaction name="menu_View"/>
|
||||
<addaction name="menu_Acquisition"/>
|
||||
<addaction name="menu_Channels"/>
|
||||
<addaction name="menu_Options"/>
|
||||
<addaction name="menu_Window"/>
|
||||
<addaction name="menu_Help"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="mainToolBar">
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusBar"/>
|
||||
<widget class="QDockWidget" name="presetDock">
|
||||
<property name="windowTitle">
|
||||
<string>Presets</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>1</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents">
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<property name="margin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item row="5" column="1">
|
||||
<widget class="QToolButton" name="presetSave">
|
||||
<property name="toolTip">
|
||||
<string>Save current settings as preset</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="resources/res.qrc">
|
||||
<normaloff>:/preset-save.png</normaloff>:/preset-save.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="5">
|
||||
<widget class="QToolButton" name="presetLoad">
|
||||
<property name="toolTip">
|
||||
<string>Load selected preset</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="resources/res.qrc">
|
||||
<normaloff>:/preset-load.png</normaloff>:/preset-load.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="3" colspan="2">
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<widget class="QToolButton" name="presetDelete">
|
||||
<property name="toolTip">
|
||||
<string>Delete selected preset</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="resources/res.qrc">
|
||||
<normaloff>:/preset-delete.png</normaloff>:/preset-delete.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" rowspan="4" colspan="5">
|
||||
<widget class="QTreeWidget" name="presetTree">
|
||||
<property name="allColumnsShowFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Frequency</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Description</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="processingDock">
|
||||
<property name="windowTitle">
|
||||
<string>Processing and Display</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>1</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents_4">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="dcOffset">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Compensate DC offset</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>DC Offset Corr</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="iqImbalance">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Compensate I/Q imbalance</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>I/Q Imbal. Corr</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="GLSpectrumGUI" name="glSpectrumGUI" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="inputDock">
|
||||
<property name="windowTitle">
|
||||
<string>Sample Source</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>1</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents_6">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QComboBox" name="sampleSource">
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToMinimumContentsLength</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="channelDock">
|
||||
<property name="windowTitle">
|
||||
<string>Channels</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>2</number>
|
||||
</attribute>
|
||||
<widget class="ChannelWindow" name="channelWindow">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<action name="action_Exit">
|
||||
<property name="text">
|
||||
<string>E&xit</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Q</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Start">
|
||||
<property name="text">
|
||||
<string>&Start</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>F5</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Stop">
|
||||
<property name="text">
|
||||
<string>&Stop</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>F6</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_View_Fullscreen">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Fullscreen</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>F11</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionOsmoSDR_Firmware_Upgrade">
|
||||
<property name="text">
|
||||
<string>OsmoSDR &Firmware Upgrade...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Oscilloscope">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Oscilloscope</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>F8</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_About">
|
||||
<property name="text">
|
||||
<string>&About SDRangelove...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Open_Website">
|
||||
<property name="text">
|
||||
<string>&Open Website</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Check_for_Update">
|
||||
<property name="text">
|
||||
<string>Check for &Update...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Preferences">
|
||||
<property name="text">
|
||||
<string>&Preferences...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Demod_NFM">
|
||||
<property name="text">
|
||||
<string>N&FM</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Tetra">
|
||||
<property name="text">
|
||||
<string>&Tetra</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Loaded_Plugins">
|
||||
<property name="text">
|
||||
<string>Loaded &Plugins...</string>
|
||||
</property>
|
||||
</action>
|
||||
<zorder>presetDock</zorder>
|
||||
<zorder>channelDock</zorder>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ButtonSwitch</class>
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/buttonswitch.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>GLSpectrum</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/glspectrum.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>GLSpectrumGUI</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/glspectrumgui.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ChannelWindow</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/channelwindow.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>presetTree</tabstop>
|
||||
<tabstop>presetSave</tabstop>
|
||||
<tabstop>presetDelete</tabstop>
|
||||
<tabstop>presetLoad</tabstop>
|
||||
<tabstop>dcOffset</tabstop>
|
||||
<tabstop>iqImbalance</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="resources/res.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>action_Exit</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>close()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>462</x>
|
||||
<y>332</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -0,0 +1,99 @@
|
||||
#include <QDockWidget>
|
||||
#include "plugin/pluginapi.h"
|
||||
#include "plugin/pluginmanager.h"
|
||||
#include "mainwindow.h"
|
||||
#include "dsp/dspengine.h"
|
||||
|
||||
QDockWidget* PluginAPI::createMainWindowDock(Qt::DockWidgetArea dockWidgetArea, const QString& title)
|
||||
{
|
||||
QDockWidget* dock = new QDockWidget(title, m_mainWindow);
|
||||
dock->setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||
dock->setAttribute(Qt::WA_DeleteOnClose);
|
||||
m_mainWindow->addDockWidget(dockWidgetArea, dock);
|
||||
m_mainWindow->addViewAction(dock->toggleViewAction());
|
||||
return dock;
|
||||
}
|
||||
|
||||
MessageQueue* PluginAPI::getMainWindowMessageQueue()
|
||||
{
|
||||
return m_mainWindow->getMessageQueue();
|
||||
}
|
||||
|
||||
void PluginAPI::setInputGUI(QWidget* inputGUI)
|
||||
{
|
||||
m_mainWindow->setInputGUI(inputGUI);
|
||||
}
|
||||
|
||||
void PluginAPI::registerChannel(const QString& channelName, PluginInterface* plugin, QAction* action)
|
||||
{
|
||||
m_pluginManager->registerChannel(channelName, plugin, action);
|
||||
}
|
||||
|
||||
void PluginAPI::registerChannelInstance(const QString& channelName, PluginGUI* pluginGUI)
|
||||
{
|
||||
m_pluginManager->registerChannelInstance(channelName, pluginGUI);
|
||||
}
|
||||
|
||||
void PluginAPI::addChannelRollup(QWidget* pluginGUI)
|
||||
{
|
||||
m_pluginManager->addChannelRollup(pluginGUI);
|
||||
}
|
||||
|
||||
void PluginAPI::removeChannelInstance(PluginGUI* pluginGUI)
|
||||
{
|
||||
m_pluginManager->removeChannelInstance(pluginGUI);
|
||||
}
|
||||
|
||||
void PluginAPI::addChannelMarker(ChannelMarker* channelMarker)
|
||||
{
|
||||
m_mainWindow->addChannelMarker(channelMarker);
|
||||
}
|
||||
|
||||
void PluginAPI::removeChannelMarker(ChannelMarker* channelMarker)
|
||||
{
|
||||
m_mainWindow->removeChannelMarker(channelMarker);
|
||||
}
|
||||
|
||||
void PluginAPI::setSampleSource(SampleSource* sampleSource)
|
||||
{
|
||||
m_dspEngine->stopAcquistion();
|
||||
m_dspEngine->setSource(sampleSource);
|
||||
}
|
||||
|
||||
void PluginAPI::addSampleSink(SampleSink* sampleSink)
|
||||
{
|
||||
m_dspEngine->addSink(sampleSink);
|
||||
}
|
||||
|
||||
void PluginAPI::removeSampleSink(SampleSink* sampleSink)
|
||||
{
|
||||
m_dspEngine->removeSink(sampleSink);
|
||||
}
|
||||
|
||||
MessageQueue* PluginAPI::getDSPEngineMessageQueue()
|
||||
{
|
||||
return m_dspEngine->getMessageQueue();
|
||||
}
|
||||
|
||||
void PluginAPI::addAudioSource(AudioFifo* audioFifo)
|
||||
{
|
||||
m_dspEngine->addAudioSource(audioFifo);
|
||||
}
|
||||
|
||||
void PluginAPI::removeAudioSource(AudioFifo* audioFifo)
|
||||
{
|
||||
m_dspEngine->removeAudioSource(audioFifo);
|
||||
}
|
||||
|
||||
void PluginAPI::registerSampleSource(const QString& sourceName, PluginInterface* plugin)
|
||||
{
|
||||
m_pluginManager->registerSampleSource(sourceName, plugin);
|
||||
}
|
||||
|
||||
PluginAPI::PluginAPI(PluginManager* pluginManager, MainWindow* mainWindow, DSPEngine* dspEngine) :
|
||||
QObject(mainWindow),
|
||||
m_pluginManager(pluginManager),
|
||||
m_mainWindow(mainWindow),
|
||||
m_dspEngine(dspEngine)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "plugin/plugingui.h"
|
||||
|
||||
QByteArray PluginGUI::serializeGeneral() const
|
||||
{
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
bool PluginGUI::deserializeGeneral(const QByteArray& data)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
quint64 PluginGUI::getCenterFrequency() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
#include "plugin/plugininterface.h"
|
||||
@@ -0,0 +1,280 @@
|
||||
#include <QApplication>
|
||||
#include <QPluginLoader>
|
||||
#include <QComboBox>
|
||||
#include "plugin/pluginmanager.h"
|
||||
#include "plugin/plugingui.h"
|
||||
#include "settings/preset.h"
|
||||
#include "mainwindow.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/samplesource/samplesource.h"
|
||||
|
||||
PluginManager::PluginManager(MainWindow* mainWindow, DSPEngine* dspEngine, QObject* parent) :
|
||||
QObject(parent),
|
||||
m_pluginAPI(this, mainWindow, dspEngine),
|
||||
m_mainWindow(mainWindow),
|
||||
m_dspEngine(dspEngine),
|
||||
m_sampleSource(),
|
||||
m_sampleSourceInstance(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
PluginManager::~PluginManager()
|
||||
{
|
||||
freeAll();
|
||||
}
|
||||
|
||||
void PluginManager::loadPlugins()
|
||||
{
|
||||
QDir pluginsDir = QDir(QApplication::instance()->applicationDirPath());
|
||||
|
||||
loadPlugins(pluginsDir);
|
||||
|
||||
qSort(m_plugins);
|
||||
|
||||
for(Plugins::const_iterator it = m_plugins.begin(); it != m_plugins.end(); ++it)
|
||||
it->plugin->initPlugin(&m_pluginAPI);
|
||||
|
||||
updateSampleSourceDevices();
|
||||
}
|
||||
|
||||
void PluginManager::registerChannel(const QString& channelName, PluginInterface* plugin, QAction* action)
|
||||
{
|
||||
m_channelRegistrations.append(ChannelRegistration(channelName, plugin));
|
||||
m_mainWindow->addChannelCreateAction(action);
|
||||
}
|
||||
|
||||
void PluginManager::registerChannelInstance(const QString& channelName, PluginGUI* pluginGUI)
|
||||
{
|
||||
m_channelInstanceRegistrations.append(ChannelInstanceRegistration(channelName, pluginGUI));
|
||||
renameChannelInstances();
|
||||
}
|
||||
|
||||
void PluginManager::addChannelRollup(QWidget* pluginGUI)
|
||||
{
|
||||
m_mainWindow->addChannelRollup(pluginGUI);
|
||||
}
|
||||
|
||||
void PluginManager::removeChannelInstance(PluginGUI* pluginGUI)
|
||||
{
|
||||
for(ChannelInstanceRegistrations::iterator it = m_channelInstanceRegistrations.begin(); it != m_channelInstanceRegistrations.end(); ++it) {
|
||||
if(it->m_gui == pluginGUI) {
|
||||
m_channelInstanceRegistrations.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
renameChannelInstances();
|
||||
}
|
||||
|
||||
void PluginManager::registerSampleSource(const QString& sourceName, PluginInterface* plugin)
|
||||
{
|
||||
m_sampleSourceRegistrations.append(SampleSourceRegistration(sourceName, plugin));
|
||||
}
|
||||
|
||||
void PluginManager::loadSettings(const Preset* preset)
|
||||
{
|
||||
qDebug("-------- [%s | %s] --------", qPrintable(preset->getGroup()), qPrintable(preset->getDescription()));
|
||||
|
||||
// copy currently open channels and clear list
|
||||
ChannelInstanceRegistrations openChannels = m_channelInstanceRegistrations;
|
||||
m_channelInstanceRegistrations.clear();
|
||||
|
||||
for(int i = 0; i < preset->getChannelCount(); i++) {
|
||||
const Preset::ChannelConfig& channelConfig = preset->getChannelConfig(i);
|
||||
ChannelInstanceRegistration reg;
|
||||
// if we have one instance available already, use it
|
||||
for(int i = 0; i < openChannels.count(); i++) {
|
||||
qDebug("compare [%s] vs [%s]", qPrintable(openChannels[i].m_channelName), qPrintable(channelConfig.m_channel));
|
||||
if(openChannels[i].m_channelName == channelConfig.m_channel) {
|
||||
qDebug("channel [%s] found", qPrintable(openChannels[i].m_channelName));
|
||||
reg = openChannels.takeAt(i);
|
||||
m_channelInstanceRegistrations.append(reg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if we haven't one already, create one
|
||||
if(reg.m_gui == NULL) {
|
||||
for(int i = 0; i < m_channelRegistrations.count(); i++) {
|
||||
if(m_channelRegistrations[i].m_channelName == channelConfig.m_channel) {
|
||||
qDebug("creating new channel [%s]", qPrintable(channelConfig.m_channel));
|
||||
reg = ChannelInstanceRegistration(channelConfig.m_channel, m_channelRegistrations[i].m_plugin->createChannel(channelConfig.m_channel));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(reg.m_gui != NULL)
|
||||
reg.m_gui->deserialize(channelConfig.m_config);
|
||||
}
|
||||
|
||||
// everything, that is still "available" is not needed anymore
|
||||
for(int i = 0; i < openChannels.count(); i++) {
|
||||
qDebug("destroying spare channel [%s]", qPrintable(openChannels[i].m_channelName));
|
||||
openChannels[i].m_gui->destroy();
|
||||
}
|
||||
|
||||
renameChannelInstances();
|
||||
|
||||
if(m_sampleSourceInstance != NULL) {
|
||||
m_sampleSourceInstance->deserializeGeneral(preset->getSourceGeneralConfig());
|
||||
if(m_sampleSource == preset->getSource()) {
|
||||
m_sampleSourceInstance->deserialize(preset->getSourceConfig());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PluginManager::saveSettings(Preset* preset) const
|
||||
{
|
||||
if(m_sampleSourceInstance != NULL) {
|
||||
preset->setSourceConfig(m_sampleSource, m_sampleSourceInstance->serializeGeneral(), m_sampleSourceInstance->serialize());
|
||||
preset->setCenterFrequency(m_sampleSourceInstance->getCenterFrequency());
|
||||
} else {
|
||||
preset->setSourceConfig(QString::null, QByteArray(), QByteArray());
|
||||
}
|
||||
for(int i = 0; i < m_channelInstanceRegistrations.count(); i++)
|
||||
preset->addChannel(m_channelInstanceRegistrations[i].m_channelName, m_channelInstanceRegistrations[i].m_gui->serialize());
|
||||
}
|
||||
|
||||
void PluginManager::freeAll()
|
||||
{
|
||||
m_dspEngine->stopAcquistion();
|
||||
|
||||
while(!m_channelInstanceRegistrations.isEmpty()) {
|
||||
ChannelInstanceRegistration reg(m_channelInstanceRegistrations.takeLast());
|
||||
reg.m_gui->destroy();
|
||||
}
|
||||
|
||||
if(m_sampleSourceInstance != NULL) {
|
||||
m_dspEngine->setSource(NULL);
|
||||
m_sampleSourceInstance->destroy();
|
||||
m_sampleSourceInstance = NULL;
|
||||
m_sampleSource.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool PluginManager::handleMessage(Message* message)
|
||||
{
|
||||
if(m_sampleSourceInstance != NULL) {
|
||||
if((message->getDestination() == NULL) || (message->getDestination() == m_sampleSourceInstance)) {
|
||||
if(m_sampleSourceInstance->handleMessage(message))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for(ChannelInstanceRegistrations::iterator it = m_channelInstanceRegistrations.begin(); it != m_channelInstanceRegistrations.end(); ++it) {
|
||||
if((message->getDestination() == NULL) || (message->getDestination() == it->m_gui)) {
|
||||
if(it->m_gui->handleMessage(message))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PluginManager::updateSampleSourceDevices()
|
||||
{
|
||||
m_sampleSourceDevices.clear();
|
||||
for(int i = 0; i < m_sampleSourceRegistrations.count(); ++i) {
|
||||
PluginInterface::SampleSourceDevices ssd = m_sampleSourceRegistrations[i].m_plugin->enumSampleSources();
|
||||
for(int j = 0; j < ssd.count(); ++j)
|
||||
m_sampleSourceDevices.append(SampleSourceDevice(m_sampleSourceRegistrations[i].m_plugin, ssd[j].displayedName, ssd[j].name, ssd[j].address));
|
||||
}
|
||||
}
|
||||
|
||||
void PluginManager::fillSampleSourceSelector(QComboBox* comboBox)
|
||||
{
|
||||
comboBox->clear();
|
||||
for(int i = 0; i < m_sampleSourceDevices.count(); i++)
|
||||
comboBox->addItem(m_sampleSourceDevices[i].m_displayName, i);
|
||||
}
|
||||
|
||||
int PluginManager::selectSampleSource(int index)
|
||||
{
|
||||
m_dspEngine->stopAcquistion();
|
||||
|
||||
if(m_sampleSourceInstance != NULL) {
|
||||
m_dspEngine->stopAcquistion();
|
||||
m_dspEngine->setSource(NULL);
|
||||
m_sampleSourceInstance->destroy();
|
||||
m_sampleSourceInstance = NULL;
|
||||
m_sampleSource.clear();
|
||||
}
|
||||
|
||||
if(index == -1) {
|
||||
if(!m_sampleSource.isEmpty()) {
|
||||
for(int i = 0; i < m_sampleSourceDevices.count(); i++) {
|
||||
if(m_sampleSourceDevices[i].m_sourceName == m_sampleSource) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(index == -1) {
|
||||
if(m_sampleSourceDevices.count() > 0)
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
if(index == -1)
|
||||
return -1;
|
||||
|
||||
m_sampleSource = m_sampleSourceDevices[index].m_sourceName;
|
||||
m_sampleSourceInstance = m_sampleSourceDevices[index].m_plugin->createSampleSource(m_sampleSource, m_sampleSourceDevices[index].m_address);
|
||||
return index;
|
||||
}
|
||||
|
||||
int PluginManager::selectSampleSource(const QString& source)
|
||||
{
|
||||
int index = -1;
|
||||
|
||||
m_dspEngine->stopAcquistion();
|
||||
|
||||
if(m_sampleSourceInstance != NULL) {
|
||||
m_dspEngine->stopAcquistion();
|
||||
m_dspEngine->setSource(NULL);
|
||||
m_sampleSourceInstance->destroy();
|
||||
m_sampleSourceInstance = NULL;
|
||||
m_sampleSource.clear();
|
||||
}
|
||||
|
||||
qDebug("finding sample source [%s]", qPrintable(source));
|
||||
for(int i = 0; i < m_sampleSourceDevices.count(); i++) {
|
||||
qDebug("*** %s vs %s", qPrintable(m_sampleSourceDevices[i].m_sourceName), qPrintable(source));
|
||||
if(m_sampleSourceDevices[i].m_sourceName == source) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(index == -1) {
|
||||
if(m_sampleSourceDevices.count() > 0)
|
||||
index = 0;
|
||||
}
|
||||
if(index == -1)
|
||||
return -1;
|
||||
|
||||
m_sampleSource = m_sampleSourceDevices[index].m_sourceName;
|
||||
m_sampleSourceInstance = m_sampleSourceDevices[index].m_plugin->createSampleSource(m_sampleSource, m_sampleSourceDevices[index].m_address);
|
||||
return index;
|
||||
}
|
||||
|
||||
void PluginManager::loadPlugins(const QDir& dir)
|
||||
{
|
||||
QDir pluginsDir(dir);
|
||||
foreach(QString fileName, pluginsDir.entryList(QDir::Files)) {
|
||||
QPluginLoader* loader = new QPluginLoader(pluginsDir.absoluteFilePath(fileName));
|
||||
PluginInterface* plugin = qobject_cast<PluginInterface*>(loader->instance());
|
||||
if(loader->isLoaded())
|
||||
qDebug("loaded plugin %s", qPrintable(fileName));
|
||||
if(plugin != NULL) {
|
||||
m_plugins.append(Plugin(fileName, loader, plugin));
|
||||
} else {
|
||||
loader->unload();
|
||||
delete loader;
|
||||
}
|
||||
}
|
||||
foreach(QString dirName, pluginsDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
|
||||
loadPlugins(pluginsDir.absoluteFilePath(dirName));
|
||||
}
|
||||
|
||||
void PluginManager::renameChannelInstances()
|
||||
{
|
||||
for(int i = 0; i < m_channelInstanceRegistrations.count(); i++) {
|
||||
m_channelInstanceRegistrations[i].m_gui->setName(QString("%1:%2").arg(m_channelInstanceRegistrations[i].m_channelName).arg(i));
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 326 B |
|
After Width: | Height: | Size: 259 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 187 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 594 B |
|
After Width: | Height: | Size: 731 B |
|
After Width: | Height: | Size: 925 B |
|
After Width: | Height: | Size: 996 B |
@@ -0,0 +1,16 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>appicon.png</file>
|
||||
<file>histogram.png</file>
|
||||
<file>waterfall.png</file>
|
||||
<file>preset-load.png</file>
|
||||
<file>preset-save.png</file>
|
||||
<file>preset-delete.png</file>
|
||||
<file>horizontal.png</file>
|
||||
<file>vertical.png</file>
|
||||
<file>logo.png</file>
|
||||
<file>maxhold.png</file>
|
||||
<file>grid.png</file>
|
||||
<file>invertspectrum.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -0,0 +1 @@
|
||||
IDI_ICON1 ICON DISCARDABLE "appicon.ico"
|
||||
|
After Width: | Height: | Size: 195 B |
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,46 @@
|
||||
#include "settings/preferences.h"
|
||||
#include "util/simpleserializer.h"
|
||||
|
||||
Preferences::Preferences()
|
||||
{
|
||||
resetToDefaults();
|
||||
}
|
||||
|
||||
void Preferences::resetToDefaults()
|
||||
{
|
||||
m_sourceType.clear();
|
||||
m_sourceDevice.clear();
|
||||
m_audioType.clear();
|
||||
m_audioDevice.clear();
|
||||
}
|
||||
|
||||
QByteArray Preferences::serialize() const
|
||||
{
|
||||
SimpleSerializer s(1);
|
||||
s.writeString(1, m_sourceType);
|
||||
s.writeString(2, m_sourceDevice);
|
||||
s.writeString(3, m_audioType);
|
||||
s.writeString(4, m_audioDevice);
|
||||
return s.final();
|
||||
}
|
||||
|
||||
bool Preferences::deserialize(const QByteArray& data)
|
||||
{
|
||||
SimpleDeserializer d(data);
|
||||
|
||||
if(!d.isValid()) {
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(d.getVersion() == 1) {
|
||||
d.readString(1, &m_sourceType);
|
||||
d.readString(2, &m_sourceDevice);
|
||||
d.readString(3, &m_audioType);
|
||||
d.readString(4, &m_audioDevice);
|
||||
return true;
|
||||
} else {
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#include "util/simpleserializer.h"
|
||||
#include "settings/preset.h"
|
||||
|
||||
Preset::Preset()
|
||||
{
|
||||
resetToDefaults();
|
||||
}
|
||||
|
||||
void Preset::resetToDefaults()
|
||||
{
|
||||
m_group = "default";
|
||||
m_description = "no name";
|
||||
m_centerFrequency = 0;
|
||||
m_spectrumConfig.clear();
|
||||
m_scopeConfig.clear();
|
||||
m_dcOffsetCorrection = true;
|
||||
m_iqImbalanceCorrection = true;
|
||||
m_showScope = true;
|
||||
m_layout.clear();
|
||||
m_spectrumConfig.clear();
|
||||
m_channelConfigs.clear();
|
||||
m_source.clear();
|
||||
m_sourceConfig.clear();
|
||||
}
|
||||
|
||||
QByteArray Preset::serialize() const
|
||||
{
|
||||
SimpleSerializer s(1);
|
||||
s.writeString(1, m_group);
|
||||
s.writeString(2, m_description);
|
||||
s.writeU64(3, m_centerFrequency);
|
||||
s.writeBool(4, m_showScope);
|
||||
s.writeBlob(5, m_layout);
|
||||
s.writeBlob(6, m_spectrumConfig);
|
||||
s.writeBool(7, m_dcOffsetCorrection);
|
||||
s.writeBool(8, m_iqImbalanceCorrection);
|
||||
s.writeBlob(9, m_scopeConfig);
|
||||
s.writeString(10, m_source);
|
||||
s.writeBlob(11, m_sourceGeneralConfig);
|
||||
s.writeBlob(12, m_sourceConfig);
|
||||
|
||||
s.writeS32(100, m_channelConfigs.size());
|
||||
for(int i = 0; i < m_channelConfigs.size(); i++) {
|
||||
s.writeString(101 + i * 2, m_channelConfigs[i].m_channel);
|
||||
s.writeBlob(102 + i * 2, m_channelConfigs[i].m_config);
|
||||
}
|
||||
|
||||
return s.final();
|
||||
}
|
||||
|
||||
bool Preset::deserialize(const QByteArray& data)
|
||||
{
|
||||
SimpleDeserializer d(data);
|
||||
|
||||
if(!d.isValid()) {
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(d.getVersion() == 1) {
|
||||
d.readString(1, &m_group, "default");
|
||||
d.readString(2, &m_description, "no name");
|
||||
d.readU64(3, &m_centerFrequency, 0);
|
||||
d.readBool(4, &m_showScope, true);
|
||||
d.readBlob(5, &m_layout);
|
||||
d.readBlob(6, &m_spectrumConfig);
|
||||
d.readBool(7, &m_dcOffsetCorrection, true);
|
||||
d.readBool(8, &m_iqImbalanceCorrection, true);
|
||||
d.readBlob(9, &m_scopeConfig);
|
||||
d.readString(10, &m_source);
|
||||
d.readBlob(11, &m_sourceGeneralConfig);
|
||||
d.readBlob(12, &m_sourceConfig);
|
||||
|
||||
qint32 channelCount = 0;
|
||||
d.readS32(100, &channelCount, 0);
|
||||
for(int i = 0; i < channelCount; i++) {
|
||||
QString channel;
|
||||
QByteArray config;
|
||||
d.readString(101 + i * 2, &channel, "unknown-channel");
|
||||
d.readBlob(102 + i * 2, &config);
|
||||
m_channelConfigs.append(ChannelConfig(channel, config));
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
#include <QSettings>
|
||||
#include <QStringList>
|
||||
#include "settings/settings.h"
|
||||
|
||||
Settings::Settings()
|
||||
{
|
||||
resetToDefaults();
|
||||
}
|
||||
|
||||
Settings::~Settings()
|
||||
{
|
||||
for(int i = 0; i < m_presets.count(); ++i)
|
||||
delete m_presets[i];
|
||||
}
|
||||
|
||||
void Settings::load()
|
||||
{
|
||||
QSettings s;
|
||||
|
||||
m_preferences.deserialize(qUncompress(QByteArray::fromBase64(s.value("preferences").toByteArray())));
|
||||
m_current.deserialize(qUncompress(QByteArray::fromBase64(s.value("current").toByteArray())));
|
||||
|
||||
QStringList groups = s.childGroups();
|
||||
for(int i = 0; i < groups.size(); ++i) {
|
||||
if(groups[i].startsWith("preset")) {
|
||||
s.beginGroup(groups[i]);
|
||||
Preset* preset = new Preset;
|
||||
if(preset->deserialize(qUncompress(QByteArray::fromBase64(s.value("data").toByteArray()))))
|
||||
m_presets.append(preset);
|
||||
else delete preset;
|
||||
s.endGroup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::save() const
|
||||
{
|
||||
QSettings s;
|
||||
|
||||
s.setValue("preferences", qCompress(m_preferences.serialize()).toBase64());
|
||||
s.setValue("current", qCompress(m_current.serialize()).toBase64());
|
||||
|
||||
QStringList groups = s.childGroups();
|
||||
for(int i = 0; i < groups.size(); ++i) {
|
||||
if(groups[i].startsWith("preset"))
|
||||
s.remove(groups[i]);
|
||||
}
|
||||
|
||||
for(int i = 0; i < m_presets.count(); ++i) {
|
||||
QString group = QString("preset-%1").arg(i + 1);
|
||||
s.beginGroup(group);
|
||||
s.setValue("data", qCompress(m_presets[i]->serialize()).toBase64());
|
||||
s.endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::resetToDefaults()
|
||||
{
|
||||
m_preferences.resetToDefaults();
|
||||
m_current.resetToDefaults();
|
||||
}
|
||||
|
||||
Preset* Settings::newPreset(const QString& group, const QString& description)
|
||||
{
|
||||
Preset* preset = new Preset();
|
||||
preset->setGroup(group);
|
||||
preset->setDescription(description);
|
||||
m_presets.append(preset);
|
||||
return preset;
|
||||
}
|
||||
|
||||
void Settings::deletePreset(const Preset* preset)
|
||||
{
|
||||
m_presets.removeAll((Preset*)preset);
|
||||
delete (Preset*)preset;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#include <QWaitCondition>
|
||||
#include <QMutex>
|
||||
#include "util/message.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
const char* Message::m_identifier = "Message";
|
||||
|
||||
Message::Message() :
|
||||
m_destination(NULL),
|
||||
m_synchronous(false),
|
||||
m_waitCondition(NULL),
|
||||
m_mutex(NULL),
|
||||
m_complete(0)
|
||||
{
|
||||
}
|
||||
|
||||
Message::~Message()
|
||||
{
|
||||
if(m_waitCondition != NULL)
|
||||
delete m_waitCondition;
|
||||
if(m_mutex != NULL)
|
||||
delete m_mutex;
|
||||
}
|
||||
|
||||
const char* Message::getIdentifier() const
|
||||
{
|
||||
return m_identifier;
|
||||
}
|
||||
|
||||
bool Message::matchIdentifier(const char* identifier) const
|
||||
{
|
||||
return m_identifier == identifier;
|
||||
}
|
||||
|
||||
bool Message::match(Message* message)
|
||||
{
|
||||
return message->matchIdentifier(m_identifier);
|
||||
}
|
||||
|
||||
void Message::submit(MessageQueue* queue, void* destination)
|
||||
{
|
||||
m_destination = destination;
|
||||
m_synchronous = false;
|
||||
queue->submit(this);
|
||||
}
|
||||
|
||||
int Message::execute(MessageQueue* queue, void* destination)
|
||||
{
|
||||
m_destination = destination;
|
||||
m_synchronous = true;
|
||||
|
||||
if(m_waitCondition == NULL)
|
||||
m_waitCondition = new QWaitCondition;
|
||||
if(m_mutex == NULL)
|
||||
m_mutex = new QMutex;
|
||||
|
||||
m_mutex->lock();
|
||||
m_complete.testAndSetAcquire(0, 1);
|
||||
queue->submit(this);
|
||||
while(!m_complete.testAndSetAcquire(0, 1))
|
||||
((QWaitCondition*)m_waitCondition)->wait(m_mutex, 100);
|
||||
m_complete = 0;
|
||||
int result = m_result;
|
||||
m_mutex->unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
void Message::completed(int result)
|
||||
{
|
||||
if(m_synchronous) {
|
||||
m_result = result;
|
||||
m_complete = 0;
|
||||
if(m_waitCondition == NULL)
|
||||
qFatal("wait condition is NULL");
|
||||
m_waitCondition->wakeAll();
|
||||
} else {
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "util/messagequeue.h"
|
||||
#include "util/message.h"
|
||||
|
||||
MessageQueue::MessageQueue(QObject* parent) :
|
||||
QObject(parent),
|
||||
m_lock(),
|
||||
m_queue()
|
||||
{
|
||||
}
|
||||
|
||||
MessageQueue::~MessageQueue()
|
||||
{
|
||||
Message* cmd;
|
||||
while((cmd = accept()) != NULL)
|
||||
cmd->completed();
|
||||
}
|
||||
|
||||
void MessageQueue::submit(Message* message)
|
||||
{
|
||||
m_lock.lock();
|
||||
m_queue.append(message);
|
||||
m_lock.unlock();
|
||||
emit messageEnqueued();
|
||||
}
|
||||
|
||||
Message* MessageQueue::accept()
|
||||
{
|
||||
SpinlockHolder spinlockHolder(&m_lock);
|
||||
|
||||
if(m_queue.isEmpty())
|
||||
return NULL;
|
||||
else return m_queue.takeFirst();
|
||||
}
|
||||
|
||||
int MessageQueue::countPending()
|
||||
{
|
||||
SpinlockHolder spinlockHolder(&m_lock);
|
||||
|
||||
return m_queue.size();
|
||||
}
|
||||
@@ -0,0 +1,704 @@
|
||||
#include <stdint.h>
|
||||
#include "util/simpleserializer.h"
|
||||
|
||||
#if __WORDSIZE == 64
|
||||
#define PRINTF_FORMAT_S32 "%d"
|
||||
#define PRINTF_FORMAT_U32 "%u"
|
||||
#define PRINTF_FORMAT_S64 "%d"
|
||||
#define PRINTF_FORMAT_U64 "%u"
|
||||
#else
|
||||
#define PRINTF_FORMAT_S32 "%d"
|
||||
#define PRINTF_FORMAT_U32 "%u"
|
||||
#define PRINTF_FORMAT_S64 "%lld"
|
||||
#define PRINTF_FORMAT_U64 "%llu"
|
||||
#endif
|
||||
|
||||
template <bool> struct Assert { };
|
||||
typedef Assert<(bool(sizeof(float) == 4))> float_must_be_32_bits[bool(sizeof(float) == 4) ? 1 : -1];
|
||||
typedef Assert<(bool(sizeof(double) == 8))> double_must_be_64_bits[bool(sizeof(double) == 8) ? 1 : -1];
|
||||
|
||||
SimpleSerializer::SimpleSerializer(uint version) :
|
||||
m_data(),
|
||||
m_finalized(false)
|
||||
{
|
||||
m_data.reserve(100);
|
||||
|
||||
// write version information
|
||||
int length;
|
||||
if(version >= (1 << 24))
|
||||
length = 4;
|
||||
else if(version >= (1 << 16))
|
||||
length = 3;
|
||||
else if(version >= (1 << 8))
|
||||
length = 2;
|
||||
else if(version > 0)
|
||||
length = 1;
|
||||
else length = 0;
|
||||
if(!writeTag(TVersion, 0, length))
|
||||
return;
|
||||
length--;
|
||||
for(int i = length; i >= 0; i--)
|
||||
m_data.push_back((char)((version >> (i * 8)) & 0xff));
|
||||
}
|
||||
|
||||
void SimpleSerializer::writeS32(quint32 id, qint32 value)
|
||||
{
|
||||
int length;
|
||||
|
||||
if(id == 0) {
|
||||
qCritical("SimpleSerializer: ID 0 is not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
if((value < -(1 << 23)) || (value >= (1 << 23)))
|
||||
length = 4;
|
||||
else if((value < -(1 << 15)) || (value >= (1 << 15)))
|
||||
length = 3;
|
||||
else if((value < -(1 << 7)) || (value >= (1 << 7)))
|
||||
length = 2;
|
||||
else if(value != 0)
|
||||
length = 1;
|
||||
else length = 0;
|
||||
|
||||
if(!writeTag(TSigned32, id, length))
|
||||
return;
|
||||
|
||||
length--;
|
||||
for(int i = length; i >= 0; i--)
|
||||
m_data.push_back((char)((value >> (i * 8)) & 0xff));
|
||||
}
|
||||
|
||||
void SimpleSerializer::writeU32(quint32 id, quint32 value)
|
||||
{
|
||||
int length;
|
||||
|
||||
if(id == 0) {
|
||||
qCritical("SimpleSerializer: ID 0 is not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
if(value >= (1 << 24))
|
||||
length = 4;
|
||||
else if(value >= (1 << 16))
|
||||
length = 3;
|
||||
else if(value >= (1 << 8))
|
||||
length = 2;
|
||||
else if(value > 0)
|
||||
length = 1;
|
||||
else length = 0;
|
||||
|
||||
if(!writeTag(TUnsigned32, id, length))
|
||||
return;
|
||||
|
||||
length--;
|
||||
for(int i = length; i >= 0; i--)
|
||||
m_data.push_back((char)((value >> (i * 8)) & 0xff));
|
||||
}
|
||||
|
||||
void SimpleSerializer::writeS64(quint32 id, qint64 value)
|
||||
{
|
||||
int length;
|
||||
|
||||
if(id == 0) {
|
||||
qCritical("SimpleSerializer: ID 0 is not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
if((value < -(1ll << 55)) || (value >= (1ll << 55)))
|
||||
length = 8;
|
||||
else if((value < -(1ll << 47)) || (value >= (1ll << 47)))
|
||||
length = 7;
|
||||
else if((value < -(1ll << 39)) || (value >= (1ll << 39)))
|
||||
length = 6;
|
||||
else if((value < -(1ll << 31)) || (value >= (1ll << 31)))
|
||||
length = 5;
|
||||
else if((value < -(1ll << 23)) || (value >= (1ll << 23)))
|
||||
length = 4;
|
||||
else if((value < -(1ll << 15)) || (value >= (1ll << 15)))
|
||||
length = 3;
|
||||
else if((value < -(1ll << 7)) || (value >= (1ll << 7)))
|
||||
length = 2;
|
||||
else if(value != 0)
|
||||
length = 1;
|
||||
else length = 0;
|
||||
|
||||
if(!writeTag(TSigned64, id, length))
|
||||
return;
|
||||
|
||||
length--;
|
||||
for(int i = length; i >= 0; i--)
|
||||
m_data.push_back((char)((value >> (i * 8)) & 0xff));
|
||||
}
|
||||
|
||||
void SimpleSerializer::writeU64(quint32 id, quint64 value)
|
||||
{
|
||||
int length;
|
||||
|
||||
if(id == 0) {
|
||||
qCritical("SimpleSerializer: ID 0 is not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
if(value >= (1ll << 56))
|
||||
length = 8;
|
||||
else if(value >= (1ll << 48))
|
||||
length = 7;
|
||||
else if(value >= (1ll << 40))
|
||||
length = 6;
|
||||
else if(value >= (1ll << 32))
|
||||
length = 5;
|
||||
else if(value >= (1ll << 24))
|
||||
length = 4;
|
||||
else if(value >= (1ll << 16))
|
||||
length = 3;
|
||||
else if(value >= (1ll << 8))
|
||||
length = 2;
|
||||
else if(value > 0)
|
||||
length = 1;
|
||||
else length = 0;
|
||||
|
||||
if(!writeTag(TUnsigned64, id, length))
|
||||
return;
|
||||
|
||||
length--;
|
||||
for(int i = length; i >= 0; i--)
|
||||
m_data.push_back((char)((value >> (i * 8)) & 0xff));
|
||||
}
|
||||
|
||||
void SimpleSerializer::writeFloat(quint32 id, float value)
|
||||
{
|
||||
if(id == 0) {
|
||||
qCritical("SimpleSerializer: ID 0 is not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!writeTag(TFloat, id, 4))
|
||||
return;
|
||||
|
||||
quint32 tmp = *((quint32*)&value);
|
||||
m_data.push_back((char)((tmp >> 24) & 0xff));
|
||||
m_data.push_back((char)((tmp >> 16) & 0xff));
|
||||
m_data.push_back((char)((tmp >> 8) & 0xff));
|
||||
m_data.push_back((char)(tmp & 0xff));
|
||||
}
|
||||
|
||||
void SimpleSerializer::writeDouble(quint32 id, double value)
|
||||
{
|
||||
if(id == 0) {
|
||||
qCritical("SimpleSerializer: ID 0 is not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!writeTag(TDouble, id, 8))
|
||||
return;
|
||||
|
||||
quint64 tmp = *((quint64*)&value);
|
||||
m_data.push_back((char)((tmp >> 56) & 0xff));
|
||||
m_data.push_back((char)((tmp >> 48) & 0xff));
|
||||
m_data.push_back((char)((tmp >> 40) & 0xff));
|
||||
m_data.push_back((char)((tmp >> 32) & 0xff));
|
||||
m_data.push_back((char)((tmp >> 24) & 0xff));
|
||||
m_data.push_back((char)((tmp >> 16) & 0xff));
|
||||
m_data.push_back((char)((tmp >> 8) & 0xff));
|
||||
m_data.push_back((char)(tmp & 0xff));
|
||||
}
|
||||
|
||||
void SimpleSerializer::writeBool(quint32 id, bool value)
|
||||
{
|
||||
if(id == 0) {
|
||||
qCritical("SimpleSerializer: ID 0 is not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!writeTag(TBool, id, 1))
|
||||
return;
|
||||
if(value)
|
||||
m_data.push_back((char)0x01);
|
||||
else m_data.push_back((char)0x00);
|
||||
}
|
||||
|
||||
void SimpleSerializer::writeString(quint32 id, const QString& value)
|
||||
{
|
||||
if(id == 0) {
|
||||
qCritical("SimpleSerializer: ID 0 is not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray utf8 = value.toUtf8();
|
||||
if(!writeTag(TString, id, utf8.size()))
|
||||
return;
|
||||
m_data.append(utf8);
|
||||
}
|
||||
|
||||
void SimpleSerializer::writeBlob(quint32 id, const QByteArray& value)
|
||||
{
|
||||
if(id == 0) {
|
||||
qCritical("SimpleSerializer: ID 0 is not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!writeTag(TBlob, id, value.size()))
|
||||
return;
|
||||
m_data.append(value);
|
||||
}
|
||||
|
||||
const QByteArray& SimpleSerializer::final()
|
||||
{
|
||||
m_finalized = true;
|
||||
return m_data;
|
||||
}
|
||||
|
||||
bool SimpleSerializer::writeTag(Type type, quint32 id, quint32 length)
|
||||
{
|
||||
if(m_finalized) {
|
||||
qCritical("SimpleSerializer: config has already been finalized (id %u)", id);
|
||||
return false;
|
||||
}
|
||||
|
||||
int idLen;
|
||||
int lengthLen;
|
||||
|
||||
if(id < (1 << 8))
|
||||
idLen = 0;
|
||||
else if(id < (1 << 16))
|
||||
idLen = 1;
|
||||
else if(id < (1 << 24))
|
||||
idLen = 2;
|
||||
else idLen = 3;
|
||||
|
||||
if(length < (1 << 8))
|
||||
lengthLen = 0;
|
||||
else if(length < (1 << 16))
|
||||
lengthLen = 1;
|
||||
else if(length < (1 << 24))
|
||||
lengthLen = 2;
|
||||
else lengthLen = 3;
|
||||
|
||||
m_data.push_back((char)((type << 4) | (idLen << 2) | lengthLen));
|
||||
for(int i = idLen; i >= 0; i--)
|
||||
m_data.push_back((char)((id >> (i * 8)) & 0xff));
|
||||
for(int i = lengthLen; i >= 0; i--)
|
||||
m_data.push_back((char)((length >> (i * 8)) & 0xff));
|
||||
return true;
|
||||
}
|
||||
|
||||
SimpleDeserializer::SimpleDeserializer(const QByteArray& data) :
|
||||
m_data(data)
|
||||
{
|
||||
m_valid = parseAll();
|
||||
|
||||
// read version information
|
||||
uint readOfs;
|
||||
Elements::const_iterator it = m_elements.constFind(0);
|
||||
if(it == m_elements.constEnd())
|
||||
goto setInvalid;
|
||||
if(it->type != TVersion)
|
||||
goto setInvalid;
|
||||
if(it->length > 4)
|
||||
goto setInvalid;
|
||||
|
||||
readOfs = it->ofs;
|
||||
m_version = 0;
|
||||
for(uint i = 0; i < it->length; i++)
|
||||
m_version = (m_version << 8) | readByte(&readOfs);
|
||||
return;
|
||||
|
||||
setInvalid:
|
||||
m_valid = false;
|
||||
}
|
||||
|
||||
bool SimpleDeserializer::readS32(quint32 id, qint32* result, qint32 def) const
|
||||
{
|
||||
uint readOfs;
|
||||
qint32 tmp;
|
||||
Elements::const_iterator it = m_elements.constFind(id);
|
||||
if(it == m_elements.constEnd())
|
||||
goto returnDefault;
|
||||
if(it->type != TSigned32)
|
||||
goto returnDefault;
|
||||
if(it->length > 4)
|
||||
goto returnDefault;
|
||||
|
||||
readOfs = it->ofs;
|
||||
tmp = 0;
|
||||
for(uint i = 0; i < it->length; i++) {
|
||||
quint8 byte = readByte(&readOfs);
|
||||
if((i == 0) && (byte & 0x80))
|
||||
tmp = -1;
|
||||
tmp = (tmp << 8) | byte;
|
||||
}
|
||||
*result = tmp;
|
||||
return true;
|
||||
|
||||
returnDefault:
|
||||
*result = def;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SimpleDeserializer::readU32(quint32 id, quint32* result, quint32 def) const
|
||||
{
|
||||
uint readOfs;
|
||||
quint32 tmp;
|
||||
Elements::const_iterator it = m_elements.constFind(id);
|
||||
if(it == m_elements.constEnd())
|
||||
goto returnDefault;
|
||||
if(it->type != TUnsigned32)
|
||||
goto returnDefault;
|
||||
if(it->length > 4)
|
||||
goto returnDefault;
|
||||
|
||||
readOfs = it->ofs;
|
||||
tmp = 0;
|
||||
for(uint i = 0; i < it->length; i++)
|
||||
tmp = (tmp << 8) | readByte(&readOfs);
|
||||
*result = tmp;
|
||||
return true;
|
||||
|
||||
returnDefault:
|
||||
*result = def;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SimpleDeserializer::readS64(quint32 id, qint64* result, qint64 def) const
|
||||
{
|
||||
uint readOfs;
|
||||
qint64 tmp;
|
||||
Elements::const_iterator it = m_elements.constFind(id);
|
||||
if(it == m_elements.constEnd())
|
||||
goto returnDefault;
|
||||
if(it->type != TSigned64)
|
||||
goto returnDefault;
|
||||
if(it->length > 8)
|
||||
goto returnDefault;
|
||||
|
||||
readOfs = it->ofs;
|
||||
tmp = 0;
|
||||
for(uint i = 0; i < it->length; i++) {
|
||||
quint8 byte = readByte(&readOfs);
|
||||
if((i == 0) && (byte & 0x80))
|
||||
tmp = -1;
|
||||
tmp = (tmp << 8) | byte;
|
||||
}
|
||||
*result = tmp;
|
||||
return true;
|
||||
|
||||
returnDefault:
|
||||
*result = def;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SimpleDeserializer::readU64(quint32 id, quint64* result, quint64 def) const
|
||||
{
|
||||
uint readOfs;
|
||||
quint64 tmp;
|
||||
Elements::const_iterator it = m_elements.constFind(id);
|
||||
if(it == m_elements.constEnd())
|
||||
goto returnDefault;
|
||||
if(it->type != TUnsigned64)
|
||||
goto returnDefault;
|
||||
if(it->length > 8)
|
||||
goto returnDefault;
|
||||
|
||||
readOfs = it->ofs;
|
||||
tmp = 0;
|
||||
for(uint i = 0; i < it->length; i++)
|
||||
tmp = (tmp << 8) | readByte(&readOfs);
|
||||
*result = tmp;
|
||||
return true;
|
||||
|
||||
returnDefault:
|
||||
*result = def;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SimpleDeserializer::readFloat(quint32 id, float* result, float def) const
|
||||
{
|
||||
uint readOfs;
|
||||
quint32 tmp;
|
||||
Elements::const_iterator it = m_elements.constFind(id);
|
||||
if(it == m_elements.constEnd())
|
||||
goto returnDefault;
|
||||
if(it->type != TFloat)
|
||||
goto returnDefault;
|
||||
if(it->length != 4)
|
||||
goto returnDefault;
|
||||
|
||||
readOfs = it->ofs;
|
||||
tmp = 0;
|
||||
for(int i = 0; i < 4; i++)
|
||||
tmp = (tmp << 8) | readByte(&readOfs);
|
||||
*result = *((float*)&tmp);
|
||||
return true;
|
||||
|
||||
returnDefault:
|
||||
*result = def;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SimpleDeserializer::readDouble(quint32 id, double* result, double def) const
|
||||
{
|
||||
uint readOfs;
|
||||
quint64 tmp;
|
||||
Elements::const_iterator it = m_elements.constFind(id);
|
||||
if(it == m_elements.constEnd())
|
||||
goto returnDefault;
|
||||
if(it->type != TDouble)
|
||||
goto returnDefault;
|
||||
if(it->length != 8)
|
||||
goto returnDefault;
|
||||
|
||||
readOfs = it->ofs;
|
||||
tmp = 0;
|
||||
for(int i = 0; i < 8; i++)
|
||||
tmp = (tmp << 8) | readByte(&readOfs);
|
||||
*result = *((double*)&tmp);
|
||||
return true;
|
||||
|
||||
returnDefault:
|
||||
*result = def;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SimpleDeserializer::readReal(quint32 id, Real* result, Real def) const
|
||||
{
|
||||
if(sizeof(Real) == 4) {
|
||||
uint readOfs;
|
||||
quint32 tmp;
|
||||
Elements::const_iterator it = m_elements.constFind(id);
|
||||
if(it == m_elements.constEnd())
|
||||
goto returnDefault32;
|
||||
if(it->type != TFloat)
|
||||
goto returnDefault32;
|
||||
if(it->length != 4)
|
||||
goto returnDefault32;
|
||||
|
||||
readOfs = it->ofs;
|
||||
tmp = 0;
|
||||
for(int i = 0; i < 4; i++)
|
||||
tmp = (tmp << 8) | readByte(&readOfs);
|
||||
*result = *((Real*)&tmp);
|
||||
return true;
|
||||
|
||||
returnDefault32:
|
||||
*result = def;
|
||||
return false;
|
||||
} else {
|
||||
uint readOfs;
|
||||
quint64 tmp;
|
||||
Elements::const_iterator it = m_elements.constFind(id);
|
||||
if(it == m_elements.constEnd())
|
||||
goto returnDefault64;
|
||||
if(it->type != TDouble)
|
||||
goto returnDefault64;
|
||||
if(it->length != 8)
|
||||
goto returnDefault64;
|
||||
|
||||
readOfs = it->ofs;
|
||||
tmp = 0;
|
||||
for(int i = 0; i < 8; i++)
|
||||
tmp = (tmp << 8) | readByte(&readOfs);
|
||||
*result = *((Real*)&tmp);
|
||||
return true;
|
||||
|
||||
returnDefault64:
|
||||
*result = def;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SimpleDeserializer::readBool(quint32 id, bool* result, bool def) const
|
||||
{
|
||||
uint readOfs;
|
||||
quint8 tmp;
|
||||
Elements::const_iterator it = m_elements.constFind(id);
|
||||
if(it == m_elements.constEnd())
|
||||
goto returnDefault;
|
||||
if(it->type != TBool)
|
||||
goto returnDefault;
|
||||
if(it->length != 1)
|
||||
goto returnDefault;
|
||||
|
||||
readOfs = it->ofs;
|
||||
tmp = readByte(&readOfs);
|
||||
if(tmp == 0x00)
|
||||
*result = false;
|
||||
else *result = true;
|
||||
return true;
|
||||
|
||||
returnDefault:
|
||||
*result = def;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SimpleDeserializer::readString(quint32 id, QString* result, const QString& def) const
|
||||
{
|
||||
Elements::const_iterator it = m_elements.constFind(id);
|
||||
if(it == m_elements.constEnd())
|
||||
goto returnDefault;
|
||||
if(it->type != TString)
|
||||
goto returnDefault;
|
||||
|
||||
*result = QString::fromUtf8(m_data.data() + it->ofs, it->length);
|
||||
return true;
|
||||
|
||||
returnDefault:
|
||||
*result = def;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SimpleDeserializer::readBlob(quint32 id, QByteArray* result, const QByteArray& def) const
|
||||
{
|
||||
Elements::const_iterator it = m_elements.constFind(id);
|
||||
if(it == m_elements.constEnd())
|
||||
goto returnDefault;
|
||||
if(it->type != TBlob)
|
||||
goto returnDefault;
|
||||
|
||||
*result = QByteArray(m_data.data() + it->ofs, it->length);
|
||||
return true;
|
||||
|
||||
returnDefault:
|
||||
*result = def;
|
||||
return false;
|
||||
}
|
||||
|
||||
void SimpleDeserializer::dump() const
|
||||
{
|
||||
if(!m_valid) {
|
||||
qDebug("SimpleDeserializer dump: data invalid");
|
||||
return;
|
||||
} else {
|
||||
qDebug("SimpleDeserializer dump: version %u", m_version);
|
||||
}
|
||||
|
||||
for(Elements::const_iterator it = m_elements.constBegin(); it != m_elements.constEnd(); ++it) {
|
||||
switch(it->type) {
|
||||
case TSigned32: {
|
||||
qint32 tmp;
|
||||
readS32(it.key(), &tmp);
|
||||
qDebug("id %d, S32, len %d: " PRINTF_FORMAT_S32, it.key(), it->length, tmp);
|
||||
break;
|
||||
}
|
||||
case TUnsigned32: {
|
||||
quint32 tmp;
|
||||
readU32(it.key(), &tmp);
|
||||
qDebug("id %d, U32, len %d: " PRINTF_FORMAT_U32, it.key(), it->length, tmp);
|
||||
break;
|
||||
}
|
||||
case TSigned64: {
|
||||
qint64 tmp;
|
||||
readS64(it.key(), &tmp);
|
||||
qDebug("id %d, S64, len %d: " PRINTF_FORMAT_S64, it.key(), it->length, (int)tmp);
|
||||
break;
|
||||
}
|
||||
case TUnsigned64: {
|
||||
quint64 tmp;
|
||||
readU64(it.key(), &tmp);
|
||||
qDebug("id %d, U64, len %d: " PRINTF_FORMAT_U64, it.key(), it->length, (uint)tmp);
|
||||
break;
|
||||
}
|
||||
case TFloat: {
|
||||
float tmp;
|
||||
readFloat(it.key(), &tmp);
|
||||
qDebug("id %d, FLOAT, len %d: %f", it.key(), it->length, tmp);
|
||||
break;
|
||||
}
|
||||
case TDouble: {
|
||||
double tmp;
|
||||
readDouble(it.key(), &tmp);
|
||||
qDebug("id %d, DOUBLE, len %d: %f", it.key(), it->length, tmp);
|
||||
break;
|
||||
}
|
||||
case TBool: {
|
||||
bool tmp;
|
||||
readBool(it.key(), &tmp);
|
||||
qDebug("id %d, BOOL, len %d: %s", it.key(), it->length, tmp ? "true" : "false");
|
||||
break;
|
||||
}
|
||||
case TString: {
|
||||
QString tmp;
|
||||
readString(it.key(), &tmp);
|
||||
qDebug("id %d, STRING, len %d: \"%s\"", it.key(), it->length, qPrintable(tmp));
|
||||
break;
|
||||
}
|
||||
case TBlob: {
|
||||
QByteArray tmp;
|
||||
readBlob(it.key(), &tmp);
|
||||
qDebug("id %d, BLOB, len %d", it.key(), it->length);
|
||||
break;
|
||||
}
|
||||
case TVersion: {
|
||||
qDebug("id %d, VERSION, len %d", it.key(), it->length);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
qDebug("id %d, UNKNOWN TYPE 0x%02x, len %d", it.key(), it->type, it->length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SimpleDeserializer::parseAll()
|
||||
{
|
||||
uint readOfs = 0;
|
||||
Type type;
|
||||
quint32 id;
|
||||
quint32 length;
|
||||
|
||||
/*
|
||||
QString hex;
|
||||
for(int i = 0; i < m_data.size(); i++)
|
||||
hex.append(QString("%1 ").arg((quint8)m_data[i], 2, 16, QChar('0')));
|
||||
qDebug("%s", qPrintable(hex));
|
||||
qDebug("==");
|
||||
*/
|
||||
|
||||
while(readOfs < (uint)m_data.size()) {
|
||||
if(!readTag(&readOfs, m_data.size(), &type, &id, &length))
|
||||
return false;
|
||||
|
||||
//qDebug("-- id %d, TYPE 0x%02x, len %d", id, type, length);
|
||||
|
||||
if(m_elements.contains(id)) {
|
||||
qDebug("SimpleDeserializer: same ID found twice (id %u)", id);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_elements.insert(id, Element(type, readOfs, length));
|
||||
|
||||
readOfs += length;
|
||||
|
||||
if(readOfs == m_data.size())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SimpleDeserializer::readTag(uint* readOfs, uint readEnd, Type* type, quint32* id, quint32* length) const
|
||||
{
|
||||
quint8 tag = readByte(readOfs);
|
||||
|
||||
*type = (Type)(tag >> 4);
|
||||
int idLen = ((tag >> 2) & 0x03) + 1;
|
||||
int lengthLen = (tag & 0x03) + 1;
|
||||
|
||||
// make sure we have enough header bytes left
|
||||
if(((*readOfs) + idLen + lengthLen) > readEnd)
|
||||
return false;
|
||||
|
||||
quint32 tmp = 0;
|
||||
for(int i = 0; i < idLen; i++)
|
||||
tmp = (tmp << 8) | readByte(readOfs);
|
||||
*id = tmp;
|
||||
tmp = 0;
|
||||
for(int i = 0; i < lengthLen; i++)
|
||||
tmp = (tmp << 8) | readByte(readOfs);
|
||||
*length = tmp;
|
||||
|
||||
// check if payload fits the buffer
|
||||
if(((*readOfs) + (*length)) > readEnd)
|
||||
return false;
|
||||
else return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#include "util/spinlock.h"
|
||||