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