]> git.gag.com Git - debian/gnuradio/blobdiff - gr-qtgui/src/lib/WaterfallDisplayPlot.cc
Merging qtgui branch-r9068:9837: this ads a qtgui_sink_c and qtgui_sink_f that displa...
[debian/gnuradio] / gr-qtgui / src / lib / WaterfallDisplayPlot.cc
diff --git a/gr-qtgui/src/lib/WaterfallDisplayPlot.cc b/gr-qtgui/src/lib/WaterfallDisplayPlot.cc
new file mode 100644 (file)
index 0000000..0f15d95
--- /dev/null
@@ -0,0 +1,483 @@
+#ifndef WATERFALL_DISPLAY_PLOT_C
+#define WATERFALL_DISPLAY_PLOT_C
+
+#include <WaterfallDisplayPlot.h>
+
+#include <qwt_color_map.h>
+#include <qwt_scale_widget.h>
+#include <qwt_scale_draw.h>
+#include <qwt_plot_zoomer.h>
+#include <qwt_plot_panner.h>
+#include <qwt_plot_layout.h>
+
+#include <qapplication.h>
+
+class FreqOffsetAndPrecisionClass
+{
+public:
+  FreqOffsetAndPrecisionClass(const int freqPrecision){
+    _frequencyPrecision = freqPrecision;
+    _centerFrequency = 0;
+  }
+
+  virtual ~FreqOffsetAndPrecisionClass(){
+
+  }
+
+  virtual unsigned int GetFrequencyPrecision()const{
+    return _frequencyPrecision;
+  }
+
+  virtual void SetFrequencyPrecision(const unsigned int newPrecision){
+    _frequencyPrecision = newPrecision;
+  }
+
+  virtual double GetCenterFrequency()const{
+    return _centerFrequency;
+  }
+
+  virtual void SetCenterFrequency(const double newFreq){
+    _centerFrequency = newFreq;
+  }
+
+protected:
+  unsigned int _frequencyPrecision;
+  double _centerFrequency;
+
+private:
+
+};
+
+class WaterfallFreqDisplayScaleDraw: public QwtScaleDraw, public FreqOffsetAndPrecisionClass{
+public:
+  WaterfallFreqDisplayScaleDraw(const unsigned int precision):QwtScaleDraw(), FreqOffsetAndPrecisionClass(precision){
+
+  }
+
+  virtual ~WaterfallFreqDisplayScaleDraw(){
+
+  }
+
+  QwtText label(double value)const{
+    return QString("%1").arg((value + GetCenterFrequency()) / ((GetFrequencyPrecision() == 0) ? 1.0 : 1000.0), 0, 'f', GetFrequencyPrecision());
+  }
+
+  virtual void initiateUpdate(){
+    invalidateCache();
+  }
+
+protected:
+
+private:
+
+};
+
+class TimeScaleData
+{
+public:
+  TimeScaleData(){
+    timespec_reset(&_zeroTime);
+    _secondsPerLine = 1.0;
+    
+  }
+  
+  virtual ~TimeScaleData(){
+    
+  }
+
+  virtual timespec GetZeroTime()const{
+    return _zeroTime;
+  }
+  
+  virtual void SetZeroTime(const timespec newTime){
+    _zeroTime = newTime;
+  }
+
+  virtual void SetSecondsPerLine(const double newTime){
+    _secondsPerLine = newTime;
+  }
+
+  virtual double GetSecondsPerLine()const{
+    return _secondsPerLine;
+  }
+
+  
+protected:
+  timespec _zeroTime;
+  double _secondsPerLine;
+  
+private:
+  
+};
+
+class QwtTimeScaleDraw: public QwtScaleDraw, public TimeScaleData
+{
+public:
+  QwtTimeScaleDraw():QwtScaleDraw(),TimeScaleData(){
+    
+  }
+
+  virtual ~QwtTimeScaleDraw(){
+    
+  }
+
+  virtual QwtText label(double value)const{
+    QwtText returnLabel("");
+
+    timespec lineTime = timespec_add(GetZeroTime(), (-value) * GetSecondsPerLine());
+    struct tm timeTm;
+    gmtime_r(&lineTime.tv_sec, &timeTm);
+    returnLabel = (QString("").sprintf("%04d/%02d/%02d\n%02d:%02d:%02d.%03ld", timeTm.tm_year+1900, timeTm.tm_mon+1, timeTm.tm_mday, timeTm.tm_hour, timeTm.tm_min, timeTm.tm_sec, lineTime.tv_nsec/1000000));
+    
+    return returnLabel;
+  }
+
+  virtual void initiateUpdate(){
+    // Do this in one call rather than when zeroTime and secondsPerLine updates is to prevent the display from being updated too often...
+    invalidateCache();
+  }
+  
+protected:
+
+private:
+
+};
+
+class WaterfallZoomer: public QwtPlotZoomer, public TimeScaleData, public FreqOffsetAndPrecisionClass
+{
+public:
+  WaterfallZoomer(QwtPlotCanvas* canvas, const unsigned int freqPrecision):QwtPlotZoomer(canvas), TimeScaleData(), FreqOffsetAndPrecisionClass(freqPrecision)
+  {
+    setTrackerMode(QwtPicker::AlwaysOn);
+  }
+
+  virtual ~WaterfallZoomer(){
+
+  }
+  
+  virtual void updateTrackerText(){
+    updateDisplay();
+  }
+
+protected:
+  virtual QwtText trackerText( const QwtDoublePoint& p ) const 
+  {
+    QString yLabel("");
+
+    timespec lineTime = timespec_add(GetZeroTime(), (-p.y()) * GetSecondsPerLine());
+    struct tm timeTm;
+    gmtime_r(&lineTime.tv_sec, &timeTm);
+    yLabel = (QString("").sprintf("%04d/%02d/%02d %02d:%02d:%02d.%03ld", timeTm.tm_year+1900, timeTm.tm_mon+1, timeTm.tm_mday, timeTm.tm_hour, timeTm.tm_min, timeTm.tm_sec, lineTime.tv_nsec/1000000));
+
+    QwtText t(QString("%1 %2, %3").arg((p.x() + GetCenterFrequency()) / ((GetFrequencyPrecision() == 0) ? 1.0 : 1000.0), 0, 'f', GetFrequencyPrecision()).arg( (GetFrequencyPrecision() == 0) ? "Hz" : "kHz").arg(yLabel));
+
+    return t;
+  }
+};
+
+
+const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR;
+const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_WHITE_HOT;
+const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_BLACK_HOT;
+const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_INCANDESCENT;
+const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_USER_DEFINED;
+
+WaterfallDisplayPlot::WaterfallDisplayPlot(QWidget* parent):QwtPlot(parent){
+  _zoomer = NULL;
+  _startFrequency = 0;
+  _stopFrequency = 4000;
+  
+  resize(parent->width(), parent->height());
+  _numPoints = 1024;
+
+  _displayIntervalTime = (1.0/5.0); // 1/5 of a second between updates
+
+  _waterfallData = new WaterfallData(_startFrequency, _stopFrequency, _numPoints, 200);
+
+  QPalette palette;
+  palette.setColor(canvas()->backgroundRole(), QColor("white"));
+  canvas()->setPalette(palette);   
+
+  setAxisTitle(QwtPlot::xBottom, "Frequency (Hz)");
+  setAxisScaleDraw(QwtPlot::xBottom, new WaterfallFreqDisplayScaleDraw(0));
+
+  setAxisTitle(QwtPlot::yLeft, "Time");
+  setAxisScaleDraw(QwtPlot::yLeft, new QwtTimeScaleDraw());
+
+  timespec_reset(&_lastReplot);
+
+  d_spectrogram = new PlotWaterfall(_waterfallData, "Waterfall Display");
+
+  _intensityColorMapType = INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR;
+
+  QwtLinearColorMap colorMap(Qt::darkCyan, Qt::white);
+  colorMap.addColorStop(0.25, Qt::cyan);
+  colorMap.addColorStop(0.5, Qt::yellow);
+  colorMap.addColorStop(0.75, Qt::red);
+
+  d_spectrogram->setColorMap(colorMap);
+  
+  d_spectrogram->attach(this);
+  
+  // LeftButton for the zooming
+  // MidButton for the panning
+  // RightButton: zoom out by 1
+  // Ctrl+RighButton: zoom out to full size
+  
+  _zoomer = new WaterfallZoomer(canvas(), 0);
+#if QT_VERSION < 0x040000
+  _zoomer->setMousePattern(QwtEventPattern::MouseSelect2,
+                          Qt::RightButton, Qt::ControlModifier);
+#else
+  _zoomer->setMousePattern(QwtEventPattern::MouseSelect2,
+                          Qt::RightButton, Qt::ControlModifier);
+#endif
+  _zoomer->setMousePattern(QwtEventPattern::MouseSelect3,
+                          Qt::RightButton);
+  
+  _panner = new QwtPlotPanner(canvas());
+  _panner->setAxisEnabled(QwtPlot::yRight, false);
+  _panner->setMouseButton(Qt::MidButton);
+  
+  // Avoid jumping when labels with more/less digits
+  // appear/disappear when scrolling vertically
+  
+  const QFontMetrics fm(axisWidget(QwtPlot::yLeft)->font());
+  QwtScaleDraw *sd = axisScaleDraw(QwtPlot::yLeft);
+  sd->setMinimumExtent( fm.width("100.00") );
+  
+  const QColor c(Qt::white);
+  _zoomer->setRubberBandPen(c);
+  _zoomer->setTrackerPen(c);
+
+  _UpdateIntensityRangeDisplay();
+}
+
+WaterfallDisplayPlot::~WaterfallDisplayPlot(){
+  delete _waterfallData;
+}
+
+void WaterfallDisplayPlot::Reset(){
+  _waterfallData->ResizeData(_startFrequency, _stopFrequency, _numPoints);
+  _waterfallData->Reset();
+
+  // Load up the new base zoom settings
+  QwtDoubleRect newSize = _zoomer->zoomBase();
+  newSize.setLeft(_startFrequency);
+  newSize.setWidth(_stopFrequency-_startFrequency);
+  _zoomer->zoom(newSize);
+  _zoomer->setZoomBase(newSize);
+  _zoomer->zoom(0);
+}
+
+void WaterfallDisplayPlot::SetFrequencyRange(const double startFreq, const double stopFreq, const double centerFreq, const bool useCenterFrequencyFlag){
+  if((stopFreq > 0) && (stopFreq > startFreq)){
+    _startFrequency = startFreq;
+    _stopFrequency = stopFreq;
+
+    if((axisScaleDraw(QwtPlot::xBottom) != NULL) && (_zoomer != NULL)){
+      WaterfallFreqDisplayScaleDraw* freqScale = ((WaterfallFreqDisplayScaleDraw*)axisScaleDraw(QwtPlot::xBottom));
+      freqScale->SetCenterFrequency(centerFreq);
+      ((WaterfallZoomer*)_zoomer)->SetCenterFrequency(centerFreq);
+      if(useCenterFrequencyFlag){
+       freqScale->SetFrequencyPrecision( 3 );
+       ((WaterfallZoomer*)_zoomer)->SetFrequencyPrecision( 3 );
+       setAxisTitle(QwtPlot::xBottom, "Frequency (kHz)");
+      }
+      else{
+       freqScale->SetFrequencyPrecision( 0 );
+       ((WaterfallZoomer*)_zoomer)->SetFrequencyPrecision( 0 );
+       setAxisTitle(QwtPlot::xBottom, "Frequency (Hz)");
+      }
+    }
+
+    Reset();
+
+    // Only replot if screen is visible
+    if(isVisible()){
+      replot();
+    }
+  }
+}
+
+
+double WaterfallDisplayPlot::GetStartFrequency()const{
+  return _startFrequency;
+}
+
+double WaterfallDisplayPlot::GetStopFrequency()const{
+  return _stopFrequency;
+}
+
+void WaterfallDisplayPlot::PlotNewData(const double* dataPoints, const int64_t numDataPoints, const double timePerFFT, const timespec timestamp, const int droppedFrames){
+  if(numDataPoints > 0){
+    if(numDataPoints != _numPoints){
+      _numPoints = numDataPoints;
+
+      Reset();
+
+      d_spectrogram->invalidateCache();
+      d_spectrogram->itemChanged();
+
+      if(isVisible()){
+       replot();
+      }
+
+      _lastReplot = get_highres_clock();
+    }
+
+    _waterfallData->addFFTData(dataPoints, numDataPoints, droppedFrames);
+    _waterfallData->IncrementNumLinesToUpdate();
+
+    QwtTimeScaleDraw* timeScale = (QwtTimeScaleDraw*)axisScaleDraw(QwtPlot::yLeft);
+    timeScale->SetSecondsPerLine(timePerFFT);
+    timeScale->SetZeroTime(timestamp);
+
+    ((WaterfallZoomer*)_zoomer)->SetSecondsPerLine(timePerFFT);
+    ((WaterfallZoomer*)_zoomer)->SetZeroTime(timestamp);
+  }
+
+  // Allow at least a 50% duty cycle
+  if(diff_timespec(get_highres_clock(), _lastReplot) > _displayIntervalTime){
+
+    d_spectrogram->invalidateCache();
+    d_spectrogram->itemChanged();
+
+    // Only update when window is visible
+    if(isVisible()){
+      replot();
+    }
+
+    _lastReplot = get_highres_clock();
+  }
+}
+
+void WaterfallDisplayPlot::SetIntensityRange(const double minIntensity, const double maxIntensity){
+  _waterfallData->setRange(QwtDoubleInterval(minIntensity, maxIntensity));
+
+  emit UpdatedLowerIntensityLevel(minIntensity);
+  emit UpdatedUpperIntensityLevel(maxIntensity);
+
+  _UpdateIntensityRangeDisplay();
+}
+
+void WaterfallDisplayPlot::replot(){
+  const timespec startTime = get_highres_clock();
+
+  QwtTimeScaleDraw* timeScale = (QwtTimeScaleDraw*)axisScaleDraw(QwtPlot::yLeft);
+  timeScale->initiateUpdate();
+
+  WaterfallFreqDisplayScaleDraw* freqScale = (WaterfallFreqDisplayScaleDraw*)axisScaleDraw(QwtPlot::xBottom);
+  freqScale->initiateUpdate();
+
+  // Update the time axis display
+  if(axisWidget(QwtPlot::yLeft) != NULL){
+    axisWidget(QwtPlot::yLeft)->update();
+  }
+
+  // Update the Frequency Offset Display
+  if(axisWidget(QwtPlot::xBottom) != NULL){
+    axisWidget(QwtPlot::xBottom)->update();
+  }
+
+  if(_zoomer != NULL){
+    ((WaterfallZoomer*)_zoomer)->updateTrackerText();
+  }
+
+  QwtPlot::replot();
+
+  double differenceTime = (diff_timespec(get_highres_clock(), startTime));
+  
+  // Require at least a 5% duty cycle
+  differenceTime *= 19.0;
+  if(differenceTime > (1.0/5.0)){
+    _displayIntervalTime = differenceTime;
+  }
+}
+
+int WaterfallDisplayPlot::GetIntensityColorMapType()const{
+  return _intensityColorMapType;
+}
+
+void WaterfallDisplayPlot::SetIntensityColorMapType(const int newType, const QColor lowColor, const QColor highColor){
+  if((_intensityColorMapType != newType) || 
+     ((newType == INTENSITY_COLOR_MAP_TYPE_USER_DEFINED) &&
+      (lowColor.isValid() && highColor.isValid()))){
+    switch(newType){
+    case INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR:{
+      _intensityColorMapType = newType;
+      QwtLinearColorMap colorMap(Qt::darkCyan, Qt::white);
+      colorMap.addColorStop(0.25, Qt::cyan);
+      colorMap.addColorStop(0.5, Qt::yellow);
+      colorMap.addColorStop(0.75, Qt::red);
+      d_spectrogram->setColorMap(colorMap);
+      break;
+    }
+    case INTENSITY_COLOR_MAP_TYPE_WHITE_HOT:{
+      _intensityColorMapType = newType;
+      QwtLinearColorMap colorMap(Qt::black, Qt::white);
+      d_spectrogram->setColorMap(colorMap);
+      break;
+    }
+    case INTENSITY_COLOR_MAP_TYPE_BLACK_HOT:{
+      _intensityColorMapType = newType;
+      QwtLinearColorMap colorMap(Qt::white, Qt::black);
+      d_spectrogram->setColorMap(colorMap);
+      break;
+    }
+    case INTENSITY_COLOR_MAP_TYPE_INCANDESCENT:{
+      _intensityColorMapType = newType;
+      QwtLinearColorMap colorMap(Qt::black, Qt::white);
+      colorMap.addColorStop(0.5, Qt::darkRed);
+      d_spectrogram->setColorMap(colorMap);
+      break;
+    }
+    case INTENSITY_COLOR_MAP_TYPE_USER_DEFINED:{
+      _userDefinedLowIntensityColor = lowColor;
+      _userDefinedHighIntensityColor = highColor;
+      _intensityColorMapType = newType;
+      QwtLinearColorMap colorMap(_userDefinedLowIntensityColor, _userDefinedHighIntensityColor);
+      d_spectrogram->setColorMap(colorMap);
+      break;
+    }
+    default: break;
+    }
+    
+    _UpdateIntensityRangeDisplay();
+  }
+}
+
+const QColor WaterfallDisplayPlot::GetUserDefinedLowIntensityColor()const{
+  return _userDefinedLowIntensityColor;
+}
+
+const QColor WaterfallDisplayPlot::GetUserDefinedHighIntensityColor()const{
+  return _userDefinedHighIntensityColor;
+}
+
+void WaterfallDisplayPlot::_UpdateIntensityRangeDisplay(){
+  QwtScaleWidget *rightAxis = axisWidget(QwtPlot::yRight);
+  rightAxis->setTitle("Intensity (dB)");
+  rightAxis->setColorBarEnabled(true);
+  rightAxis->setColorMap(d_spectrogram->data()->range(),
+                        d_spectrogram->colorMap());
+  
+  setAxisScale(QwtPlot::yRight, 
+              d_spectrogram->data()->range().minValue(),
+              d_spectrogram->data()->range().maxValue() );
+  enableAxis(QwtPlot::yRight);
+  
+  plotLayout()->setAlignCanvasToScales(true);
+
+  // Tell the display to redraw everything
+  d_spectrogram->invalidateCache();
+  d_spectrogram->itemChanged();
+
+  // Draw again
+  replot();
+
+  // Update the last replot timer
+  _lastReplot = get_highres_clock();
+}
+
+#endif /* WATERFALL_DISPLAY_PLOT_C */