Audacity  3.0.3
Ruler.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  Ruler.cpp
6 
7  Dominic Mazzoni
8 
9 *******************************************************************//***************************************************************//***************************************************************//******************************************************************/
56 
57 
58 #include "Ruler.h"
59 
60 #include <wx/dcclient.h>
61 #include <wx/dcscreen.h>
62 
63 #include "../AColor.h"
64 #include "../AllThemeResources.h"
65 #include "../Envelope.h"
66 #include "NumberScale.h"
67 #include "../Theme.h"
68 #include "ViewInfo.h"
69 
70 using std::min;
71 using std::max;
72 
73 //wxColour Ruler::mTickColour{ 153, 153, 153 };
74 
75 //
76 // Ruler
77 //
78 
80 {
81  mMin = mHiddenMin = 0.0;
82  mMax = mHiddenMax = 100.0;
83  mOrientation = wxHORIZONTAL;
84  mSpacing = 6;
85  mHasSetSpacing = false;
87  mFlip = false;
88  mLog = false;
89  mLabelEdges = false;
90 
91  mLeft = -1;
92  mTop = -1;
93  mRight = -1;
94  mBottom = -1;
95  mbTicksOnly = true;
96  mbTicksAtExtremes = false;
97  mTickColour = wxColour( theTheme.Colour( clrTrackPanelText ));
98  mPen.SetColour(mTickColour);
99  mDbMirrorValue = 0.0;
100 
101  // Note: the font size is now adjusted automatically whenever
102  // Invalidate is called on a horizontal Ruler, unless the user
103  // calls SetFonts manually. So the defaults here are not used
104  // often.
105 
106  int fontSize = 10;
107 #ifdef __WXMSW__
108  fontSize = 8;
109 #endif
110 
111  mLength = 0;
112 
113  mCustom = false;
114  mbMinor = true;
115 
116  mTwoTone = false;
117 
118  mUseZoomInfo = NULL;
119 }
120 
122 {
123  Invalidate(); // frees up our arrays
124 }
125 
126 void Ruler::SetTwoTone(bool twoTone)
127 {
128  mTwoTone = twoTone;
129 }
130 
132 {
133  // IntFormat, RealFormat, RealLogFormat, TimeFormat, or LinearDBFormat
134 
135  if (mFormat != format) {
136  mFormat = format;
137 
138  Invalidate();
139  }
140 }
141 
142 void Ruler::SetLog(bool log)
143 {
144  // Logarithmic
145 
146  if (mLog != log) {
147  mLog = log;
148 
149  Invalidate();
150  }
151 }
152 
154 {
155  // Specify the name of the units (like "dB") if you
156  // want numbers like "1.6" formatted as "1.6 dB".
157 
158  if (mUnits != units) {
159  mUnits = units;
160 
161  Invalidate();
162  }
163 }
164 
165 void Ruler::SetDbMirrorValue( const double d )
166 {
167  if (mDbMirrorValue != d) {
168  mDbMirrorValue = d;
169 
170  Invalidate();
171  }
172 }
173 
174 void Ruler::SetOrientation(int orient)
175 {
176  // wxHORIZONTAL || wxVERTICAL
177 
178  if (mOrientation != orient) {
179  mOrientation = orient;
180 
181  if (mOrientation == wxVERTICAL && !mHasSetSpacing)
182  mSpacing = 2;
183 
184  Invalidate();
185  }
186 }
187 
188 void Ruler::SetRange(double min, double max)
189 {
190  SetRange(min, max, min, max);
191 }
192 
194  (double min, double max, double hiddenMin, double hiddenMax)
195 {
196  // For a horizontal ruler,
197  // min is the value in the center of pixel "left",
198  // max is the value in the center of pixel "right".
199 
200  // In the special case of a time ruler,
201  // hiddenMin and hiddenMax are values that would be shown with the fisheye
202  // turned off. In other cases they equal min and max respectively.
203 
204  if (mMin != min || mMax != max ||
205  mHiddenMin != hiddenMin || mHiddenMax != hiddenMax) {
206  mMin = min;
207  mMax = max;
208  mHiddenMin = hiddenMin;
209  mHiddenMax = hiddenMax;
210 
211  Invalidate();
212  }
213 }
214 
215 void Ruler::SetSpacing(int spacing)
216 {
217  mHasSetSpacing = true;
218 
219  if (mSpacing != spacing) {
220  mSpacing = spacing;
221 
222  Invalidate();
223  }
224 }
225 
226 void Ruler::SetLabelEdges(bool labelEdges)
227 {
228  // If this is true, the edges of the ruler will always
229  // receive a label. If not, the nearest round number is
230  // labeled (which may or may not be the edge).
231 
232  if (mLabelEdges != labelEdges) {
233  mLabelEdges = labelEdges;
234 
235  Invalidate();
236  }
237 }
238 
239 void Ruler::SetFlip(bool flip)
240 {
241  // If this is true, the orientation of the tick marks
242  // is reversed from the default; eg. above the line
243  // instead of below
244 
245  if (mFlip != flip) {
246  mFlip = flip;
247 
248  Invalidate();
249  }
250 }
251 
252 void Ruler::SetMinor(bool value)
253 {
254  mbMinor = value;
255 }
256 
257 namespace {
259  wxCoord &height, wxCoord &lead, wxDC &dc, const wxFont &font )
260 {
261  wxCoord strW, strH, strD, strL;
262  static const wxString exampleText = wxT("0.9"); //ignored for height calcs on all platforms
263  dc.SetFont( font );
264  dc.GetTextExtent(exampleText, &strW, &strH, &strD, &strL);
265  height = strH - strD - strL;
266  lead = strL;
267 }
268 
270  wxCoord &height, wxCoord &lead,
271  wxDC &dc, int fontSize, wxFontWeight weight = wxFONTWEIGHT_NORMAL )
272 {
273  const wxFont font{ fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, weight };
274  FindFontHeights( height, lead, dc, font );
275 }
276 }
277 
278 void Ruler::SetFonts(const wxFont &minorFont, const wxFont &majorFont, const wxFont &minorMinorFont)
279 {
280  // Won't override these fonts
281 
282  mpUserFonts = std::make_unique<Fonts>(
283  Fonts{ majorFont, minorFont, minorMinorFont, 0 } );
284 
285  wxScreenDC dc;
286  wxCoord height;
287  FindFontHeights( height, mpUserFonts->lead, dc, majorFont );
288 
289  mpFonts.reset();
290  mpFonts.reset();
291  Invalidate();
292 }
293 
295 {
296  if ( mNumberScale != scale ) {
297  mNumberScale = scale;
298  Invalidate();
299  }
300 }
301 
302 void Ruler::OfflimitsPixels(int start, int end)
303 {
304  int length = mLength;
305  if (mOrientation == wxHORIZONTAL)
306  length = mRight - mLeft;
307  else
308  length = mBottom - mTop;
309  if( length < 0 )
310  return;
311 
312  auto size = static_cast<size_t>( length + 1 );
313  if ( mUserBits.size() < size ) {
314  mLength = length;
315  mUserBits.resize( size, false );
316  }
317 
318  if (end < start)
319  std::swap( start, end );
320 
321  if (start < 0)
322  start = 0;
323  if (end > mLength)
324  end = mLength;
325 
326  for(int i = start; i <= end; i++)
327  mUserBits[i] = true;
328 
329  Invalidate();
330 }
331 
332 void Ruler::SetBounds(int left, int top, int right, int bottom)
333 {
334  if (mLeft != left || mTop != top ||
335  mRight != right || mBottom != bottom) {
336  mLeft = left;
337  mTop = top;
338  mRight = right;
339  mBottom = bottom;
340 
341  Invalidate();
342  }
343 }
344 
346 {
347  if (mOrientation == wxHORIZONTAL)
348  mLength = mRight-mLeft;
349  else
350  mLength = mBottom-mTop;
351 
352  mpCache.reset();
353  // Bug 2316 we must preserve off-limit pixels.
354  // mUserBits.clear();
355 }
356 
358 {
359  bool useMajor = true;
360 
361  double mMajor;
362  double mMinor;
363 
364  int mDigits;
365 
366 TickSizes( double UPP, int orientation, RulerFormat format, bool log )
367 {
368  //TODO: better dynamic digit computation for the log case
369  (void)log;
370 
371  // Given the dimensions of the ruler, the range of values it
372  // has to display, and the format (i.e. Int, Real, Time),
373  // figure out how many units are in one Minor tick, and
374  // in one Major tick.
375  //
376  // The goal is to always put tick marks on nice round numbers
377  // that are easy for humans to grok. This is the most tricky
378  // with time.
379 
380  double d;
381 
382  // As a heuristic, we want at least 22 pixels between each
383  // minor tick. We want to show numbers like "-48"
384  // in that space.
385  // If vertical, we don't need as much space.
386  double units = ((orientation == wxHORIZONTAL) ? 22 : 16) * fabs(UPP);
387 
388  mDigits = 0;
389 
390  switch(format) {
391  case LinearDBFormat:
392  if (units < 0.001) {
393  mMinor = 0.001;
394  mMajor = 0.005;
395  return;
396  }
397  if (units < 0.01) {
398  mMinor = 0.01;
399  mMajor = 0.05;
400  return;
401  }
402  if (units < 0.1) {
403  mMinor = 0.1;
404  mMajor = 0.5;
405  return;
406  }
407  if (units < 1.0) {
408  mMinor = 1.0;
409  mMajor = 6.0;
410  return;
411  }
412  if (units < 3.0) {
413  mMinor = 3.0;
414  mMajor = 12.0;
415  return;
416  }
417  if (units < 6.0) {
418  mMinor = 6.0;
419  mMajor = 24.0;
420  return;
421  }
422  if (units < 12.0) {
423  mMinor = 12.0;
424  mMajor = 48.0;
425  return;
426  }
427  if (units < 24.0) {
428  mMinor = 24.0;
429  mMajor = 96.0;
430  return;
431  }
432  d = 20.0;
433  for(;;) {
434  if (units < d) {
435  mMinor = d;
436  mMajor = d*5.0;
437  return;
438  }
439  d *= 5.0;
440  if (units < d) {
441  mMinor = d;
442  mMajor = d*5.0;
443  return;
444  }
445  d *= 2.0;
446  }
447  break;
448 
449  case IntFormat:
450  d = 1.0;
451  for(;;) {
452  if (units < d) {
453  mMinor = d;
454  mMajor = d*5.0;
455  return;
456  }
457  d *= 5.0;
458  if (units < d) {
459  mMinor = d;
460  mMajor = d*2.0;
461  return;
462  }
463  d *= 2.0;
464  }
465  break;
466 
467  case TimeFormat:
468  if (units > 0.5) {
469  if (units < 1.0) { // 1 sec
470  mMinor = 1.0;
471  mMajor = 5.0;
472  return;
473  }
474  if (units < 5.0) { // 5 sec
475  mMinor = 5.0;
476  mMajor = 15.0;
477  return;
478  }
479  if (units < 10.0) {
480  mMinor = 10.0;
481  mMajor = 30.0;
482  return;
483  }
484  if (units < 15.0) {
485  mMinor = 15.0;
486  mMajor = 60.0;
487  return;
488  }
489  if (units < 30.0) {
490  mMinor = 30.0;
491  mMajor = 60.0;
492  return;
493  }
494  if (units < 60.0) { // 1 min
495  mMinor = 60.0;
496  mMajor = 300.0;
497  return;
498  }
499  if (units < 300.0) { // 5 min
500  mMinor = 300.0;
501  mMajor = 900.0;
502  return;
503  }
504  if (units < 600.0) { // 10 min
505  mMinor = 600.0;
506  mMajor = 1800.0;
507  return;
508  }
509  if (units < 900.0) { // 15 min
510  mMinor = 900.0;
511  mMajor = 3600.0;
512  return;
513  }
514  if (units < 1800.0) { // 30 min
515  mMinor = 1800.0;
516  mMajor = 3600.0;
517  return;
518  }
519  if (units < 3600.0) { // 1 hr
520  mMinor = 3600.0;
521  mMajor = 6*3600.0;
522  return;
523  }
524  if (units < 6*3600.0) { // 6 hrs
525  mMinor = 6*3600.0;
526  mMajor = 24*3600.0;
527  return;
528  }
529  if (units < 24*3600.0) { // 1 day
530  mMinor = 24*3600.0;
531  mMajor = 7*24*3600.0;
532  return;
533  }
534 
535  mMinor = 24.0 * 7.0 * 3600.0; // 1 week
536  mMajor = 24.0 * 7.0 * 3600.0;
537  }
538 
539  // Otherwise fall through to RealFormat
540  // (fractions of a second should be dealt with
541  // the same way as for RealFormat)
542 
543  case RealFormat:
544  d = 0.000001;
545  // mDigits is number of digits after the decimal point.
546  mDigits = 6;
547  for(;;) {
548  if (units < d) {
549  mMinor = d;
550  mMajor = d*5.0;
551  return;
552  }
553  d *= 5.0;
554  if (units < d) {
555  mMinor = d;
556  mMajor = d*2.0;
557  return;
558  }
559  d *= 2.0;
560  mDigits--;
561  // More than 10 digit numbers? Something is badly wrong.
562  // Probably units is coming in with too high a value.
563  wxASSERT( mDigits >= -10 );
564  if( mDigits < -10 )
565  break;
566  }
567  mMinor = d;
568  mMajor = d * 2.0;
569  break;
570 
571  case RealLogFormat:
572  d = 0.000001;
573  // mDigits is number of digits after the decimal point.
574  mDigits = 6;
575  for(;;) {
576  if (units < d) {
577  mMinor = d;
578  mMajor = d*5.0;
579  return;
580  }
581  d *= 5.0;
582  if (units < d) {
583  mMinor = d;
584  mMajor = d*2.0;
585  return;
586  }
587  d *= 2.0;
588  mDigits--;
589  // More than 10 digit numbers? Something is badly wrong.
590  // Probably units is coming in with too high a value.
591  wxASSERT( mDigits >= -10 );
592  if( mDigits < -10 )
593  break;
594  }
595  mDigits++;
596  mMinor = d;
597  mMajor = d * 2.0;
598  break;
599  }
600 }
601 
603  double d, RulerFormat format, const TranslatableString &units )
604  const
605 {
606  // Given a value, turn it into a string according
607  // to the current ruler format. The number of digits of
608  // accuracy depends on the resolution of the ruler,
609  // i.e. how far zoomed in or out you are.
610 
611  wxString s;
612 
613  // PRL Todo: are all these cases properly localized? (Decimal points,
614  // hour-minute-second, etc.?)
615 
616  // Replace -0 with 0
617  if (d < 0.0 && (d+mMinor > 0.0) && ( format != RealLogFormat ))
618  d = 0.0;
619 
620  switch( format ) {
621  case IntFormat:
622  s.Printf(wxT("%d"), (int)floor(d+0.5));
623  break;
624  case LinearDBFormat:
625  if (mMinor >= 1.0)
626  s.Printf(wxT("%d"), (int)floor(d+0.5));
627  else {
628  int precision = -log10(mMinor);
629  s.Printf(wxT("%.*f"), precision, d);
630  }
631  break;
632  case RealFormat:
633  if (mMinor >= 1.0)
634  s.Printf(wxT("%d"), (int)floor(d+0.5));
635  else {
636  s.Printf(wxString::Format(wxT("%%.%df"), mDigits), d);
637  }
638  break;
639  case RealLogFormat:
640  if (mMinor >= 1.0)
641  s.Printf(wxT("%d"), (int)floor(d+0.5));
642  else {
643  s.Printf(wxString::Format(wxT("%%.%df"), mDigits), d);
644  }
645  break;
646  case TimeFormat:
647  if (useMajor) {
648  if (d < 0) {
649  s = wxT("-");
650  d = -d;
651  }
652 
653  #if ALWAYS_HH_MM_SS
654  int secs = (int)(d + 0.5);
655  if (mMinor >= 1.0) {
656  s.Printf(wxT("%d:%02d:%02d"), secs/3600, (secs/60)%60, secs%60);
657  }
658  else {
659  wxString t1, t2, format;
660  t1.Printf(wxT("%d:%02d:"), secs/3600, (secs/60)%60);
661  format.Printf(wxT("%%0%d.%dlf"), mDigits+3, mDigits);
662  t2.Printf(format, fmod(d, 60.0));
663  s += t1 + t2;
664  }
665  break;
666  #endif
667 
668  if (mMinor >= 3600.0) {
669  int hrs = (int)(d / 3600.0 + 0.5);
670  wxString h;
671  h.Printf(wxT("%d:00:00"), hrs);
672  s += h;
673  }
674  else if (mMinor >= 60.0) {
675  int minutes = (int)(d / 60.0 + 0.5);
676  wxString m;
677  if (minutes >= 60)
678  m.Printf(wxT("%d:%02d:00"), minutes/60, minutes%60);
679  else
680  m.Printf(wxT("%d:00"), minutes);
681  s += m;
682  }
683  else if (mMinor >= 1.0) {
684  int secs = (int)(d + 0.5);
685  wxString t;
686  if (secs >= 3600)
687  t.Printf(wxT("%d:%02d:%02d"), secs/3600, (secs/60)%60, secs%60);
688  else if (secs >= 60)
689  t.Printf(wxT("%d:%02d"), secs/60, secs%60);
690  else
691  t.Printf(wxT("%d"), secs);
692  s += t;
693  }
694  else {
695 // Commented out old and incorrect code for avoiding the 40mins and 60 seconds problem
696 // It was causing Bug 463 - Incorrect Timeline numbering (where at high zoom and long tracks,
697 // numbers did not change.
698 #if 0
699  // The casting to float is working around an issue where 59 seconds
700  // would show up as 60 when using g++ (Ubuntu 4.3.3-5ubuntu4) 4.3.3.
701  int secs = (int)(float)(d);
702  wxString t1, t2, format;
703 
704  if (secs >= 3600)
705  t1.Printf(wxT("%d:%02d:"), secs/3600, (secs/60)%60);
706  else if (secs >= 60)
707  t1.Printf(wxT("%d:"), secs/60);
708 
709  if (secs >= 60)
710  format.Printf(wxT("%%0%d.%dlf"), mDigits+3, mDigits);
711  else
712  format.Printf(wxT("%%%d.%dlf"), mDigits+3, mDigits);
713  // The casting to float is working around an issue where 59 seconds
714  // would show up as 60 when using g++ (Ubuntu 4.3.3-5ubuntu4) 4.3.3.
715  t2.Printf(format, fmod((float)d, (float)60.0));
716 #else
717  // For d in the range of hours, d is just very slightly below the value it should
718  // have, because of using a double, which in turn yields values like 59:59:999999
719  // mins:secs:nanosecs when we want 1:00:00:000000
720  // so adjust by less than a nano second per hour to get nicer number formatting.
721  double dd = d * 1.000000000000001;
722  int secs = (int)(dd);
723  wxString t1, t2, format;
724 
725  if (secs >= 3600)
726  t1.Printf(wxT("%d:%02d:"), secs/3600, (secs/60)%60);
727  else if (secs >= 60)
728  t1.Printf(wxT("%d:"), secs/60);
729 
730  if (secs >= 60)
731  format.Printf(wxT("%%0%d.%dlf"), mDigits+3, mDigits);
732  else
733  format.Printf(wxT("%%%d.%dlf"), mDigits+3, mDigits);
734  // dd will be reduced to just the seconds and fractional part.
735  dd = dd - secs + (secs%60);
736  // truncate to appropriate number of digits, so that the print formatting
737  // doesn't round up 59.9999999 to 60.
738  double multiplier = pow( 10, mDigits);
739  dd = ((int)(dd * multiplier))/multiplier;
740  t2.Printf(format, dd);
741 #endif
742  s += t1 + t2;
743  }
744  }
745  else {
746  }
747  }
748 
749  auto result = Verbatim( s );
750  if (!units.empty())
751  result += units;
752 
753  return result;
754 }
755 
756 }; // struct Ruler::TickSizes
757 
759  Label lab,
760  wxDC &dc, wxFont font,
761  std::vector<bool> &bits,
762  int left, int top, int spacing, int lead,
763  bool flip, int orientation )
764  -> std::pair< wxRect, Label >
765 {
766  lab.lx = left - 1000; // don't display
767  lab.ly = top - 1000; // don't display
768 
769  auto length = bits.size() - 1;
770  auto pos = lab.pos;
771 
772  dc.SetFont( font );
773 
774  wxCoord strW, strH, strD, strL;
775  auto str = lab.text;
776  // Do not put the text into results until we are sure it does not overlap
777  lab.text = {};
778  dc.GetTextExtent(str.Translation(), &strW, &strH, &strD, &strL);
779 
780  int strPos, strLen, strLeft, strTop;
781  if ( orientation == wxHORIZONTAL ) {
782  strLen = strW;
783  strPos = pos - strW/2;
784  if (strPos < 0)
785  strPos = 0;
786  if (strPos + strW >= length)
787  strPos = length - strW;
788  strLeft = left + strPos;
789  if ( flip )
790  strTop = top + 4;
791  else
792  strTop = -strH - lead;
793 // strTop = top - lead + 4;// More space was needed...
794  }
795  else {
796  strLen = strH;
797  strPos = pos - strH/2;
798  if (strPos < 0)
799  strPos = 0;
800  if (strPos + strH >= length)
801  strPos = length - strH;
802  strTop = top + strPos;
803  if ( flip )
804  strLeft = left + 5;
805  else
806  strLeft = -strW - 6;
807  }
808 
809  // FIXME: we shouldn't even get here if strPos < 0.
810  // Ruler code currently does not handle very small or
811  // negative sized windows (i.e. don't draw) properly.
812  if( strPos < 0 )
813  return { {}, lab };
814 
815  // See if any of the pixels we need to draw this
816  // label is already covered
817 
818  int i;
819  for(i=0; i<strLen; i++)
820  if ( bits[strPos+i] )
821  return { {}, lab };
822 
823  // If not, position the label
824 
825  lab.lx = strLeft;
826  lab.ly = strTop;
827 
828  // And mark these pixels, plus some surrounding
829  // ones (the spacing between labels), as covered
830  int leftMargin = spacing;
831  if (strPos < leftMargin)
832  leftMargin = strPos;
833  strPos -= leftMargin;
834  strLen += leftMargin;
835 
836  int rightMargin = spacing;
837  if (strPos + strLen > length - spacing)
838  rightMargin = length - strPos - strLen;
839  strLen += rightMargin;
840 
841  for(i=0; i<strLen; i++)
842  bits[strPos+i] = true;
843 
844  // Good to display the text
845  lab.text = str;
846  return { { strLeft, strTop, strW, strH }, lab };
847 }
848 
850  const Ruler &mRuler;
852 
853  explicit Updater( const Ruler &ruler, const ZoomInfo *z )
854  : mRuler{ ruler }
855  , zoomInfo{ z }
856  {}
857 
859  const int mLength = mRuler.mLength;
862 
863  const int mLeft = mRuler.mLeft;
864  const int mTop = mRuler.mTop;
865  const int mBottom = mRuler.mBottom;
866  const int mRight = mRuler.mRight;
867 
868  const int mSpacing = mRuler.mSpacing;
870  const bool mFlip = mRuler.mFlip;
871 
872  const bool mCustom = mRuler.mCustom;
874  const bool mLog = mRuler.mLog;
875  const double mHiddenMin = mRuler.mHiddenMin;
876  const double mHiddenMax = mRuler.mHiddenMax;
878  const double mMin = mRuler.mMin;
879  const double mMax = mRuler.mMax;
882 
883  struct TickOutputs;
884 
885  bool Tick( wxDC &dc,
886  int pos, double d, const TickSizes &tickSizes, wxFont font,
887  TickOutputs outputs
888  ) const;
889 
890  // Another tick generator for custom ruler case (noauto) .
891  bool TickCustom( wxDC &dc, int labelIdx, wxFont font,
892  TickOutputs outputs
893  ) const;
894 
895  static void ChooseFonts(
896  std::unique_ptr<Fonts> &pFonts, const Fonts *pUserFonts,
897  wxDC &dc, int desiredPixelHeight );
898 
899  struct UpdateOutputs;
900 
901  void Update(
902  wxDC &dc, const Envelope* envelope,
903  UpdateOutputs &allOutputs
904  )// Envelope *speedEnv, long minSpeed, long maxSpeed )
905  const;
906 
907  void UpdateCustom( wxDC &dc, UpdateOutputs &allOutputs ) const;
908  void UpdateLinear(
909  wxDC &dc, const Envelope *envelope, UpdateOutputs &allOutputs ) const;
910  void UpdateNonlinear( wxDC &dc, UpdateOutputs &allOutputs ) const;
911 };
912 
913 struct Ruler::Cache {
916  wxRect mRect;
917 };
918 
923  wxRect &box;
924 };
925 
926 bool Ruler::Updater::Tick( wxDC &dc,
927  int pos, double d, const TickSizes &tickSizes, wxFont font,
928  // in/out:
929  TickOutputs outputs ) const
930 {
931  // Bug 521. dB view for waveforms needs a 2-sided scale.
932  if(( mDbMirrorValue > 1.0 ) && ( -d > mDbMirrorValue ))
933  d = -2*mDbMirrorValue - d;
934 
935  // FIXME: We don't draw a tick if off end of our label arrays
936  // But we shouldn't have an array of labels.
937  if( outputs.labels.size() >= mLength )
938  return false;
939 
940  Label lab;
941  lab.value = d;
942  lab.pos = pos;
943  lab.text = tickSizes.LabelString( d, mFormat, mUnits );
944 
945  const auto result = MakeTick(
946  lab,
947  dc, font,
948  outputs.bits,
950  mFlip,
951  mOrientation );
952 
953  auto &rect = result.first;
954  outputs.box.Union( rect );
955  outputs.labels.emplace_back( result.second );
956  return !rect.IsEmpty();
957 }
958 
959 bool Ruler::Updater::TickCustom( wxDC &dc, int labelIdx, wxFont font,
960  // in/out:
961  TickOutputs outputs ) const
962 {
963  // FIXME: We don't draw a tick if of end of our label arrays
964  // But we shouldn't have an array of labels.
965  if( labelIdx >= outputs.labels.size() )
966  return false;
967 
968  //This should only used in the mCustom case
969 
970  Label lab;
971  lab.value = 0.0;
972 
973  const auto result = MakeTick(
974  lab,
975 
976  dc, font,
977  outputs.bits,
978  mLeft, mTop, mSpacing, mFonts.lead,
979  mFlip,
980  mOrientation );
981 
982  auto &rect = result.first;
983  outputs.box.Union( rect );
984  outputs.labels[labelIdx] = ( result.second );
985  return !rect.IsEmpty();
986 }
987 
988 namespace {
989 double ComputeWarpedLength(const Envelope &env, double t0, double t1)
990 {
991  return env.IntegralOfInverse(t0, t1);
992 }
993 
994 double SolveWarpedLength(const Envelope &env, double t0, double length)
995 {
996  return env.SolveIntegralOfInverse(t0, length);
997 }
998 }
999 
1000 static constexpr int MinPixelHeight =
1001 #ifdef __WXMSW__
1002  12;
1003 #else
1004  10;
1005 #endif
1006 
1007 static constexpr int MaxPixelHeight =
1008 #ifdef __WXMSW__
1009  14;
1010 #elif __WXMAC__
1011  10;
1012 #else
1013  12;
1014 #endif
1015 
1016 
1018  std::unique_ptr<Fonts> &pFonts, const Fonts *pUserFonts,
1019  wxDC &dc, int desiredPixelHeight )
1020 {
1021  if ( pFonts )
1022  return;
1023 
1024  if ( pUserFonts ) {
1025  pFonts = std::make_unique<Fonts>( *pUserFonts );
1026  return;
1027  }
1028 
1029  pFonts = std::make_unique<Fonts>( Fonts{ {}, {}, {}, 0 } );
1030  auto &fonts = *pFonts;
1031 
1032  int fontSize = 4;
1033 
1034  desiredPixelHeight =
1035  std::max(MinPixelHeight, std::min(MaxPixelHeight, -desiredPixelHeight));
1036 
1037  // Keep making the font bigger until it's too big, then subtract one.
1038  wxCoord height;
1039  FindFontHeights( height, fonts.lead, dc, fontSize, wxFONTWEIGHT_BOLD );
1040  while (height <= desiredPixelHeight && fontSize < 40) {
1041  fontSize++;
1042  FindFontHeights( height, fonts.lead, dc, fontSize, wxFONTWEIGHT_BOLD );
1043  }
1044  fontSize--;
1045  FindFontHeights( height, fonts.lead, dc, fontSize );
1046 
1047  fonts.major = wxFont{ fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD };
1048  fonts.minor = wxFont{ fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL };
1049  fonts.minorMinor = wxFont{ fontSize - 1, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL };
1050 }
1051 
1052 void Ruler::Updater::UpdateCustom( wxDC &dc, UpdateOutputs &allOutputs ) const
1053 {
1054  TickOutputs majorOutputs{
1055  allOutputs.majorLabels, allOutputs.bits, allOutputs.box };
1056 
1057  // SET PARAMETER IN MCUSTOM CASE
1058  // Works only with major labels
1059 
1060  int numLabel = allOutputs.majorLabels.size();
1061 
1062  for( int i = 0; (i<numLabel) && (i<=mLength); ++i )
1063  TickCustom( dc, i, mFonts.major, majorOutputs );
1064 }
1065 
1067  wxDC &dc, const Envelope *envelope, UpdateOutputs &allOutputs ) const
1068 {
1069  TickOutputs majorOutputs{
1070  allOutputs.majorLabels, allOutputs.bits, allOutputs.box };
1071 
1072  // Use the "hidden" min and max to determine the tick size.
1073  // That may make a difference with fisheye.
1074  // Otherwise you may see the tick size for the whole ruler change
1075  // when the fisheye approaches start or end.
1076  double UPP = (mHiddenMax-mHiddenMin)/mLength; // Units per pixel
1077  TickSizes tickSizes{ UPP, mOrientation, mFormat, false };
1078 
1079  auto TickAtValue =
1080  [this, &tickSizes, &dc, &majorOutputs]
1081  ( double value ) -> int {
1082  // Make a tick only if the value is strictly between the bounds
1083  if ( value <= std::min( mMin, mMax ) )
1084  return -1;
1085  if ( value >= std::max( mMin, mMax ) )
1086  return -1;
1087 
1088  int mid;
1089  if (zoomInfo != NULL) {
1090  // Tick only at zero
1091  if ( value )
1092  return -1;
1093  mid = (int)(zoomInfo->TimeToPosition(0.0, mLeftOffset));
1094  }
1095  else
1096  mid = (int)(mLength*((mMin - value) / (mMin - mMax)) + 0.5);
1097 
1098  const int iMaxPos = (mOrientation == wxHORIZONTAL) ? mRight : mBottom - 5;
1099  if (mid >= 0 && mid < iMaxPos)
1100  Tick( dc, mid, value, tickSizes, mFonts.major, majorOutputs );
1101  else
1102  return -1;
1103 
1104  return mid;
1105  };
1106 
1107  if ( mDbMirrorValue ) {
1108  // For dB scale, let the zeroes prevail over the extreme values if
1109  // not the same, and let midline prevail over all
1110 
1111  // Do the midline
1112  TickAtValue( -mDbMirrorValue );
1113 
1114  // Do the upper zero
1115  TickAtValue( 0.0 );
1116 
1117  // Do the other zero
1118  TickAtValue( -2 * mDbMirrorValue );
1119  }
1120 
1121  // Extreme values
1122  if (mLabelEdges) {
1123  Tick( dc, 0, mMin, tickSizes, mFonts.major, majorOutputs );
1124  Tick( dc, mLength, mMax, tickSizes, mFonts.major, majorOutputs );
1125  }
1126 
1127  if ( !mDbMirrorValue ) {
1128  // Zero (if it's strictly in the middle somewhere)
1129  TickAtValue( 0.0 );
1130  }
1131 
1132  double sg = UPP > 0.0? 1.0: -1.0;
1133 
1134  int nDroppedMinorLabels=0;
1135  // Major and minor ticks
1136  for (int jj = 0; jj < 2; ++jj) {
1137  const double denom = jj == 0 ? tickSizes.mMajor : tickSizes.mMinor;
1138  auto font = jj == 0 ? mFonts.major : mFonts.minor;
1139  TickOutputs outputs{
1140  (jj == 0 ? allOutputs.majorLabels : allOutputs.minorLabels),
1141  allOutputs.bits, allOutputs.box
1142  };
1143  int ii = -1, j = 0;
1144  double d, warpedD, nextD;
1145 
1146  double prevTime = 0.0, time = 0.0;
1147  if (zoomInfo != NULL) {
1148  j = zoomInfo->TimeToPosition(mMin);
1149  prevTime = zoomInfo->PositionToTime(--j);
1150  time = zoomInfo->PositionToTime(++j);
1151  d = (prevTime + time) / 2.0;
1152  }
1153  else
1154  d = mMin - UPP / 2;
1155  if (envelope)
1156  warpedD = ComputeWarpedLength(*envelope, 0.0, d);
1157  else
1158  warpedD = d;
1159  // using ints doesn't work, as
1160  // this will overflow and be negative at high zoom.
1161  double step = floor(sg * warpedD / denom);
1162  while (ii <= mLength) {
1163  ii++;
1164  if (zoomInfo)
1165  {
1166  prevTime = time;
1167  time = zoomInfo->PositionToTime(++j);
1168  nextD = (prevTime + time) / 2.0;
1169  // wxASSERT(time >= prevTime);
1170  }
1171  else
1172  nextD = d + UPP;
1173  if (envelope)
1174  warpedD += ComputeWarpedLength(*envelope, d, nextD);
1175  else
1176  warpedD = nextD;
1177  d = nextD;
1178 
1179  if (floor(sg * warpedD / denom) > step) {
1180  step = floor(sg * warpedD / denom);
1181  bool major = jj == 0;
1182  tickSizes.useMajor = major;
1183  bool ticked = Tick( dc, ii, sg * step * denom, tickSizes,
1184  font, outputs );
1185  if( !major && !ticked ){
1186  nDroppedMinorLabels++;
1187  }
1188  }
1189  }
1190  }
1191 
1192  tickSizes.useMajor = true;
1193 
1194  // If we've dropped minor labels through overcrowding, then don't show
1195  // any of them. We're allowed though to drop ones which correspond to the
1196  // major numbers.
1197  if( nDroppedMinorLabels >
1198  (allOutputs.majorLabels.size() + (mLabelEdges ? 2:0)) ){
1199  // Old code dropped the labels AND their ticks, like so:
1200  // mMinorLabels.clear();
1201  // Nowadays we just drop the labels.
1202  for( auto &label : allOutputs.minorLabels )
1203  label.text = {};
1204  }
1205 
1206  // Left and Right Edges
1207  if (mLabelEdges) {
1208  Tick( dc, 0, mMin, tickSizes, mFonts.major, majorOutputs );
1209  Tick( dc, mLength, mMax, tickSizes, mFonts.major, majorOutputs );
1210  }
1211 }
1212 
1214  wxDC &dc, UpdateOutputs &allOutputs ) const
1215 {
1216  TickOutputs majorOutputs{
1217  allOutputs.majorLabels, allOutputs.bits, allOutputs.box };
1218 
1219  auto numberScale = ( mNumberScale == NumberScale{} )
1221  : mNumberScale;
1222 
1223  double UPP = (mHiddenMax-mHiddenMin)/mLength; // Units per pixel
1224  TickSizes tickSizes{ UPP, mOrientation, mFormat, true };
1225 
1226  tickSizes.mDigits = 2; //TODO: implement dynamic digit computation
1227 
1228  double loLog = log10(mMin);
1229  double hiLog = log10(mMax);
1230  int loDecade = (int) floor(loLog);
1231 
1232  double val;
1233  double startDecade = pow(10., (double)loDecade);
1234 
1235  // Major ticks are the decades
1236  double decade = startDecade;
1237  double delta=hiLog-loLog, steps=fabs(delta);
1238  double step = delta>=0 ? 10 : 0.1;
1239  double rMin=std::min(mMin, mMax), rMax=std::max(mMin, mMax);
1240  for(int i=0; i<=steps; i++)
1241  { // if(i!=0)
1242  { val = decade;
1243  if(val >= rMin && val < rMax) {
1244  const int pos(0.5 + mLength * numberScale.ValueToPosition(val));
1245  Tick( dc, pos, val, tickSizes, mFonts.major, majorOutputs );
1246  }
1247  }
1248  decade *= step;
1249  }
1250 
1251  // Minor ticks are multiples of decades
1252  decade = startDecade;
1253  float start, end, mstep;
1254  if (delta > 0)
1255  { start=2; end=10; mstep=1;
1256  }else
1257  { start=9; end=1; mstep=-1;
1258  }
1259  steps++;
1260  tickSizes.useMajor = false;
1261  TickOutputs minorOutputs{
1262  allOutputs.minorLabels, allOutputs.bits, allOutputs.box };
1263  for(int i=0; i<=steps; i++) {
1264  for(int j=start; j!=end; j+=mstep) {
1265  val = decade * j;
1266  if(val >= rMin && val < rMax) {
1267  const int pos(0.5 + mLength * numberScale.ValueToPosition(val));
1268  Tick( dc, pos, val, tickSizes, mFonts.minor, minorOutputs );
1269  }
1270  }
1271  decade *= step;
1272  }
1273 
1274  // MinorMinor ticks are multiples of decades
1275  decade = startDecade;
1276  if (delta > 0)
1277  { start= 10; end=100; mstep= 1;
1278  }else
1279  { start=100; end= 10; mstep=-1;
1280  }
1281  steps++;
1282  TickOutputs minorMinorOutputs{
1283  allOutputs.minorMinorLabels, allOutputs.bits, allOutputs.box };
1284  for (int i = 0; i <= steps; i++) {
1285  // PRL: Bug1038. Don't label 1.6, rounded, as a duplicate tick for "2"
1286  if (!(mFormat == IntFormat && decade < 10.0)) {
1287  for (int f = start; f != (int)(end); f += mstep) {
1288  if ((int)(f / 10) != f / 10.0f) {
1289  val = decade * f / 10;
1290  if (val >= rMin && val < rMax) {
1291  const int pos(0.5 + mLength * numberScale.ValueToPosition(val));
1292  Tick( dc, pos, val, tickSizes,
1293  mFonts.minorMinor, minorMinorOutputs );
1294  }
1295  }
1296  }
1297  }
1298  decade *= step;
1299  }
1300 }
1301 
1303  wxDC &dc, const Envelope* envelope,
1304  UpdateOutputs &allOutputs
1305 )// Envelope *speedEnv, long minSpeed, long maxSpeed )
1306  const
1307 {
1308  TickOutputs majorOutputs{
1309  allOutputs.majorLabels, allOutputs.bits, allOutputs.box };
1310 
1311  if ( mCustom )
1312  UpdateCustom( dc, allOutputs );
1313  else if ( !mLog )
1314  UpdateLinear( dc, envelope, allOutputs );
1315  else
1316  UpdateNonlinear( dc, allOutputs );
1317 
1318  int displacementx=0, displacementy=0;
1319  auto &box = allOutputs.box;
1320  if (!mFlip) {
1321  if (mOrientation==wxHORIZONTAL) {
1322  int d = mTop + box.GetHeight() + 5;
1323  box.Offset(0,d);
1324  box.Inflate(0,5);
1325  displacementx=0;
1326  displacementy=d;
1327  }
1328  else {
1329  int d = mLeft - box.GetLeft() + 5;
1330  box.Offset(d,0);
1331  box.Inflate(5,0);
1332  displacementx=d;
1333  displacementy=0;
1334  }
1335  }
1336  else {
1337  if (mOrientation==wxHORIZONTAL) {
1338  box.Inflate(0,5);
1339  displacementx=0;
1340  displacementy=0;
1341  }
1342  }
1343  auto update = [=]( Label &label ){
1344  label.lx += displacementx;
1345  label.ly += displacementy;
1346  };
1347  for( auto &label : allOutputs.majorLabels )
1348  update( label );
1349  for( auto &label : allOutputs.minorLabels )
1350  update( label );
1351  for( auto &label : allOutputs.minorMinorLabels )
1352  update( label );
1353 }
1354 
1355 void Ruler::ChooseFonts( wxDC &dc ) const
1356 {
1358  mOrientation == wxHORIZONTAL
1359  ? mBottom - mTop - 5 // height less ticks and 1px gap
1360  : MaxPixelHeight
1361  );
1362 }
1363 
1365  wxDC &dc, const Envelope* envelope )
1366  const // Envelope *speedEnv, long minSpeed, long maxSpeed )
1367 {
1368  if ( mpCache )
1369  return;
1370 
1371  const ZoomInfo *zoomInfo = NULL;
1372  if (!mLog && mOrientation == wxHORIZONTAL)
1373  zoomInfo = mUseZoomInfo;
1374 
1375  // This gets called when something has been changed
1376  // (i.e. we've been invalidated). Recompute all
1377  // tick positions and font size.
1378 
1379  ChooseFonts( dc );
1380  mpCache = std::make_unique< Cache >();
1381  auto &cache = *mpCache;
1382 
1383  // If ruler is being resized, we could end up with it being too small.
1384  // Values of mLength of zero or below cause bad array allocations and
1385  // division by zero. So...
1386  // IF too small THEN bail out and don't draw.
1387  if( mLength <= 0 )
1388  return;
1389 
1390  if (mOrientation == wxHORIZONTAL)
1391  cache.mRect = { 0, 0, mLength, 0 };
1392  else
1393  cache.mRect = { 0, 0, 0, mLength };
1394 
1395  // FIXME: Surely we do not need to allocate storage for the labels?
1396  // We can just recompute them as we need them? Yes, but only if
1397  // mCustom is false!!!!
1398 
1399  if(!mCustom) {
1400  cache.mMajorLabels.clear();
1401  cache.mMinorLabels.clear();
1402  cache.mMinorMinorLabels.clear();
1403  }
1404 
1405  cache.mBits = mUserBits;
1406  cache.mBits.resize( static_cast<size_t>(mLength + 1), false );
1407 
1408  // Keep Updater const! We want no hidden state changes affecting its
1409  // computations.
1410  const Updater updater{ *this, zoomInfo };
1411  Updater::UpdateOutputs allOutputs{
1412  cache.mMajorLabels, cache.mMinorLabels, cache.mMinorMinorLabels,
1413  cache.mBits, cache.mRect
1414  };
1415  updater.Update(dc, envelope, allOutputs);
1416 }
1417 
1418 auto Ruler::GetFonts() const -> Fonts
1419 {
1420  if ( !mpFonts ) {
1421  wxScreenDC dc;
1422  ChooseFonts( dc );
1423  }
1424 
1425  return *mpFonts;
1426 }
1427 
1428 void Ruler::Draw(wxDC& dc) const
1429 {
1430  Draw( dc, NULL);
1431 }
1432 
1433 void Ruler::Draw(wxDC& dc, const Envelope* envelope) const
1434 {
1435  if( mLength <=0 )
1436  return;
1437 
1438  UpdateCache( dc, envelope );
1439  auto &cache = *mpCache;
1440 
1441  dc.SetTextForeground( mTickColour );
1442 #ifdef EXPERIMENTAL_THEMING
1443  dc.SetPen(mPen);
1444 #else
1445  dc.SetPen(*wxBLACK_PEN);
1446 #endif
1447 
1448  // Draws a long line the length of the ruler.
1449  if( !mbTicksOnly )
1450  {
1451  if (mOrientation == wxHORIZONTAL) {
1452  if (mFlip)
1453  AColor::Line(dc, mLeft, mTop, mRight, mTop);
1454  else
1456  }
1457  else {
1458  if (mFlip)
1460  else
1461  {
1462  // These calculations appear to be wrong, and to never have been used (so not tested) prior to MixerBoard.
1463  // AColor::Line(dc, mRect.x-mRect.width, mTop, mRect.x-mRect.width, mBottom);
1464  const int nLineX = mRight - 1;
1465  AColor::Line(dc, nLineX, mTop, nLineX, mBottom);
1466  }
1467  }
1468  }
1469 
1470  dc.SetFont( mpFonts->major );
1471 
1472  // We may want to not show the ticks at the extremes,
1473  // though still showing the labels.
1474  // This gives a better look when the ruler is on a bevelled
1475  // button, since otherwise the tick is drawn on the bevel.
1476  int iMaxPos = (mOrientation==wxHORIZONTAL)? mRight : mBottom-5;
1477 
1478  auto drawLabel = [this, iMaxPos, &dc]( const Label &label, int length ){
1479  int pos = label.pos;
1480 
1481  if( mbTicksAtExtremes || ((pos!=0)&&(pos!=iMaxPos)))
1482  {
1483  if (mOrientation == wxHORIZONTAL) {
1484  if (mFlip)
1485  AColor::Line(dc, mLeft + pos, mTop,
1486  mLeft + pos, mTop + length);
1487  else
1488  AColor::Line(dc, mLeft + pos, mBottom - length,
1489  mLeft + pos, mBottom);
1490  }
1491  else {
1492  if (mFlip)
1493  AColor::Line(dc, mLeft, mTop + pos,
1494  mLeft + length, mTop + pos);
1495  else
1496  AColor::Line(dc, mRight - length, mTop + pos,
1497  mRight, mTop + pos);
1498  }
1499  }
1500 
1501  label.Draw(dc, mTwoTone, mTickColour);
1502  };
1503 
1504  for( const auto &label : cache.mMajorLabels )
1505  drawLabel( label, 4 );
1506 
1507  if( mbMinor ) {
1508  dc.SetFont( mpFonts->minor );
1509  for( const auto &label : cache.mMinorLabels )
1510  drawLabel( label, 2 );
1511  }
1512 
1513  dc.SetFont( mpFonts->minorMinor );
1514 
1515  for( const auto &label : cache.mMinorMinorLabels )
1516  if ( !label.text.empty() )
1517  drawLabel( label, 2 );
1518 }
1519 
1520 // ********** Draw grid ***************************
1521 void Ruler::DrawGrid(wxDC& dc,
1522  const int gridLineLength,
1523  const bool minorGrid, const bool majorGrid, int xOffset, int yOffset)
1524  const
1525 {
1526  UpdateCache( dc, nullptr );
1527  auto &cache = *mpCache;
1528 
1529  int gridPos;
1530  wxPen gridPen;
1531 
1532  if(mbMinor && (minorGrid && (gridLineLength != 0 ))) {
1533  gridPen.SetColour(178, 178, 178); // very light grey
1534  dc.SetPen(gridPen);
1535  for( const auto &label : cache.mMinorLabels ) {
1536  gridPos = label.pos;
1537  if(mOrientation == wxHORIZONTAL) {
1538  if((gridPos != 0) && (gridPos != gridLineLength))
1539  AColor::Line(dc, gridPos+xOffset, yOffset, gridPos+xOffset, gridLineLength-1+yOffset);
1540  }
1541  else {
1542  if((gridPos != 0) && (gridPos != gridLineLength))
1543  AColor::Line(dc, xOffset, gridPos+yOffset, gridLineLength-1+xOffset, gridPos+yOffset);
1544  }
1545  }
1546  }
1547 
1548  if(majorGrid && (gridLineLength != 0 )) {
1549  gridPen.SetColour(127, 127, 127); // light grey
1550  dc.SetPen(gridPen);
1551  for( const auto &label : cache.mMajorLabels ) {
1552  gridPos = label.pos;
1553  if(mOrientation == wxHORIZONTAL) {
1554  if((gridPos != 0) && (gridPos != gridLineLength))
1555  AColor::Line(dc, gridPos+xOffset, yOffset, gridPos+xOffset, gridLineLength-1+yOffset);
1556  }
1557  else {
1558  if((gridPos != 0) && (gridPos != gridLineLength))
1559  AColor::Line(dc, xOffset, gridPos+yOffset, gridLineLength-1+xOffset, gridPos+yOffset);
1560  }
1561  }
1562 
1563  int zeroPosition = GetZeroPosition();
1564  if(zeroPosition > 0) {
1565  // Draw 'zero' grid line in black
1566  dc.SetPen(*wxBLACK_PEN);
1567  if(mOrientation == wxHORIZONTAL) {
1568  if(zeroPosition != gridLineLength)
1569  AColor::Line(dc, zeroPosition+xOffset, yOffset, zeroPosition+xOffset, gridLineLength-1+yOffset);
1570  }
1571  else {
1572  if(zeroPosition != gridLineLength)
1573  AColor::Line(dc, xOffset, zeroPosition+yOffset, gridLineLength-1+xOffset, zeroPosition+yOffset);
1574  }
1575  }
1576  }
1577 }
1578 
1579 int Ruler::FindZero( const Labels &labels ) const
1580 {
1581  auto begin = labels.begin(), end = labels.end(),
1582  iter = std::find_if( begin, end, []( const Label &label ){
1583  return label.value == 0.0;
1584  } );
1585 
1586  if ( iter == end )
1587  return -1;
1588  else
1589  return iter->pos;
1590 }
1591 
1593 {
1594  wxASSERT( mpCache );
1595  auto &cache = *mpCache;
1596  int zero;
1597  if( (zero = FindZero( cache.mMajorLabels ) ) < 0)
1598  zero = FindZero( cache.mMinorLabels );
1599  // PRL: don't consult minor minor??
1600  return zero;
1601 }
1602 
1603 void Ruler::GetMaxSize(wxCoord *width, wxCoord *height)
1604 {
1605  if ( !mpCache ) {
1606  wxScreenDC sdc;
1607  UpdateCache( sdc, nullptr );
1608  }
1609 
1610  auto &cache = *mpCache;
1611  if (width)
1612  *width = cache.mRect.GetWidth();
1613 
1614  if (height)
1615  *height = cache.mRect.GetHeight();
1616 }
1617 
1618 
1619 void Ruler::SetCustomMode(bool value)
1620 {
1621  if ( mCustom != value ) {
1622  mCustom = value;
1623  Invalidate();
1624  }
1625 }
1626 
1627 #if 0
1628 // These two unused functions need reconsideration of their interactions with
1629 // the cache and update
1631  const TranslatableStrings &labels, int start, int step)
1632 {
1633  SetCustomMode( true );
1634  mpCache = std::make_unique<Cache>();
1635  auto &cache = *mpCache;
1636  auto &mMajorLabels = cache.mMajorLabels;
1637 
1638  const auto numLabel = labels.size();
1639  mMajorLabels.resize( numLabel );
1640 
1641  for(size_t i = 0; i<numLabel; i++) {
1642  mMajorLabels[i].text = labels[i];
1643  mMajorLabels[i].pos = start + i*step;
1644  }
1645 }
1646 
1648  const TranslatableStrings &labels, int start, int step)
1649 {
1650  SetCustomMode( true );
1651  mpCache = std::make_unique<Cache>();
1652  auto &cache = *mpCache;
1653  auto &mMinorLabels = cache.mMinorLabels;
1654 
1655  const auto numLabel = labels.size();
1656  mMinorLabels.resize( numLabel );
1657 
1658  for(size_t i = 0; i<numLabel; i++) {
1659  mMinorLabels[i].text = labels[i];
1660  mMinorLabels[i].pos = start + i*step;
1661  }
1662 }
1663 #endif
1664 
1665 void Ruler::Label::Draw(wxDC&dc, bool twoTone, wxColour c) const
1666 {
1667  if (!text.empty()) {
1668  bool altColor = twoTone && value < 0.0;
1669 
1670 #ifdef EXPERIMENTAL_THEMING
1671  dc.SetTextForeground(altColor ? theTheme.Colour( clrTextNegativeNumbers) : c);
1672 #else
1673  dc.SetTextForeground(altColor ? *wxBLUE : *wxBLACK);
1674 #endif
1675  dc.SetBackgroundMode(wxTRANSPARENT);
1676  dc.DrawText(text.Translation(), lx, ly);
1677  }
1678 }
1679 
1680 void Ruler::SetUseZoomInfo(int leftOffset, const ZoomInfo *zoomInfo)
1681 {
1682 
1683  if ( mLeftOffset != leftOffset ||
1684  // Hm, is this invalidation sufficient? What if *zoomInfo changes under us?
1685  mUseZoomInfo != zoomInfo
1686  ) {
1687  mLeftOffset = leftOffset;
1688  mUseZoomInfo = zoomInfo;
1689  Invalidate();
1690  }
1691 }
1692 
1693 //
1694 // RulerPanel
1695 //
1696 
1697 BEGIN_EVENT_TABLE(RulerPanel, wxPanelWrapper)
1698  EVT_ERASE_BACKGROUND(RulerPanel::OnErase)
1699  EVT_PAINT(RulerPanel::OnPaint)
1700  EVT_SIZE(RulerPanel::OnSize)
1702 
1704 
1705 RulerPanel::RulerPanel(wxWindow* parent, wxWindowID id,
1706  wxOrientation orientation,
1707  const wxSize &bounds,
1708  const Range &range,
1710  const TranslatableString &units,
1711  const Options &options,
1712  const wxPoint& pos /*= wxDefaultPosition*/,
1713  const wxSize& size /*= wxDefaultSize*/):
1714  wxPanelWrapper(parent, id, pos, size)
1715 {
1716  ruler.SetBounds( 0, 0, bounds.x, bounds.y );
1717  ruler.SetOrientation(orientation);
1718  ruler.SetRange( range.first, range.second );
1719  ruler.SetLog( options.log );
1721  ruler.SetUnits( units );
1722  ruler.SetFlip( options.flip );
1723  ruler.SetLabelEdges( options.labelEdges );
1724  ruler.mbTicksAtExtremes = options.ticksAtExtremes;
1725  if (orientation == wxVERTICAL) {
1726  wxCoord w;
1727  ruler.GetMaxSize(&w, NULL);
1728  SetMinSize(wxSize(w, 150)); // height needed for wxGTK
1729  }
1730  else if (orientation == wxHORIZONTAL) {
1731  wxCoord h;
1732  ruler.GetMaxSize(NULL, &h);
1733  SetMinSize(wxSize(wxDefaultCoord, h));
1734  }
1735  if (options.hasTickColour)
1736  ruler.SetTickColour( options.tickColour );
1737 }
1738 
1740 {
1741 }
1742 
1743 void RulerPanel::OnErase(wxEraseEvent & WXUNUSED(evt))
1744 {
1745  // Ignore it to prevent flashing
1746 }
1747 
1748 void RulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
1749 {
1750  wxPaintDC dc(this);
1751 
1752 #if defined(__WXMSW__)
1753  dc.Clear();
1754 #endif
1755 
1756  ruler.Draw(dc);
1757 }
1758 
1759 void RulerPanel::OnSize(wxSizeEvent & WXUNUSED(evt))
1760 {
1761  Refresh();
1762 }
1763 
1764 // LL: We're overloading DoSetSize so that we can update the ruler bounds immediately
1765 // instead of waiting for a wxEVT_SIZE to come through. This is needed by (at least)
1766 // FrequencyPlotDialog since it needs to have an updated ruler before RulerPanel gets the
1767 // size event.
1768 void RulerPanel::DoSetSize(int x, int y,
1769  int width, int height,
1770  int sizeFlags)
1771 {
1772  wxPanelWrapper::DoSetSize(x, y, width, height, sizeFlags);
1773 
1774  int w, h;
1775  GetClientSize(&w, &h);
1776 
1777  ruler.SetBounds(0, 0, w-1, h-1);
1778 }
Ruler::mOrientation
int mOrientation
Definition: Ruler.h:210
Ruler::UpdateCache
void UpdateCache(wxDC &dc, const Envelope *envelope) const
Definition: Ruler.cpp:1364
Ruler::mLog
bool mLog
Definition: Ruler.h:216
Ruler::ChooseFonts
void ChooseFonts(wxDC &dc) const
Definition: Ruler.cpp:1355
Ruler::SetLabelEdges
void SetLabelEdges(bool labelEdges)
Definition: Ruler.cpp:226
TranslatableString
Holds a msgid for the translation catalog; may also bind format arguments.
Definition: TranslatableString.h:32
Ruler::Updater::mFonts
const Fonts & mFonts
Definition: Ruler.cpp:873
Ruler::mLeft
int mLeft
Definition: Ruler.h:184
TranslatableString::empty
bool empty() const
Definition: TranslatableString.h:72
Ruler::TickSizes::mDigits
int mDigits
Definition: Ruler.cpp:364
Ruler::mHiddenMin
double mHiddenMin
Definition: Ruler.h:191
RulerPanel
RulerPanel class allows you to work with a Ruler like any other wxWindow.
Definition: Ruler.h:228
Ruler::Updater::TickCustom
bool TickCustom(wxDC &dc, int labelIdx, wxFont font, TickOutputs outputs) const
Definition: Ruler.cpp:959
Ruler::LinearDBFormat
@ LinearDBFormat
Definition: Ruler.h:34
Ruler::FindZero
int FindZero(const Labels &labels) const
Definition: Ruler.cpp:1579
Ruler::Label::Draw
void Draw(wxDC &dc, bool twoTone, wxColour c) const
Definition: Ruler.cpp:1665
NumberScale.h
RulerPanel::DoSetSize
void DoSetSize(int x, int y, int width, int height, int sizeFlags=wxSIZE_AUTO) override
Definition: Ruler.cpp:1768
Ruler::SetCustomMode
void SetCustomMode(bool value)
Definition: Ruler.cpp:1619
Ruler::Label::value
double value
Definition: Ruler.h:159
Ruler::Cache::mMinorLabels
Labels mMinorLabels
Definition: Ruler.cpp:915
TranslatableStrings
std::vector< TranslatableString > TranslatableStrings
Definition: TranslatableString.h:295
Ruler::mTwoTone
bool mTwoTone
Definition: Ruler.h:221
RulerPanel::OnErase
void OnErase(wxEraseEvent &evt)
Definition: Ruler.cpp:1743
Ruler::SetMinor
void SetMinor(bool value)
Definition: Ruler.cpp:252
wxPanelWrapper
Definition: wxPanelWrapper.h:41
RulerPanel::~RulerPanel
~RulerPanel()
Definition: Ruler.cpp:1739
Ruler::mUseZoomInfo
const ZoomInfo * mUseZoomInfo
Definition: Ruler.h:222
ZoomInfo
Definition: ZoomInfo.h:47
Ruler::Cache::mMinorMinorLabels
Labels mMinorMinorLabels
Definition: Ruler.cpp:915
Ruler::Updater::mRight
const int mRight
Definition: Ruler.cpp:866
AColor::Line
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:112
Ruler::SetLog
void SetLog(bool log)
Definition: Ruler.cpp:142
Envelope
Piecewise linear or piecewise exponential function from double to double.
Definition: Envelope.h:71
MinPixelHeight
static constexpr int MinPixelHeight
Definition: Ruler.cpp:1000
Ruler::Label::pos
int pos
Definition: Ruler.h:160
Ruler::Labels
std::vector< Label > Labels
Definition: Ruler.h:166
Ruler::mLength
int mLength
Definition: Ruler.h:185
Ruler::SetTickColour
void SetTickColour(const wxColour &colour)
Definition: Ruler.h:148
Ruler::mTickColour
wxColour mTickColour
Definition: Ruler.h:181
Ruler::SetFormat
void SetFormat(RulerFormat format)
Definition: Ruler.cpp:131
Ruler::TickSizes::mMinor
double mMinor
Definition: Ruler.cpp:362
Ruler::SetDbMirrorValue
void SetDbMirrorValue(const double d)
Definition: Ruler.cpp:165
Ruler::MakeTick
static std::pair< wxRect, Label > MakeTick(Label lab, wxDC &dc, wxFont font, std::vector< bool > &bits, int left, int top, int spacing, int lead, bool flip, int orientation)
Definition: Ruler.cpp:758
Ruler::Draw
void Draw(wxDC &dc) const
Definition: Ruler.cpp:1428
Ruler::RealFormat
@ RealFormat
Definition: Ruler.h:31
Ruler::mpCache
std::unique_ptr< Cache > mpCache
Definition: Ruler.h:202
Ruler::Updater::mLabelEdges
const bool mLabelEdges
Definition: Ruler.cpp:877
anonymous_namespace{Ruler.cpp}::SolveWarpedLength
double SolveWarpedLength(const Envelope &env, double t0, double length)
Definition: Ruler.cpp:994
Ruler::Updater::TickOutputs
Definition: Ruler.cpp:919
Ruler::Updater::UpdateOutputs::box
wxRect & box
Definition: Ruler.cpp:923
Ruler::Updater::mHiddenMax
const double mHiddenMax
Definition: Ruler.cpp:876
anonymous_namespace{TimeTrackVRulerControls.cpp}::ruler
Ruler & ruler()
Definition: TimeTrackVRulerControls.cpp:34
Ruler::SetUseZoomInfo
void SetUseZoomInfo(int leftOffset, const ZoomInfo *zoomInfo)
Definition: Ruler.cpp:1680
Ruler::Updater::mMax
const double mMax
Definition: Ruler.cpp:879
Ruler::Cache::mMajorLabels
Labels mMajorLabels
Definition: Ruler.cpp:915
Ruler::Ruler
Ruler()
Definition: Ruler.cpp:79
Envelope::IntegralOfInverse
double IntegralOfInverse(double t0, double t1) const
Definition: Envelope.cpp:1233
Ruler::mPen
wxPen mPen
Definition: Ruler.h:182
Ruler::SetUnits
void SetUnits(const TranslatableString &units)
Definition: Ruler.cpp:153
Ruler::Updater::mLog
const bool mLog
Definition: Ruler.cpp:874
Ruler::Cache
Definition: Ruler.cpp:913
Ruler::SetFlip
void SetFlip(bool flip)
Definition: Ruler.cpp:239
Ruler::TimeFormat
@ TimeFormat
Definition: Ruler.h:33
Ruler::mLeftOffset
int mLeftOffset
Definition: Ruler.h:223
Ruler::OfflimitsPixels
void OfflimitsPixels(int start, int end)
Definition: Ruler.cpp:302
Ruler::mFormat
RulerFormat mFormat
Definition: Ruler.h:215
Ruler::GetMaxSize
void GetMaxSize(wxCoord *width, wxCoord *height)
Definition: Ruler.cpp:1603
Ruler::mMax
double mMax
Definition: Ruler.h:190
RulerPanel::Range
std::pair< double, double > Range
Definition: Ruler.h:232
Ruler::mbTicksOnly
bool mbTicksOnly
Definition: Ruler.h:174
Ruler::Cache::mBits
Bits mBits
Definition: Ruler.cpp:914
Ruler::TickSizes
Definition: Ruler.cpp:358
Ruler::mDbMirrorValue
double mDbMirrorValue
Definition: Ruler.h:212
Ruler::mbTicksAtExtremes
bool mbTicksAtExtremes
Definition: Ruler.h:178
Ruler::Bits
std::vector< bool > Bits
Definition: Ruler.h:168
Ruler::mLabelEdges
bool mLabelEdges
Definition: Ruler.h:214
label
TranslatableString label
Definition: Tags.cpp:756
Ruler::Updater::Update
void Update(wxDC &dc, const Envelope *envelope, UpdateOutputs &allOutputs) const
Definition: Ruler.cpp:1302
Ruler::Updater::mBottom
const int mBottom
Definition: Ruler.cpp:865
RulerPanel::OnSize
void OnSize(wxSizeEvent &evt)
Definition: Ruler.cpp:1759
anonymous_namespace{Ruler.cpp}::FindFontHeights
void FindFontHeights(wxCoord &height, wxCoord &lead, wxDC &dc, int fontSize, wxFontWeight weight=wxFONTWEIGHT_NORMAL)
Definition: Ruler.cpp:269
Ruler::Updater::ChooseFonts
static void ChooseFonts(std::unique_ptr< Fonts > &pFonts, const Fonts *pUserFonts, wxDC &dc, int desiredPixelHeight)
Definition: Ruler.cpp:1017
Ruler::mNumberScale
NumberScale mNumberScale
Definition: Ruler.h:225
Ruler::RulerFormat
RulerFormat
Definition: Ruler.h:29
Ruler
Used to display a Ruler.
Definition: Ruler.h:26
Ruler::Updater::mTop
const int mTop
Definition: Ruler.cpp:864
Ruler::Updater::mOrientation
const int mOrientation
Definition: Ruler.cpp:869
Ruler::mpUserFonts
std::unique_ptr< Fonts > mpUserFonts
Definition: Ruler.h:187
Ruler::mUnits
TranslatableString mUnits
Definition: Ruler.h:220
Ruler::Updater::mSpacing
const int mSpacing
Definition: Ruler.cpp:868
Ruler::SetSpacing
void SetSpacing(int spacing)
Definition: Ruler.cpp:215
Ruler::Updater::mCustom
const bool mCustom
Definition: Ruler.cpp:872
format
int format
Definition: ExportPCM.cpp:56
Ruler::Updater::TickOutputs::box
wxRect & box
Definition: Ruler.cpp:919
Ruler::Updater::UpdateLinear
void UpdateLinear(wxDC &dc, const Envelope *envelope, UpdateOutputs &allOutputs) const
Definition: Ruler.cpp:1066
Ruler::Updater::mMin
const double mMin
Definition: Ruler.cpp:878
Ruler::GetZeroPosition
int GetZeroPosition() const
Definition: Ruler.cpp:1592
Ruler::mHiddenMax
double mHiddenMax
Definition: Ruler.h:191
Ruler::Invalidate
void Invalidate()
Definition: Ruler.cpp:345
Ruler::SetRange
void SetRange(double min, double max)
Definition: Ruler.cpp:188
Ruler::IntFormat
@ IntFormat
Definition: Ruler.h:30
Ruler::Updater::UpdateOutputs::minorLabels
Labels & minorLabels
Definition: Ruler.cpp:921
Ruler::SetOrientation
void SetOrientation(int orient)
Definition: Ruler.cpp:174
anonymous_namespace{NoteTrack.cpp}::swap
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:735
ViewInfo.h
Ruler.h
Ruler::RealLogFormat
@ RealLogFormat
Definition: Ruler.h:32
Ruler::Updater
Definition: Ruler.cpp:849
Ruler::Updater::UpdateCustom
void UpdateCustom(wxDC &dc, UpdateOutputs &allOutputs) const
Definition: Ruler.cpp:1052
Envelope::SolveIntegralOfInverse
double SolveIntegralOfInverse(double t0, double area) const
Definition: Envelope.cpp:1296
Ruler::mRight
int mRight
Definition: Ruler.h:184
Ruler::mFlip
bool mFlip
Definition: Ruler.h:217
min
int min(int a, int b)
Definition: CompareAudioCommand.cpp:106
Ruler::Updater::Updater
Updater(const Ruler &ruler, const ZoomInfo *z)
Definition: Ruler.cpp:853
Ruler::Label::text
TranslatableString text
Definition: Ruler.h:162
Ruler::Updater::zoomInfo
const ZoomInfo * zoomInfo
Definition: Ruler.cpp:851
Ruler::Updater::UpdateOutputs::bits
Bits & bits
Definition: Ruler.cpp:922
Ruler::TickSizes::LabelString
TranslatableString LabelString(double d, RulerFormat format, const TranslatableString &units) const
Definition: Ruler.cpp:602
Ruler::Updater::mNumberScale
const NumberScale mNumberScale
Definition: Ruler.cpp:881
Ruler::TickSizes::mMajor
double mMajor
Definition: Ruler.cpp:361
Ruler::mpFonts
std::unique_ptr< Fonts > mpFonts
Definition: Ruler.h:188
Ruler::TickSizes::useMajor
bool useMajor
Definition: Ruler.cpp:359
Ruler::SetFonts
void SetFonts(const wxFont &minorFont, const wxFont &majorFont, const wxFont &minorMinorFont)
Definition: Ruler.cpp:278
Ruler::mBottom
int mBottom
Definition: Ruler.h:184
nstLogarithmic
@ nstLogarithmic
Definition: NumberScale.h:19
Ruler::mMin
double mMin
Definition: Ruler.h:190
anonymous_namespace{Ruler.cpp}::ComputeWarpedLength
double ComputeWarpedLength(const Envelope &env, double t0, double t1)
Definition: Ruler.cpp:989
Ruler::Updater::mRuler
const Ruler & mRuler
Definition: Ruler.cpp:850
Ruler::TickSizes::TickSizes
TickSizes(double UPP, int orientation, RulerFormat format, bool log)
Definition: Ruler.cpp:366
Ruler::Updater::UpdateOutputs::majorLabels
Labels & majorLabels
Definition: Ruler.cpp:921
Ruler::Updater::mLeft
const int mLeft
Definition: Ruler.cpp:863
RulerPanel::OnPaint
void OnPaint(wxPaintEvent &evt)
Definition: Ruler.cpp:1748
Ruler::Updater::UpdateOutputs::minorMinorLabels
Labels & minorMinorLabels
Definition: Ruler.cpp:921
Ruler::SetBounds
void SetBounds(int left, int top, int right, int bottom)
Definition: Ruler.cpp:332
Ruler::Updater::Tick
bool Tick(wxDC &dc, int pos, double d, const TickSizes &tickSizes, wxFont font, TickOutputs outputs) const
Definition: Ruler.cpp:926
RulerPanel::Options
Definition: Ruler.h:234
Ruler::Updater::mHiddenMin
const double mHiddenMin
Definition: Ruler.cpp:875
Verbatim
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
Definition: TranslatableString.h:321
Ruler::SetNumberScale
void SetNumberScale(const NumberScale &scale)
Definition: Ruler.cpp:294
IMPLEMENT_CLASS
IMPLEMENT_CLASS(ControlToolBar, ToolBar)
Ruler::Updater::mDbMirrorValue
const double mDbMirrorValue
Definition: Ruler.cpp:858
ThemeBase::Colour
wxColour & Colour(int iIndex)
Definition: Theme.cpp:1197
Ruler::Fonts
Definition: Ruler.h:100
Ruler::mSpacing
int mSpacing
Definition: Ruler.h:211
MaxPixelHeight
static constexpr int MaxPixelHeight
Definition: Ruler.cpp:1007
Ruler::Updater::mLeftOffset
const int mLeftOffset
Definition: Ruler.cpp:880
NumberScale
Definition: NumberScale.h:31
theTheme
AUDACITY_DLL_API Theme theTheme
Definition: Theme.cpp:203
Ruler::mTop
int mTop
Definition: Ruler.h:184
Ruler::Cache::mRect
wxRect mRect
Definition: Ruler.cpp:916
Ruler::Updater::UpdateOutputs
Definition: Ruler.cpp:920
Ruler::mCustom
bool mCustom
Definition: Ruler.h:218
Ruler::mHasSetSpacing
bool mHasSetSpacing
Definition: Ruler.h:213
Ruler::Updater::UpdateNonlinear
void UpdateNonlinear(wxDC &dc, UpdateOutputs &allOutputs) const
Definition: Ruler.cpp:1213
Ruler::Updater::mFormat
const RulerFormat mFormat
Definition: Ruler.cpp:860
Ruler::DrawGrid
void DrawGrid(wxDC &dc, int length, bool minor=true, bool major=true, int xOffset=0, int yOffset=0) const
Definition: Ruler.cpp:1521
Ruler::Updater::mLength
const int mLength
Definition: Ruler.cpp:859
Ruler::SetCustomMajorLabels
void SetCustomMajorLabels(const TranslatableStrings &labels, int start, int step)
Ruler::SetCustomMinorLabels
void SetCustomMinorLabels(const TranslatableStrings &labels, int start, int step)
END_EVENT_TABLE
END_EVENT_TABLE()
Ruler::GetFonts
Fonts GetFonts() const
Definition: Ruler.cpp:1418
Ruler::Updater::TickOutputs::bits
Bits & bits
Definition: Ruler.cpp:919
Ruler::SetTwoTone
void SetTwoTone(bool twoTone)
Definition: Ruler.cpp:126
Ruler::~Ruler
~Ruler()
Definition: Ruler.cpp:121
Ruler::mUserBits
Bits mUserBits
Definition: Ruler.h:193
Ruler::Updater::mUnits
const TranslatableString mUnits
Definition: Ruler.cpp:861
Ruler::Label
An array of these created by the Ruler is used to determine what and where text annotations to the nu...
Definition: Ruler.h:157
Ruler::Updater::TickOutputs::labels
Labels & labels
Definition: Ruler.cpp:919
Ruler::mbMinor
bool mbMinor
Definition: Ruler.h:219
Ruler::Updater::mFlip
const bool mFlip
Definition: Ruler.cpp:870
Ruler::Fonts::lead
int lead
Definition: Ruler.h:102