Fixing constellation zoomer's label.
[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, 
164                        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   void SetUnitType(const std::string &type)
184   {
185     _unitType = type;
186   }
187
188 protected:
189   virtual QwtText trackerText( const QwtDoublePoint& p ) const 
190   {
191     QString yLabel("");
192
193     timespec lineTime = timespec_add(GetZeroTime(), (-p.y()) * GetSecondsPerLine());
194     struct tm timeTm;
195     gmtime_r(&lineTime.tv_sec, &timeTm);
196     yLabel = (QString("").sprintf("%04d/%02d/%02d %02d:%02d:%02d.%03ld",
197                                   timeTm.tm_year+1900, timeTm.tm_mon+1,
198                                   timeTm.tm_mday, timeTm.tm_hour, timeTm.tm_min,
199                                   timeTm.tm_sec, lineTime.tv_nsec/1000000));
200
201     QwtText t(QString("%1 %2, %3").
202               arg(p.x(), 0, 'f', GetFrequencyPrecision()).
203               arg(_unitType.c_str()).arg(yLabel));
204     return t;
205   }
206
207 private:
208   std::string _unitType;
209 };
210
211
212 const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR;
213 const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_WHITE_HOT;
214 const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_BLACK_HOT;
215 const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_INCANDESCENT;
216 const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_USER_DEFINED;
217
218 WaterfallDisplayPlot::WaterfallDisplayPlot(QWidget* parent)
219   : QwtPlot(parent)
220 {
221   _zoomer = NULL;
222   _startFrequency = 0;
223   _stopFrequency = 4000;
224   
225   resize(parent->width(), parent->height());
226   _numPoints = 1024;
227
228   _waterfallData = new WaterfallData(_startFrequency, _stopFrequency, _numPoints, 200);
229
230   QPalette palette;
231   palette.setColor(canvas()->backgroundRole(), QColor("white"));
232   canvas()->setPalette(palette);   
233
234   setAxisTitle(QwtPlot::xBottom, "Frequency (Hz)");
235   setAxisScaleDraw(QwtPlot::xBottom, new WaterfallFreqDisplayScaleDraw(0));
236
237   setAxisTitle(QwtPlot::yLeft, "Time");
238   setAxisScaleDraw(QwtPlot::yLeft, new QwtTimeScaleDraw());
239
240   timespec_reset(&_lastReplot);
241
242   d_spectrogram = new PlotWaterfall(_waterfallData, "Waterfall Display");
243
244   _intensityColorMapType = INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR;
245
246   QwtLinearColorMap colorMap(Qt::darkCyan, Qt::white);
247   colorMap.addColorStop(0.25, Qt::cyan);
248   colorMap.addColorStop(0.5, Qt::yellow);
249   colorMap.addColorStop(0.75, Qt::red);
250
251   d_spectrogram->setColorMap(colorMap);
252   
253   d_spectrogram->attach(this);
254   
255   // LeftButton for the zooming
256   // MidButton for the panning
257   // RightButton: zoom out by 1
258   // Ctrl+RighButton: zoom out to full size
259   
260   _zoomer = new WaterfallZoomer(canvas(), 0);
261 #if QT_VERSION < 0x040000
262   _zoomer->setMousePattern(QwtEventPattern::MouseSelect2,
263                            Qt::RightButton, Qt::ControlModifier);
264 #else
265   _zoomer->setMousePattern(QwtEventPattern::MouseSelect2,
266                            Qt::RightButton, Qt::ControlModifier);
267 #endif
268   _zoomer->setMousePattern(QwtEventPattern::MouseSelect3,
269                            Qt::RightButton);
270   
271   _panner = new QwtPlotPanner(canvas());
272   _panner->setAxisEnabled(QwtPlot::yRight, false);
273   _panner->setMouseButton(Qt::MidButton);
274   
275   // Avoid jumping when labels with more/less digits
276   // appear/disappear when scrolling vertically
277   
278   const QFontMetrics fm(axisWidget(QwtPlot::yLeft)->font());
279   QwtScaleDraw *sd = axisScaleDraw(QwtPlot::yLeft);
280   sd->setMinimumExtent( fm.width("100.00") );
281   
282   const QColor c(Qt::white);
283   _zoomer->setRubberBandPen(c);
284   _zoomer->setTrackerPen(c);
285
286   _UpdateIntensityRangeDisplay();
287 }
288
289 WaterfallDisplayPlot::~WaterfallDisplayPlot()
290 {
291   delete _waterfallData;
292 }
293
294 void 
295 WaterfallDisplayPlot::Reset()
296 {
297   _waterfallData->ResizeData(_startFrequency, _stopFrequency, _numPoints);
298   _waterfallData->Reset();
299
300   // Load up the new base zoom settings
301   QwtDoubleRect newSize = _zoomer->zoomBase();
302   newSize.setLeft(_startFrequency);
303   newSize.setWidth(_stopFrequency-_startFrequency);
304   _zoomer->zoom(newSize);
305   _zoomer->setZoomBase(newSize);
306   _zoomer->zoom(0);
307 }
308
309 void
310 WaterfallDisplayPlot::SetFrequencyRange(const double constStartFreq,
311                                         const double constStopFreq,
312                                         const double constCenterFreq,
313                                         const bool useCenterFrequencyFlag,
314                                         const double units, const std::string &strunits)
315 {
316   double startFreq = constStartFreq / units;
317   double stopFreq = constStopFreq / units;
318   double centerFreq = constCenterFreq / units;
319
320   _useCenterFrequencyFlag = useCenterFrequencyFlag;
321
322   if(_useCenterFrequencyFlag){
323     startFreq = (startFreq + centerFreq);
324     stopFreq = (stopFreq + centerFreq);
325   }
326
327   bool reset = false;
328   if((startFreq != _startFrequency) || (stopFreq != _stopFrequency))
329     reset = true;
330
331   if(stopFreq > startFreq) {
332     _startFrequency = startFreq;
333     _stopFrequency = stopFreq;
334
335  
336     if((axisScaleDraw(QwtPlot::xBottom) != NULL) && (_zoomer != NULL)){
337       double display_units = ceil(log10(units)/2.0);
338       setAxisScale(QwtPlot::xBottom, _startFrequency, _stopFrequency);
339       setAxisScaleDraw(QwtPlot::xBottom, new WaterfallFreqDisplayScaleDraw(display_units));
340
341       if(reset) {
342         Reset();
343       }
344
345       ((WaterfallZoomer*)_zoomer)->SetFrequencyPrecision(display_units);
346       ((WaterfallZoomer*)_zoomer)->SetUnitType(strunits);
347
348       // Load up the new base zoom settings
349       _zoomer->setZoomBase();
350       
351       // Zooms back to the base and clears any other zoom levels
352       _zoomer->zoom(0);
353     }
354   }
355 }
356
357
358 double
359 WaterfallDisplayPlot::GetStartFrequency() const
360 {
361   return _startFrequency;
362 }
363
364 double
365 WaterfallDisplayPlot::GetStopFrequency() const
366 {
367   return _stopFrequency;
368 }
369
370 void
371 WaterfallDisplayPlot::PlotNewData(const double* dataPoints, 
372                                   const int64_t numDataPoints,
373                                   const double timePerFFT,
374                                   const timespec timestamp,
375                                   const int droppedFrames)
376 {
377   if(numDataPoints > 0){
378     if(numDataPoints != _numPoints){
379       _numPoints = numDataPoints;
380       
381       Reset();
382       
383       d_spectrogram->invalidateCache();
384       d_spectrogram->itemChanged();
385       
386       if(isVisible()){
387         replot();
388       }
389       
390       _lastReplot = get_highres_clock();
391     }
392
393     if(diff_timespec(get_highres_clock(), _lastReplot) > timePerFFT) {
394       //FIXME: We may want to average the data between these updates to smooth display
395       _waterfallData->addFFTData(dataPoints, numDataPoints, droppedFrames);
396       _waterfallData->IncrementNumLinesToUpdate();
397       
398       QwtTimeScaleDraw* timeScale = (QwtTimeScaleDraw*)axisScaleDraw(QwtPlot::yLeft);
399       timeScale->SetSecondsPerLine(timePerFFT);
400       timeScale->SetZeroTime(timestamp);
401       
402       ((WaterfallZoomer*)_zoomer)->SetSecondsPerLine(timePerFFT);
403       ((WaterfallZoomer*)_zoomer)->SetZeroTime(timestamp);
404       
405       d_spectrogram->invalidateCache();
406       d_spectrogram->itemChanged();
407       
408       replot();
409
410       _lastReplot = get_highres_clock();
411     }
412   }
413 }
414
415 void
416 WaterfallDisplayPlot::SetIntensityRange(const double minIntensity, 
417                                              const double maxIntensity)
418 {
419   _waterfallData->setRange(QwtDoubleInterval(minIntensity, maxIntensity));
420
421   emit UpdatedLowerIntensityLevel(minIntensity);
422   emit UpdatedUpperIntensityLevel(maxIntensity);
423
424   _UpdateIntensityRangeDisplay();
425 }
426
427 void
428 WaterfallDisplayPlot::replot()
429 {
430   QwtTimeScaleDraw* timeScale = (QwtTimeScaleDraw*)axisScaleDraw(QwtPlot::yLeft);
431   timeScale->initiateUpdate();
432
433   WaterfallFreqDisplayScaleDraw* freqScale = (WaterfallFreqDisplayScaleDraw*)axisScaleDraw(QwtPlot::xBottom);
434   freqScale->initiateUpdate();
435
436   // Update the time axis display
437   if(axisWidget(QwtPlot::yLeft) != NULL){
438     axisWidget(QwtPlot::yLeft)->update();
439   }
440
441   // Update the Frequency Offset Display
442   if(axisWidget(QwtPlot::xBottom) != NULL){
443     axisWidget(QwtPlot::xBottom)->update();
444   }
445
446   if(_zoomer != NULL){
447     ((WaterfallZoomer*)_zoomer)->updateTrackerText();
448   }
449
450   QwtPlot::replot();
451 }
452
453 void
454 WaterfallDisplayPlot::resizeSlot( QSize *s )
455 {
456   resize(s->width(), s->height());
457 }
458
459 int
460 WaterfallDisplayPlot::GetIntensityColorMapType() const
461 {
462   return _intensityColorMapType;
463 }
464
465 void
466 WaterfallDisplayPlot::SetIntensityColorMapType(const int newType, 
467                                                const QColor lowColor, 
468                                                const QColor highColor)
469 {
470   if((_intensityColorMapType != newType) || 
471      ((newType == INTENSITY_COLOR_MAP_TYPE_USER_DEFINED) &&
472       (lowColor.isValid() && highColor.isValid()))){
473     switch(newType){
474     case INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR:{
475       _intensityColorMapType = newType;
476       QwtLinearColorMap colorMap(Qt::darkCyan, Qt::white);
477       colorMap.addColorStop(0.25, Qt::cyan);
478       colorMap.addColorStop(0.5, Qt::yellow);
479       colorMap.addColorStop(0.75, Qt::red);
480       d_spectrogram->setColorMap(colorMap);
481       break;
482     }
483     case INTENSITY_COLOR_MAP_TYPE_WHITE_HOT:{
484       _intensityColorMapType = newType;
485       QwtLinearColorMap colorMap(Qt::black, Qt::white);
486       d_spectrogram->setColorMap(colorMap);
487       break;
488     }
489     case INTENSITY_COLOR_MAP_TYPE_BLACK_HOT:{
490       _intensityColorMapType = newType;
491       QwtLinearColorMap colorMap(Qt::white, Qt::black);
492       d_spectrogram->setColorMap(colorMap);
493       break;
494     }
495     case INTENSITY_COLOR_MAP_TYPE_INCANDESCENT:{
496       _intensityColorMapType = newType;
497       QwtLinearColorMap colorMap(Qt::black, Qt::white);
498       colorMap.addColorStop(0.5, Qt::darkRed);
499       d_spectrogram->setColorMap(colorMap);
500       break;
501     }
502     case INTENSITY_COLOR_MAP_TYPE_USER_DEFINED:{
503       _userDefinedLowIntensityColor = lowColor;
504       _userDefinedHighIntensityColor = highColor;
505       _intensityColorMapType = newType;
506       QwtLinearColorMap colorMap(_userDefinedLowIntensityColor, _userDefinedHighIntensityColor);
507       d_spectrogram->setColorMap(colorMap);
508       break;
509     }
510     default: break;
511     }
512     
513     _UpdateIntensityRangeDisplay();
514   }
515 }
516
517 const QColor
518 WaterfallDisplayPlot::GetUserDefinedLowIntensityColor() const
519 {
520   return _userDefinedLowIntensityColor;
521 }
522
523 const QColor
524 WaterfallDisplayPlot::GetUserDefinedHighIntensityColor() const
525 {
526   return _userDefinedHighIntensityColor;
527 }
528
529 void
530 WaterfallDisplayPlot::_UpdateIntensityRangeDisplay()
531 {
532   QwtScaleWidget *rightAxis = axisWidget(QwtPlot::yRight);
533   rightAxis->setTitle("Intensity (dB)");
534   rightAxis->setColorBarEnabled(true);
535   rightAxis->setColorMap(d_spectrogram->data()->range(),
536                          d_spectrogram->colorMap());
537
538   setAxisScale(QwtPlot::yRight, 
539                d_spectrogram->data()->range().minValue(),
540                d_spectrogram->data()->range().maxValue() );
541   enableAxis(QwtPlot::yRight);
542   
543   plotLayout()->setAlignCanvasToScales(true);
544
545   // Tell the display to redraw everything
546   d_spectrogram->invalidateCache();
547   d_spectrogram->itemChanged();
548
549   // Draw again
550   replot();
551
552   // Update the last replot timer
553   _lastReplot = get_highres_clock();
554 }
555
556 #endif /* WATERFALL_DISPLAY_PLOT_C */