1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   Ruler.cpp
6 
7   Dominic Mazzoni
8 
9 *******************************************************************//**
10 
11 \class Ruler
12 \brief Used to display a Ruler.
13 
14   This is a generic class which can be used to display just about
15   any kind of ruler.
16 
17   At a minimum, the user must specify the dimensions of the
18   ruler, its orientation (horizontal or vertical), and the
19   values displayed at the two ends of the ruler (min and max).
20   By default, this class will display tick marks at reasonable
21   round numbers and fractions, for example, 100, 50, 10, 5, 1,
22   0.5, 0.1, etc.
23 
24   The class is designed to display a small handful of
25   labeled Major ticks, and a few Minor ticks between each of
26   these.  Minor ticks are labeled if there is enough space.
27   Labels will never run into each other.
28 
29   In addition to Real numbers, the Ruler currently supports
30   two other formats for its display:
31 
32   Integer - never shows tick marks for fractions of an integer
33 
34   Time - Assumes values represent seconds, and labels the tick
35          marks in "HH:MM:SS" format, e.g. 4000 seconds becomes
36          "1:06:40", for example.  Will display fractions of
37          a second, and tick marks are all reasonable round
38          numbers for time (i.e. 15 seconds, 30 seconds, etc.)
39 *//***************************************************************//**
40 
41 \class RulerPanel
42 \brief RulerPanel class allows you to work with a Ruler like
43   any other wxWindow.
44 
45 *//***************************************************************//**
46 
47 
48 \class Ruler::Label
49 \brief An array of these created by the Ruler is used to determine
50 what and where text annotations to the numbers on the Ruler get drawn.
51 
52 \todo Check whether Ruler is costing too much time in allocation/free of
53 array of Ruler::Label.
54 
55 *//******************************************************************/
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 
Ruler()79 Ruler::Ruler()
80 {
81    mMin = mHiddenMin = 0.0;
82    mMax = mHiddenMax = 100.0;
83    mOrientation = wxHORIZONTAL;
84    mSpacing = 6;
85    mHasSetSpacing = false;
86    mFormat = RealFormat;
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 
~Ruler()121 Ruler::~Ruler()
122 {
123    Invalidate();  // frees up our arrays
124 }
125 
SetTwoTone(bool twoTone)126 void Ruler::SetTwoTone(bool twoTone)
127 {
128    mTwoTone = twoTone;
129 }
130 
SetFormat(RulerFormat format)131 void Ruler::SetFormat(RulerFormat format)
132 {
133    // IntFormat, RealFormat, RealLogFormat, TimeFormat, or LinearDBFormat
134 
135    if (mFormat != format) {
136       mFormat = format;
137 
138       Invalidate();
139    }
140 }
141 
SetLog(bool log)142 void Ruler::SetLog(bool log)
143 {
144    // Logarithmic
145 
146    if (mLog != log) {
147       mLog = log;
148 
149       Invalidate();
150    }
151 }
152 
SetUnits(const TranslatableString & units)153 void Ruler::SetUnits(const TranslatableString &units)
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 
SetDbMirrorValue(const double d)165 void Ruler::SetDbMirrorValue( const double d )
166 {
167    if (mDbMirrorValue != d) {
168       mDbMirrorValue = d;
169 
170       Invalidate();
171    }
172 }
173 
SetOrientation(int orient)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 
SetRange(double min,double max)188 void Ruler::SetRange(double min, double max)
189 {
190    SetRange(min, max, min, max);
191 }
192 
SetRange(double min,double max,double hiddenMin,double hiddenMax)193 void Ruler::SetRange
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 
SetSpacing(int spacing)215 void Ruler::SetSpacing(int spacing)
216 {
217    mHasSetSpacing = true;
218 
219    if (mSpacing != spacing) {
220       mSpacing = spacing;
221 
222       Invalidate();
223    }
224 }
225 
SetLabelEdges(bool labelEdges)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 
SetFlip(bool flip)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 
SetMinor(bool value)252 void Ruler::SetMinor(bool value)
253 {
254    mbMinor = value;
255 }
256 
257 namespace {
FindFontHeights(wxCoord & height,wxCoord & lead,wxDC & dc,const wxFont & font)258 void FindFontHeights(
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 
FindFontHeights(wxCoord & height,wxCoord & lead,wxDC & dc,int fontSize,wxFontWeight weight=wxFONTWEIGHT_NORMAL)269 void FindFontHeights(
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 
SetFonts(const wxFont & minorFont,const wxFont & majorFont,const wxFont & minorMinorFont)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 
SetNumberScale(const NumberScale & scale)294 void Ruler::SetNumberScale(const NumberScale &scale)
295 {
296    if ( mNumberScale != scale ) {
297       mNumberScale = scale;
298       Invalidate();
299    }
300 }
301 
OfflimitsPixels(int start,int end)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 
SetBounds(int left,int top,int right,int bottom)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 
Invalidate()345 void Ruler::Invalidate()
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 
357 struct Ruler::TickSizes
358 {
359    bool useMajor = true;
360 
361    double       mMajor;
362    double       mMinor;
363 
364    int          mDigits;
365 
TickSizesRuler::TickSizes366 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 
LabelStringRuler::TickSizes602 TranslatableString LabelString(
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 
MakeTick(Label lab,wxDC & dc,wxFont font,std::vector<bool> & bits,int left,int top,int spacing,int lead,bool flip,int orientation)758 auto Ruler::MakeTick(
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 
849 struct Ruler::Updater {
850    const Ruler &mRuler;
851    const ZoomInfo *zoomInfo;
852 
UpdaterRuler::Updater853    explicit Updater( const Ruler &ruler, const ZoomInfo *z )
854    : mRuler{ ruler }
855    , zoomInfo{ z }
856    {}
857 
858    const double mDbMirrorValue = mRuler.mDbMirrorValue;
859    const int mLength = mRuler.mLength;
860    const RulerFormat mFormat = mRuler.mFormat;
861    const TranslatableString mUnits = mRuler.mUnits;
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;
869    const int mOrientation = mRuler.mOrientation;
870    const bool mFlip = mRuler.mFlip;
871 
872    const bool mCustom = mRuler.mCustom;
873    const Fonts &mFonts = *mRuler.mpFonts;
874    const bool mLog = mRuler.mLog;
875    const double mHiddenMin = mRuler.mHiddenMin;
876    const double mHiddenMax = mRuler.mHiddenMax;
877    const bool mLabelEdges = mRuler.mLabelEdges;
878    const double mMin = mRuler.mMin;
879    const double mMax = mRuler.mMax;
880    const int mLeftOffset = mRuler.mLeftOffset;
881    const NumberScale mNumberScale = mRuler.mNumberScale;
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 {
914    Bits mBits;
915    Labels mMajorLabels, mMinorLabels, mMinorMinorLabels;
916    wxRect mRect;
917 };
918 
919 struct Ruler::Updater::TickOutputs{ Labels &labels; Bits &bits; wxRect &box; };
920 struct Ruler::Updater::UpdateOutputs {
921    Labels &majorLabels, &minorLabels, &minorMinorLabels;
922    Bits &bits;
923    wxRect &box;
924 };
925 
Tick(wxDC & dc,int pos,double d,const TickSizes & tickSizes,wxFont font,TickOutputs outputs) const926 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,
949       mLeft, mTop, mSpacing, mFonts.lead,
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 
TickCustom(wxDC & dc,int labelIdx,wxFont font,TickOutputs outputs) const959 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 {
ComputeWarpedLength(const Envelope & env,double t0,double t1)989 double ComputeWarpedLength(const Envelope &env, double t0, double t1)
990 {
991    return env.IntegralOfInverse(t0, t1);
992 }
993 
SolveWarpedLength(const Envelope & env,double t0,double length)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 
ChooseFonts(std::unique_ptr<Fonts> & pFonts,const Fonts * pUserFonts,wxDC & dc,int desiredPixelHeight)1017 void Ruler::Updater::ChooseFonts(
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 
UpdateCustom(wxDC & dc,UpdateOutputs & allOutputs) const1052 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 
UpdateLinear(wxDC & dc,const Envelope * envelope,UpdateOutputs & allOutputs) const1066 void Ruler::Updater::UpdateLinear(
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 
UpdateNonlinear(wxDC & dc,UpdateOutputs & allOutputs) const1213 void Ruler::Updater::UpdateNonlinear(
1214     wxDC &dc, UpdateOutputs &allOutputs ) const
1215 {
1216    TickOutputs majorOutputs{
1217       allOutputs.majorLabels, allOutputs.bits, allOutputs.box };
1218 
1219    auto numberScale = ( mNumberScale == NumberScale{} )
1220       ? NumberScale( nstLogarithmic, mMin, mMax )
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 
Update(wxDC & dc,const Envelope * envelope,UpdateOutputs & allOutputs) const1302 void Ruler::Updater::Update(
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 
ChooseFonts(wxDC & dc) const1355 void Ruler::ChooseFonts( wxDC &dc ) const
1356 {
1357    Updater::ChooseFonts( mpFonts, mpUserFonts.get(), dc,
1358       mOrientation == wxHORIZONTAL
1359          ? mBottom - mTop - 5 // height less ticks and 1px gap
1360          : MaxPixelHeight
1361    );
1362 }
1363 
UpdateCache(wxDC & dc,const Envelope * envelope) const1364 void Ruler::UpdateCache(
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 
GetFonts() const1418 auto Ruler::GetFonts() const -> Fonts
1419 {
1420    if ( !mpFonts ) {
1421       wxScreenDC dc;
1422       ChooseFonts( dc );
1423    }
1424 
1425    return *mpFonts;
1426 }
1427 
Draw(wxDC & dc) const1428 void Ruler::Draw(wxDC& dc) const
1429 {
1430    Draw( dc, NULL);
1431 }
1432 
Draw(wxDC & dc,const Envelope * envelope) const1433 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
1455             AColor::Line(dc, mLeft, mBottom, mRight, mBottom);
1456       }
1457       else {
1458          if (mFlip)
1459             AColor::Line(dc, mLeft, mTop, mLeft, mBottom);
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 ***************************
DrawGrid(wxDC & dc,const int gridLineLength,const bool minorGrid,const bool majorGrid,int xOffset,int yOffset) const1521 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 
FindZero(const Labels & labels) const1579 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 
GetZeroPosition() const1592 int Ruler::GetZeroPosition() const
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 
GetMaxSize(wxCoord * width,wxCoord * height)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 
SetCustomMode(bool value)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
1630 void Ruler::SetCustomMajorLabels(
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 
1647 void Ruler::SetCustomMinorLabels(
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 
Draw(wxDC & dc,bool twoTone,wxColour c) const1665 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 
SetUseZoomInfo(int leftOffset,const ZoomInfo * zoomInfo)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 
BEGIN_EVENT_TABLE(RulerPanel,wxPanelWrapper)1697 BEGIN_EVENT_TABLE(RulerPanel, wxPanelWrapper)
1698    EVT_ERASE_BACKGROUND(RulerPanel::OnErase)
1699    EVT_PAINT(RulerPanel::OnPaint)
1700    EVT_SIZE(RulerPanel::OnSize)
1701 END_EVENT_TABLE()
1702 
1703 IMPLEMENT_CLASS(RulerPanel, wxPanelWrapper)
1704 
1705 RulerPanel::RulerPanel(wxWindow* parent, wxWindowID id,
1706                        wxOrientation orientation,
1707                        const wxSize &bounds,
1708                        const Range &range,
1709                        Ruler::RulerFormat format,
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 );
1720    ruler.SetFormat(format);
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 
~RulerPanel()1739 RulerPanel::~RulerPanel()
1740 {
1741 }
1742 
OnErase(wxEraseEvent & WXUNUSED (evt))1743 void RulerPanel::OnErase(wxEraseEvent & WXUNUSED(evt))
1744 {
1745    // Ignore it to prevent flashing
1746 }
1747 
OnPaint(wxPaintEvent & WXUNUSED (evt))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 
OnSize(wxSizeEvent & WXUNUSED (evt))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.
DoSetSize(int x,int y,int width,int height,int sizeFlags)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 }
1779