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
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):QwtPlot(parent){
186   _zoomer = NULL;
187   _startFrequency = 0;
188   _stopFrequency = 4000;
189   
190   resize(parent->width(), parent->height());
191   _numPoints = 1024;
192
193   _displayIntervalTime = (1.0/5.0); // 1/5 of a second between updates
194
195   _waterfallData = new WaterfallData(_startFrequency, _stopFrequency, _numPoints, 200);
196
197   QPalette palette;
198   palette.setColor(canvas()->backgroundRole(), QColor("white"));
199   canvas()->setPalette(palette);   
200
201   setAxisTitle(QwtPlot::xBottom, "Frequency (Hz)");
202   setAxisScaleDraw(QwtPlot::xBottom, new WaterfallFreqDisplayScaleDraw(0));
203
204   setAxisTitle(QwtPlot::yLeft, "Time");
205   setAxisScaleDraw(QwtPlot::yLeft, new QwtTimeScaleDraw());
206
207   timespec_reset(&_lastReplot);
208
209   d_spectrogram = new PlotWaterfall(_waterfallData, "Waterfall Display");
210
211   _intensityColorMapType = INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR;
212
213   QwtLinearColorMap colorMap(Qt::darkCyan, Qt::white);
214   colorMap.addColorStop(0.25, Qt::cyan);
215   colorMap.addColorStop(0.5, Qt::yellow);
216   colorMap.addColorStop(0.75, Qt::red);
217
218   d_spectrogram->setColorMap(colorMap);
219   
220   d_spectrogram->attach(this);
221   
222   // LeftButton for the zooming
223   // MidButton for the panning
224   // RightButton: zoom out by 1
225   // Ctrl+RighButton: zoom out to full size
226   
227   _zoomer = new WaterfallZoomer(canvas(), 0);
228 #if QT_VERSION < 0x040000
229   _zoomer->setMousePattern(QwtEventPattern::MouseSelect2,
230                            Qt::RightButton, Qt::ControlModifier);
231 #else
232   _zoomer->setMousePattern(QwtEventPattern::MouseSelect2,
233                            Qt::RightButton, Qt::ControlModifier);
234 #endif
235   _zoomer->setMousePattern(QwtEventPattern::MouseSelect3,
236                            Qt::RightButton);
237   
238   _panner = new QwtPlotPanner(canvas());
239   _panner->setAxisEnabled(QwtPlot::yRight, false);
240   _panner->setMouseButton(Qt::MidButton);
241   
242   // Avoid jumping when labels with more/less digits
243   // appear/disappear when scrolling vertically
244   
245   const QFontMetrics fm(axisWidget(QwtPlot::yLeft)->font());
246   QwtScaleDraw *sd = axisScaleDraw(QwtPlot::yLeft);
247   sd->setMinimumExtent( fm.width("100.00") );
248   
249   const QColor c(Qt::white);
250   _zoomer->setRubberBandPen(c);
251   _zoomer->setTrackerPen(c);
252
253   _UpdateIntensityRangeDisplay();
254 }
255
256 WaterfallDisplayPlot::~WaterfallDisplayPlot(){
257   delete _waterfallData;
258 }
259
260 void WaterfallDisplayPlot::Reset(){
261   _waterfallData->ResizeData(_startFrequency, _stopFrequency, _numPoints);
262   _waterfallData->Reset();
263
264   // Load up the new base zoom settings
265   QwtDoubleRect newSize = _zoomer->zoomBase();
266   newSize.setLeft(_startFrequency);
267   newSize.setWidth(_stopFrequency-_startFrequency);
268   _zoomer->zoom(newSize);
269   _zoomer->setZoomBase(newSize);
270   _zoomer->zoom(0);
271 }
272
273 void WaterfallDisplayPlot::SetFrequencyRange(const double startFreq, const double stopFreq, const double centerFreq, const bool useCenterFrequencyFlag){
274   if((stopFreq > 0) && (stopFreq > startFreq)){
275     _startFrequency = startFreq;
276     _stopFrequency = stopFreq;
277
278     if((axisScaleDraw(QwtPlot::xBottom) != NULL) && (_zoomer != NULL)){
279       WaterfallFreqDisplayScaleDraw* freqScale = ((WaterfallFreqDisplayScaleDraw*)axisScaleDraw(QwtPlot::xBottom));
280       freqScale->SetCenterFrequency(centerFreq);
281       ((WaterfallZoomer*)_zoomer)->SetCenterFrequency(centerFreq);
282       if(useCenterFrequencyFlag){
283         freqScale->SetFrequencyPrecision( 3 );
284         ((WaterfallZoomer*)_zoomer)->SetFrequencyPrecision( 3 );
285         setAxisTitle(QwtPlot::xBottom, "Frequency (kHz)");
286       }
287       else{
288         freqScale->SetFrequencyPrecision( 0 );
289         ((WaterfallZoomer*)_zoomer)->SetFrequencyPrecision( 0 );
290         setAxisTitle(QwtPlot::xBottom, "Frequency (Hz)");
291       }
292     }
293
294     Reset();
295
296     // Only replot if screen is visible
297     if(isVisible()){
298       replot();
299     }
300   }
301 }
302
303
304 double WaterfallDisplayPlot::GetStartFrequency()const{
305   return _startFrequency;
306 }
307
308 double WaterfallDisplayPlot::GetStopFrequency()const{
309   return _stopFrequency;
310 }
311
312 void WaterfallDisplayPlot::PlotNewData(const double* dataPoints, const int64_t numDataPoints, const double timePerFFT, const timespec timestamp, const int droppedFrames){
313   if(numDataPoints > 0){
314     if(numDataPoints != _numPoints){
315       _numPoints = numDataPoints;
316
317       Reset();
318
319       d_spectrogram->invalidateCache();
320       d_spectrogram->itemChanged();
321
322       if(isVisible()){
323         replot();
324       }
325
326       _lastReplot = get_highres_clock();
327     }
328
329     _waterfallData->addFFTData(dataPoints, numDataPoints, droppedFrames);
330     _waterfallData->IncrementNumLinesToUpdate();
331
332     QwtTimeScaleDraw* timeScale = (QwtTimeScaleDraw*)axisScaleDraw(QwtPlot::yLeft);
333     timeScale->SetSecondsPerLine(timePerFFT);
334     timeScale->SetZeroTime(timestamp);
335
336     ((WaterfallZoomer*)_zoomer)->SetSecondsPerLine(timePerFFT);
337     ((WaterfallZoomer*)_zoomer)->SetZeroTime(timestamp);
338   }
339
340   // Allow at least a 50% duty cycle
341   if(diff_timespec(get_highres_clock(), _lastReplot) > _displayIntervalTime){
342
343     d_spectrogram->invalidateCache();
344     d_spectrogram->itemChanged();
345
346     // Only update when window is visible
347     if(isVisible()){
348       replot();
349     }
350
351     _lastReplot = get_highres_clock();
352   }
353 }
354
355 void WaterfallDisplayPlot::SetIntensityRange(const double minIntensity, const double maxIntensity){
356   _waterfallData->setRange(QwtDoubleInterval(minIntensity, maxIntensity));
357
358   emit UpdatedLowerIntensityLevel(minIntensity);
359   emit UpdatedUpperIntensityLevel(maxIntensity);
360
361   _UpdateIntensityRangeDisplay();
362 }
363
364 void WaterfallDisplayPlot::replot(){
365   const timespec startTime = get_highres_clock();
366
367   QwtTimeScaleDraw* timeScale = (QwtTimeScaleDraw*)axisScaleDraw(QwtPlot::yLeft);
368   timeScale->initiateUpdate();
369
370   WaterfallFreqDisplayScaleDraw* freqScale = (WaterfallFreqDisplayScaleDraw*)axisScaleDraw(QwtPlot::xBottom);
371   freqScale->initiateUpdate();
372
373   // Update the time axis display
374   if(axisWidget(QwtPlot::yLeft) != NULL){
375     axisWidget(QwtPlot::yLeft)->update();
376   }
377
378   // Update the Frequency Offset Display
379   if(axisWidget(QwtPlot::xBottom) != NULL){
380     axisWidget(QwtPlot::xBottom)->update();
381   }
382
383   if(_zoomer != NULL){
384     ((WaterfallZoomer*)_zoomer)->updateTrackerText();
385   }
386
387   QwtPlot::replot();
388
389   double differenceTime = (diff_timespec(get_highres_clock(), startTime));
390   
391   // Require at least a 5% duty cycle
392   differenceTime *= 19.0;
393   if(differenceTime > (1.0/5.0)){
394     _displayIntervalTime = differenceTime;
395   }
396 }
397
398 int WaterfallDisplayPlot::GetIntensityColorMapType()const{
399   return _intensityColorMapType;
400 }
401
402 void WaterfallDisplayPlot::SetIntensityColorMapType(const int newType, const QColor lowColor, const QColor highColor){
403   if((_intensityColorMapType != newType) || 
404      ((newType == INTENSITY_COLOR_MAP_TYPE_USER_DEFINED) &&
405       (lowColor.isValid() && highColor.isValid()))){
406     switch(newType){
407     case INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR:{
408       _intensityColorMapType = newType;
409       QwtLinearColorMap colorMap(Qt::darkCyan, Qt::white);
410       colorMap.addColorStop(0.25, Qt::cyan);
411       colorMap.addColorStop(0.5, Qt::yellow);
412       colorMap.addColorStop(0.75, Qt::red);
413       d_spectrogram->setColorMap(colorMap);
414       break;
415     }
416     case INTENSITY_COLOR_MAP_TYPE_WHITE_HOT:{
417       _intensityColorMapType = newType;
418       QwtLinearColorMap colorMap(Qt::black, Qt::white);
419       d_spectrogram->setColorMap(colorMap);
420       break;
421     }
422     case INTENSITY_COLOR_MAP_TYPE_BLACK_HOT:{
423       _intensityColorMapType = newType;
424       QwtLinearColorMap colorMap(Qt::white, Qt::black);
425       d_spectrogram->setColorMap(colorMap);
426       break;
427     }
428     case INTENSITY_COLOR_MAP_TYPE_INCANDESCENT:{
429       _intensityColorMapType = newType;
430       QwtLinearColorMap colorMap(Qt::black, Qt::white);
431       colorMap.addColorStop(0.5, Qt::darkRed);
432       d_spectrogram->setColorMap(colorMap);
433       break;
434     }
435     case INTENSITY_COLOR_MAP_TYPE_USER_DEFINED:{
436       _userDefinedLowIntensityColor = lowColor;
437       _userDefinedHighIntensityColor = highColor;
438       _intensityColorMapType = newType;
439       QwtLinearColorMap colorMap(_userDefinedLowIntensityColor, _userDefinedHighIntensityColor);
440       d_spectrogram->setColorMap(colorMap);
441       break;
442     }
443     default: break;
444     }
445     
446     _UpdateIntensityRangeDisplay();
447   }
448 }
449
450 const QColor WaterfallDisplayPlot::GetUserDefinedLowIntensityColor()const{
451   return _userDefinedLowIntensityColor;
452 }
453
454 const QColor WaterfallDisplayPlot::GetUserDefinedHighIntensityColor()const{
455   return _userDefinedHighIntensityColor;
456 }
457
458 void WaterfallDisplayPlot::_UpdateIntensityRangeDisplay(){
459   QwtScaleWidget *rightAxis = axisWidget(QwtPlot::yRight);
460   rightAxis->setTitle("Intensity (dB)");
461   rightAxis->setColorBarEnabled(true);
462   rightAxis->setColorMap(d_spectrogram->data()->range(),
463                          d_spectrogram->colorMap());
464   
465   setAxisScale(QwtPlot::yRight, 
466                d_spectrogram->data()->range().minValue(),
467                d_spectrogram->data()->range().maxValue() );
468   enableAxis(QwtPlot::yRight);
469   
470   plotLayout()->setAlignCanvasToScales(true);
471
472   // Tell the display to redraw everything
473   d_spectrogram->invalidateCache();
474   d_spectrogram->itemChanged();
475
476   // Draw again
477   replot();
478
479   // Update the last replot timer
480   _lastReplot = get_highres_clock();
481 }
482
483 #endif /* WATERFALL_DISPLAY_PLOT_C */