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