Imported Upstream version 3.2.2
[debian/gnuradio] / gr-qtgui / src / lib / WaterfallDisplayPlot.cc
1 #ifndef WATERFALL_DISPLAY_PLOT_C
2 #define WATERFALL_DISPLAY_PLOT_C
3
4 #include <WaterfallDisplayPlot.h>
5
6 #include <qwt_color_map.h>
7 #include <qwt_scale_widget.h>
8 #include <qwt_scale_draw.h>
9 #include <qwt_plot_zoomer.h>
10 #include <qwt_plot_panner.h>
11 #include <qwt_plot_layout.h>
12
13 #include <qapplication.h>
14
15 class FreqOffsetAndPrecisionClass
16 {
17 public:
18   FreqOffsetAndPrecisionClass(const int freqPrecision){
19     _frequencyPrecision = freqPrecision;
20     _centerFrequency = 0;
21   }
22
23   virtual ~FreqOffsetAndPrecisionClass(){
24
25   }
26
27   virtual unsigned int GetFrequencyPrecision()const{
28     return _frequencyPrecision;
29   }
30
31   virtual void SetFrequencyPrecision(const unsigned int newPrecision){
32     _frequencyPrecision = newPrecision;
33   }
34
35   virtual double GetCenterFrequency()const{
36     return _centerFrequency;
37   }
38
39   virtual void SetCenterFrequency(const double newFreq){
40     _centerFrequency = newFreq;
41   }
42
43 protected:
44   unsigned int _frequencyPrecision;
45   double _centerFrequency;
46
47 private:
48
49 };
50
51 class WaterfallFreqDisplayScaleDraw: public QwtScaleDraw, public FreqOffsetAndPrecisionClass{
52 public:
53   WaterfallFreqDisplayScaleDraw(const unsigned int precision):QwtScaleDraw(), FreqOffsetAndPrecisionClass(precision){
54
55   }
56
57   virtual ~WaterfallFreqDisplayScaleDraw(){
58
59   }
60
61   QwtText label(double value)const{
62     return QString("%1").arg((value + GetCenterFrequency()) / ((GetFrequencyPrecision() == 0) ? 1.0 : 1000.0), 0, 'f', GetFrequencyPrecision());
63   }
64
65   virtual void initiateUpdate(){
66     invalidateCache();
67   }
68
69 protected:
70
71 private:
72
73 };
74
75 class TimeScaleData
76 {
77 public:
78   TimeScaleData(){
79     timespec_reset(&_zeroTime);
80     _secondsPerLine = 1.0;
81     
82   }
83   
84   virtual ~TimeScaleData(){
85     
86   }
87
88   virtual timespec GetZeroTime()const{
89     return _zeroTime;
90   }
91   
92   virtual void SetZeroTime(const timespec newTime){
93     _zeroTime = newTime;
94   }
95
96   virtual void SetSecondsPerLine(const double newTime){
97     _secondsPerLine = newTime;
98   }
99
100   virtual double GetSecondsPerLine()const{
101     return _secondsPerLine;
102   }
103
104   
105 protected:
106   timespec _zeroTime;
107   double _secondsPerLine;
108   
109 private:
110   
111 };
112
113 class QwtTimeScaleDraw: public QwtScaleDraw, public TimeScaleData
114 {
115 public:
116   QwtTimeScaleDraw():QwtScaleDraw(),TimeScaleData(){
117     
118   }
119
120   virtual ~QwtTimeScaleDraw(){
121     
122   }
123
124   virtual QwtText label(double value)const{
125     QwtText returnLabel("");
126
127     timespec lineTime = timespec_add(GetZeroTime(), (-value) * GetSecondsPerLine());
128     struct tm timeTm;
129     gmtime_r(&lineTime.tv_sec, &timeTm);
130     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));
131     
132     return returnLabel;
133   }
134
135   virtual void initiateUpdate(){
136     // Do this in one call rather than when zeroTime and secondsPerLine updates is to prevent the display from being updated too often...
137     invalidateCache();
138   }
139   
140 protected:
141
142 private:
143
144 };
145
146 class WaterfallZoomer: public QwtPlotZoomer, public TimeScaleData, public FreqOffsetAndPrecisionClass
147 {
148 public:
149   WaterfallZoomer(QwtPlotCanvas* canvas, const unsigned int freqPrecision):QwtPlotZoomer(canvas), TimeScaleData(), FreqOffsetAndPrecisionClass(freqPrecision)
150   {
151     setTrackerMode(QwtPicker::AlwaysOn);
152   }
153
154   virtual ~WaterfallZoomer(){
155
156   }
157   
158   virtual void updateTrackerText(){
159     updateDisplay();
160   }
161
162 protected:
163   virtual QwtText trackerText( const QwtDoublePoint& p ) const 
164   {
165     QString yLabel("");
166
167     timespec lineTime = timespec_add(GetZeroTime(), (-p.y()) * GetSecondsPerLine());
168     struct tm timeTm;
169     gmtime_r(&lineTime.tv_sec, &timeTm);
170     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));
171
172     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));
173
174     return t;
175   }
176 };
177
178
179 const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR;
180 const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_WHITE_HOT;
181 const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_BLACK_HOT;
182 const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_INCANDESCENT;
183 const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_USER_DEFINED;
184
185 WaterfallDisplayPlot::WaterfallDisplayPlot(QWidget* parent)
186   : QwtPlot(parent)
187 {
188   _zoomer = NULL;
189   _startFrequency = 0;
190   _stopFrequency = 4000;
191   
192   resize(parent->width(), parent->height());
193   _numPoints = 1024;
194
195   _displayIntervalTime = (1.0/5.0); // 1/5 of a second between updates
196
197   _waterfallData = new WaterfallData(_startFrequency, _stopFrequency, _numPoints, 200);
198
199   QPalette palette;
200   palette.setColor(canvas()->backgroundRole(), QColor("white"));
201   canvas()->setPalette(palette);   
202
203   setAxisTitle(QwtPlot::xBottom, "Frequency (Hz)");
204   setAxisScaleDraw(QwtPlot::xBottom, new WaterfallFreqDisplayScaleDraw(0));
205
206   setAxisTitle(QwtPlot::yLeft, "Time");
207   setAxisScaleDraw(QwtPlot::yLeft, new QwtTimeScaleDraw());
208
209   timespec_reset(&_lastReplot);
210
211   d_spectrogram = new PlotWaterfall(_waterfallData, "Waterfall Display");
212
213   _intensityColorMapType = INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR;
214
215   QwtLinearColorMap colorMap(Qt::darkCyan, Qt::white);
216   colorMap.addColorStop(0.25, Qt::cyan);
217   colorMap.addColorStop(0.5, Qt::yellow);
218   colorMap.addColorStop(0.75, Qt::red);
219
220   d_spectrogram->setColorMap(colorMap);
221   
222   d_spectrogram->attach(this);
223   
224   // LeftButton for the zooming
225   // MidButton for the panning
226   // RightButton: zoom out by 1
227   // Ctrl+RighButton: zoom out to full size
228   
229   _zoomer = new WaterfallZoomer(canvas(), 0);
230 #if QT_VERSION < 0x040000
231   _zoomer->setMousePattern(QwtEventPattern::MouseSelect2,
232                            Qt::RightButton, Qt::ControlModifier);
233 #else
234   _zoomer->setMousePattern(QwtEventPattern::MouseSelect2,
235                            Qt::RightButton, Qt::ControlModifier);
236 #endif
237   _zoomer->setMousePattern(QwtEventPattern::MouseSelect3,
238                            Qt::RightButton);
239   
240   _panner = new QwtPlotPanner(canvas());
241   _panner->setAxisEnabled(QwtPlot::yRight, false);
242   _panner->setMouseButton(Qt::MidButton);
243   
244   // Avoid jumping when labels with more/less digits
245   // appear/disappear when scrolling vertically
246   
247   const QFontMetrics fm(axisWidget(QwtPlot::yLeft)->font());
248   QwtScaleDraw *sd = axisScaleDraw(QwtPlot::yLeft);
249   sd->setMinimumExtent( fm.width("100.00") );
250   
251   const QColor c(Qt::white);
252   _zoomer->setRubberBandPen(c);
253   _zoomer->setTrackerPen(c);
254
255   _UpdateIntensityRangeDisplay();
256 }
257
258 WaterfallDisplayPlot::~WaterfallDisplayPlot()
259 {
260   delete _waterfallData;
261 }
262
263 void 
264 WaterfallDisplayPlot::Reset()
265 {
266   _waterfallData->ResizeData(_startFrequency, _stopFrequency, _numPoints);
267   _waterfallData->Reset();
268
269   // Load up the new base zoom settings
270   QwtDoubleRect newSize = _zoomer->zoomBase();
271   newSize.setLeft(_startFrequency);
272   newSize.setWidth(_stopFrequency-_startFrequency);
273   _zoomer->zoom(newSize);
274   _zoomer->setZoomBase(newSize);
275   _zoomer->zoom(0);
276 }
277
278 void
279 WaterfallDisplayPlot::SetFrequencyRange(const double constStartFreq,
280                                         const double constStopFreq,
281                                         const double constCenterFreq,
282                                         const bool useCenterFrequencyFlag,
283                                         const double units, const std::string &strunits)
284 {
285   double startFreq = constStartFreq / units;
286   double stopFreq = constStopFreq / units;
287   double centerFreq = constCenterFreq / units;
288
289   if(stopFreq > startFreq) {
290     _startFrequency = 1000*startFreq;
291     _stopFrequency = 1000*stopFreq;
292
293     setAxisScale(QwtPlot::xBottom, _startFrequency, _stopFrequency);
294
295     if((axisScaleDraw(QwtPlot::xBottom) != NULL) && (_zoomer != NULL)){
296       WaterfallFreqDisplayScaleDraw* freqScale = ((WaterfallFreqDisplayScaleDraw*)axisScaleDraw(QwtPlot::xBottom));
297       freqScale->SetCenterFrequency(centerFreq);
298       ((WaterfallZoomer*)_zoomer)->SetCenterFrequency(centerFreq);
299
300       freqScale->SetFrequencyPrecision( 2 );
301       ((WaterfallZoomer*)_zoomer)->SetFrequencyPrecision( 2 );
302       setAxisTitle(QwtPlot::xBottom, QString("Frequency (%1)").arg(strunits.c_str()));
303     }
304
305     Reset();
306
307     // Only replot if screen is visible
308     if(isVisible()){
309       replot();
310     }
311   }
312 }
313
314
315 double WaterfallDisplayPlot::GetStartFrequency()const{
316   return _startFrequency;
317 }
318
319 double WaterfallDisplayPlot::GetStopFrequency()const{
320   return _stopFrequency;
321 }
322
323 void WaterfallDisplayPlot::PlotNewData(const double* dataPoints, const int64_t numDataPoints, const double timePerFFT, const timespec timestamp, const int droppedFrames){
324   if(numDataPoints > 0){
325     if(numDataPoints != _numPoints){
326       _numPoints = numDataPoints;
327
328       Reset();
329
330       d_spectrogram->invalidateCache();
331       d_spectrogram->itemChanged();
332
333       if(isVisible()){
334         replot();
335       }
336
337       _lastReplot = get_highres_clock();
338     }
339
340     _waterfallData->addFFTData(dataPoints, numDataPoints, droppedFrames);
341     _waterfallData->IncrementNumLinesToUpdate();
342
343     QwtTimeScaleDraw* timeScale = (QwtTimeScaleDraw*)axisScaleDraw(QwtPlot::yLeft);
344     timeScale->SetSecondsPerLine(timePerFFT);
345     timeScale->SetZeroTime(timestamp);
346
347     ((WaterfallZoomer*)_zoomer)->SetSecondsPerLine(timePerFFT);
348     ((WaterfallZoomer*)_zoomer)->SetZeroTime(timestamp);
349   }
350
351   // Allow at least a 50% duty cycle
352   if(diff_timespec(get_highres_clock(), _lastReplot) > _displayIntervalTime){
353
354     d_spectrogram->invalidateCache();
355     d_spectrogram->itemChanged();
356
357     // Only update when window is visible
358     if(isVisible()){
359       replot();
360     }
361
362     _lastReplot = get_highres_clock();
363   }
364 }
365
366 void WaterfallDisplayPlot::SetIntensityRange(const double minIntensity, const double maxIntensity){
367   _waterfallData->setRange(QwtDoubleInterval(minIntensity, maxIntensity));
368
369   emit UpdatedLowerIntensityLevel(minIntensity);
370   emit UpdatedUpperIntensityLevel(maxIntensity);
371
372   _UpdateIntensityRangeDisplay();
373 }
374
375 void WaterfallDisplayPlot::replot(){
376   const timespec startTime = get_highres_clock();
377
378   QwtTimeScaleDraw* timeScale = (QwtTimeScaleDraw*)axisScaleDraw(QwtPlot::yLeft);
379   timeScale->initiateUpdate();
380
381   WaterfallFreqDisplayScaleDraw* freqScale = (WaterfallFreqDisplayScaleDraw*)axisScaleDraw(QwtPlot::xBottom);
382   freqScale->initiateUpdate();
383
384   // Update the time axis display
385   if(axisWidget(QwtPlot::yLeft) != NULL){
386     axisWidget(QwtPlot::yLeft)->update();
387   }
388
389   // Update the Frequency Offset Display
390   if(axisWidget(QwtPlot::xBottom) != NULL){
391     axisWidget(QwtPlot::xBottom)->update();
392   }
393
394   if(_zoomer != NULL){
395     ((WaterfallZoomer*)_zoomer)->updateTrackerText();
396   }
397
398   QwtPlot::replot();
399
400   double differenceTime = (diff_timespec(get_highres_clock(), startTime));
401   
402   // Require at least a 5% duty cycle
403   differenceTime *= 19.0;
404   if(differenceTime > (1.0/5.0)){
405     _displayIntervalTime = differenceTime;
406   }
407 }
408
409 int WaterfallDisplayPlot::GetIntensityColorMapType()const{
410   return _intensityColorMapType;
411 }
412
413 void WaterfallDisplayPlot::SetIntensityColorMapType(const int newType, const QColor lowColor, const QColor highColor){
414   if((_intensityColorMapType != newType) || 
415      ((newType == INTENSITY_COLOR_MAP_TYPE_USER_DEFINED) &&
416       (lowColor.isValid() && highColor.isValid()))){
417     switch(newType){
418     case INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR:{
419       _intensityColorMapType = newType;
420       QwtLinearColorMap colorMap(Qt::darkCyan, Qt::white);
421       colorMap.addColorStop(0.25, Qt::cyan);
422       colorMap.addColorStop(0.5, Qt::yellow);
423       colorMap.addColorStop(0.75, Qt::red);
424       d_spectrogram->setColorMap(colorMap);
425       break;
426     }
427     case INTENSITY_COLOR_MAP_TYPE_WHITE_HOT:{
428       _intensityColorMapType = newType;
429       QwtLinearColorMap colorMap(Qt::black, Qt::white);
430       d_spectrogram->setColorMap(colorMap);
431       break;
432     }
433     case INTENSITY_COLOR_MAP_TYPE_BLACK_HOT:{
434       _intensityColorMapType = newType;
435       QwtLinearColorMap colorMap(Qt::white, Qt::black);
436       d_spectrogram->setColorMap(colorMap);
437       break;
438     }
439     case INTENSITY_COLOR_MAP_TYPE_INCANDESCENT:{
440       _intensityColorMapType = newType;
441       QwtLinearColorMap colorMap(Qt::black, Qt::white);
442       colorMap.addColorStop(0.5, Qt::darkRed);
443       d_spectrogram->setColorMap(colorMap);
444       break;
445     }
446     case INTENSITY_COLOR_MAP_TYPE_USER_DEFINED:{
447       _userDefinedLowIntensityColor = lowColor;
448       _userDefinedHighIntensityColor = highColor;
449       _intensityColorMapType = newType;
450       QwtLinearColorMap colorMap(_userDefinedLowIntensityColor, _userDefinedHighIntensityColor);
451       d_spectrogram->setColorMap(colorMap);
452       break;
453     }
454     default: break;
455     }
456     
457     _UpdateIntensityRangeDisplay();
458   }
459 }
460
461 const QColor WaterfallDisplayPlot::GetUserDefinedLowIntensityColor()const{
462   return _userDefinedLowIntensityColor;
463 }
464
465 const QColor WaterfallDisplayPlot::GetUserDefinedHighIntensityColor()const{
466   return _userDefinedHighIntensityColor;
467 }
468
469 void WaterfallDisplayPlot::_UpdateIntensityRangeDisplay(){
470   QwtScaleWidget *rightAxis = axisWidget(QwtPlot::yRight);
471   rightAxis->setTitle("Intensity (dB)");
472   rightAxis->setColorBarEnabled(true);
473   rightAxis->setColorMap(d_spectrogram->data()->range(),
474                          d_spectrogram->colorMap());
475
476   setAxisScale(QwtPlot::yRight, 
477                d_spectrogram->data()->range().minValue(),
478                d_spectrogram->data()->range().maxValue() );
479   enableAxis(QwtPlot::yRight);
480   
481   plotLayout()->setAlignCanvasToScales(true);
482
483   // Tell the display to redraw everything
484   d_spectrogram->invalidateCache();
485   d_spectrogram->itemChanged();
486
487   // Draw again
488   replot();
489
490   // Update the last replot timer
491   _lastReplot = get_highres_clock();
492 }
493
494 #endif /* WATERFALL_DISPLAY_PLOT_C */