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