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