Audacity  2.2.2
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 *******************************************************************//***************************************************************//***************************************************************//***************************************************************//******************************************************************/
65 
66 #include "../Audacity.h"
67 #include "Ruler.h"
68 
69 #include <math.h>
70 
71 #include <wx/app.h>
72 #include <wx/dcscreen.h>
73 #include <wx/dcmemory.h>
74 #include <wx/dcbuffer.h>
75 #include <wx/settings.h>
76 #include <wx/menu.h>
77 #include <wx/menuitem.h>
78 #include <wx/tooltip.h>
79 
80 #include "AButton.h"
81 #include "../AColor.h"
82 #include "../AudioIO.h"
83 #include "../Internat.h"
84 #include "../Project.h"
85 #include "../toolbars/ControlToolBar.h"
86 #include "../Theme.h"
87 #include "../AllThemeResources.h"
88 #include "../Experimental.h"
89 #include "../TimeTrack.h"
90 #include "../TrackPanel.h"
91 #include "../TrackPanelCellIterator.h"
92 #include "../NumberScale.h"
93 #include "../Prefs.h"
94 #include "../Snap.h"
95 #include "../tracks/ui/Scrubbing.h"
96 #include "../prefs/PlaybackPrefs.h"
97 #include "../prefs/TracksPrefs.h"
98 #include "../prefs/TracksBehaviorsPrefs.h"
99 #include "../widgets/Grabber.h"
100 #include "../commands/CommandContext.h"
101 
102 //#define SCRUB_ABOVE
103 
104 using std::min;
105 using std::max;
106 
107 #define SELECT_TOLERANCE_PIXEL 4
108 
109 #define PLAY_REGION_TRIANGLE_SIZE 6
110 #define PLAY_REGION_RECT_WIDTH 1
111 #define PLAY_REGION_RECT_HEIGHT 3
112 #define PLAY_REGION_GLOBAL_OFFSET_Y 7
113 
114 //wxColour Ruler::mTickColour{ 153, 153, 153 };
115 
116 //
117 // Ruler
118 //
119 
121  : mpNumberScale{}
122 {
123  mMin = mHiddenMin = 0.0;
124  mMax = mHiddenMax = 100.0;
125  mOrientation = wxHORIZONTAL;
126  mSpacing = 6;
127  mHasSetSpacing = false;
128  mFormat = RealFormat;
129  mFlip = false;
130  mLog = false;
131  mLabelEdges = false;
132  mUnits = wxT("");
133 
134  mLeft = -1;
135  mTop = -1;
136  mRight = -1;
137  mBottom = -1;
138  mbTicksOnly = true;
139  mbTicksAtExtremes = false;
140  mTickColour = wxColour( theTheme.Colour( clrTrackPanelText ));
141  mPen.SetColour(mTickColour);
142  mDbMirrorValue = 0.0;
143 
144  // Note: the font size is now adjusted automatically whenever
145  // Invalidate is called on a horizontal Ruler, unless the user
146  // calls SetFonts manually. So the defaults here are not used
147  // often.
148 
149  int fontSize = 10;
150 #ifdef __WXMSW__
151  fontSize = 8;
152 #endif
153 
154  mMinorMinorFont = std::make_unique<wxFont>(fontSize - 1, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
155  mMinorFont = std::make_unique<wxFont>(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
156  mMajorFont = std::make_unique<wxFont>(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
157 
158  mUserFonts = false;
159 
160  mLengthOld = 0;
161  mLength = 0;
162  mUserBitLen = 0;
163 
164  mValid = false;
165 
166  mCustom = false;
167  mbMinor = true;
168 
169  mGridLineLength = 0;
170  mMajorGrid = false;
171  mMinorGrid = false;
172 
173  mTwoTone = false;
174 
175  mUseZoomInfo = NULL;
176 }
177 
179 {
180  Invalidate(); // frees up our arrays
181 }
182 
183 void Ruler::SetTwoTone(bool twoTone)
184 {
185  mTwoTone = twoTone;
186 }
187 
189 {
190  // IntFormat, RealFormat, RealLogFormat, TimeFormat, or LinearDBFormat
191 
192  if (mFormat != format) {
193  mFormat = format;
194 
195  Invalidate();
196  }
197 }
198 
199 void Ruler::SetLog(bool log)
200 {
201  // Logarithmic
202 
203  if (mLog != log) {
204  mLog = log;
205 
206  Invalidate();
207  }
208 }
209 
210 void Ruler::SetUnits(const wxString &units)
211 {
212  // Specify the name of the units (like "dB") if you
213  // want numbers like "1.6" formatted as "1.6 dB".
214 
215  if (mUnits != units) {
216  mUnits = units;
217 
218  Invalidate();
219  }
220 }
221 
222 void Ruler::SetOrientation(int orient)
223 {
224  // wxHORIZONTAL || wxVERTICAL
225 
226  if (mOrientation != orient) {
227  mOrientation = orient;
228 
229  if (mOrientation == wxVERTICAL && !mHasSetSpacing)
230  mSpacing = 2;
231 
232  Invalidate();
233  }
234 }
235 
236 void Ruler::SetRange(double min, double max)
237 {
238  SetRange(min, max, min, max);
239 }
240 
241 void Ruler::SetRange
242  (double min, double max, double hiddenMin, double hiddenMax)
243 {
244  // For a horizontal ruler,
245  // min is the value in the center of pixel "left",
246  // max is the value in the center of pixel "right".
247 
248  // In the special case of a time ruler,
249  // hiddenMin and hiddenMax are values that would be shown with the fisheye
250  // turned off. In other cases they equal min and max respectively.
251 
252  if (mMin != min || mMax != max ||
253  mHiddenMin != hiddenMin || mHiddenMax != hiddenMax) {
254  mMin = min;
255  mMax = max;
256  mHiddenMin = hiddenMin;
257  mHiddenMax = hiddenMax;
258 
259  Invalidate();
260  }
261 }
262 
263 void Ruler::SetSpacing(int spacing)
264 {
265  mHasSetSpacing = true;
266 
267  if (mSpacing != spacing) {
268  mSpacing = spacing;
269 
270  Invalidate();
271  }
272 }
273 
274 void Ruler::SetLabelEdges(bool labelEdges)
275 {
276  // If this is true, the edges of the ruler will always
277  // receive a label. If not, the nearest round number is
278  // labeled (which may or may not be the edge).
279 
280  if (mLabelEdges != labelEdges) {
281  mLabelEdges = labelEdges;
282 
283  Invalidate();
284  }
285 }
286 
287 void Ruler::SetFlip(bool flip)
288 {
289  // If this is true, the orientation of the tick marks
290  // is reversed from the default; eg. above the line
291  // instead of below
292 
293  if (mFlip != flip) {
294  mFlip = flip;
295 
296  Invalidate();
297  }
298 }
299 
300 void Ruler::SetMinor(bool value)
301 {
302  mbMinor = value;
303 }
304 
305 void Ruler::SetFonts(const wxFont &minorFont, const wxFont &majorFont, const wxFont &minorMinorFont)
306 {
307  *mMinorMinorFont = minorMinorFont;
308  *mMinorFont = minorFont;
309  *mMajorFont = majorFont;
310 
311  // Won't override these fonts
312  mUserFonts = true;
313 
314  Invalidate();
315 }
316 
318 {
319  if (!pScale) {
320  if (mpNumberScale) {
321  mpNumberScale.reset();
322  Invalidate();
323  }
324  }
325  else {
326  if (!mpNumberScale || *mpNumberScale != *pScale) {
327  mpNumberScale = std::make_unique<NumberScale>(*pScale);
328  Invalidate();
329  }
330  }
331 }
332 
333 void Ruler::OfflimitsPixels(int start, int end)
334 {
335  if (!mUserBits) {
336  if (mOrientation == wxHORIZONTAL)
337  mLength = mRight-mLeft;
338  else
339  mLength = mBottom-mTop;
340  if( mLength < 0 )
341  return;
342  mUserBits.reinit(static_cast<size_t>(mLength+1), true);
343  mUserBitLen = mLength+1;
344  }
345 
346  if (end < start)
347  std::swap( start, end );
348 
349  if (start < 0)
350  start = 0;
351  if (end > mLength)
352  end = mLength;
353 
354  for(int i = start; i <= end; i++)
355  mUserBits[i] = 1;
356 }
357 
358 void Ruler::SetBounds(int left, int top, int right, int bottom)
359 {
360  if (mLeft != left || mTop != top ||
361  mRight != right || mBottom != bottom) {
362  mLeft = left;
363  mTop = top;
364  mRight = right;
365  mBottom = bottom;
366 
367  Invalidate();
368  }
369 }
370 
372 {
373  mValid = false;
374 
375  if (mOrientation == wxHORIZONTAL)
376  mLength = mRight-mLeft;
377  else
378  mLength = mBottom-mTop;
379 
380  mBits.reset();
381  if (mUserBits && mLength+1 != mUserBitLen) {
382  mUserBits.reset();
383  mUserBitLen = 0;
384  }
385 }
386 
388 {
389  // Given the dimensions of the ruler, the range of values it
390  // has to display, and the format (i.e. Int, Real, Time),
391  // figure out how many units are in one Minor tick, and
392  // in one Major tick.
393  //
394  // The goal is to always put tick marks on nice round numbers
395  // that are easy for humans to grok. This is the most tricky
396  // with time.
397 
398  double d;
399 
400  // As a heuristic, we want at least 22 pixels between each
401  // minor tick. We want to show numbers like "-48"
402  // in that space.
403  // If vertical, we don't need as much space.
404  double units = ((mOrientation == wxHORIZONTAL) ? 22 : 16) * fabs(UPP);
405 
406  mDigits = 0;
407 
408  switch(mFormat) {
409  case LinearDBFormat:
410  if (units < 0.001) {
411  mMinor = 0.001;
412  mMajor = 0.005;
413  return;
414  }
415  if (units < 0.01) {
416  mMinor = 0.01;
417  mMajor = 0.05;
418  return;
419  }
420  if (units < 0.1) {
421  mMinor = 0.1;
422  mMajor = 0.5;
423  return;
424  }
425  if (units < 1.0) {
426  mMinor = 1.0;
427  mMajor = 6.0;
428  return;
429  }
430  if (units < 3.0) {
431  mMinor = 3.0;
432  mMajor = 12.0;
433  return;
434  }
435  if (units < 6.0) {
436  mMinor = 6.0;
437  mMajor = 24.0;
438  return;
439  }
440  if (units < 12.0) {
441  mMinor = 12.0;
442  mMajor = 48.0;
443  return;
444  }
445  if (units < 24.0) {
446  mMinor = 24.0;
447  mMajor = 96.0;
448  return;
449  }
450  d = 20.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*5.0;
461  return;
462  }
463  d *= 2.0;
464  }
465  break;
466 
467  case IntFormat:
468  d = 1.0;
469  for(;;) {
470  if (units < d) {
471  mMinor = d;
472  mMajor = d*5.0;
473  return;
474  }
475  d *= 5.0;
476  if (units < d) {
477  mMinor = d;
478  mMajor = d*2.0;
479  return;
480  }
481  d *= 2.0;
482  }
483  break;
484 
485  case TimeFormat:
486  if (units > 0.5) {
487  if (units < 1.0) { // 1 sec
488  mMinor = 1.0;
489  mMajor = 5.0;
490  return;
491  }
492  if (units < 5.0) { // 5 sec
493  mMinor = 5.0;
494  mMajor = 15.0;
495  return;
496  }
497  if (units < 10.0) {
498  mMinor = 10.0;
499  mMajor = 30.0;
500  return;
501  }
502  if (units < 15.0) {
503  mMinor = 15.0;
504  mMajor = 60.0;
505  return;
506  }
507  if (units < 30.0) {
508  mMinor = 30.0;
509  mMajor = 60.0;
510  return;
511  }
512  if (units < 60.0) { // 1 min
513  mMinor = 60.0;
514  mMajor = 300.0;
515  return;
516  }
517  if (units < 300.0) { // 5 min
518  mMinor = 300.0;
519  mMajor = 900.0;
520  return;
521  }
522  if (units < 600.0) { // 10 min
523  mMinor = 600.0;
524  mMajor = 1800.0;
525  return;
526  }
527  if (units < 900.0) { // 15 min
528  mMinor = 900.0;
529  mMajor = 3600.0;
530  return;
531  }
532  if (units < 1800.0) { // 30 min
533  mMinor = 1800.0;
534  mMajor = 3600.0;
535  return;
536  }
537  if (units < 3600.0) { // 1 hr
538  mMinor = 3600.0;
539  mMajor = 6*3600.0;
540  return;
541  }
542  if (units < 6*3600.0) { // 6 hrs
543  mMinor = 6*3600.0;
544  mMajor = 24*3600.0;
545  return;
546  }
547  if (units < 24*3600.0) { // 1 day
548  mMinor = 24*3600.0;
549  mMajor = 7*24*3600.0;
550  return;
551  }
552 
553  mMinor = 24.0 * 7.0 * 3600.0; // 1 week
554  mMajor = 24.0 * 7.0 * 3600.0;
555  }
556 
557  // Otherwise fall through to RealFormat
558  // (fractions of a second should be dealt with
559  // the same way as for RealFormat)
560 
561  case RealFormat:
562  d = 0.000001;
563  // mDigits is number of digits after the decimal point.
564  mDigits = 6;
565  for(;;) {
566  if (units < d) {
567  mMinor = d;
568  mMajor = d*5.0;
569  return;
570  }
571  d *= 5.0;
572  if (units < d) {
573  mMinor = d;
574  mMajor = d*2.0;
575  return;
576  }
577  d *= 2.0;
578  mDigits--;
579  // More than 10 digit numbers? Something is badly wrong.
580  // Probably units is coming in with too high a value.
581  wxASSERT( mDigits >= -10 );
582  if( mDigits < -10 )
583  break;
584  }
585  mMinor = d;
586  mMajor = d * 2.0;
587  break;
588 
589  case RealLogFormat:
590  d = 0.000001;
591  // mDigits is number of digits after the decimal point.
592  mDigits = 6;
593  for(;;) {
594  if (units < d) {
595  mMinor = d;
596  mMajor = d*5.0;
597  return;
598  }
599  d *= 5.0;
600  if (units < d) {
601  mMinor = d;
602  mMajor = d*2.0;
603  return;
604  }
605  d *= 2.0;
606  mDigits--;
607  // More than 10 digit numbers? Something is badly wrong.
608  // Probably units is coming in with too high a value.
609  wxASSERT( mDigits >= -10 );
610  if( mDigits < -10 )
611  break;
612  }
613  mDigits++;
614  mMinor = d;
615  mMajor = d * 2.0;
616  break;
617  }
618 }
619 
620 wxString Ruler::LabelString(double d, bool major)
621 {
622  // Given a value, turn it into a string according
623  // to the current ruler format. The number of digits of
624  // accuracy depends on the resolution of the ruler,
625  // i.e. how far zoomed in or out you are.
626 
627  wxString s;
628 
629  // Replace -0 with 0
630  if (d < 0.0 && (d+mMinor > 0.0) && ( mFormat != RealLogFormat ))
631  d = 0.0;
632 
633  switch(mFormat) {
634  case IntFormat:
635  s.Printf(wxT("%d"), (int)floor(d+0.5));
636  break;
637  case LinearDBFormat:
638  if (mMinor >= 1.0)
639  s.Printf(wxT("%d"), (int)floor(d+0.5));
640  else {
641  int precision = -log10(mMinor);
642  s.Printf(wxT("%.*f"), precision, d);
643  }
644  break;
645  case RealFormat:
646  if (mMinor >= 1.0)
647  s.Printf(wxT("%d"), (int)floor(d+0.5));
648  else {
649  s.Printf(wxString::Format(wxT("%%.%df"), mDigits), d);
650  }
651  break;
652  case RealLogFormat:
653  if (mMinor >= 1.0)
654  s.Printf(wxT("%d"), (int)floor(d+0.5));
655  else {
656  s.Printf(wxString::Format(wxT("%%.%df"), mDigits), d);
657  }
658  break;
659  case TimeFormat:
660  if (major) {
661  if (d < 0) {
662  s = wxT("-");
663  d = -d;
664  }
665 
666  #if ALWAYS_HH_MM_SS
667  int secs = (int)(d + 0.5);
668  if (mMinor >= 1.0) {
669  s.Printf(wxT("%d:%02d:%02d"), secs/3600, (secs/60)%60, secs%60);
670  }
671  else {
672  wxString t1, t2, format;
673  t1.Printf(wxT("%d:%02d:"), secs/3600, (secs/60)%60);
674  format.Printf(wxT("%%0%d.%dlf"), mDigits+3, mDigits);
675  t2.Printf(format, fmod(d, 60.0));
676  s += t1 + t2;
677  }
678  break;
679  #endif
680 
681  if (mMinor >= 3600.0) {
682  int hrs = (int)(d / 3600.0 + 0.5);
683  wxString h;
684  h.Printf(wxT("%d:00:00"), hrs);
685  s += h;
686  }
687  else if (mMinor >= 60.0) {
688  int minutes = (int)(d / 60.0 + 0.5);
689  wxString m;
690  if (minutes >= 60)
691  m.Printf(wxT("%d:%02d:00"), minutes/60, minutes%60);
692  else
693  m.Printf(wxT("%d:00"), minutes);
694  s += m;
695  }
696  else if (mMinor >= 1.0) {
697  int secs = (int)(d + 0.5);
698  wxString t;
699  if (secs >= 3600)
700  t.Printf(wxT("%d:%02d:%02d"), secs/3600, (secs/60)%60, secs%60);
701  else if (secs >= 60)
702  t.Printf(wxT("%d:%02d"), secs/60, secs%60);
703  else
704  t.Printf(wxT("%d"), secs);
705  s += t;
706  }
707  else {
708 // Commented out old and incorrect code for avoiding the 40mins and 60 seconds problem
709 // It was causing Bug 463 - Incorrect Timeline numbering (where at high zoom and long tracks,
710 // numbers did not change.
711 #if 0
712  // The casting to float is working around an issue where 59 seconds
713  // would show up as 60 when using g++ (Ubuntu 4.3.3-5ubuntu4) 4.3.3.
714  int secs = (int)(float)(d);
715  wxString t1, t2, format;
716 
717  if (secs >= 3600)
718  t1.Printf(wxT("%d:%02d:"), secs/3600, (secs/60)%60);
719  else if (secs >= 60)
720  t1.Printf(wxT("%d:"), secs/60);
721 
722  if (secs >= 60)
723  format.Printf(wxT("%%0%d.%dlf"), mDigits+3, mDigits);
724  else
725  format.Printf(wxT("%%%d.%dlf"), mDigits+3, mDigits);
726  // The casting to float is working around an issue where 59 seconds
727  // would show up as 60 when using g++ (Ubuntu 4.3.3-5ubuntu4) 4.3.3.
728  t2.Printf(format, fmod((float)d, (float)60.0));
729 #else
730  // For d in the range of hours, d is just very slightly below the value it should
731  // have, because of using a double, which in turn yields values like 59:59:999999
732  // mins:secs:nanosecs when we want 1:00:00:000000
733  // so adjust by less than a nano second per hour to get nicer number formatting.
734  double dd = d * 1.000000000000001;
735  int secs = (int)(dd);
736  wxString t1, t2, format;
737 
738  if (secs >= 3600)
739  t1.Printf(wxT("%d:%02d:"), secs/3600, (secs/60)%60);
740  else if (secs >= 60)
741  t1.Printf(wxT("%d:"), secs/60);
742 
743  if (secs >= 60)
744  format.Printf(wxT("%%0%d.%dlf"), mDigits+3, mDigits);
745  else
746  format.Printf(wxT("%%%d.%dlf"), mDigits+3, mDigits);
747  // dd will be reduced to just the seconds and fractional part.
748  dd = dd - secs + (secs%60);
749  // truncate to appropriate number of digits, so that the print formatting
750  // doesn't round up 59.9999999 to 60.
751  double multiplier = pow( 10, mDigits);
752  dd = ((int)(dd * multiplier))/multiplier;
753  t2.Printf(format, dd);
754 #endif
755  s += t1 + t2;
756  }
757  }
758  else {
759  }
760  }
761 
762  if (mUnits != wxT(""))
763  s = (s + mUnits);
764 
765  return s;
766 }
767 
768 void Ruler::Tick(int pos, double d, bool major, bool minor)
769 {
770  wxString l;
771  wxCoord strW, strH, strD, strL;
772  int strPos, strLen, strLeft, strTop;
773 
774  // FIXME: We don't draw a tick if off end of our label arrays
775  // But we shouldn't have an array of labels.
776  if( mNumMinorMinor >= mLength )
777  return;
778  if( mNumMinor >= mLength )
779  return;
780  if( mNumMajor >= mLength )
781  return;
782 
783  Label *label;
784  if (major)
785  label = &mMajorLabels[mNumMajor++];
786  else if (minor)
787  label = &mMinorLabels[mNumMinor++];
788  else
789  label = &mMinorMinorLabels[mNumMinorMinor++];
790 
791  label->value = d;
792  label->pos = pos;
793  label->lx = mLeft - 1000; // don't display
794  label->ly = mTop - 1000; // don't display
795  label->text = wxT("");
796 
797  mDC->SetFont(major? *mMajorFont: minor? *mMinorFont : *mMinorMinorFont);
798  // Bug 521. dB view for waveforms needs a 2-sided scale.
799  if(( mDbMirrorValue > 1.0 ) && ( -d > mDbMirrorValue ))
800  d = -2*mDbMirrorValue - d;
801  l = LabelString(d, major);
802  mDC->GetTextExtent(l, &strW, &strH, &strD, &strL);
803 
804  if (mOrientation == wxHORIZONTAL) {
805  strLen = strW;
806  strPos = pos - strW/2;
807  if (strPos < 0)
808  strPos = 0;
809  if (strPos + strW >= mLength)
810  strPos = mLength - strW;
811  strLeft = mLeft + strPos;
812  if (mFlip) {
813  strTop = mTop + 4;
814  mMaxHeight = max(mMaxHeight, strH + 4);
815  }
816  else {
817  strTop =-strH-mLead;
818  mMaxHeight = max(mMaxHeight, strH + 6);
819  }
820  }
821  else {
822  strLen = strH;
823  strPos = pos - strH/2;
824  if (strPos < 0)
825  strPos = 0;
826  if (strPos + strH >= mLength)
827  strPos = mLength - strH;
828  strTop = mTop + strPos;
829  if (mFlip) {
830  strLeft = mLeft + 5;
831  mMaxWidth = max(mMaxWidth, strW + 5);
832  }
833  else
834  strLeft =-strW-6;
835  }
836 
837 
838  // FIXME: we shouldn't even get here if strPos < 0.
839  // Ruler code currently does not handle very small or
840  // negative sized windows (i.e. don't draw) properly.
841  if( strPos < 0 )
842  return;
843 
844  // See if any of the pixels we need to draw this
845  // label is already covered
846 
847  int i;
848  for(i=0; i<strLen; i++)
849  if (mBits[strPos+i])
850  return;
851 
852  // If not, position the label and give it text
853 
854  label->lx = strLeft;
855  label->ly = strTop;
856  label->text = l;
857 
858  // And mark these pixels, plus some surrounding
859  // ones (the spacing between labels), as covered
860  int leftMargin = mSpacing;
861  if (strPos < leftMargin)
862  leftMargin = strPos;
863  strPos -= leftMargin;
864  strLen += leftMargin;
865 
866  int rightMargin = mSpacing;
867  if (strPos + strLen > mLength - mSpacing)
868  rightMargin = mLength - strPos - strLen;
869  strLen += rightMargin;
870 
871  for(i=0; i<strLen; i++)
872  mBits[strPos+i] = 1;
873 
874  wxRect r(strLeft, strTop, strW, strH);
875  mRect.Union(r);
876 
877 }
878 
879 void Ruler::TickCustom(int labelIdx, bool major, bool minor)
880 {
881  //This should only used in the mCustom case
882  // Many code comes from 'Tick' method: this should
883  // be optimized.
884 
885  int pos;
886  wxString l;
887  wxCoord strW, strH, strD, strL;
888  int strPos, strLen, strLeft, strTop;
889 
890  // FIXME: We don't draw a tick if of end of our label arrays
891  // But we shouldn't have an array of labels.
892  if( mNumMinor >= mLength )
893  return;
894  if( mNumMajor >= mLength )
895  return;
896 
897  Label *label;
898  if (major)
899  label = &mMajorLabels[labelIdx];
900  else if (minor)
901  label = &mMinorLabels[labelIdx];
902  else
903  label = &mMinorMinorLabels[labelIdx];
904 
905  label->value = 0.0;
906  pos = label->pos; // already stored in label class
907  l = label->text;
908  label->lx = mLeft - 1000; // don't display
909  label->ly = mTop - 1000; // don't display
910 
911  mDC->SetFont(major? *mMajorFont: minor? *mMinorFont : *mMinorMinorFont);
912 
913  mDC->GetTextExtent(l, &strW, &strH, &strD, &strL);
914 
915  if (mOrientation == wxHORIZONTAL) {
916  strLen = strW;
917  strPos = pos - strW/2;
918  if (strPos < 0)
919  strPos = 0;
920  if (strPos + strW >= mLength)
921  strPos = mLength - strW;
922  strLeft = mLeft + strPos;
923  if (mFlip) {
924  strTop = mTop + 4;
925  mMaxHeight = max(mMaxHeight, strH + 4);
926  }
927  else {
928 
929  strTop = mTop- mLead+4;// More space was needed...
930  mMaxHeight = max(mMaxHeight, strH + 6);
931  }
932  }
933  else {
934  strLen = strH;
935  strPos = pos - strH/2;
936  if (strPos < 0)
937  strPos = 0;
938  if (strPos + strH >= mLength)
939  strPos = mLength - strH;
940  strTop = mTop + strPos;
941  if (mFlip) {
942  strLeft = mLeft + 5;
943  mMaxWidth = max(mMaxWidth, strW + 5);
944  }
945  else {
946 
947  strLeft =-strW-6;
948  }
949  }
950 
951 
952  // FIXME: we shouldn't even get here if strPos < 0.
953  // Ruler code currently does not handle very small or
954  // negative sized windows (i.e. don't draw) properly.
955  if( strPos < 0 )
956  return;
957 
958  // See if any of the pixels we need to draw this
959  // label is already covered
960 
961  int i;
962  for(i=0; i<strLen; i++)
963  if (mBits[strPos+i])
964  return;
965 
966  // If not, position the label
967 
968  label->lx = strLeft;
969  label->ly = strTop;
970 
971  // And mark these pixels, plus some surrounding
972  // ones (the spacing between labels), as covered
973  int leftMargin = mSpacing;
974  if (strPos < leftMargin)
975  leftMargin = strPos;
976  strPos -= leftMargin;
977  strLen += leftMargin;
978 
979  int rightMargin = mSpacing;
980  if (strPos + strLen > mLength - mSpacing)
981  rightMargin = mLength - strPos - strLen;
982  strLen += rightMargin;
983 
984  for(i=0; i<strLen; i++)
985  mBits[strPos+i] = 1;
986 
987 
988  wxRect r(strLeft, strTop, strW, strH);
989  mRect.Union(r);
990 
991 }
992 
994 {
995  Update(NULL);
996 }
997 
998 void Ruler::Update(const TimeTrack* timetrack)// Envelope *speedEnv, long minSpeed, long maxSpeed )
999 {
1000  const ZoomInfo *zoomInfo = NULL;
1001  if (!mLog && mOrientation == wxHORIZONTAL)
1002  zoomInfo = mUseZoomInfo;
1003 
1004  // This gets called when something has been changed
1005  // (i.e. we've been invalidated). Recompute all
1006  // tick positions and font size.
1007 
1008  int i;
1009  int j;
1010 
1011  if (!mUserFonts) {
1012  int fontSize = 4;
1013  wxCoord strW, strH, strD, strL;
1014  wxString exampleText = wxT("0.9"); //ignored for height calcs on all platforms
1015  int desiredPixelHeight;
1016 
1017 
1018  static const int MinPixelHeight = 10; // 8;
1019  static const int MaxPixelHeight =
1020 #ifdef __WXMAC__
1021  10
1022 #else
1023  12
1024 #endif
1025  ;
1026 
1027  if (mOrientation == wxHORIZONTAL)
1028  desiredPixelHeight = mBottom - mTop - 5; // height less ticks and 1px gap
1029  else
1030  desiredPixelHeight = MaxPixelHeight;
1031 
1032  desiredPixelHeight =
1033  std::max(MinPixelHeight, std::min(MaxPixelHeight,
1034  desiredPixelHeight));
1035 
1036  // Keep making the font bigger until it's too big, then subtract one.
1037  mDC->SetFont(wxFont(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
1038  mDC->GetTextExtent(exampleText, &strW, &strH, &strD, &strL);
1039  while ((strH - strD - strL) <= desiredPixelHeight && fontSize < 40) {
1040  fontSize++;
1041  mDC->SetFont(wxFont(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
1042  mDC->GetTextExtent(exampleText, &strW, &strH, &strD, &strL);
1043  }
1044  fontSize--;
1045  mDC->SetFont(wxFont(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
1046  mDC->GetTextExtent(exampleText, &strW, &strH, &strD, &strL);
1047  mLead = strL;
1048 
1049  mMajorFont = std::make_unique<wxFont>(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
1050 
1051  mMinorFont = std::make_unique<wxFont>(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
1052 
1053  mMinorMinorFont = std::make_unique<wxFont>(fontSize - 1, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
1054  }
1055 
1056  // If ruler is being resized, we could end up with it being too small.
1057  // Values of mLength of zero or below cause bad array allocations and
1058  // division by zero. So...
1059  // IF too small THEN bail out and don't draw.
1060  if( mLength <= 0 )
1061  return;
1062 
1063  if (mOrientation == wxHORIZONTAL) {
1064  mMaxWidth = mLength;
1065  mMaxHeight = 0;
1066  mRect = wxRect(0,0, mLength,0);
1067  }
1068  else {
1069  mMaxWidth = 0;
1070  mMaxHeight = mLength;
1071  mRect = wxRect(0,0, 0,mLength);
1072  }
1073 
1074  // FIXME: Surely we do not need to allocate storage for the labels?
1075  // We can just recompute them as we need them? Yes, but only if
1076  // mCustom is false!!!!
1077 
1078  auto size = static_cast<size_t>(mLength + 1);
1079  if(!mCustom) {
1080  mNumMajor = 0;
1081  mNumMinor = 0;
1082  mNumMinorMinor = 0;
1083  if (mLength!=mLengthOld) {
1084  mMajorLabels.reinit(size);
1085  mMinorLabels.reinit(size);
1086  mMinorMinorLabels.reinit(size);
1087  mLengthOld = mLength;
1088  }
1089  }
1090 
1091  mBits.reinit(size);
1092  if (mUserBits)
1093  for(i=0; i<=mLength; i++)
1094  mBits[i] = mUserBits[i];
1095  else
1096  for(i=0; i<=mLength; i++)
1097  mBits[i] = 0;
1098 
1099  // *************** Label calculation routine **************
1100  if(mCustom == true) {
1101 
1102  // SET PARAMETER IN MCUSTOM CASE
1103  // Works only with major labels
1104 
1105  int numLabel = mNumMajor;
1106 
1107  i = 0;
1108  while((i<numLabel) && (i<=mLength)) {
1109 
1110  TickCustom(i, true, false);
1111  i++;
1112  }
1113 
1114  } else if(mLog==false) {
1115 
1116  // Use the "hidden" min and max to determine the tick size.
1117  // That may make a difference with fisheye.
1118  // Otherwise you may see the tick size for the whole ruler change
1119  // when the fisheye approaches start or end.
1120  double UPP = (mHiddenMax-mHiddenMin)/mLength; // Units per pixel
1121  FindLinearTickSizes(UPP);
1122 
1123  // Left and Right Edges
1124  if (mLabelEdges) {
1125  Tick(0, mMin, true, false);
1126  Tick(mLength, mMax, true, false);
1127  }
1128 
1129  // Zero (if it's in the middle somewhere)
1130  if (mMin * mMax < 0.0) {
1131  int mid;
1132  if (zoomInfo != NULL)
1133  mid = (int)(zoomInfo->TimeToPosition(0.0, mLeftOffset));
1134  else
1135  mid = (int)(mLength*(mMin / (mMin - mMax)) + 0.5);
1136  const int iMaxPos = (mOrientation == wxHORIZONTAL) ? mRight : mBottom - 5;
1137  if (mid >= 0 && mid < iMaxPos)
1138  Tick(mid, 0.0, true, false);
1139  }
1140 
1141  double sg = UPP > 0.0? 1.0: -1.0;
1142 
1143  int nDroppedMinorLabels=0;
1144  // Major and minor ticks
1145  for (int jj = 0; jj < 2; ++jj) {
1146  const double denom = jj == 0 ? mMajor : mMinor;
1147  i = -1; j = 0;
1148  double d, warpedD, nextD;
1149 
1150  double prevTime = 0.0, time = 0.0;
1151  if (zoomInfo != NULL) {
1152  j = zoomInfo->TimeToPosition(mMin);
1153  prevTime = zoomInfo->PositionToTime(--j);
1154  time = zoomInfo->PositionToTime(++j);
1155  d = (prevTime + time) / 2.0;
1156  }
1157  else
1158  d = mMin - UPP / 2;
1159  if (timetrack)
1160  warpedD = timetrack->ComputeWarpedLength(0.0, d);
1161  else
1162  warpedD = d;
1163  // using ints doesn't work, as
1164  // this will overflow and be negative at high zoom.
1165  double step = floor(sg * warpedD / denom);
1166  while (i <= mLength) {
1167  i++;
1168  if (zoomInfo)
1169  {
1170  prevTime = time;
1171  time = zoomInfo->PositionToTime(++j);
1172  nextD = (prevTime + time) / 2.0;
1173  // wxASSERT(time >= prevTime);
1174  }
1175  else
1176  nextD = d + UPP;
1177  if (timetrack)
1178  warpedD += timetrack->ComputeWarpedLength(d, nextD);
1179  else
1180  warpedD = nextD;
1181  d = nextD;
1182 
1183  if (floor(sg * warpedD / denom) > step) {
1184  step = floor(sg * warpedD / denom);
1185  bool major = jj == 0;
1186  Tick(i, sg * step * denom, major, !major);
1187  if( !major && mMinorLabels[mNumMinor-1].text.IsEmpty() ){
1188  nDroppedMinorLabels++;
1189  }
1190  }
1191  }
1192  // If we've dropped minor labels through overcrowding, then don't show
1193  // any of them. We're allowed though to drop ones which correspond to the
1194  // major numbers.
1195  if( nDroppedMinorLabels > (mNumMajor+ (mLabelEdges ? 2:0)) )
1196  mNumMinor = 0;
1197  }
1198 
1199  // Left and Right Edges
1200  if (mLabelEdges) {
1201  Tick(0, mMin, true, false);
1202  Tick(mLength, mMax, true, false);
1203  }
1204  }
1205  else {
1206  // log case
1207 
1208  NumberScale numberScale(mpNumberScale
1209  ? *mpNumberScale
1211  );
1212 
1213  mDigits=2; //TODO: implement dynamic digit computation
1214  double loLog = log10(mMin);
1215  double hiLog = log10(mMax);
1216  int loDecade = (int) floor(loLog);
1217 
1218  double val;
1219  double startDecade = pow(10., (double)loDecade);
1220 
1221  // Major ticks are the decades
1222  double decade = startDecade;
1223  double delta=hiLog-loLog, steps=fabs(delta);
1224  double step = delta>=0 ? 10 : 0.1;
1225  double rMin=std::min(mMin, mMax), rMax=std::max(mMin, mMax);
1226  for(i=0; i<=steps; i++)
1227  { // if(i!=0)
1228  { val = decade;
1229  if(val >= rMin && val < rMax) {
1230  const int pos(0.5 + mLength * numberScale.ValueToPosition(val));
1231  Tick(pos, val, true, false);
1232  }
1233  }
1234  decade *= step;
1235  }
1236 
1237  // Minor ticks are multiples of decades
1238  decade = startDecade;
1239  float start, end, mstep;
1240  if (delta > 0)
1241  { start=2; end=10; mstep=1;
1242  }else
1243  { start=9; end=1; mstep=-1;
1244  }
1245  steps++;
1246  for(i=0; i<=steps; i++) {
1247  for(j=start; j!=end; j+=mstep) {
1248  val = decade * j;
1249  if(val >= rMin && val < rMax) {
1250  const int pos(0.5 + mLength * numberScale.ValueToPosition(val));
1251  Tick(pos, val, false, true);
1252  }
1253  }
1254  decade *= step;
1255  }
1256 
1257  // MinorMinor ticks are multiples of decades
1258  decade = startDecade;
1259  if (delta > 0)
1260  { start= 10; end=100; mstep= 1;
1261  }else
1262  { start=100; end= 10; mstep=-1;
1263  }
1264  steps++;
1265  for (i = 0; i <= steps; i++) {
1266  // PRL: Bug1038. Don't label 1.6, rounded, as a duplicate tick for "2"
1267  if (!(mFormat == IntFormat && decade < 10.0)) {
1268  for (int f = start; f != (int)(end); f += mstep) {
1269  if ((int)(f / 10) != f / 10.0f) {
1270  val = decade * f / 10;
1271  if (val >= rMin && val < rMax) {
1272  const int pos(0.5 + mLength * numberScale.ValueToPosition(val));
1273  Tick(pos, val, false, false);
1274  }
1275  }
1276  }
1277  }
1278  decade *= step;
1279  }
1280  }
1281 
1282  int displacementx=0, displacementy=0;
1283  if (!mFlip) {
1284  if (mOrientation==wxHORIZONTAL) {
1285  int d=mTop+mRect.GetHeight()+5;
1286  mRect.Offset(0,d);
1287  mRect.Inflate(0,5);
1288  displacementx=0;
1289  displacementy=d;
1290  }
1291  else {
1292  int d=mLeft-mRect.GetLeft()+5;
1293  mRect.Offset(d,0);
1294  mRect.Inflate(5,0);
1295  displacementx=d;
1296  displacementy=0;
1297  }
1298  }
1299  else {
1300  if (mOrientation==wxHORIZONTAL) {
1301  mRect.Inflate(0,5);
1302  displacementx=0;
1303  displacementy=0;
1304  }
1305  }
1306  for(i=0; i<mNumMajor; i++) {
1307  mMajorLabels[i].lx+= displacementx;
1308  mMajorLabels[i].ly+= displacementy;
1309  }
1310  for(i=0; i<mNumMinor; i++) {
1311  mMinorLabels[i].lx+= displacementx;
1312  mMinorLabels[i].ly+= displacementy;
1313  }
1314  for(i=0; i<mNumMinorMinor; i++) {
1315  mMinorMinorLabels[i].lx+= displacementx;
1316  mMinorMinorLabels[i].ly+= displacementy;
1317  }
1318  mMaxWidth = mRect.GetWidth ();
1319  mMaxHeight= mRect.GetHeight();
1320  mValid = true;
1321 }
1322 
1323 void Ruler::Draw(wxDC& dc)
1324 {
1325  Draw( dc, NULL);
1326 }
1327 
1328 void Ruler::Draw(wxDC& dc, const TimeTrack* timetrack)
1329 {
1330  mDC = &dc;
1331  if( mLength <=0 )
1332  return;
1333 
1334  if (!mValid)
1335  Update(timetrack);
1336 
1337  mDC->SetTextForeground( mTickColour );
1338 #ifdef EXPERIMENTAL_THEMING
1339  mDC->SetPen(mPen);
1340 #else
1341  mDC->SetPen(*wxBLACK_PEN);
1342 #endif
1343 
1344  // Draws a long line the length of the ruler.
1345  if( !mbTicksOnly )
1346  {
1347  if (mOrientation == wxHORIZONTAL) {
1348  if (mFlip)
1349  mDC->DrawLine(mLeft, mTop, mRight+1, mTop);
1350  else
1351  mDC->DrawLine(mLeft, mBottom, mRight+1, mBottom);
1352  }
1353  else {
1354  if (mFlip)
1355  mDC->DrawLine(mLeft, mTop, mLeft, mBottom+1);
1356  else
1357  {
1358  // These calculations appear to be wrong, and to never have been used (so not tested) prior to MixerBoard.
1359  // mDC->DrawLine(mRect.x-mRect.width, mTop, mRect.x-mRect.width, mBottom+1);
1360  const int nLineX = mRight - 1;
1361  mDC->DrawLine(nLineX, mTop, nLineX, mBottom+1);
1362  }
1363  }
1364  }
1365 
1366  int i;
1367 
1368  mDC->SetFont(*mMajorFont);
1369 
1370  // We may want to not show the ticks at the extremes,
1371  // though still showing the labels.
1372  // This gives a better look when the ruler is on a bevelled
1373  // button, since otherwise the tick is drawn on the bevel.
1374  int iMaxPos = (mOrientation==wxHORIZONTAL)? mRight : mBottom-5;
1375 
1376  for(i=0; i<mNumMajor; i++) {
1377  int pos = mMajorLabels[i].pos;
1378 
1379  if( mbTicksAtExtremes || ((pos!=0)&&(pos!=iMaxPos)))
1380  {
1381  if (mOrientation == wxHORIZONTAL) {
1382  if (mFlip)
1383  mDC->DrawLine(mLeft + pos, mTop,
1384  mLeft + pos, mTop + 4);
1385  else
1386  mDC->DrawLine(mLeft + pos, mBottom - 4,
1387  mLeft + pos, mBottom);
1388  }
1389  else {
1390  if (mFlip)
1391  mDC->DrawLine(mLeft, mTop + pos,
1392  mLeft + 4, mTop + pos);
1393  else
1394  mDC->DrawLine(mRight - 4, mTop + pos,
1395  mRight, mTop + pos);
1396  }
1397  }
1398 
1399  mMajorLabels[i].Draw(*mDC, mTwoTone, mTickColour);
1400  }
1401 
1402  if(mbMinor == true) {
1403  mDC->SetFont(*mMinorFont);
1404  for(i=0; i<mNumMinor; i++) {
1405  int pos = mMinorLabels[i].pos;
1406  if( mbTicksAtExtremes || ((pos!=0)&&(pos!=iMaxPos)))
1407  {
1408  if (mOrientation == wxHORIZONTAL)
1409  {
1410  if (mFlip)
1411  mDC->DrawLine(mLeft + pos, mTop,
1412  mLeft + pos, mTop + 2);
1413  else
1414  mDC->DrawLine(mLeft + pos, mBottom - 2,
1415  mLeft + pos, mBottom);
1416  }
1417  else
1418  {
1419  if (mFlip)
1420  mDC->DrawLine(mLeft, mTop + pos,
1421  mLeft + 2, mTop + pos);
1422  else
1423  mDC->DrawLine(mRight - 2, mTop + pos,
1424  mRight, mTop + pos);
1425  }
1426  }
1427  mMinorLabels[i].Draw(*mDC, mTwoTone, mTickColour);
1428  }
1429  }
1430 
1431  mDC->SetFont(*mMinorMinorFont);
1432 
1433  for(i=0; i<mNumMinorMinor; i++) {
1434  if (mMinorMinorLabels[i].text != wxT(""))
1435  {
1436  int pos = mMinorMinorLabels[i].pos;
1437 
1438  if( mbTicksAtExtremes || ((pos!=0)&&(pos!=iMaxPos)))
1439  {
1440  if (mOrientation == wxHORIZONTAL)
1441  {
1442  if (mFlip)
1443  mDC->DrawLine(mLeft + pos, mTop,
1444  mLeft + pos, mTop + 2);
1445  else
1446  mDC->DrawLine(mLeft + pos, mBottom - 2,
1447  mLeft + pos, mBottom);
1448  }
1449  else
1450  {
1451  if (mFlip)
1452  mDC->DrawLine(mLeft, mTop + pos,
1453  mLeft + 2, mTop + pos);
1454  else
1455  mDC->DrawLine(mRight - 2, mTop + pos,
1456  mRight, mTop + pos);
1457  }
1458  }
1460  }
1461  }
1462 }
1463 
1464 // ********** Draw grid ***************************
1465 void Ruler::DrawGrid(wxDC& dc, int length, bool minor, bool major, int xOffset, int yOffset)
1466 {
1467  mGridLineLength = length;
1468  mMajorGrid = major;
1469  mMinorGrid = minor;
1470  mDC = &dc;
1471 
1472  Update();
1473 
1474  int gridPos;
1475  wxPen gridPen;
1476 
1477  if(mbMinor && (mMinorGrid && (mGridLineLength != 0 ))) {
1478  gridPen.SetColour(178, 178, 178); // very light grey
1479  mDC->SetPen(gridPen);
1480  for(int i=0; i<mNumMinor; i++) {
1481  gridPos = mMinorLabels[i].pos;
1482  if(mOrientation == wxHORIZONTAL) {
1483  if((gridPos != 0) && (gridPos != mGridLineLength))
1484  mDC->DrawLine(gridPos+xOffset, yOffset, gridPos+xOffset, mGridLineLength+yOffset);
1485  }
1486  else {
1487  if((gridPos != 0) && (gridPos != mGridLineLength))
1488  mDC->DrawLine(xOffset, gridPos+yOffset, mGridLineLength+xOffset, gridPos+yOffset);
1489  }
1490  }
1491  }
1492 
1493  if(mMajorGrid && (mGridLineLength != 0 )) {
1494  gridPen.SetColour(127, 127, 127); // light grey
1495  mDC->SetPen(gridPen);
1496  for(int i=0; i<mNumMajor; i++) {
1497  gridPos = mMajorLabels[i].pos;
1498  if(mOrientation == wxHORIZONTAL) {
1499  if((gridPos != 0) && (gridPos != mGridLineLength))
1500  mDC->DrawLine(gridPos+xOffset, yOffset, gridPos+xOffset, mGridLineLength+yOffset);
1501  }
1502  else {
1503  if((gridPos != 0) && (gridPos != mGridLineLength))
1504  mDC->DrawLine(xOffset, gridPos+yOffset, mGridLineLength+xOffset, gridPos+yOffset);
1505  }
1506  }
1507 
1508  int zeroPosition = GetZeroPosition();
1509  if(zeroPosition > 0) {
1510  // Draw 'zero' grid line in black
1511  mDC->SetPen(*wxBLACK_PEN);
1512  if(mOrientation == wxHORIZONTAL) {
1513  if(zeroPosition != mGridLineLength)
1514  mDC->DrawLine(zeroPosition+xOffset, yOffset, zeroPosition+xOffset, mGridLineLength+yOffset);
1515  }
1516  else {
1517  if(zeroPosition != mGridLineLength)
1518  mDC->DrawLine(xOffset, zeroPosition+yOffset, mGridLineLength+xOffset, zeroPosition+yOffset);
1519  }
1520  }
1521  }
1522 }
1523 
1524 int Ruler::FindZero(Label * label, const int len)
1525 {
1526  int i = 0;
1527  double d = 1.0; // arbitrary
1528 
1529  do {
1530  d = label[i].value;
1531  i++;
1532  } while( (i < len) && (d != 0.0) );
1533 
1534  if(d == 0.0)
1535  return (label[i - 1].pos) ;
1536  else
1537  return -1;
1538 }
1539 
1541 {
1542  int zero;
1543  if((zero = FindZero(mMajorLabels.get(), mNumMajor)) < 0)
1544  zero = FindZero(mMinorLabels.get(), mNumMinor);
1545  // PRL: don't consult minor minor??
1546  return zero;
1547 }
1548 
1549 void Ruler::GetMaxSize(wxCoord *width, wxCoord *height)
1550 {
1551  if (!mValid) {
1552  wxScreenDC sdc;
1553  mDC = &sdc;
1554  Update(NULL);
1555  }
1556 
1557  if (width)
1558  *width = mRect.GetWidth(); //mMaxWidth;
1559 
1560  if (height)
1561  *height = mRect.GetHeight(); //mMaxHeight;
1562 }
1563 
1564 
1565 void Ruler::SetCustomMode(bool value) { mCustom = value; }
1566 
1567 void Ruler::SetCustomMajorLabels(wxArrayString *label, size_t numLabel, int start, int step)
1568 {
1569  mNumMajor = numLabel;
1570  mMajorLabels.reinit(numLabel);
1571 
1572  for(size_t i = 0; i<numLabel; i++) {
1573  mMajorLabels[i].text = label->Item(i);
1574  mMajorLabels[i].pos = start + i*step;
1575  }
1576  //Remember: DELETE majorlabels....
1577 }
1578 
1579 void Ruler::SetCustomMinorLabels(wxArrayString *label, size_t numLabel, int start, int step)
1580 {
1581  mNumMinor = numLabel;
1582  mMinorLabels.reinit(numLabel);
1583 
1584  for(size_t i = 0; i<numLabel; i++) {
1585  mMinorLabels[i].text = label->Item(i);
1586  mMinorLabels[i].pos = start + i*step;
1587  }
1588  //Remember: DELETE majorlabels....
1589 }
1590 
1591 void Ruler::Label::Draw(wxDC&dc, bool twoTone, wxColour c) const
1592 {
1593  if (text != wxT("")) {
1594  bool altColor = twoTone && value < 0.0;
1595 
1596 #ifdef EXPERIMENTAL_THEMING
1597  dc.SetTextForeground(altColor ? theTheme.Colour( clrTextNegativeNumbers) : c);
1598 #else
1599  dc.SetTextForeground(altColor ? *wxBLUE : *wxBLACK);
1600 #endif
1601 
1602  dc.DrawText(text, lx, ly);
1603  }
1604 }
1605 
1606 void Ruler::SetUseZoomInfo(int leftOffset, const ZoomInfo *zoomInfo)
1607 {
1608  mLeftOffset = leftOffset;
1609  mUseZoomInfo = zoomInfo;
1610 }
1611 
1612 //
1613 // RulerPanel
1614 //
1615 
1616 BEGIN_EVENT_TABLE(RulerPanel, wxPanelWrapper)
1617  EVT_ERASE_BACKGROUND(RulerPanel::OnErase)
1618  EVT_PAINT(RulerPanel::OnPaint)
1619  EVT_SIZE(RulerPanel::OnSize)
1621 
1623 
1624 RulerPanel::RulerPanel(wxWindow* parent, wxWindowID id,
1625  wxOrientation orientation,
1626  const wxSize &bounds,
1627  const Range &range,
1629  const wxString &units,
1630  const Options &options,
1631  const wxPoint& pos /*= wxDefaultPosition*/,
1632  const wxSize& size /*= wxDefaultSize*/):
1633  wxPanelWrapper(parent, id, pos, size)
1634 {
1635  ruler.SetBounds( 0, 0, bounds.x, bounds.y );
1636  ruler.SetOrientation(orientation);
1637  ruler.SetRange( range.first, range.second );
1638  ruler.SetLog( options.log );
1639  ruler.SetFormat(format);
1640  ruler.SetUnits( units );
1641  ruler.SetFlip( options.flip );
1642  ruler.SetLabelEdges( options.labelEdges );
1643  ruler.mbTicksAtExtremes = options.ticksAtExtremes;
1644  if (orientation == wxVERTICAL) {
1645  wxCoord w;
1646  ruler.GetMaxSize(&w, NULL);
1647  SetMinSize(wxSize(w, 150)); // height needed for wxGTK
1648  }
1649  else if (orientation == wxHORIZONTAL) {
1650  wxCoord h;
1651  ruler.GetMaxSize(NULL, &h);
1652  SetMinSize(wxSize(wxDefaultCoord, h));
1653  }
1654  if (options.hasTickColour)
1655  ruler.SetTickColour( options.tickColour );
1656 }
1657 
1659 {
1660 }
1661 
1662 void RulerPanel::OnErase(wxEraseEvent & WXUNUSED(evt))
1663 {
1664  // Ignore it to prevent flashing
1665 }
1666 
1667 void RulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
1668 {
1669  wxPaintDC dc(this);
1670 
1671 #if defined(__WXMSW__)
1672  dc.Clear();
1673 #endif
1674 
1675  ruler.Draw(dc);
1676 }
1677 
1678 void RulerPanel::OnSize(wxSizeEvent & WXUNUSED(evt))
1679 {
1680  Refresh();
1681 }
1682 
1683 // LL: We're overloading DoSetSize so that we can update the ruler bounds immediately
1684 // instead of waiting for a wxEVT_SIZE to come through. This is needed by (at least)
1685 // FreqWindow since it needs to have an updated ruler before RulerPanel gets the
1686 // size event.
1687 void RulerPanel::DoSetSize(int x, int y,
1688  int width, int height,
1689  int sizeFlags)
1690 {
1691  wxPanelWrapper::DoSetSize(x, y, width, height, sizeFlags);
1692 
1693  int w, h;
1694  GetClientSize(&w, &h);
1695 
1696  ruler.SetBounds(0, 0, w-1, h-1);
1697 }
1698 
1699 
1700 /*********************************************************************/
1701 enum : int {
1705 
1707  BottomMargin = 2, // for bottom bevel and bottom line
1709 
1711 };
1712 
1713 enum {
1716 };
1717 
1718 inline int IndicatorHeightForWidth(int width)
1719 {
1720  return ((width / 2) * 3) / 2;
1721 }
1722 
1723 inline int IndicatorWidthForHeight(int height)
1724 {
1725  // Not an exact inverse of the above, with rounding, but good enough
1726  return std::max(static_cast<int>(IndicatorSmallWidth),
1727  (((height) * 2) / 3) * 2
1728  );
1729 }
1730 
1732 {
1733  return std::max((int)(ScrubHeight - TopMargin),
1734  (int)(IndicatorMediumWidth));
1735 }
1736 
1737 inline int IndicatorBigWidth()
1738 {
1740 }
1741 
1742 /**********************************************************************
1743 
1744 QuickPlayRulerOverlay.
1745 Graphical helper for AdornedRulerPanel.
1746 
1747 **********************************************************************/
1748 
1750 
1751 // This is an overlay drawn on the ruler. It draws the little triangle or
1752 // the double-headed arrow.
1753 class QuickPlayRulerOverlay final : public Overlay
1754 {
1755 public:
1757  virtual ~QuickPlayRulerOverlay();
1758 
1759  void Update(wxCoord xx) { mNewQPIndicatorPos = xx; }
1760 
1761 private:
1762  AdornedRulerPanel *GetRuler() const;
1763 
1764  std::pair<wxRect, bool> DoGetRectangle(wxSize size) override;
1765  void Draw(OverlayPanel &panel, wxDC &dc) override;
1766 
1768  int mOldQPIndicatorPos { -1 }, mNewQPIndicatorPos { -1 };
1769 };
1770 
1771 /**********************************************************************
1772 
1773  QuickPlayIndicatorOverlay.
1774  Graphical helper for AdornedRulerPanel.
1775 
1776  **********************************************************************/
1777 
1778 // This is an overlay drawn on a different window, the track panel.
1779 // It draws the pale guide line that follows mouse movement.
1781 {
1783 
1784 public:
1786 
1787  virtual ~QuickPlayIndicatorOverlay();
1788 
1789  void Update(int x, bool snapped = false, bool previewScrub = false);
1790 
1791 private:
1792  std::pair<wxRect, bool> DoGetRectangle(wxSize size) override;
1793  void Draw(OverlayPanel &panel, wxDC &dc) override;
1794 
1796 
1797  std::unique_ptr<QuickPlayRulerOverlay> mPartner
1798  { std::make_unique<QuickPlayRulerOverlay>(*this) };
1799 
1800  int mOldQPIndicatorPos { -1 }, mNewQPIndicatorPos { -1 };
1801  bool mOldQPIndicatorSnapped {}, mNewQPIndicatorSnapped {};
1802  bool mOldPreviewingScrub {}, mNewPreviewingScrub {};
1803 };
1804 
1805 /**********************************************************************
1806 
1807  Implementation of QuickPlayRulerOverlay.
1808 
1809  **********************************************************************/
1810 
1812 : mPartner(partner)
1813 {
1814  GetRuler()->AddOverlay(this);
1815 }
1816 
1818 {
1819  auto ruler = GetRuler();
1820  if (ruler)
1821  ruler->RemoveOverlay(this);
1822 }
1823 
1825 {
1826  return mPartner.mProject->GetRulerPanel();
1827 }
1828 
1829 std::pair<wxRect, bool> QuickPlayRulerOverlay::DoGetRectangle(wxSize /*size*/)
1830 {
1831  const auto x = mOldQPIndicatorPos;
1832  if (x >= 0) {
1833  // These dimensions are always sufficient, even if a little
1834  // excessive for the small triangle:
1835  const int width = IndicatorBigWidth() * 3 / 2;
1836  //const auto height = IndicatorHeightForWidth(width);
1837 
1838  const int indsize = width / 2;
1839 
1840  auto xx = x - indsize;
1841  auto yy = 0;
1842  return {
1843  { xx, yy,
1844  indsize * 2 + 1,
1845  GetRuler()->GetSize().GetHeight() },
1846  (x != mNewQPIndicatorPos)
1847  };
1848  }
1849  else
1850  return { {}, mNewQPIndicatorPos >= 0 };
1851 }
1852 
1853 void QuickPlayRulerOverlay::Draw(OverlayPanel & /*panel*/, wxDC &dc)
1854 {
1856  if (mOldQPIndicatorPos >= 0) {
1857  auto ruler = GetRuler();
1858  const auto &scrubber = mPartner.mProject->GetScrubber();
1859  auto scrub = ruler->ShowingScrubControl();
1860  auto seek = scrub && (scrubber.Seeks() || scrubber.TemporarilySeeks());
1861  auto width = scrub ? IndicatorBigWidth() : IndicatorSmallWidth;
1862  ruler->DoDrawIndicator(&dc, mOldQPIndicatorPos, true, width, scrub, seek);
1863  }
1864 }
1865 
1866 /**********************************************************************
1867 
1868  Implementation of QuickPlayIndicatorOverlay.
1869 
1870  **********************************************************************/
1871 
1873  : mProject(project)
1874 {
1875  auto tp = mProject->GetTrackPanel();
1876  tp->AddOverlay(this);
1877 }
1878 
1880 {
1881  auto tp = mProject->GetTrackPanel();
1882  if (tp)
1883  tp->RemoveOverlay(this);
1884 }
1885 
1886 void QuickPlayIndicatorOverlay::Update(int x, bool snapped, bool previewScrub)
1887 {
1888  mNewQPIndicatorPos = x;
1889  mPartner->Update(x);
1890  mNewQPIndicatorSnapped = snapped;
1891  mNewPreviewingScrub = previewScrub;
1892 }
1893 
1894 std::pair<wxRect, bool> QuickPlayIndicatorOverlay::DoGetRectangle(wxSize size)
1895 {
1896  wxRect rect(mOldQPIndicatorPos, 0, 1, size.GetHeight());
1897  return std::make_pair(
1898  rect,
1902  );
1903 }
1904 
1906 {
1910 
1911  if (mOldQPIndicatorPos >= 0) {
1913  ? AColor::IndicatorColor(&dc, true) // Draw green line for preview.
1915  ? AColor::SnapGuidePen(&dc)
1916  : AColor::Light(&dc, false)
1917  ;
1918 
1919  // Draw indicator in all visible tracks
1920  for ( const auto &data : static_cast<TrackPanel&>(panel).Cells() )
1921  {
1922  Track *const pTrack = dynamic_cast<Track*>(data.first.get());
1923  if (!pTrack)
1924  continue;
1925  const wxRect &rect = data.second;
1926 
1927  // Draw the NEW indicator in its NEW location
1928  AColor::Line(dc,
1930  rect.GetTop(),
1932  rect.GetBottom());
1933  }
1934  }
1935 }
1936 
1937 /**********************************************************************
1938 
1939  Implementation of AdornedRulerPanel.
1940  Either we find a way to make this more generic, Or it will move
1941  out of the widgets subdirectory into its own source file.
1942 
1943 **********************************************************************/
1944 
1945 #include "../ViewInfo.h"
1946 #include "../AColor.h"
1947 
1948 enum {
1955 
1957 };
1958 
1959 BEGIN_EVENT_TABLE(AdornedRulerPanel, OverlayPanel)
1960  EVT_PAINT(AdornedRulerPanel::OnPaint)
1961  EVT_SIZE(AdornedRulerPanel::OnSize)
1962  EVT_MOUSE_EVENTS(AdornedRulerPanel::OnMouseEvents)
1963  EVT_MOUSE_CAPTURE_LOST(AdornedRulerPanel::OnCaptureLost)
1964 
1965  // Context menu commands
1966  EVT_MENU(OnToggleQuickPlayID, AdornedRulerPanel::OnToggleQuickPlay)
1967  EVT_MENU(OnSyncQuickPlaySelID, AdornedRulerPanel::OnSyncSelToQuickPlay)
1968  EVT_MENU(OnTimelineToolTipID, AdornedRulerPanel::OnTimelineToolTips)
1969  EVT_MENU(OnAutoScrollID, AdornedRulerPanel::OnAutoScroll)
1971  EVT_MENU(OnScrubRulerID, AdornedRulerPanel::OnToggleScrubRulerFromMenu)
1972 
1973  // Pop up menus on Windows
1974  EVT_CONTEXT_MENU(AdornedRulerPanel::OnContextMenu)
1975 
1977  wxEVT_COMMAND_BUTTON_CLICKED,
1978  AdornedRulerPanel::OnTogglePinnedState )
1979 
1981 
1983  wxWindow *parent,
1984  wxWindowID id,
1985  const wxPoint& pos,
1986  const wxSize& size,
1987  ViewInfo *viewinfo)
1988 : OverlayPanel(parent, id, pos, size)
1989 , mProject(project)
1990 , mViewInfo(viewinfo)
1991 {
1992  for (auto &button : mButtons)
1993  button = nullptr;
1994 
1995  SetLabel( _("Timeline") );
1996  SetName(GetLabel());
1997  SetBackgroundStyle(wxBG_STYLE_PAINT);
1998 
1999  mCursorDefault = wxCursor(wxCURSOR_DEFAULT);
2000  mCursorHand = wxCursor(wxCURSOR_HAND);
2001  mCursorSizeWE = wxCursor(wxCURSOR_SIZEWE);
2002  mIsWE = false;
2003 
2004  mLeftOffset = 0;
2005  mIndTime = -1;
2006 
2007  mPlayRegionStart = -1;
2008  mPlayRegionLock = false;
2009  mPlayRegionEnd = -1;
2010  mOldPlayRegionStart = -1;
2011  mOldPlayRegionEnd = -1;
2012  mLeftDownClick = -1;
2013  mMouseEventState = mesNone;
2014  mIsDragging = false;
2015 
2016  mOuter = GetClientRect();
2017 
2018  mRuler.SetUseZoomInfo(mLeftOffset, mViewInfo);
2019  mRuler.SetLabelEdges( false );
2020  mRuler.SetFormat( Ruler::TimeFormat );
2021 
2022  mTracks = project->GetTracks();
2023 
2024  mSnapManager = NULL;
2025  mIsSnapped = false;
2026 
2027  mIsRecording = false;
2028 
2029  mTimelineToolTip = !!gPrefs->Read(wxT("/QuickPlay/ToolTips"), 1L);
2030  mPlayRegionDragsSelection = (gPrefs->Read(wxT("/QuickPlay/DragSelection"), 0L) == 1)? true : false;
2031  mQuickPlayEnabled = !!gPrefs->Read(wxT("/QuickPlay/QuickPlayEnabled"), 1L);
2032 
2033 #if wxUSE_TOOLTIPS
2034  wxToolTip::Enable(true);
2035 #endif
2036 
2037  wxTheApp->Bind(EVT_AUDIOIO_CAPTURE,
2039  this);
2040 }
2041 
2043 {
2044  if(HasCapture())
2045  ReleaseMouse();
2046 }
2047 
2048 #if 1
2049 namespace {
2050  static const wxChar *scrubEnabledPrefName = wxT("/QuickPlay/ScrubbingEnabled");
2051 
2052  bool ReadScrubEnabledPref()
2053  {
2054  bool result {};
2055 // DA: Scrub is disabled by default.
2056 #ifdef EXPERIMENTAL_DA
2057  gPrefs->Read(scrubEnabledPrefName, &result, false);
2058 #else
2059  gPrefs->Read(scrubEnabledPrefName, &result, true);
2060 #endif
2061  return result;
2062  }
2063 
2064  void WriteScrubEnabledPref(bool value)
2065  {
2066  gPrefs->Write(scrubEnabledPrefName, value);
2067  }
2068 }
2069 #endif
2070 
2072 {
2073  // Update button texts for language change
2075 
2076 #ifdef EXPERIMENTAL_SCROLLING_LIMITS
2077 #ifdef EXPERIMENTAL_TWO_TONE_TIME_RULER
2078  {
2079  bool scrollBeyondZero = false;
2080  gPrefs->Read(TracksBehaviorsPrefs::ScrollingPreferenceKey(), &scrollBeyondZero,
2082  mRuler.SetTwoTone(scrollBeyondZero);
2083  }
2084 #endif
2085 #endif
2086 
2087  mShowScrubbing = ReadScrubEnabledPref();
2088  // Affected by the last
2089  UpdateRects();
2090  SetPanelSize();
2091 
2093 }
2094 
2096 {
2097  // TODO: Should we do this to destroy the grabber??
2098  // Get rid of any children we may have
2099  // DestroyChildren();
2100 
2101  SetBackgroundColour(theTheme.Colour( clrMedium ));
2102 
2103  for (auto & button : mButtons) {
2104  if (button)
2105  button->Destroy();
2106  button = nullptr;
2107  }
2108 
2109  size_t iButton = 0;
2110  // Make the short row of time ruler pushbottons.
2111  // Don't bother with sizers. Their sizes and positions are fixed.
2112  // Add a grabber converted to a spacer.
2113  // This makes it visually clearer that the button is a button.
2114 
2115  wxPoint position( 1, 0 );
2116 
2117  Grabber * pGrabber = safenew Grabber(this, this->GetId());
2118  pGrabber->SetAsSpacer( true );
2119  //pGrabber->SetSize( 10, 27 ); // default is 10,27
2120  pGrabber->SetPosition( position );
2121 
2122  position.x = 12;
2123 
2124  auto size = theTheme.ImageSize( bmpRecoloredUpSmall );
2125  size.y = std::min(size.y, GetRulerHeight(false));
2126 
2127  auto buttonMaker = [&]
2128  (wxWindowID id, teBmps bitmap, bool toggle)
2129  {
2130  const auto button =
2132  this,
2133  bmpRecoloredUpSmall, bmpRecoloredDownSmall,
2134  bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
2135  bitmap, bitmap, bitmap,
2136  id, position, toggle, size
2137  );
2138 
2139  position.x += size.GetWidth();
2140  mButtons[iButton++] = button;
2141  return button;
2142  };
2143  auto button = buttonMaker(OnTogglePinnedStateID, bmpPlayPointerPinned, true);
2145  *button, 3,
2146  bmpRecoloredUpSmall, bmpRecoloredDownSmall,
2147  bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
2148  //bmpUnpinnedPlayHead, bmpUnpinnedPlayHead, bmpUnpinnedPlayHead,
2149  bmpRecordPointer, bmpRecordPointer, bmpRecordPointer,
2150  size);
2152  *button, 2,
2153  bmpRecoloredUpSmall, bmpRecoloredDownSmall,
2154  bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
2155  //bmpUnpinnedPlayHead, bmpUnpinnedPlayHead, bmpUnpinnedPlayHead,
2156  bmpRecordPointerPinned, bmpRecordPointerPinned, bmpRecordPointerPinned,
2157  size);
2159  *button, 1,
2160  bmpRecoloredUpSmall, bmpRecoloredDownSmall,
2161  bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
2162  //bmpUnpinnedPlayHead, bmpUnpinnedPlayHead, bmpUnpinnedPlayHead,
2163  bmpPlayPointer, bmpPlayPointer, bmpPlayPointer,
2164  size);
2165 
2167 }
2168 
2170 {
2171  mRuler.Invalidate();
2172 }
2173 
2174 namespace {
2175  const wxString StartScrubbingMessage(const Scrubber &/*scrubber*/)
2176  {
2177  /* i18n-hint: These commands assist the user in finding a sound by ear. ...
2178  "Scrubbing" is variable-speed playback, ...
2179  "Seeking" is normal speed playback but with skips
2180  */
2181 #if 0
2182  if(scrubber.Seeks())
2183  return _("Click or drag to begin Seek");
2184  else
2185  return _("Click or drag to begin Scrub");
2186 #else
2187  return _("Click & move to Scrub. Click & drag to Seek.");
2188 #endif
2189  }
2190 
2191  const wxString ContinueScrubbingMessage(const Scrubber &scrubber)
2192  {
2193  /* i18n-hint: These commands assist the user in finding a sound by ear. ...
2194  "Scrubbing" is variable-speed playback, ...
2195  "Seeking" is normal speed playback but with skips
2196  */
2197 #if 0
2198  if(scrubber.Seeks())
2199  return _("Move to Seek");
2200  else
2201  return _("Move to Scrub");
2202 #else
2203  wxMouseState State = wxGetMouseState();
2204  if( State.LeftIsDown() ) {
2205  // Since mouse is down, mention dragging first.
2206  // IsScrubbing is true if Scrubbing OR seeking.
2207  if( scrubber.IsOneShotSeeking() )
2208  return _("Drag to Seek. Release to stop seeking.");
2209  else
2210  return _("Drag to Seek. Release and move to Scrub.");
2211  }
2212  // Since mouse is up, mention moving first.
2213  return _("Move to Scrub. Drag to Seek.");
2214 #endif
2215  }
2216 
2217  const wxString ScrubbingMessage(const Scrubber &scrubber)
2218  {
2219  if (scrubber.HasStartedScrubbing())
2220  return ContinueScrubbingMessage(scrubber);
2221  else
2222  return StartScrubbingMessage(scrubber);
2223  }
2224 }
2225 
2227 {
2228 #if wxUSE_TOOLTIPS
2229  if (mTimelineToolTip) {
2230  if (mIsRecording) {
2231  this->SetToolTip(_("Timeline actions disabled during recording"));
2232  }
2233  else {
2234  switch(choice) {
2236  if (!mQuickPlayEnabled) {
2237  this->SetToolTip(_("Quick-Play disabled"));
2238  }
2239  else {
2240  this->SetToolTip(_("Quick-Play enabled"));
2241  }
2242  break;
2244  {
2245  const auto message = ScrubbingMessage(mProject->GetScrubber());
2246  this->SetToolTip(message);
2247  }
2248  break;
2249  default:
2250  this->SetToolTip(NULL);
2251  break;
2252  }
2253  }
2254  }
2255  else {
2256  this->SetToolTip(NULL);
2257  }
2258 #endif
2259 }
2260 
2261 void AdornedRulerPanel::OnCapture(wxCommandEvent & evt)
2262 {
2263  evt.Skip();
2264 
2265  if (evt.GetInt() != 0)
2266  {
2267  // Set cursor immediately because OnMouseEvents is not called
2268  // if recording is initiated by a modal window (Timer Record).
2269  SetCursor(mCursorDefault);
2270  mIsRecording = true;
2272 
2273  // The quick play indicator is useless during recording
2275  }
2276  else {
2277  SetCursor(mCursorHand);
2278  mIsRecording = false;
2280  }
2282 }
2283 
2284 void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
2285 {
2286  if (mNeedButtonUpdate) {
2287  // Visit this block once only in the lifetime of this panel
2288  mNeedButtonUpdate = false;
2289  // Do this first time setting of button status texts
2290  // when we are sure the CommandManager is initialized.
2291  ReCreateButtons();
2292  // Sends a resize event, which will cause a second paint.
2293  UpdatePrefs();
2294  }
2295 
2296  wxPaintDC dc(this);
2297 
2298  auto &backDC = GetBackingDCForRepaint();
2299 
2300  DoDrawBackground(&backDC);
2301 
2303  {
2304  DoDrawSelection(&backDC);
2305  }
2306 
2307  DoDrawMarks(&backDC, true);
2308 
2309  DoDrawPlayRegion(&backDC);
2310 
2311  DoDrawEdge(&backDC);
2312 
2313  DisplayBitmap(dc);
2314 
2315  // Stroke extras direct to the client area,
2316  // maybe outside of the damaged area
2317  // As with TrackPanel, do not make a NEW wxClientDC or else Mac flashes badly!
2318  dc.DestroyClippingRegion();
2319  DrawOverlays(true, &dc);
2320 }
2321 
2322 void AdornedRulerPanel::OnSize(wxSizeEvent &evt)
2323 {
2324  mOuter = GetClientRect();
2325  if (mOuter.GetWidth() == 0 || mOuter.GetHeight() == 0)
2326  {
2327  return;
2328  }
2329 
2330  UpdateRects();
2331 
2332  OverlayPanel::OnSize(evt);
2333 }
2334 
2336 {
2337  mInner = mOuter;
2338  mInner.x += LeftMargin;
2339  mInner.width -= (LeftMargin + RightMargin);
2340 
2341  auto top = &mInner;
2342  auto bottom = &mInner;
2343 
2344  if (mShowScrubbing) {
2345  mScrubZone = mInner;
2346  auto scrubHeight = std::min(mScrubZone.height, (int)(ScrubHeight));
2347 
2348  int topHeight;
2349 #ifdef SCRUB_ABOVE
2350  top = &mScrubZone, topHeight = scrubHeight;
2351 #else
2352  auto qpHeight = mScrubZone.height - scrubHeight;
2353  bottom = &mScrubZone, topHeight = qpHeight;
2354  // Increase scrub zone height so that hit testing finds it and
2355  // not QP region, when on bottom 'edge'.
2356  mScrubZone.height+=BottomMargin;
2357 #endif
2358 
2359  top->height = topHeight;
2360  bottom->height -= topHeight;
2361  bottom->y += topHeight;
2362  }
2363 
2364  top->y += TopMargin;
2365  top->height -= TopMargin;
2366 
2367  bottom->height -= BottomMargin;
2368 
2369  if (!mShowScrubbing)
2370  mScrubZone = mInner;
2371 
2372  mRuler.SetBounds(mInner.GetLeft(),
2373  mInner.GetTop(),
2374  mInner.GetRight(),
2375  mInner.GetBottom());
2376 
2377 }
2378 
2379 double AdornedRulerPanel::Pos2Time(int p, bool ignoreFisheye)
2380 {
2382  , ignoreFisheye
2383  );
2384 }
2385 
2386 int AdornedRulerPanel::Time2Pos(double t, bool ignoreFisheye)
2387 {
2389  , ignoreFisheye
2390  );
2391 }
2392 
2393 
2394 bool AdornedRulerPanel::IsWithinMarker(int mousePosX, double markerTime)
2395 {
2396  if (markerTime < 0)
2397  return false;
2398 
2399  int pixelPos = Time2Pos(markerTime);
2400  int boundLeft = pixelPos - SELECT_TOLERANCE_PIXEL;
2401  int boundRight = pixelPos + SELECT_TOLERANCE_PIXEL;
2402 
2403  return mousePosX >= boundLeft && mousePosX < boundRight;
2404 }
2405 
2406 void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
2407 {
2408  // Disable mouse actions on Timeline while recording.
2409  if (mIsRecording) {
2410  if (HasCapture())
2411  ReleaseMouse();
2412  return;
2413  }
2414 
2415  const auto position = evt.GetPosition();
2416 
2417  const bool inScrubZone =
2418  // only if scrubbing is allowed now
2419  mProject->GetScrubber().CanScrub() &&
2420  mShowScrubbing &&
2421  mScrubZone.Contains(position);
2422  const bool inQPZone =
2423  (!inScrubZone) && mInner.Contains(position);
2424 
2425  const StatusChoice zone =
2426  evt.Leaving()
2428  : inScrubZone
2430  : inQPZone
2433  const bool changeInZone = (zone != mPrevZone);
2434  const bool changing = evt.Leaving() || evt.Entering() || changeInZone;
2435 
2436  wxCoord xx = evt.GetX();
2437  wxCoord mousePosX = xx;
2438  UpdateQuickPlayPos(mousePosX);
2439  HandleSnapping();
2440 
2441  // If not looping, restrict selection to end of project
2442  if (zone == StatusChoice::EnteringQP && !evt.ShiftDown()) {
2443  const double t1 = mTracks->GetEndTime();
2445  }
2446 
2447  // Handle status bar messages
2449 
2450  mPrevZone = zone;
2451 
2452  auto &scrubber = mProject->GetScrubber();
2453  if (scrubber.HasStartedScrubbing()) {
2454  if (evt.RightDown() )
2455  // Fall through to context menu handling
2456  ;
2457  else if ( evt.LeftUp() && inScrubZone)
2458  // Fall through to seeking changes to scrubbing
2459  ;
2460 // else if ( evt.LeftDown() && inScrubZone)
2461 // // Fall through to ready to seek
2462 // ;
2463  else {
2464  bool switchToQP = (zone == StatusChoice::EnteringQP && mQuickPlayEnabled);
2465  if (switchToQP && evt.LeftDown()) {
2466  // We can't stop scrubbing yet (see comments in Bug 1391), but we can pause it.
2468  // Don't return, fall through
2469  }
2470  else if (scrubber.IsPaused())
2471  // Just fall through
2472  ;
2473  else {
2474  // If already clicked for scrub, preempt the usual event handling,
2475  // no matter what the y coordinate.
2476 
2477  // Do this hack so scrubber can detect mouse drags anywhere
2478  evt.ResumePropagation(wxEVENT_PROPAGATE_MAX);
2479 
2480  //if (scrubber.IsScrubbing())
2481  evt.Skip();
2482  //else
2483  //evt.Skip();
2484 
2485  // Don't do this, it slows down drag-scrub on Mac.
2486  // Timer updates of display elsewhere make it unnecessary.
2487  // Done here, it's too frequent.
2488  // ShowQuickPlayIndicator();
2489 
2490  return;
2491  }
2492  }
2493  }
2494 
2495  // Store the initial play region state
2496  if(mMouseEventState == mesNone) {
2500  }
2501 
2502  // Handle entering and leaving of the bar, or movement from
2503  // one portion (quick play or scrub) to the other
2504  if (evt.Leaving() || (changeInZone && zone != StatusChoice::EnteringQP)) {
2505  if (evt.Leaving()) {
2506  // Erase the line
2508  }
2509 
2510  SetCursor(mCursorDefault);
2511  mIsWE = false;
2512 
2513  mSnapManager.reset();
2514 
2515  if(evt.Leaving())
2516  return;
2517  // else, may detect a scrub click below
2518  }
2519  else if (evt.Entering() || (changeInZone && zone == StatusChoice::EnteringQP)) {
2520  SetCursor(mCursorHand);
2522  return;
2523  }
2524 
2525  // Handle popup menus
2526  if (!HasCapture() && evt.RightDown() && !(evt.LeftIsDown())) {
2528  (inScrubZone ? MenuChoice::Scrub : MenuChoice::QuickPlay,
2529  &position);
2530  return;
2531  }
2532  else if( !HasCapture() && evt.LeftUp() && inScrubZone ) {
2533  if( scrubber.IsOneShotSeeking() ){
2534  scrubber.mInOneShotMode = false;
2535  return;
2536  }
2537  //wxLogDebug("up");
2538  // mouse going up => we shift to scrubbing.
2539  scrubber.MarkScrubStart(evt.m_x,
2542  // repaint_all so that the indicator changes shape.
2543  bool repaint_all = true;
2544  ShowQuickPlayIndicator(repaint_all);
2545  return;
2546  }
2547  else if ( !HasCapture() && inScrubZone) {
2548  // mouse going down => we are (probably) seeking
2549  bool repaint_all = false;
2550  if (evt.LeftDown()) {
2551  //wxLogDebug("down");
2552  scrubber.mInOneShotMode = !scrubber.IsScrubbing();
2553  scrubber.MarkScrubStart(evt.m_x,
2556  }
2557  else if( changeInZone ) {
2558  repaint_all = true;
2559  }
2560 
2561  ShowQuickPlayIndicator(repaint_all);
2562  return;
2563  }
2564  else if ( mQuickPlayEnabled) {
2565  bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegionStart);
2566  bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegionEnd);
2567 
2568  if (isWithinStart || isWithinEnd) {
2569  if (!mIsWE) {
2570  SetCursor(mCursorSizeWE);
2571  mIsWE = true;
2572  }
2573  }
2574  else {
2575  if (mIsWE) {
2576  SetCursor(mCursorHand);
2577  mIsWE = false;
2578  }
2579  }
2580 
2581  if (evt.LeftDown()) {
2582  if( inQPZone ){
2583  HandleQPClick(evt, mousePosX);
2584  HandleQPDrag(evt, mousePosX);
2586  }
2587  }
2588  else if (evt.LeftIsDown() && HasCapture()) {
2589  HandleQPDrag(evt, mousePosX);
2591  }
2592  else if (evt.LeftUp() && HasCapture()) {
2593  HandleQPRelease(evt);
2595  }
2596  else // if (!inScrubZone)
2598  }
2599 }
2600 
2601 void AdornedRulerPanel::HandleQPClick(wxMouseEvent &evt, wxCoord mousePosX)
2602 {
2603  // Temporarily unlock locked play region
2604  if (mPlayRegionLock && evt.LeftDown()) {
2605  //mPlayRegionLock = true;
2607  }
2608 
2611  bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegionStart);
2612  bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegionEnd);
2613 
2614  if (isWithinStart || isWithinEnd) {
2615  // If Quick-Play is playing from a point, we need to treat it as a click
2616  // not as dragging.
2619  // otherwise check which marker is nearer
2620  else {
2621  // Don't compare times, compare positions.
2622  //if (fabs(mQuickPlayPos - mPlayRegionStart) < fabs(mQuickPlayPos - mPlayRegionEnd))
2626  else
2628  }
2629  }
2630  else {
2631  // Clicked but not yet dragging
2633  }
2634 
2635  // Check if we are dragging BEFORE CaptureMouse.
2636  if (mMouseEventState != mesNone)
2637  SetCursor(mCursorSizeWE);
2638  if ( !HasCapture() )
2639  CaptureMouse();
2640 }
2641 
2642 void AdornedRulerPanel::HandleQPDrag(wxMouseEvent &/*event*/, wxCoord mousePosX)
2643 {
2644  bool isWithinClick =
2645  (mLeftDownClickUnsnapped >= 0) &&
2647  bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegionStart);
2648  bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegionEnd);
2649  bool canDragSel = !mPlayRegionLock && mPlayRegionDragsSelection;
2650 
2651  switch (mMouseEventState)
2652  {
2653  case mesNone:
2654  // If close to either end of play region, snap to closest
2655  if (isWithinStart || isWithinEnd) {
2657 
2660  else
2662  }
2663  break;
2666 
2667  // Don't start dragging until beyond tolerance initial playback start
2668  if (!mIsDragging && isWithinStart)
2670  else
2671  mIsDragging = true;
2672  // avoid accidental tiny selection
2673  if (isWithinEnd)
2676  if (canDragSel) {
2677  DragSelection();
2678  }
2679  break;
2681  if (!mIsDragging && isWithinEnd) {
2683 
2685  }
2686  else
2687  mIsDragging = true;
2688  if (isWithinStart) {
2690 
2692  }
2694  if (canDragSel) {
2695  DragSelection();
2696  }
2697  break;
2699 
2700  // Don't start dragging until mouse is beyond tolerance of initial click.
2701  if (isWithinClick || mLeftDownClick == -1) {
2703 
2707  }
2708  else {
2710  }
2711  break;
2713  if (isWithinClick) {
2715 
2717  }
2718 
2719  if (mQuickPlayPos < mLeftDownClick) {
2722  }
2723  else {
2726  }
2727  if (canDragSel) {
2728  DragSelection();
2729  }
2730  break;
2731  }
2732  Refresh();
2733  Update();
2734 }
2735 
2736 void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
2737 {
2738  if (HasCapture())
2739  ReleaseMouse();
2740  else
2741  return;
2742 
2744 
2746  // Swap values to ensure mPlayRegionStart < mPlayRegionEnd
2747  double tmp = mPlayRegionStart;
2749  mPlayRegionEnd = tmp;
2750  }
2751 
2752  const double t0 = mTracks->GetStartTime();
2753  const double t1 = mTracks->GetEndTime();
2754  const double sel0 = mProject->GetSel0();
2755  const double sel1 = mProject->GetSel1();
2756 
2757  // We want some audio in the selection, but we allow a dragged
2758  // region to include selected white-space and space before audio start.
2759  if (evt.ShiftDown() && (mPlayRegionStart == mPlayRegionEnd)) {
2760  // Looping the selection or project.
2761  // Disable if track selection is in white-space beyond end of tracks and
2762  // play position is outside of track contents.
2763  if (((sel1 < t0) || (sel0 > t1)) &&
2764  ((mPlayRegionStart < t0) || (mPlayRegionStart > t1))) {
2765  ClearPlayRegion();
2766  }
2767  }
2768  // Disable if beyond end.
2769  else if (mPlayRegionStart >= t1) {
2770  ClearPlayRegion();
2771  }
2772  // Disable if empty selection before start.
2773  // (allow Quick-Play region to include 'pre-roll' white space)
2774  else if (((mPlayRegionEnd - mPlayRegionStart) > 0.0) && (mPlayRegionEnd < t0)) {
2775  ClearPlayRegion();
2776  }
2777 
2779  mIsDragging = false;
2780  mLeftDownClick = -1;
2781 
2782  auto cleanup = finally( [&] {
2783  if (mPlayRegionLock) {
2784  // Restore Locked Play region
2787  // and release local lock
2788  mPlayRegionLock = false;
2789  }
2790  } );
2791 
2792  StartQPPlay(evt.ShiftDown(), evt.ControlDown());
2793 }
2794 
2795 void AdornedRulerPanel::StartQPPlay(bool looped, bool cutPreview)
2796 {
2797  const double t0 = mTracks->GetStartTime();
2798  const double t1 = mTracks->GetEndTime();
2799  const double sel0 = mProject->GetSel0();
2800  const double sel1 = mProject->GetSel1();
2801 
2802  // Start / Restart playback on left click.
2803  bool startPlaying = (mPlayRegionStart >= 0);
2804 
2805  if (startPlaying) {
2807  ctb->StopPlaying();
2808 
2809  bool loopEnabled = true;
2810  double start, end;
2811 
2812  if ((mPlayRegionEnd - mPlayRegionStart == 0.0) && looped) {
2813  // Loop play a point will loop either a selection or the project.
2814  if ((mPlayRegionStart > sel0) && (mPlayRegionStart < sel1)) {
2815  // we are in a selection, so use the selection
2816  start = sel0;
2817  end = sel1;
2818  } // not in a selection, so use the project
2819  else {
2820  start = t0;
2821  end = t1;
2822  }
2823  }
2824  else {
2825  start = mPlayRegionStart;
2826  end = mPlayRegionEnd;
2827  }
2828  // Looping a tiny selection may freeze, so just play it once.
2829  loopEnabled = ((end - start) > 0.001)? true : false;
2830 
2832  options.playLooped = (loopEnabled && looped);
2833 
2834  auto oldStart = mPlayRegionStart;
2835  if (!cutPreview)
2836  options.pStartTime = &oldStart;
2837  else
2838  options.timeTrack = NULL;
2839 
2840  ControlToolBar::PlayAppearance appearance =
2842  : options.playLooped ? ControlToolBar::PlayAppearance::Looped
2844 
2845  mPlayRegionStart = start;
2846  mPlayRegionEnd = end;
2847  Refresh();
2848 
2849  ctb->PlayPlayRegion((SelectedRegion(start, end)),
2850  options, PlayMode::normalPlay,
2851  appearance,
2852  false,
2853  true);
2854 
2855  }
2856 }
2857 
2859 {
2860  if (choice == StatusChoice::NoChange)
2861  return;
2862 
2863  wxString message {};
2864 
2865  const auto &scrubber = mProject->GetScrubber();
2866  const bool scrubbing = scrubber.HasStartedScrubbing();
2867  if (scrubbing && choice != StatusChoice::Leaving)
2868  // Don't distinguish zones
2870 
2871  switch (choice) {
2873  {
2874  // message = Insert timeline status bar message here
2875  }
2876  break;
2877 
2879  {
2880  message = ScrubbingMessage(scrubber);
2881  }
2882  break;
2883 
2884  default:
2885  break;
2886  }
2887 
2888  // Display a message, or empty message
2890 
2891  RegenerateTooltips(choice);
2892 }
2893 
2894 // This version toggles ruler state indirectly via the scrubber
2895 // to ensure that all the places where the state is shown update.
2896 // For example buttons and menus must update.
2898 {
2899  auto &scrubber = mProject->GetScrubber();
2900  scrubber.OnToggleScrubRuler(*mProject);
2901 }
2902 
2903 void AdornedRulerPanel::OnToggleScrubRuler(/*wxCommandEvent&*/)
2904 {
2906  WriteScrubEnabledPref(mShowScrubbing);
2907  gPrefs->Flush();
2908  SetPanelSize();
2909 }
2910 
2912 {
2913  wxSize size { GetSize().GetWidth(), GetRulerHeight(mShowScrubbing) };
2914  SetSize(size);
2915  SetMinSize(size);
2916  GetParent()->PostSizeEventToParent();
2917 }
2918 
2920 {
2921  const auto &scrubber = mProject->GetScrubber();
2922  return
2925  (scrubber.HasStartedScrubbing()));
2926 }
2927 
2928 void AdornedRulerPanel::OnContextMenu(wxContextMenuEvent & WXUNUSED(event))
2929 {
2931 }
2932 
2934 {
2935  auto common = [this]
2936  (AButton &button, const wxString &commandName, const wxString &label) {
2937  TranslatedInternalString command{ commandName, label };
2938  ToolBar::SetButtonToolTip( button, &command, 1u );
2939  button.SetLabel(button.GetToolTipText());
2940 
2941  button.UpdateStatus();
2942  };
2943 
2944  {
2945  bool state = TracksPrefs::GetPinnedHeadPreference();
2946  auto pinButton = static_cast<AButton*>(FindWindow(OnTogglePinnedStateID));
2947  if( !state )
2948  pinButton->PopUp();
2949  else
2950  pinButton->PushDown();
2951  pinButton->SetAlternateIdx((mIsRecording ? 2 : 0) + (state ? 0 : 1));
2952  // Bug 1584: Toltip now shows what clicking will do.
2953  const auto label = state
2954  ? _("Click to unpin")
2955  : _("Click to pin");
2956  common(*pinButton, wxT("PinnedHead"), label);
2957  }
2958 }
2959 
2960 void AdornedRulerPanel::OnTogglePinnedState(wxCommandEvent & /*event*/)
2961 {
2964 }
2965 
2966 void AdornedRulerPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(evt))
2967 {
2969 
2970  wxMouseEvent e(wxEVT_LEFT_UP);
2971  e.m_x = mLastMouseX;
2972  OnMouseEvents(e);
2973 }
2974 
2976 {
2977  // Keep Quick-Play within usable track area.
2979  int width;
2980  tp->GetTracksUsableArea(&width, NULL);
2981  mousePosX = std::max(mousePosX, tp->GetLeftOffset());
2982  mousePosX = std::min(mousePosX, tp->GetLeftOffset() + width - 1);
2983 
2984  mLastMouseX = mousePosX;
2986 }
2987 
2988 // Pop-up menus
2989 
2990 void AdornedRulerPanel::ShowMenu(const wxPoint & pos)
2991 {
2992  wxMenu rulerMenu;
2993 
2994  if (mQuickPlayEnabled)
2995  rulerMenu.Append(OnToggleQuickPlayID, _("Disable Quick-Play"));
2996  else
2997  rulerMenu.Append(OnToggleQuickPlayID, _("Enable Quick-Play"));
2998 
2999  wxMenuItem *dragitem;
3001  dragitem = rulerMenu.Append(OnSyncQuickPlaySelID, _("Disable dragging selection"));
3002  else
3003  dragitem = rulerMenu.Append(OnSyncQuickPlaySelID, _("Enable dragging selection"));
3004  dragitem->Enable(mQuickPlayEnabled && !mProject->IsPlayRegionLocked());
3005 
3006 #if wxUSE_TOOLTIPS
3007  if (mTimelineToolTip)
3008  rulerMenu.Append(OnTimelineToolTipID, _("Disable Timeline Tooltips"));
3009  else
3010  rulerMenu.Append(OnTimelineToolTipID, _("Enable Timeline Tooltips"));
3011 #endif
3012 
3014  rulerMenu.Append(OnAutoScrollID, _("Do not scroll while playing"));
3015  else
3016  rulerMenu.Append(OnAutoScrollID, _("Update display while playing"));
3017 
3018  wxMenuItem *prlitem;
3019  if (!mProject->IsPlayRegionLocked())
3020  prlitem = rulerMenu.Append(OnLockPlayRegionID, _("Lock Play Region"));
3021  else
3022  prlitem = rulerMenu.Append(OnLockPlayRegionID, _("Unlock Play Region"));
3023  prlitem->Enable(mProject->IsPlayRegionLocked() || (mPlayRegionStart != mPlayRegionEnd));
3024 
3025  wxMenuItem *ruleritem;
3026  if (mShowScrubbing)
3027  ruleritem = rulerMenu.Append(OnScrubRulerID, _("Disable Scrub Ruler"));
3028  else
3029  ruleritem = rulerMenu.Append(OnScrubRulerID, _("Enable Scrub Ruler"));
3030 
3031  PopupMenu(&rulerMenu, pos);
3032 }
3033 
3034 void AdornedRulerPanel::ShowScrubMenu(const wxPoint & pos)
3035 {
3036  auto &scrubber = mProject->GetScrubber();
3037  PushEventHandler(&scrubber);
3038  auto cleanup = finally([this]{ PopEventHandler(); });
3039 
3040  wxMenu rulerMenu;
3041  mProject->GetScrubber().PopulatePopupMenu(rulerMenu);
3042  PopupMenu(&rulerMenu, pos);
3043 }
3044 
3046 {
3047  mQuickPlayEnabled = (mQuickPlayEnabled)? false : true;
3048  gPrefs->Write(wxT("/QuickPlay/QuickPlayEnabled"), mQuickPlayEnabled);
3049  gPrefs->Flush();
3051 }
3052 
3054 {
3056  gPrefs->Write(wxT("/QuickPlay/DragSelection"), mPlayRegionDragsSelection);
3057  gPrefs->Flush();
3058 }
3059 
3061 {
3064 }
3065 
3067 {
3068  if (!mSnapManager) {
3069  mSnapManager = std::make_unique<SnapManager>(mTracks, mViewInfo);
3070  }
3071 
3072  auto results = mSnapManager->Snap(NULL, mQuickPlayPos, false);
3073  mQuickPlayPos = results.outTime;
3074  mIsSnapped = results.Snapped();
3075 }
3076 
3078 {
3079  mTimelineToolTip = (mTimelineToolTip)? false : true;
3080  gPrefs->Write(wxT("/QuickPlay/ToolTips"), mTimelineToolTip);
3081  gPrefs->Flush();
3082 #if wxUSE_TOOLTIPS
3084 #endif
3085 }
3086 
3087 void AdornedRulerPanel::OnAutoScroll(wxCommandEvent&)
3088 {
3090  gPrefs->Write(wxT("/GUI/AutoScroll"), false);
3091  else
3092  gPrefs->Write(wxT("/GUI/AutoScroll"), true);
3093  mProject->UpdatePrefs();
3094  gPrefs->Flush();
3095 }
3096 
3097 
3099 {
3102  else
3104 }
3105 
3106 
3107 // Draws the horizontal <===>
3109 {
3110  double start, end;
3111  GetPlayRegion(&start, &end);
3112 
3113  if (start >= 0)
3114  {
3115  const int x1 = Time2Pos(start);
3116  const int x2 = Time2Pos(end)-2;
3117  int y = mInner.y - TopMargin + mInner.height/2;
3118 
3119  bool isLocked = mProject->IsPlayRegionLocked();
3120  AColor::PlayRegionColor(dc, isLocked);
3121 
3122  wxPoint tri[3];
3123  wxRect r;
3124 
3125  tri[0].x = x1;
3126  tri[0].y = y + PLAY_REGION_GLOBAL_OFFSET_Y;
3127  tri[1].x = x1 + PLAY_REGION_TRIANGLE_SIZE;
3128  tri[1].y = y - PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
3129  tri[2].x = x1 + PLAY_REGION_TRIANGLE_SIZE;
3130  tri[2].y = y + PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
3131  dc->DrawPolygon(3, tri);
3132 
3133  r.x = x1;
3134  r.y = y - PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
3135  r.width = PLAY_REGION_RECT_WIDTH;
3136  r.height = PLAY_REGION_TRIANGLE_SIZE*2 + 1;
3137  dc->DrawRectangle(r);
3138 
3139  if (end != start)
3140  {
3141  tri[0].x = x2;
3142  tri[0].y = y + PLAY_REGION_GLOBAL_OFFSET_Y;
3143  tri[1].x = x2 - PLAY_REGION_TRIANGLE_SIZE;
3144  tri[1].y = y - PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
3145  tri[2].x = x2 - PLAY_REGION_TRIANGLE_SIZE;
3146  tri[2].y = y + PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
3147  dc->DrawPolygon(3, tri);
3148 
3149  r.x = x2 - PLAY_REGION_RECT_WIDTH + 1;
3150  r.y = y - PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
3151  r.width = PLAY_REGION_RECT_WIDTH;
3152  r.height = PLAY_REGION_TRIANGLE_SIZE*2 + 1;
3153  dc->DrawRectangle(r);
3154 
3155  r.x = x1 + PLAY_REGION_TRIANGLE_SIZE;
3157  r.width = std::max(0, x2-x1 - PLAY_REGION_TRIANGLE_SIZE*2);
3158  r.height = PLAY_REGION_RECT_HEIGHT;
3159  dc->DrawRectangle(r);
3160  }
3161  }
3162 }
3163 
3164 void AdornedRulerPanel::ShowContextMenu( MenuChoice choice, const wxPoint *pPosition)
3165 {
3166  wxPoint position;
3167  if(pPosition)
3168  position = *pPosition;
3169  else
3170  {
3171  auto rect = GetRect();
3172  position = { rect.GetLeft() + 1, rect.GetBottom() + 1 };
3173  }
3174 
3175  switch (choice) {
3176  case MenuChoice::QuickPlay:
3177  ShowMenu(position); break;
3178  case MenuChoice::Scrub:
3179  ShowScrubMenu(position); break;
3180  default:
3181  return;
3182  }
3183 
3184  // dismiss and clear Quick-Play indicator
3186 
3187  if (HasCapture())
3188  ReleaseMouse();
3189 }
3190 
3192 {
3193  // Draw AdornedRulerPanel border
3194  AColor::UseThemeColour( dc, clrTrackInfo );
3195  dc->DrawRectangle( mInner );
3196 
3197  if (mShowScrubbing) {
3198  // Let's distinguish the scrubbing area by using a themable
3199  // colour and a line to set it off.
3200  AColor::UseThemeColour(dc, clrScrubRuler, clrTrackPanelText );
3201  wxRect ScrubRect = mScrubZone;
3202  ScrubRect.Inflate( 1,0 );
3203  dc->DrawRectangle(ScrubRect);
3204  }
3205 }
3206 
3208 {
3209  wxRect r = mOuter;
3210  r.width -= RightMargin;
3211  r.height -= BottomMargin;
3212  AColor::BevelTrackInfo( *dc, true, r );
3213 
3214  // Black stroke at bottom
3215  dc->SetPen( *wxBLACK_PEN );
3216  dc->DrawLine( mOuter.x,
3217  mOuter.y + mOuter.height - 1,
3218  mOuter.x + mOuter.width,
3219  mOuter.y + mOuter.height - 1 );
3220 }
3221 
3222 void AdornedRulerPanel::DoDrawMarks(wxDC * dc, bool /*text */ )
3223 {
3224  const double min = Pos2Time(0);
3225  const double hiddenMin = Pos2Time(0, true);
3226  const double max = Pos2Time(mInner.width);
3227  const double hiddenMax = Pos2Time(mInner.width, true);
3228 
3229  mRuler.SetTickColour( theTheme.Colour( clrTrackPanelText ) );
3230  mRuler.SetRange( min, max, hiddenMin, hiddenMax );
3231  mRuler.Draw( *dc );
3232 }
3233 
3235 {
3236  Refresh();
3237 }
3238 
3240 {
3241  // Draw selection
3242  const int p0 = max(1, Time2Pos(mViewInfo->selectedRegion.t0()));
3243  const int p1 = min(mInner.width, Time2Pos(mViewInfo->selectedRegion.t1()));
3244 
3245  dc->SetBrush( wxBrush( theTheme.Colour( clrRulerBackground )) );
3246  dc->SetPen( wxPen( theTheme.Colour( clrRulerBackground )) );
3247 
3248  wxRect r;
3249  r.x = p0;
3250  r.y = mInner.y;
3251  r.width = p1 - p0 - 1;
3252  r.height = mInner.height;
3253  dc->DrawRectangle( r );
3254 }
3255 
3257 {
3258  return ProperRulerHeight + (showScrubBar ? ScrubHeight : 0);
3259 }
3260 
3262 {
3263  mLeftOffset = offset;
3264  mRuler.SetUseZoomInfo(offset, mViewInfo);
3265 }
3266 
3267 // Draws the play/recording position indicator.
3269  (wxDC * dc, wxCoord xx, bool playing, int width, bool scrub, bool seek)
3270 {
3271  ADCChanger changer(dc); // Undo pen and brush changes at function exit
3272 
3273  AColor::IndicatorColor( dc, playing );
3274 
3275  wxPoint tri[ 3 ];
3276  if (seek) {
3277  auto height = IndicatorHeightForWidth(width);
3278  // Make four triangles
3279  const int TriangleWidth = width * 3 / 8;
3280 
3281  // Double-double headed, left-right
3282  auto yy = mShowScrubbing
3283  ? mScrubZone.y
3284  : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
3285  tri[ 0 ].x = xx - IndicatorOffset;
3286  tri[ 0 ].y = yy;
3287  tri[ 1 ].x = xx - IndicatorOffset;
3288  tri[ 1 ].y = yy + height;
3289  tri[ 2 ].x = xx - TriangleWidth;
3290  tri[ 2 ].y = yy + height / 2;
3291  dc->DrawPolygon( 3, tri );
3292 
3293  tri[ 0 ].x -= TriangleWidth;
3294  tri[ 1 ].x -= TriangleWidth;
3295  tri[ 2 ].x -= TriangleWidth;
3296  dc->DrawPolygon( 3, tri );
3297 
3298  tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
3299  tri[ 2 ].x = xx + TriangleWidth;
3300  dc->DrawPolygon( 3, tri );
3301 
3302 
3303  tri[ 0 ].x += TriangleWidth;
3304  tri[ 1 ].x += TriangleWidth;
3305  tri[ 2 ].x += TriangleWidth;
3306  dc->DrawPolygon( 3, tri );
3307  }
3308  else if (scrub) {
3309  auto height = IndicatorHeightForWidth(width);
3310  const int IndicatorHalfWidth = width / 2;
3311 
3312  // Double headed, left-right
3313  auto yy = mShowScrubbing
3314  ? mScrubZone.y
3315  : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
3316  tri[ 0 ].x = xx - IndicatorOffset;
3317  tri[ 0 ].y = yy;
3318  tri[ 1 ].x = xx - IndicatorOffset;
3319  tri[ 1 ].y = yy + height;
3320  tri[ 2 ].x = xx - IndicatorHalfWidth;
3321  tri[ 2 ].y = yy + height / 2;
3322  dc->DrawPolygon( 3, tri );
3323  tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
3324  tri[ 2 ].x = xx + IndicatorHalfWidth;
3325  dc->DrawPolygon( 3, tri );
3326  }
3327  else {
3328  bool pinned = TracksPrefs::GetPinnedHeadPreference();
3329  wxBitmap & bmp = theTheme.Bitmap( pinned ?
3330  (playing ? bmpPlayPointerPinned : bmpRecordPointerPinned) :
3331  (playing ? bmpPlayPointer : bmpRecordPointer)
3332  );
3333  const int IndicatorHalfWidth = bmp.GetWidth() / 2;
3334  dc->DrawBitmap( bmp, xx - IndicatorHalfWidth -1, mInner.y );
3335 #if 0
3336 
3337  // Down pointing triangle
3338  auto height = IndicatorHeightForWidth(width);
3339  const int IndicatorHalfWidth = width / 2;
3340  tri[ 0 ].x = xx - IndicatorHalfWidth;
3341  tri[ 0 ].y = mInner.y;
3342  tri[ 1 ].x = xx + IndicatorHalfWidth;
3343  tri[ 1 ].y = mInner.y;
3344  tri[ 2 ].x = xx;
3345  tri[ 2 ].y = mInner.y + height;
3346  dc->DrawPolygon( 3, tri );
3347 #endif
3348  }
3349 }
3350 
3352 {
3353  if (!mOverlay)
3354  mOverlay = std::make_unique<QuickPlayIndicatorOverlay>(mProject);
3355 
3356  return mOverlay.get();
3357 }
3358 
3360 {
3361  ShowOrHideQuickPlayIndicator(true, repaint_all);
3362 }
3363 
3365 {
3366  ShowOrHideQuickPlayIndicator(false, repaint_all);
3367 }
3368 
3369 // Draws the vertical line and green triangle indicating the Quick Play cursor position.
3370 void AdornedRulerPanel::ShowOrHideQuickPlayIndicator(bool show, bool repaint_all)
3371 {
3372  double latestEnd = std::max(mTracks->GetEndTime(), mProject->GetSel1());
3373  if (!show || (mQuickPlayPos >= latestEnd)) {
3374  GetOverlay()->Update(-1);
3375  }
3376  else {
3377  const int x = Time2Pos(mQuickPlayPos);
3378  bool previewScrub =
3381  GetOverlay()->Update(x, mIsSnapped, previewScrub);
3382  }
3383 
3384  mProject->GetTrackPanel()->DrawOverlays(repaint_all);
3385  DrawOverlays(repaint_all);
3386 }
3387 
3388 void AdornedRulerPanel::SetPlayRegion(double playRegionStart,
3389  double playRegionEnd)
3390 {
3391  // This is called by AudacityProject to make the play region follow
3392  // the current selection. But while the user is selecting a play region
3393  // with the mouse directly in the ruler, changes from outside are blocked.
3394  if (mMouseEventState != mesNone)
3395  return;
3396 
3397  mPlayRegionStart = playRegionStart;
3398  mPlayRegionEnd = playRegionEnd;
3399 
3400  Refresh();
3401 }
3402 
3404 {
3406  ctb->StopPlaying();
3407 
3408  mPlayRegionStart = -1;
3409  mPlayRegionEnd = -1;
3410 
3412 
3413  Refresh();
3414 }
3415 
3416 void AdornedRulerPanel::GetPlayRegion(double* playRegionStart,
3417  double* playRegionEnd)
3418 {
3419  if (mPlayRegionStart >= 0 && mPlayRegionEnd >= 0 &&
3421  {
3422  // swap values to make sure end > start
3423  *playRegionStart = mPlayRegionEnd;
3424  *playRegionEnd = mPlayRegionStart;
3425  } else
3426  {
3427  *playRegionStart = mPlayRegionStart;
3428  *playRegionEnd = mPlayRegionEnd;
3429  }
3430 }
3431 
3432 void AdornedRulerPanel::GetMaxSize(wxCoord *width, wxCoord *height)
3433 {
3434  mRuler.GetMaxSize(width, height);
3435 }
3436 
3437 bool AdornedRulerPanel::s_AcceptsFocus{ false };
3438 
3440  s_AcceptsFocus = true;
3441  return TempAllowFocus{ &s_AcceptsFocus };
3442 }
3443 
3445 {
3446  auto temp = TemporarilyAllowFocus();
3447  SetFocus();
3448 }
bool mLabelEdges
Definition: Ruler.h:229
RulerFormat
Definition: Ruler.h:35
void ShowContextMenu(MenuChoice choice, const wxPoint *pPosition)
Definition: Ruler.cpp:3164
void GetMaxSize(wxCoord *width, wxCoord *height)
Definition: Ruler.cpp:3432
bool mMajorGrid
Definition: Ruler.h:235
static void PlayRegionColor(wxDC *dc, bool locked)
Definition: AColor.cpp:376
void ShowOrHideQuickPlayIndicator(bool show, bool repaint_all=false)
Definition: Ruler.cpp:3370
void SetLog(bool log)
Definition: Ruler.cpp:199
void Draw(wxDC &dc)
Definition: Ruler.cpp:1323
bool mIsDragging
Definition: Ruler.h:485
A ToolBar that has the main Transport buttons.
bool mQuickPlayEnabled
Definition: Ruler.h:471
AudacityPrefs * gPrefs
Definition: Prefs.cpp:73
void OnSize(wxSizeEvent &event)
Definition: BackedPanel.cpp:70
double mLeftDownClick
Definition: Ruler.h:483
void SetSpacing(int spacing)
Definition: Ruler.cpp:263
bool HasStartedScrubbing() const
Definition: Scrubbing.h:95
AUDACITY_DLL_API Theme theTheme
Definition: Theme.cpp:209
bool mHasSetSpacing
Definition: Ruler.h:228
AudioIOStartStreamOptions GetDefaultPlayOptions()
Definition: Project.cpp:1298
double ComputeWarpedLength(double t0, double t1) const
Compute the duration (in seconds at playback) of the specified region of the track.
Definition: TimeTrack.cpp:160
void RegenerateTooltips(StatusChoice choice)
Definition: Ruler.cpp:2226
bool mPlayRegionDragsSelection
Definition: Ruler.h:469
#define SELECT_TOLERANCE_PIXEL
Definition: Ruler.cpp:107
AdornedRulerPanel * GetRuler() const
Definition: Ruler.cpp:1824
bool bUpdateTrackIndicator
Definition: ViewInfo.h:182
double t0() const
std::unique_ptr< wxFont > mMinorMinorFont
Definition: Ruler.h:184
void HandleQPClick(wxMouseEvent &event, wxCoord mousePosX)
Definition: Ruler.cpp:2601
static const wxChar * ScrollingPreferenceKey()
bool mIsSnapped
Definition: Ruler.h:442
ViewInfo is used mainly to hold the zooming, selection and scroll information. It also has some statu...
Definition: ViewInfo.h:141
void InvalidateRuler()
Definition: Ruler.cpp:2169
double GetSel0() const
Definition: Project.h:204
bool isPoint() const
void Tick(int pos, double d, bool major, bool minor)
Definition: Ruler.cpp:768
void PopUp()
Definition: AButton.cpp:525
Scrubber & GetScrubber()
Definition: Project.h:801
SelectedRegion selectedRegion
Definition: ViewInfo.h:160
wxString mUnits
Definition: Ruler.h:238
double GetStartTime() const
Definition: Track.cpp:1413
wxPen mPen
Definition: Ruler.h:176
wxRect mInner
Definition: Ruler.h:432
wxCursor mCursorHand
Definition: Ruler.h:421
AudacityProject * mProject
Definition: Ruler.cpp:1795
int mNumMinor
Definition: Ruler.h:213
float ValueToPosition(float val) const
Definition: NumberScale.h:251
int mLengthOld
Definition: Ruler.h:181
Ruler mRuler
Definition: Ruler.h:425
void UpdateRects()
Definition: Ruler.cpp:2335
std::unique_ptr< SnapManager > mSnapManager
Definition: Ruler.h:441
bool mbMinor
Definition: Ruler.h:234
std::unique_ptr< wxFont > mMinorFont
Definition: Ruler.h:184
double mOldPlayRegionStart
Definition: Ruler.h:447
void OnToggleScrubRuler()
Definition: Ruler.cpp:2903
void OnLockPlayRegion(const CommandContext &context)
Definition: Menus.cpp:9150
void reinit(Integral count, bool initialize=false)
Definition: MemoryX.h:117
ArrayOf< Label > mMajorLabels
Definition: Ruler.h:212
QuickPlayIndicatorOverlay * GetOverlay()
Definition: Ruler.cpp:3351
bool mFlip
Definition: Ruler.h:232
int IndicatorBigHeight()
Definition: Ruler.cpp:1731
An array of these created by the Ruler is used to determine what and where text annotations to the nu...
Definition: Ruler.h:201
int IndicatorBigWidth()
Definition: Ruler.cpp:1737
void TickCustom(int labelIdx, bool major, bool minor)
Definition: Ruler.cpp:879
bool IsPaused() const
Definition: Scrubbing.cpp:683
double GetEndTime() const
Definition: Track.cpp:1418
void DoDrawEdge(wxDC *dc)
Definition: Ruler.cpp:3207
bool mUserFonts
Definition: Ruler.h:185
void SetBounds(int left, int top, int right, int bottom)
Definition: Ruler.cpp:358
ArrayOf< int > mUserBits
Definition: Ruler.h:195
double mDbMirrorValue
Definition: Ruler.h:227
void GetMaxSize(wxCoord *width, wxCoord *height)
Definition: Ruler.cpp:1549
wxString label
Definition: Tags.cpp:733
double mMax
Definition: Ruler.h:187
void Update(wxCoord xx)
Definition: Ruler.cpp:1759
ArrayOf< int > mBits
Definition: Ruler.h:196
double PositionToTime(wxInt64 position, wxInt64 origin=0, bool ignoreFisheye=false) const
Definition: ViewInfo.cpp:49
int mMaxWidth
Definition: Ruler.h:178
double mMin
Definition: Ruler.h:187
void OnUnlockPlayRegion(const CommandContext &context)
Definition: Menus.cpp:9164
void HandleSnapping()
Definition: Ruler.cpp:3066
void SetNumberScale(const NumberScale *pScale)
Definition: Ruler.cpp:317
void DoDrawMarks(wxDC *dc, bool)
Definition: Ruler.cpp:3222
bool mValid
Definition: Ruler.h:199
void StopPlaying(bool stopStream=true)
void OnContextMenu(wxContextMenuEvent &WXUNUSED(event))
Definition: Ruler.cpp:2928
void OnAutoScroll(wxCommandEvent &evt)
Definition: Ruler.cpp:3087
void OnMouseEvents(wxMouseEvent &evt)
Definition: Ruler.cpp:2406
void OnLockPlayRegion(const CommandContext &context)
void ShowScrubMenu(const wxPoint &pos)
Definition: Ruler.cpp:3034
void DrawSelection()
Definition: Ruler.cpp:3234
ViewInfo *const mViewInfo
Definition: Ruler.h:427
void HandleQPRelease(wxMouseEvent &event)
Definition: Ruler.cpp:2736
wxRect mOuter
Definition: Ruler.h:430
int GetRulerHeight()
Definition: Ruler.h:333
int teBmps
Definition: Theme.h:28
void OnSyncSelToQuickPlay(wxCommandEvent &evt)
Definition: Ruler.cpp:3053
wxSize ImageSize(int iIndex)
Definition: Theme.cpp:1257
std::unique_ptr< QuickPlayIndicatorOverlay > mOverlay
Definition: Ruler.h:487
bool RemoveOverlay(Overlay *pOverlay)
void OnCapture(wxCommandEvent &evt)
Definition: Ruler.cpp:2261
void DoDrawBackground(wxDC *dc)
Definition: Ruler.cpp:3191
static AButton * MakeButton(wxWindow *parent, teBmps eUp, teBmps eDown, teBmps eHilite, teBmps eDownHi, teBmps eStandardUp, teBmps eStandardDown, teBmps eDisabled, wxWindowID id, wxPoint placement, bool processdownevents, wxSize size)
Definition: ToolBar.cpp:769
void DoSetSize(int x, int y, int width, int height, int sizeFlags=wxSIZE_AUTO) override
Definition: Ruler.cpp:1687
std::unique_ptr< QuickPlayRulerOverlay > mPartner
Definition: Ruler.cpp:1798
double t1() const
int lx
Definition: Ruler.h:205
RulerPanel class allows you to work with a Ruler like any other wxWindow.
Definition: Ruler.h:246
std::unique_ptr< NumberScale > mpNumberScale
Definition: Ruler.h:243
void OnTogglePinnedHead(const CommandContext &context)
Definition: Menus.cpp:2985
bool mMinorGrid
Definition: Ruler.h:236
std::pair< wxRect, bool > DoGetRectangle(wxSize size) override
Definition: Ruler.cpp:1829
~RulerPanel()
Definition: Ruler.cpp:1658
static TempAllowFocus TemporarilyAllowFocus()
Definition: Ruler.cpp:3439
wxRect mRect
Definition: Ruler.h:172
void Invalidate()
Definition: Ruler.cpp:371
#define safenew
Definition: Audacity.h:230
MouseEventState mMouseEventState
Definition: Ruler.h:481
void SetOrientation(int orient)
Definition: Ruler.cpp:222
void Draw(wxDC &dc, bool twoTone, wxColour c) const
Definition: Ruler.cpp:1591
int mTop
Definition: Ruler.h:179
void HideQuickPlayIndicator(bool repaint_all=false)
Definition: Ruler.cpp:3364
bool IsOneShotSeeking() const
Definition: Scrubbing.h:150
static void IndicatorColor(wxDC *dc, bool bIsNotRecording)
Definition: AColor.cpp:367
wxString LabelString(double d, bool major)
Definition: Ruler.cpp:620
double mPlayRegionStart
Definition: Ruler.h:445
#define PLAY_REGION_RECT_WIDTH
Definition: Ruler.cpp:110
bool mCustom
Definition: Ruler.h:233
wxBitmap & Bitmap(int iIndex)
Definition: Theme.cpp:1244
static bool s_AcceptsFocus
Definition: Ruler.h:398
void GetTracksUsableArea(int *width, int *height) const
Definition: TrackPanel.cpp:421
double mQuickPlayPos
Definition: Ruler.h:439
void FindLinearTickSizes(double UPP)
Definition: Ruler.cpp:387
void DisplayBitmap(wxDC &dc)
Definition: BackedPanel.cpp:65
int FindZero(Label *label, int len)
Definition: Ruler.cpp:1524
void SetTwoTone(bool twoTone)
Definition: Ruler.cpp:183
AudacityProject provides the main window, with tools and tracks contained within it.
Definition: Project.h:176
A kind of Track used to 'warp time'.
Definition: TimeTrack.h:29
wxColour mTickColour
Definition: Ruler.h:175
void HandleQPDrag(wxMouseEvent &event, wxCoord mousePosX)
Definition: Ruler.cpp:2642
void StartQPPlay(bool looped, bool cutPreview)
Definition: Ruler.cpp:2795
int mRight
Definition: Ruler.h:179
void SetCustomMinorLabels(wxArrayString *label, size_t numLabel, int start, int step)
Definition: Ruler.cpp:1579
bool IsWithinMarker(int mousePosX, double markerTime)
Definition: Ruler.cpp:2394
void DoDrawIndicator(wxDC *dc, wxCoord xx, bool playing, int width, bool scrub, bool seek)
Definition: Ruler.cpp:3269
int format
Definition: ExportPCM.cpp:56
double mMinor
Definition: Ruler.h:191
bool IsScrubbing() const
Definition: Scrubbing.cpp:575
bool mShowScrubbing
Definition: Ruler.h:491
Defines a selected portion of a project.
void SetPlayRegion(double playRegionStart, double playRegionEnd)
Definition: Ruler.cpp:3388
The TrackPanel class coordinates updates and operations on the main part of the screen which contains...
Definition: TrackPanel.h:245
bool mLog
Definition: Ruler.h:231
void SetLabelEdges(bool labelEdges)
Definition: Ruler.cpp:274
void ClearPlayRegion()
Definition: Ruler.cpp:3403
void OnToggleScrubRuler(const CommandContext &)
Definition: Scrubbing.cpp:924
int ly
Definition: Ruler.h:205
bool setT1(double t, bool maySwap=true)
struct holding stream options, including a pointer to the TimeTrack and AudioIOListener and whether t...
Definition: AudioIO.h:123
QuickPlayIndicatorOverlay & mPartner
Definition: Ruler.cpp:1767
int mLeftOffset
Definition: Ruler.h:241
AdornedRulerPanel * GetRulerPanel()
Definition: Project.cpp:1439
int IndicatorHeightForWidth(int width)
Definition: Ruler.cpp:1718
~Ruler()
Definition: Ruler.cpp:178
void ShowQuickPlayIndicator(bool repaint_all=false)
Definition: Ruler.cpp:3359
StatusChoice mPrevZone
Definition: Ruler.h:489
int mBottom
Definition: Ruler.h:179
void OnToggleQuickPlay(wxCommandEvent &evt)
Definition: Ruler.cpp:3045
void SetPanelSize()
Definition: Ruler.cpp:2911
QuickPlayRulerOverlay(QuickPlayIndicatorOverlay &partner)
Definition: Ruler.cpp:1811
void SetFlip(bool flip)
Definition: Ruler.cpp:287
EVT_COMMAND(OnTogglePinnedStateID, wxEVT_COMMAND_BUTTON_CLICKED, AdornedRulerPanel::OnTogglePinnedState) AdornedRulerPanel
Definition: Ruler.cpp:1976
Fundamental data object of Audacity, placed in the TrackPanel. Classes derived form it include the Wa...
Definition: Track.h:102
bool Seeks() const
Definition: Scrubbing.cpp:628
TrackList * mTracks
Definition: Ruler.h:428
void OnCaptureLost(wxMouseCaptureLostEvent &evt)
Definition: Ruler.cpp:2966
void DrawOverlays(bool repaint_all, wxDC *pDC=nullptr)
RulerFormat mFormat
Definition: Ruler.h:230
bool mbTicksOnly
Definition: Ruler.h:170
wxInt64 TimeToPosition(double time, wxInt64 origin=0, bool ignoreFisheye=false) const
STM: Converts a project time to screen x position.
Definition: ViewInfo.cpp:59
int mMaxHeight
Definition: Ruler.h:178
bool ShowingScrubControl() const
Definition: Ruler.cpp:2919
void OnPaint(wxPaintEvent &evt)
Definition: Ruler.cpp:2284
void SetUnits(const wxString &units)
Definition: Ruler.cpp:210
int min(int a, int b)
wxRect mScrubZone
Definition: Ruler.h:431
ArrayOf< Label > mMinorMinorLabels
Definition: Ruler.h:216
const ZoomInfo * mUseZoomInfo
Definition: Ruler.h:240
double mHiddenMin
Definition: Ruler.h:188
static void UseThemeColour(wxDC *dc, int iBrush, int iPen=-1)
Definition: AColor.cpp:289
int Time2Pos(double t, bool ignoreFisheye=false)
Definition: Ruler.cpp:2386
bool mTwoTone
Definition: Ruler.h:239
void TP_DisplayStatusMessage(const wxString &msg) override
Definition: Project.cpp:5356
QuickPlayIndicatorOverlay(AudacityProject *project)
Definition: Ruler.cpp:1872
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:122
static void Light(wxDC *dc, bool selected, bool highlight=false)
Definition: AColor.cpp:308
bool CanScrub() const
Definition: Scrubbing.cpp:986
std::unique_ptr< bool, Resetter > TempAllowFocus
Definition: Ruler.h:400
void OnTogglePinnedState(wxCommandEvent &event)
Definition: Ruler.cpp:2960
wxDC & GetBackingDCForRepaint()
Definition: BackedPanel.cpp:35
#define PLAY_REGION_GLOBAL_OFFSET_Y
Definition: Ruler.cpp:112
void UpdatePrefs()
Definition: Project.cpp:1344
int mUserBitLen
Definition: Ruler.h:197
void Draw(OverlayPanel &panel, wxDC &dc) override
Definition: Ruler.cpp:1853
int mLead
Definition: Ruler.h:179
bool mPlayRegionLock
Definition: Ruler.h:444
void AddOverlay(Overlay *pOverlay)
std::pair< wxRect, bool > DoGetRectangle(wxSize size) override
Definition: Ruler.cpp:1894
IMPLEMENT_CLASS(ControlToolBar, ToolBar)
int mLength
Definition: Ruler.h:180
void UpdateStatusBarAndTooltips(StatusChoice choice)
Definition: Ruler.cpp:2858
wxDC * mDC
Definition: Ruler.h:182
static void MakeAlternateImages(AButton &button, int idx, teBmps eUp, teBmps eDown, teBmps eHilite, teBmps eDownHi, teBmps eStandardUp, teBmps eStandardDown, teBmps eDisabled, wxSize size)
Definition: ToolBar.cpp:802
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom")).Raw()), OnMoveTrack)#define SET_TRACK_NAME_PLUGIN_SYMBOLclass SetTrackNameCommand:public AudacityCommand
AudacityProject *const mProject
Definition: Ruler.h:426
double mQuickPlayPosUnsnapped
Definition: Ruler.h:438
void DrawGrid(wxDC &dc, int length, bool minor=true, bool major=true, int xOffset=0, int yOffset=0)
Definition: Ruler.cpp:1465
int PlayPlayRegion(const SelectedRegion &selectedRegion, const AudioIOStartStreamOptions &options, PlayMode playMode, PlayAppearance appearance=PlayAppearance::Straight, bool backwards=false, bool playWhiteSpace=false)
void SetFormat(RulerFormat format)
Definition: Ruler.cpp:188
void SetRange(double min, double max)
Definition: Ruler.cpp:236
void DoDrawPlayRegion(wxDC *dc)
Definition: Ruler.cpp:3108
#define PLAY_REGION_TRIANGLE_SIZE
Definition: Ruler.cpp:109
ControlToolBar * GetControlToolBar()
Definition: Project.cpp:5003
int mNumMajor
Definition: Ruler.h:211
Used to display a Ruler.
Definition: Ruler.h:32
static void SnapGuidePen(wxDC *dc)
Definition: AColor.cpp:391
void OnPause(const CommandContext &context)
Definition: Menus.cpp:2876
bool mTimelineToolTip
Definition: Ruler.h:470
double value
Definition: Ruler.h:203
static bool GetPinnedHeadPreference()
double mHiddenMax
Definition: Ruler.h:188
wxCursor mCursorSizeWE
Definition: Ruler.h:422
int mGridLineLength
Definition: Ruler.h:237
static bool ScrollingPreferenceDefault()
void SetAsSpacer(bool bIsSpacer)
Definition: Grabber.cpp:101
void Draw(OverlayPanel &panel, wxDC &dc) override
Definition: Ruler.cpp:1905
int mNumMinorMinor
Definition: Ruler.h:215
void OnLockPlayRegion(wxCommandEvent &evt)
Definition: Ruler.cpp:3098
void SetUseZoomInfo(int leftOffset, const ZoomInfo *zoomInfo)
Definition: Ruler.cpp:1606
void SetTickColour(const wxColour &colour)
Definition: Ruler.h:151
bool mInOneShotMode
Definition: Scrubbing.h:150
void ShowMenu(const wxPoint &pos)
Definition: Ruler.cpp:2990
std::pair< double, double > Range
Definition: Ruler.h:250
void OnTimelineToolTips(wxCommandEvent &evt)
Definition: Ruler.cpp:3077
void OnSize(wxSizeEvent &evt)
Definition: Ruler.cpp:2322
int mDigits
Definition: Ruler.h:193
void PopulatePopupMenu(wxMenu &menu)
Definition: Scrubbing.cpp:1022
The widget to the left of a ToolBar that allows it to be dragged around to NEW positions.
Definition: Grabber.h:98
int mLeft
Definition: Ruler.h:179
TrackPanel * GetTrackPanel()
Definition: Project.h:307
void OnToggleScrubRulerFromMenu(wxCommandEvent &)
Definition: Ruler.cpp:2897
#define PLAY_REGION_RECT_HEIGHT
Definition: Ruler.cpp:111
Ruler()
Definition: Ruler.cpp:120
wxWindow * mButtons[3]
Definition: Ruler.h:495
static void BevelTrackInfo(wxDC &dc, bool up, const wxRect &r, bool highlight=false)
Definition: AColor.cpp:262
void SetFonts(const wxFont &minorFont, const wxFont &majorFont, const wxFont &minorMinorFont)
Definition: Ruler.cpp:305
virtual ~QuickPlayRulerOverlay()
Definition: Ruler.cpp:1817
int mOrientation
Definition: Ruler.h:225
void UpdateQuickPlayPos(wxCoord &mousPosX)
Definition: Ruler.cpp:2975
This is an Audacity Specific ruler panel which additionally has border, selection markers...
Definition: Ruler.h:316
wxColour & Colour(int iIndex)
Definition: Theme.cpp:1225
bool mbTicksAtExtremes
Definition: Ruler.h:171
int GetZeroPosition()
Definition: Ruler.cpp:1540
bool setT0(double t, bool maySwap=true)
bool mIsRecording
Definition: Ruler.h:450
int GetLeftOffset() const
Definition: TrackPanel.h:287
END_EVENT_TABLE()
void ReCreateButtons()
Definition: Ruler.cpp:2095
double mOldPlayRegionEnd
Definition: Ruler.h:448
double GetSel1() const
Definition: Project.h:205
void OfflimitsPixels(int start, int end)
Definition: Ruler.cpp:333
void DragSelection()
Definition: Ruler.cpp:3060
bool IsPlayRegionLocked()
Definition: Project.h:213
virtual ~QuickPlayIndicatorOverlay()
Definition: Ruler.cpp:1879
static void SetButtonToolTip(AButton &button, const TranslatedInternalString commands[], size_t nCommands)
Definition: ToolBar.cpp:827
void SetCustomMajorLabels(wxArrayString *label, size_t numLabel, int start, int step)
Definition: Ruler.cpp:1567
double Pos2Time(int p, bool ignoreFisheye=false)
Definition: Ruler.cpp:2379
wxString text
Definition: Ruler.h:206
void SetFocusFromKbd() override
Definition: Ruler.cpp:3444
void GetPlayRegion(double *playRegionStart, double *playRegionEnd)
Definition: Ruler.cpp:3416
double mMajor
Definition: Ruler.h:190
bool mNeedButtonUpdate
Definition: Ruler.h:496
void MarkScrubStart(wxCoord xx, bool smoothScrolling, bool seek)
Definition: Scrubbing.cpp:263
void UpdateButtonStates()
Definition: Ruler.cpp:2933
double mLeftDownClickUnsnapped
Definition: Ruler.h:482
int IndicatorWidthForHeight(int height)
Definition: Ruler.cpp:1723
void OnPaint(wxPaintEvent &evt)
Definition: Ruler.cpp:1667
std::unique_ptr< wxFont > mMajorFont
Definition: Ruler.h:184
int pos
Definition: Ruler.h:204
void Update()
Definition: Ruler.cpp:993
void Update(int x, bool snapped=false, bool previewScrub=false)
Definition: Ruler.cpp:1886
void SetLeftOffset(int offset)
Definition: Ruler.cpp:3261
void OnSize(wxSizeEvent &evt)
Definition: Ruler.cpp:1678
void SetCustomMode(bool value)
Definition: Ruler.cpp:1565
void DoDrawSelection(wxDC *dc)
Definition: Ruler.cpp:3239
Makes temporary drawing context changes that you back out of, RAII style.
Definition: AColor.h:42
double mPlayRegionEnd
Definition: Ruler.h:446
void UpdatePrefs()
Definition: Ruler.cpp:2071
void OnErase(wxEraseEvent &evt)
Definition: Ruler.cpp:1662
A wxButton with mouse-over behaviour.
Definition: AButton.h:28
int mSpacing
Definition: Ruler.h:226
void SetMinor(bool value)
Definition: Ruler.cpp:300
ArrayOf< Label > mMinorLabels
Definition: Ruler.h:214
wxCursor mCursorDefault
Definition: Ruler.h:420
void UpdateStatus()
Definition: AButton.cpp:421