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  mPartner.mProject->GetRulerPanel()->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 =
1860  ruler->mMouseEventState == AdornedRulerPanel::mesNone &&
1862  (scrubber.HasStartedScrubbing()));
1863  auto seek = scrub && (scrubber.Seeks() || scrubber.TemporarilySeeks());
1864  auto width = scrub ? IndicatorBigWidth() : IndicatorSmallWidth;
1865  ruler->DoDrawIndicator(&dc, mOldQPIndicatorPos, true, width, scrub, seek);
1866  }
1867 }
1868 
1869 /**********************************************************************
1870 
1871  Implementation of QuickPlayIndicatorOverlay.
1872 
1873  **********************************************************************/
1874 
1876  : mProject(project)
1877 {
1878  auto tp = mProject->GetTrackPanel();
1879  tp->AddOverlay(this);
1880 }
1881 
1883 {
1884  auto tp = mProject->GetTrackPanel();
1885  if (tp)
1886  tp->RemoveOverlay(this);
1887 }
1888 
1889 void QuickPlayIndicatorOverlay::Update(int x, bool snapped, bool previewScrub)
1890 {
1891  mNewQPIndicatorPos = x;
1892  mPartner->Update(x);
1893  mNewQPIndicatorSnapped = snapped;
1894  mNewPreviewingScrub = previewScrub;
1895 }
1896 
1897 std::pair<wxRect, bool> QuickPlayIndicatorOverlay::DoGetRectangle(wxSize size)
1898 {
1899  wxRect rect(mOldQPIndicatorPos, 0, 1, size.GetHeight());
1900  return std::make_pair(
1901  rect,
1905  );
1906 }
1907 
1909 {
1913 
1914  if (mOldQPIndicatorPos >= 0) {
1916  ? AColor::IndicatorColor(&dc, true) // Draw green line for preview.
1918  ? AColor::SnapGuidePen(&dc)
1919  : AColor::Light(&dc, false)
1920  ;
1921 
1922  // Draw indicator in all visible tracks
1923  for ( const auto &data : static_cast<TrackPanel&>(panel).Cells() )
1924  {
1925  Track *const pTrack = dynamic_cast<Track*>(data.first.get());
1926  if (!pTrack)
1927  continue;
1928  const wxRect &rect = data.second;
1929 
1930  // Draw the NEW indicator in its NEW location
1931  AColor::Line(dc,
1933  rect.GetTop(),
1935  rect.GetBottom());
1936  }
1937  }
1938 }
1939 
1940 /**********************************************************************
1941 
1942  Implementation of AdornedRulerPanel.
1943  Either we find a way to make this more generic, Or it will move
1944  out of the widgets subdirectory into its own source file.
1945 
1946 **********************************************************************/
1947 
1948 #include "../ViewInfo.h"
1949 #include "../AColor.h"
1950 
1951 enum {
1958 
1960 };
1961 
1962 BEGIN_EVENT_TABLE(AdornedRulerPanel, OverlayPanel)
1963  EVT_PAINT(AdornedRulerPanel::OnPaint)
1964  EVT_SIZE(AdornedRulerPanel::OnSize)
1965  EVT_MOUSE_EVENTS(AdornedRulerPanel::OnMouseEvents)
1966  EVT_MOUSE_CAPTURE_LOST(AdornedRulerPanel::OnCaptureLost)
1967 
1968  // Context menu commands
1969  EVT_MENU(OnToggleQuickPlayID, AdornedRulerPanel::OnToggleQuickPlay)
1970  EVT_MENU(OnSyncQuickPlaySelID, AdornedRulerPanel::OnSyncSelToQuickPlay)
1971  EVT_MENU(OnTimelineToolTipID, AdornedRulerPanel::OnTimelineToolTips)
1972  EVT_MENU(OnAutoScrollID, AdornedRulerPanel::OnAutoScroll)
1974  EVT_MENU(OnScrubRulerID, AdornedRulerPanel::OnToggleScrubRulerFromMenu)
1975 
1976  // Pop up menus on Windows
1977  EVT_CONTEXT_MENU(AdornedRulerPanel::OnContextMenu)
1978 
1980  wxEVT_COMMAND_BUTTON_CLICKED,
1981  AdornedRulerPanel::OnTogglePinnedState )
1982 
1984 
1986  wxWindow *parent,
1987  wxWindowID id,
1988  const wxPoint& pos,
1989  const wxSize& size,
1990  ViewInfo *viewinfo)
1991 : OverlayPanel(parent, id, pos, size)
1992 , mProject(project)
1993 , mViewInfo(viewinfo)
1994 {
1995  for (auto &button : mButtons)
1996  button = nullptr;
1997 
1998  SetLabel( _("Timeline") );
1999  SetName(GetLabel());
2000  SetBackgroundStyle(wxBG_STYLE_PAINT);
2001 
2002  mCursorDefault = wxCursor(wxCURSOR_DEFAULT);
2003  mCursorHand = wxCursor(wxCURSOR_HAND);
2004  mCursorSizeWE = wxCursor(wxCURSOR_SIZEWE);
2005  mIsWE = false;
2006 
2007  mLeftOffset = 0;
2008  mIndTime = -1;
2009 
2010  mPlayRegionStart = -1;
2011  mPlayRegionLock = false;
2012  mPlayRegionEnd = -1;
2013  mOldPlayRegionStart = -1;
2014  mOldPlayRegionEnd = -1;
2015  mLeftDownClick = -1;
2016  mMouseEventState = mesNone;
2017  mIsDragging = false;
2018 
2019  mOuter = GetClientRect();
2020 
2021  mRuler.SetUseZoomInfo(mLeftOffset, mViewInfo);
2022  mRuler.SetLabelEdges( false );
2023  mRuler.SetFormat( Ruler::TimeFormat );
2024 
2025  mTracks = project->GetTracks();
2026 
2027  mSnapManager = NULL;
2028  mIsSnapped = false;
2029 
2030  mIsRecording = false;
2031 
2032  mTimelineToolTip = !!gPrefs->Read(wxT("/QuickPlay/ToolTips"), 1L);
2033  mPlayRegionDragsSelection = (gPrefs->Read(wxT("/QuickPlay/DragSelection"), 0L) == 1)? true : false;
2034  mQuickPlayEnabled = !!gPrefs->Read(wxT("/QuickPlay/QuickPlayEnabled"), 1L);
2035 
2036 #if wxUSE_TOOLTIPS
2037  wxToolTip::Enable(true);
2038 #endif
2039 
2040  wxTheApp->Bind(EVT_AUDIOIO_CAPTURE,
2042  this);
2043 }
2044 
2046 {
2047  if(HasCapture())
2048  ReleaseMouse();
2049 }
2050 
2051 #if 1
2052 namespace {
2053  static const wxChar *scrubEnabledPrefName = wxT("/QuickPlay/ScrubbingEnabled");
2054 
2055  bool ReadScrubEnabledPref()
2056  {
2057  bool result {};
2058 // DA: Scrub is disabled by default.
2059 #ifdef EXPERIMENTAL_DA
2060  gPrefs->Read(scrubEnabledPrefName, &result, false);
2061 #else
2062  gPrefs->Read(scrubEnabledPrefName, &result, true);
2063 #endif
2064  return result;
2065  }
2066 
2067  void WriteScrubEnabledPref(bool value)
2068  {
2069  gPrefs->Write(scrubEnabledPrefName, value);
2070  }
2071 }
2072 #endif
2073 
2075 {
2076  // Update button texts for language change
2078 
2079 #ifdef EXPERIMENTAL_SCROLLING_LIMITS
2080 #ifdef EXPERIMENTAL_TWO_TONE_TIME_RULER
2081  {
2082  bool scrollBeyondZero = false;
2083  gPrefs->Read(TracksBehaviorsPrefs::ScrollingPreferenceKey(), &scrollBeyondZero,
2085  mRuler.SetTwoTone(scrollBeyondZero);
2086  }
2087 #endif
2088 #endif
2089 
2090  mShowScrubbing = ReadScrubEnabledPref();
2091  // Affected by the last
2092  UpdateRects();
2093  SetPanelSize();
2094 
2096 }
2097 
2099 {
2100  // TODO: Should we do this to destroy the grabber??
2101  // Get rid of any children we may have
2102  // DestroyChildren();
2103 
2104  SetBackgroundColour(theTheme.Colour( clrMedium ));
2105 
2106  for (auto & button : mButtons) {
2107  if (button)
2108  button->Destroy();
2109  button = nullptr;
2110  }
2111 
2112  size_t iButton = 0;
2113  // Make the short row of time ruler pushbottons.
2114  // Don't bother with sizers. Their sizes and positions are fixed.
2115  // Add a grabber converted to a spacer.
2116  // This makes it visually clearer that the button is a button.
2117 
2118  wxPoint position( 1, 0 );
2119 
2120  Grabber * pGrabber = safenew Grabber(this, this->GetId());
2121  pGrabber->SetAsSpacer( true );
2122  //pGrabber->SetSize( 10, 27 ); // default is 10,27
2123  pGrabber->SetPosition( position );
2124 
2125  position.x = 12;
2126 
2127  auto size = theTheme.ImageSize( bmpRecoloredUpSmall );
2128  size.y = std::min(size.y, GetRulerHeight(false));
2129 
2130  auto buttonMaker = [&]
2131  (wxWindowID id, teBmps bitmap, bool toggle)
2132  {
2133  const auto button =
2135  this,
2136  bmpRecoloredUpSmall, bmpRecoloredDownSmall,
2137  bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
2138  bitmap, bitmap, bitmap,
2139  id, position, toggle, size
2140  );
2141 
2142  position.x += size.GetWidth();
2143  mButtons[iButton++] = button;
2144  return button;
2145  };
2146  auto button = buttonMaker(OnTogglePinnedStateID, bmpPlayPointerPinned, true);
2148  *button, 1,
2149  bmpRecoloredUpSmall, bmpRecoloredDownSmall,
2150  bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
2151  //bmpUnpinnedPlayHead, bmpUnpinnedPlayHead, bmpUnpinnedPlayHead,
2152  bmpPlayPointer, bmpPlayPointer, bmpPlayPointer,
2153  size);
2154 
2156 }
2157 
2159 {
2160  mRuler.Invalidate();
2161 }
2162 
2163 namespace {
2164  const wxString StartScrubbingMessage(const Scrubber &/*scrubber*/)
2165  {
2166  /* i18n-hint: These commands assist the user in finding a sound by ear. ...
2167  "Scrubbing" is variable-speed playback, ...
2168  "Seeking" is normal speed playback but with skips
2169  */
2170 #if 0
2171  if(scrubber.Seeks())
2172  return _("Click or drag to begin Seek");
2173  else
2174  return _("Click or drag to begin Scrub");
2175 #else
2176  return _("Click & move to Scrub. Click & drag to Seek.");
2177 #endif
2178  }
2179 
2180  const wxString ContinueScrubbingMessage(const Scrubber &scrubber)
2181  {
2182  /* i18n-hint: These commands assist the user in finding a sound by ear. ...
2183  "Scrubbing" is variable-speed playback, ...
2184  "Seeking" is normal speed playback but with skips
2185  */
2186 #if 0
2187  if(scrubber.Seeks())
2188  return _("Move to Seek");
2189  else
2190  return _("Move to Scrub");
2191 #else
2192  wxMouseState State = wxGetMouseState();
2193  if( State.LeftIsDown() ) {
2194  // Since mouse is down, mention dragging first.
2195  // IsScrubbing is true if Scrubbing OR seeking.
2196  if( scrubber.IsOneShotSeeking() )
2197  return _("Drag to Seek. Release to stop seeking.");
2198  else
2199  return _("Drag to Seek. Release and move to Scrub.");
2200  }
2201  // Since mouse is up, mention moving first.
2202  return _("Move to Scrub. Drag to Seek.");
2203 #endif
2204  }
2205 
2206  const wxString ScrubbingMessage(const Scrubber &scrubber)
2207  {
2208  if (scrubber.HasStartedScrubbing())
2209  return ContinueScrubbingMessage(scrubber);
2210  else
2211  return StartScrubbingMessage(scrubber);
2212  }
2213 }
2214 
2216 {
2217 #if wxUSE_TOOLTIPS
2218  if (mTimelineToolTip) {
2219  if (mIsRecording) {
2220  this->SetToolTip(_("Timeline actions disabled during recording"));
2221  }
2222  else {
2223  switch(choice) {
2225  if (!mQuickPlayEnabled) {
2226  this->SetToolTip(_("Quick-Play disabled"));
2227  }
2228  else {
2229  this->SetToolTip(_("Quick-Play enabled"));
2230  }
2231  break;
2233  {
2234  const auto message = ScrubbingMessage(mProject->GetScrubber());
2235  this->SetToolTip(message);
2236  }
2237  break;
2238  default:
2239  this->SetToolTip(NULL);
2240  break;
2241  }
2242  }
2243  }
2244  else {
2245  this->SetToolTip(NULL);
2246  }
2247 #endif
2248 }
2249 
2250 void AdornedRulerPanel::OnCapture(wxCommandEvent & evt)
2251 {
2252  evt.Skip();
2253 
2254  if (evt.GetInt() != 0)
2255  {
2256  // Set cursor immediately because OnMouseEvents is not called
2257  // if recording is initiated by a modal window (Timer Record).
2258  SetCursor(mCursorDefault);
2259  mIsRecording = true;
2260 
2261  // The quick play indicator is useless during recording
2263  }
2264  else {
2265  SetCursor(mCursorHand);
2266  mIsRecording = false;
2267  }
2269 }
2270 
2271 void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
2272 {
2273  if (mNeedButtonUpdate) {
2274  // Visit this block once only in the lifetime of this panel
2275  mNeedButtonUpdate = false;
2276  // Do this first time setting of button status texts
2277  // when we are sure the CommandManager is initialized.
2278  ReCreateButtons();
2279  // Sends a resize event, which will cause a second paint.
2280  UpdatePrefs();
2281  }
2282 
2283  wxPaintDC dc(this);
2284 
2285  auto &backDC = GetBackingDCForRepaint();
2286 
2287  DoDrawBackground(&backDC);
2288 
2290  {
2291  DoDrawSelection(&backDC);
2292  }
2293 
2294  DoDrawMarks(&backDC, true);
2295 
2296  DoDrawPlayRegion(&backDC);
2297 
2298  DoDrawEdge(&backDC);
2299 
2300  DisplayBitmap(dc);
2301 
2302  // Stroke extras direct to the client area,
2303  // maybe outside of the damaged area
2304  // As with TrackPanel, do not make a NEW wxClientDC or else Mac flashes badly!
2305  dc.DestroyClippingRegion();
2306  DrawOverlays(true, &dc);
2307 }
2308 
2309 void AdornedRulerPanel::OnSize(wxSizeEvent &evt)
2310 {
2311  mOuter = GetClientRect();
2312  if (mOuter.GetWidth() == 0 || mOuter.GetHeight() == 0)
2313  {
2314  return;
2315  }
2316 
2317  UpdateRects();
2318 
2319  OverlayPanel::OnSize(evt);
2320 }
2321 
2323 {
2324  mInner = mOuter;
2325  mInner.x += LeftMargin;
2326  mInner.width -= (LeftMargin + RightMargin);
2327 
2328  auto top = &mInner;
2329  auto bottom = &mInner;
2330 
2331  if (mShowScrubbing) {
2332  mScrubZone = mInner;
2333  auto scrubHeight = std::min(mScrubZone.height, (int)(ScrubHeight));
2334 
2335  int topHeight;
2336 #ifdef SCRUB_ABOVE
2337  top = &mScrubZone, topHeight = scrubHeight;
2338 #else
2339  auto qpHeight = mScrubZone.height - scrubHeight;
2340  bottom = &mScrubZone, topHeight = qpHeight;
2341  // Increase scrub zone height so that hit testing finds it and
2342  // not QP region, when on bottom 'edge'.
2343  mScrubZone.height+=BottomMargin;
2344 #endif
2345 
2346  top->height = topHeight;
2347  bottom->height -= topHeight;
2348  bottom->y += topHeight;
2349  }
2350 
2351  top->y += TopMargin;
2352  top->height -= TopMargin;
2353 
2354  bottom->height -= BottomMargin;
2355 
2356  if (!mShowScrubbing)
2357  mScrubZone = mInner;
2358 
2359  mRuler.SetBounds(mInner.GetLeft(),
2360  mInner.GetTop(),
2361  mInner.GetRight(),
2362  mInner.GetBottom());
2363 
2364 }
2365 
2366 double AdornedRulerPanel::Pos2Time(int p, bool ignoreFisheye)
2367 {
2369  , ignoreFisheye
2370  );
2371 }
2372 
2373 int AdornedRulerPanel::Time2Pos(double t, bool ignoreFisheye)
2374 {
2376  , ignoreFisheye
2377  );
2378 }
2379 
2380 
2381 bool AdornedRulerPanel::IsWithinMarker(int mousePosX, double markerTime)
2382 {
2383  if (markerTime < 0)
2384  return false;
2385 
2386  int pixelPos = Time2Pos(markerTime);
2387  int boundLeft = pixelPos - SELECT_TOLERANCE_PIXEL;
2388  int boundRight = pixelPos + SELECT_TOLERANCE_PIXEL;
2389 
2390  return mousePosX >= boundLeft && mousePosX < boundRight;
2391 }
2392 
2393 void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
2394 {
2395  // Disable mouse actions on Timeline while recording.
2396  if (mIsRecording) {
2397  if (HasCapture())
2398  ReleaseMouse();
2399  return;
2400  }
2401 
2402  const auto position = evt.GetPosition();
2403 
2404  const bool inScrubZone =
2405  // only if scrubbing is allowed now
2406  mProject->GetScrubber().CanScrub() &&
2407  mShowScrubbing &&
2408  mScrubZone.Contains(position);
2409  const bool inQPZone =
2410  (!inScrubZone) && mInner.Contains(position);
2411 
2412  const StatusChoice zone =
2413  evt.Leaving()
2415  : inScrubZone
2417  : inQPZone
2420  const bool changeInZone = (zone != mPrevZone);
2421  const bool changing = evt.Leaving() || evt.Entering() || changeInZone;
2422 
2423  wxCoord xx = evt.GetX();
2424  wxCoord mousePosX = xx;
2425  UpdateQuickPlayPos(mousePosX);
2426  HandleSnapping();
2427 
2428  // If not looping, restrict selection to end of project
2429  if (zone == StatusChoice::EnteringQP && !evt.ShiftDown()) {
2430  const double t1 = mTracks->GetEndTime();
2432  }
2433 
2434  // Handle status bar messages
2436 
2437  mPrevZone = zone;
2438 
2439  auto &scrubber = mProject->GetScrubber();
2440  if (scrubber.HasStartedScrubbing()) {
2441  if (evt.RightDown() )
2442  // Fall through to context menu handling
2443  ;
2444  else if ( evt.LeftUp() && inScrubZone)
2445  // Fall through to seeking changes to scrubbing
2446  ;
2447 // else if ( evt.LeftDown() && inScrubZone)
2448 // // Fall through to ready to seek
2449 // ;
2450  else {
2451  bool switchToQP = (zone == StatusChoice::EnteringQP && mQuickPlayEnabled);
2452  if (switchToQP && evt.LeftDown()) {
2453  // We can't stop scrubbing yet (see comments in Bug 1391), but we can pause it.
2455  // Don't return, fall through
2456  }
2457  else if (scrubber.IsPaused())
2458  // Just fall through
2459  ;
2460  else {
2461  // If already clicked for scrub, preempt the usual event handling,
2462  // no matter what the y coordinate.
2463 
2464  // Do this hack so scrubber can detect mouse drags anywhere
2465  evt.ResumePropagation(wxEVENT_PROPAGATE_MAX);
2466 
2467  //if (scrubber.IsScrubbing())
2468  evt.Skip();
2469  //else
2470  //evt.Skip();
2471 
2472  // Don't do this, it slows down drag-scrub on Mac.
2473  // Timer updates of display elsewhere make it unnecessary.
2474  // Done here, it's too frequent.
2475  // ShowQuickPlayIndicator();
2476 
2477  return;
2478  }
2479  }
2480  }
2481 
2482  // Store the initial play region state
2483  if(mMouseEventState == mesNone) {
2487  }
2488 
2489  // Handle entering and leaving of the bar, or movement from
2490  // one portion (quick play or scrub) to the other
2491  if (evt.Leaving() || (changeInZone && zone != StatusChoice::EnteringQP)) {
2492  if (evt.Leaving()) {
2493  // Erase the line
2495  }
2496 
2497  SetCursor(mCursorDefault);
2498  mIsWE = false;
2499 
2500  mSnapManager.reset();
2501 
2502  if(evt.Leaving())
2503  return;
2504  // else, may detect a scrub click below
2505  }
2506  else if (evt.Entering() || (changeInZone && zone == StatusChoice::EnteringQP)) {
2507  SetCursor(mCursorHand);
2509  return;
2510  }
2511 
2512  // Handle popup menus
2513  if (!HasCapture() && evt.RightDown() && !(evt.LeftIsDown())) {
2515  (inScrubZone ? MenuChoice::Scrub : MenuChoice::QuickPlay,
2516  &position);
2517  return;
2518  }
2519  else if( !HasCapture() && evt.LeftUp() && inScrubZone ) {
2520  if( scrubber.IsOneShotSeeking() ){
2521  scrubber.mInOneShotMode = false;
2522  return;
2523  }
2524  //wxLogDebug("up");
2525  // mouse going up => we shift to scrubbing.
2526  scrubber.MarkScrubStart(evt.m_x,
2529  // repaint_all so that the indicator changes shape.
2530  bool repaint_all = true;
2531  ShowQuickPlayIndicator(repaint_all);
2532  return;
2533  }
2534  else if ( !HasCapture() && inScrubZone) {
2535  // mouse going down => we are (probably) seeking
2536  bool repaint_all = false;
2537  if (evt.LeftDown()) {
2538  //wxLogDebug("down");
2539  scrubber.mInOneShotMode = !scrubber.IsScrubbing();
2540  scrubber.MarkScrubStart(evt.m_x,
2543  }
2544  else if( changeInZone ) {
2545  repaint_all = true;
2546  }
2547 
2548  ShowQuickPlayIndicator(repaint_all);
2549  return;
2550  }
2551  else if ( mQuickPlayEnabled) {
2552  bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegionStart);
2553  bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegionEnd);
2554 
2555  if (isWithinStart || isWithinEnd) {
2556  if (!mIsWE) {
2557  SetCursor(mCursorSizeWE);
2558  mIsWE = true;
2559  }
2560  }
2561  else {
2562  if (mIsWE) {
2563  SetCursor(mCursorHand);
2564  mIsWE = false;
2565  }
2566  }
2567 
2568  if (evt.LeftDown()) {
2569  if( inQPZone ){
2570  HandleQPClick(evt, mousePosX);
2571  HandleQPDrag(evt, mousePosX);
2573  }
2574  }
2575  else if (evt.LeftIsDown() && HasCapture()) {
2576  HandleQPDrag(evt, mousePosX);
2578  }
2579  else if (evt.LeftUp() && HasCapture()) {
2580  HandleQPRelease(evt);
2582  }
2583  else // if (!inScrubZone)
2585  }
2586 }
2587 
2588 void AdornedRulerPanel::HandleQPClick(wxMouseEvent &evt, wxCoord mousePosX)
2589 {
2590  // Temporarily unlock locked play region
2591  if (mPlayRegionLock && evt.LeftDown()) {
2592  //mPlayRegionLock = true;
2594  }
2595 
2598  bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegionStart);
2599  bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegionEnd);
2600 
2601  if (isWithinStart || isWithinEnd) {
2602  // If Quick-Play is playing from a point, we need to treat it as a click
2603  // not as dragging.
2606  // otherwise check which marker is nearer
2607  else {
2608  // Don't compare times, compare positions.
2609  //if (fabs(mQuickPlayPos - mPlayRegionStart) < fabs(mQuickPlayPos - mPlayRegionEnd))
2613  else
2615  }
2616  }
2617  else {
2618  // Clicked but not yet dragging
2620  }
2621 
2622  // Check if we are dragging BEFORE CaptureMouse.
2623  if (mMouseEventState != mesNone)
2624  SetCursor(mCursorSizeWE);
2625  if ( !HasCapture() )
2626  CaptureMouse();
2627 }
2628 
2629 void AdornedRulerPanel::HandleQPDrag(wxMouseEvent &/*event*/, wxCoord mousePosX)
2630 {
2631  bool isWithinClick =
2632  (mLeftDownClickUnsnapped >= 0) &&
2634  bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegionStart);
2635  bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegionEnd);
2636  bool canDragSel = !mPlayRegionLock && mPlayRegionDragsSelection;
2637 
2638  switch (mMouseEventState)
2639  {
2640  case mesNone:
2641  // If close to either end of play region, snap to closest
2642  if (isWithinStart || isWithinEnd) {
2644 
2647  else
2649  }
2650  break;
2653 
2654  // Don't start dragging until beyond tolerance initial playback start
2655  if (!mIsDragging && isWithinStart)
2657  else
2658  mIsDragging = true;
2659  // avoid accidental tiny selection
2660  if (isWithinEnd)
2663  if (canDragSel) {
2664  DragSelection();
2665  }
2666  break;
2668  if (!mIsDragging && isWithinEnd) {
2670 
2672  }
2673  else
2674  mIsDragging = true;
2675  if (isWithinStart) {
2677 
2679  }
2681  if (canDragSel) {
2682  DragSelection();
2683  }
2684  break;
2686 
2687  // Don't start dragging until mouse is beyond tolerance of initial click.
2688  if (isWithinClick || mLeftDownClick == -1) {
2690 
2694  }
2695  else {
2697  }
2698  break;
2700  if (isWithinClick) {
2702 
2704  }
2705 
2706  if (mQuickPlayPos < mLeftDownClick) {
2709  }
2710  else {
2713  }
2714  if (canDragSel) {
2715  DragSelection();
2716  }
2717  break;
2718  }
2719  Refresh();
2720  Update();
2721 }
2722 
2723 void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
2724 {
2725  if (HasCapture())
2726  ReleaseMouse();
2727  else
2728  return;
2729 
2731 
2733  // Swap values to ensure mPlayRegionStart < mPlayRegionEnd
2734  double tmp = mPlayRegionStart;
2736  mPlayRegionEnd = tmp;
2737  }
2738 
2739  const double t0 = mTracks->GetStartTime();
2740  const double t1 = mTracks->GetEndTime();
2741  const double sel0 = mProject->GetSel0();
2742  const double sel1 = mProject->GetSel1();
2743 
2744  // We want some audio in the selection, but we allow a dragged
2745  // region to include selected white-space and space before audio start.
2746  if (evt.ShiftDown() && (mPlayRegionStart == mPlayRegionEnd)) {
2747  // Looping the selection or project.
2748  // Disable if track selection is in white-space beyond end of tracks and
2749  // play position is outside of track contents.
2750  if (((sel1 < t0) || (sel0 > t1)) &&
2751  ((mPlayRegionStart < t0) || (mPlayRegionStart > t1))) {
2752  ClearPlayRegion();
2753  }
2754  }
2755  // Disable if beyond end.
2756  else if (mPlayRegionStart >= t1) {
2757  ClearPlayRegion();
2758  }
2759  // Disable if empty selection before start.
2760  // (allow Quick-Play region to include 'pre-roll' white space)
2761  else if (((mPlayRegionEnd - mPlayRegionStart) > 0.0) && (mPlayRegionEnd < t0)) {
2762  ClearPlayRegion();
2763  }
2764 
2766  mIsDragging = false;
2767  mLeftDownClick = -1;
2768 
2769  auto cleanup = finally( [&] {
2770  if (mPlayRegionLock) {
2771  // Restore Locked Play region
2774  // and release local lock
2775  mPlayRegionLock = false;
2776  }
2777  } );
2778 
2779  StartQPPlay(evt.ShiftDown(), evt.ControlDown());
2780 }
2781 
2782 void AdornedRulerPanel::StartQPPlay(bool looped, bool cutPreview)
2783 {
2784  const double t0 = mTracks->GetStartTime();
2785  const double t1 = mTracks->GetEndTime();
2786  const double sel0 = mProject->GetSel0();
2787  const double sel1 = mProject->GetSel1();
2788 
2789  // Start / Restart playback on left click.
2790  bool startPlaying = (mPlayRegionStart >= 0);
2791 
2792  if (startPlaying) {
2794  ctb->StopPlaying();
2795 
2796  bool loopEnabled = true;
2797  double start, end;
2798 
2799  if ((mPlayRegionEnd - mPlayRegionStart == 0.0) && looped) {
2800  // Loop play a point will loop either a selection or the project.
2801  if ((mPlayRegionStart > sel0) && (mPlayRegionStart < sel1)) {
2802  // we are in a selection, so use the selection
2803  start = sel0;
2804  end = sel1;
2805  } // not in a selection, so use the project
2806  else {
2807  start = t0;
2808  end = t1;
2809  }
2810  }
2811  else {
2812  start = mPlayRegionStart;
2813  end = mPlayRegionEnd;
2814  }
2815  // Looping a tiny selection may freeze, so just play it once.
2816  loopEnabled = ((end - start) > 0.001)? true : false;
2817 
2819  options.playLooped = (loopEnabled && looped);
2820 
2821  auto oldStart = mPlayRegionStart;
2822  if (!cutPreview)
2823  options.pStartTime = &oldStart;
2824  else
2825  options.timeTrack = NULL;
2826 
2827  ControlToolBar::PlayAppearance appearance =
2829  : options.playLooped ? ControlToolBar::PlayAppearance::Looped
2831 
2832  mPlayRegionStart = start;
2833  mPlayRegionEnd = end;
2834  Refresh();
2835 
2836  ctb->PlayPlayRegion((SelectedRegion(start, end)),
2837  options, PlayMode::normalPlay,
2838  appearance,
2839  false,
2840  true);
2841 
2842  }
2843 }
2844 
2846 {
2847  if (choice == StatusChoice::NoChange)
2848  return;
2849 
2850  wxString message {};
2851 
2852  const auto &scrubber = mProject->GetScrubber();
2853  const bool scrubbing = scrubber.HasStartedScrubbing();
2854  if (scrubbing && choice != StatusChoice::Leaving)
2855  // Don't distinguish zones
2857 
2858  switch (choice) {
2860  {
2861  // message = Insert timeline status bar message here
2862  }
2863  break;
2864 
2866  {
2867  message = ScrubbingMessage(scrubber);
2868  }
2869  break;
2870 
2871  default:
2872  break;
2873  }
2874 
2875  // Display a message, or empty message
2877 
2878  RegenerateTooltips(choice);
2879 }
2880 
2881 // This version toggles ruler state indirectly via the scrubber
2882 // to ensure that all the places where the state is shown update.
2883 // For example buttons and menus must update.
2885 {
2886  auto &scrubber = mProject->GetScrubber();
2887  scrubber.OnToggleScrubRuler(*mProject);
2888 }
2889 
2890 void AdornedRulerPanel::OnToggleScrubRuler(/*wxCommandEvent&*/)
2891 {
2893  WriteScrubEnabledPref(mShowScrubbing);
2894  gPrefs->Flush();
2895  SetPanelSize();
2896 }
2897 
2899 {
2900  wxSize size { GetSize().GetWidth(), GetRulerHeight(mShowScrubbing) };
2901  SetSize(size);
2902  SetMinSize(size);
2903  GetParent()->PostSizeEventToParent();
2904 }
2905 
2906 void AdornedRulerPanel::OnContextMenu(wxContextMenuEvent & WXUNUSED(event))
2907 {
2909 }
2910 
2912 {
2913  auto common = [this]
2914  (AButton &button, const wxString &commandName, const wxString &label) {
2915  TranslatedInternalString command{ commandName, label };
2916  ToolBar::SetButtonToolTip( button, &command, 1u );
2917  button.SetLabel(button.GetToolTipText());
2918 
2919  button.UpdateStatus();
2920  };
2921 
2922  {
2923  bool state = TracksPrefs::GetPinnedHeadPreference();
2924  auto pinButton = static_cast<AButton*>(FindWindow(OnTogglePinnedStateID));
2925  if( !state )
2926  pinButton->PopUp();
2927  else
2928  pinButton->PushDown();
2929  pinButton->SetAlternateIdx(state ? 0 : 1);
2930  // Bug 1584: Toltip now shows what clicking will do.
2931  const auto label = state
2932  ? _("Click to unpin")
2933  : _("Click to pin");
2934  common(*pinButton, wxT("PinnedHead"), label);
2935  }
2936 }
2937 
2938 void AdornedRulerPanel::OnTogglePinnedState(wxCommandEvent & /*event*/)
2939 {
2942 }
2943 
2944 void AdornedRulerPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(evt))
2945 {
2947 
2948  wxMouseEvent e(wxEVT_LEFT_UP);
2949  e.m_x = mLastMouseX;
2950  OnMouseEvents(e);
2951 }
2952 
2954 {
2955  // Keep Quick-Play within usable track area.
2957  int width;
2958  tp->GetTracksUsableArea(&width, NULL);
2959  mousePosX = std::max(mousePosX, tp->GetLeftOffset());
2960  mousePosX = std::min(mousePosX, tp->GetLeftOffset() + width - 1);
2961 
2962  mLastMouseX = mousePosX;
2964 }
2965 
2966 // Pop-up menus
2967 
2968 void AdornedRulerPanel::ShowMenu(const wxPoint & pos)
2969 {
2970  wxMenu rulerMenu;
2971 
2972  if (mQuickPlayEnabled)
2973  rulerMenu.Append(OnToggleQuickPlayID, _("Disable Quick-Play"));
2974  else
2975  rulerMenu.Append(OnToggleQuickPlayID, _("Enable Quick-Play"));
2976 
2977  wxMenuItem *dragitem;
2979  dragitem = rulerMenu.Append(OnSyncQuickPlaySelID, _("Disable dragging selection"));
2980  else
2981  dragitem = rulerMenu.Append(OnSyncQuickPlaySelID, _("Enable dragging selection"));
2982  dragitem->Enable(mQuickPlayEnabled && !mProject->IsPlayRegionLocked());
2983 
2984 #if wxUSE_TOOLTIPS
2985  if (mTimelineToolTip)
2986  rulerMenu.Append(OnTimelineToolTipID, _("Disable Timeline Tooltips"));
2987  else
2988  rulerMenu.Append(OnTimelineToolTipID, _("Enable Timeline Tooltips"));
2989 #endif
2990 
2992  rulerMenu.Append(OnAutoScrollID, _("Do not scroll while playing"));
2993  else
2994  rulerMenu.Append(OnAutoScrollID, _("Update display while playing"));
2995 
2996  wxMenuItem *prlitem;
2997  if (!mProject->IsPlayRegionLocked())
2998  prlitem = rulerMenu.Append(OnLockPlayRegionID, _("Lock Play Region"));
2999  else
3000  prlitem = rulerMenu.Append(OnLockPlayRegionID, _("Unlock Play Region"));
3001  prlitem->Enable(mProject->IsPlayRegionLocked() || (mPlayRegionStart != mPlayRegionEnd));
3002 
3003  wxMenuItem *ruleritem;
3004  if (mShowScrubbing)
3005  ruleritem = rulerMenu.Append(OnScrubRulerID, _("Disable Scrub Ruler"));
3006  else
3007  ruleritem = rulerMenu.Append(OnScrubRulerID, _("Enable Scrub Ruler"));
3008 
3009  PopupMenu(&rulerMenu, pos);
3010 }
3011 
3012 void AdornedRulerPanel::ShowScrubMenu(const wxPoint & pos)
3013 {
3014  auto &scrubber = mProject->GetScrubber();
3015  PushEventHandler(&scrubber);
3016  auto cleanup = finally([this]{ PopEventHandler(); });
3017 
3018  wxMenu rulerMenu;
3019  mProject->GetScrubber().PopulatePopupMenu(rulerMenu);
3020  PopupMenu(&rulerMenu, pos);
3021 }
3022 
3024 {
3025  mQuickPlayEnabled = (mQuickPlayEnabled)? false : true;
3026  gPrefs->Write(wxT("/QuickPlay/QuickPlayEnabled"), mQuickPlayEnabled);
3027  gPrefs->Flush();
3029 }
3030 
3032 {
3034  gPrefs->Write(wxT("/QuickPlay/DragSelection"), mPlayRegionDragsSelection);
3035  gPrefs->Flush();
3036 }
3037 
3039 {
3042 }
3043 
3045 {
3046  if (!mSnapManager) {
3047  mSnapManager = std::make_unique<SnapManager>(mTracks, mViewInfo);
3048  }
3049 
3050  auto results = mSnapManager->Snap(NULL, mQuickPlayPos, false);
3051  mQuickPlayPos = results.outTime;
3052  mIsSnapped = results.Snapped();
3053 }
3054 
3056 {
3057  mTimelineToolTip = (mTimelineToolTip)? false : true;
3058  gPrefs->Write(wxT("/QuickPlay/ToolTips"), mTimelineToolTip);
3059  gPrefs->Flush();
3060 #if wxUSE_TOOLTIPS
3062 #endif
3063 }
3064 
3065 void AdornedRulerPanel::OnAutoScroll(wxCommandEvent&)
3066 {
3068  gPrefs->Write(wxT("/GUI/AutoScroll"), false);
3069  else
3070  gPrefs->Write(wxT("/GUI/AutoScroll"), true);
3071  mProject->UpdatePrefs();
3072  gPrefs->Flush();
3073 }
3074 
3075 
3077 {
3080  else
3082 }
3083 
3084 
3085 // Draws the horizontal <===>
3087 {
3088  double start, end;
3089  GetPlayRegion(&start, &end);
3090 
3091  if (start >= 0)
3092  {
3093  const int x1 = Time2Pos(start);
3094  const int x2 = Time2Pos(end)-2;
3095  int y = mInner.y - TopMargin + mInner.height/2;
3096 
3097  bool isLocked = mProject->IsPlayRegionLocked();
3098  AColor::PlayRegionColor(dc, isLocked);
3099 
3100  wxPoint tri[3];
3101  wxRect r;
3102 
3103  tri[0].x = x1;
3104  tri[0].y = y + PLAY_REGION_GLOBAL_OFFSET_Y;
3105  tri[1].x = x1 + PLAY_REGION_TRIANGLE_SIZE;
3106  tri[1].y = y - PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
3107  tri[2].x = x1 + PLAY_REGION_TRIANGLE_SIZE;
3108  tri[2].y = y + PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
3109  dc->DrawPolygon(3, tri);
3110 
3111  r.x = x1;
3112  r.y = y - PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
3113  r.width = PLAY_REGION_RECT_WIDTH;
3114  r.height = PLAY_REGION_TRIANGLE_SIZE*2 + 1;
3115  dc->DrawRectangle(r);
3116 
3117  if (end != start)
3118  {
3119  tri[0].x = x2;
3120  tri[0].y = y + PLAY_REGION_GLOBAL_OFFSET_Y;
3121  tri[1].x = x2 - PLAY_REGION_TRIANGLE_SIZE;
3122  tri[1].y = y - PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
3123  tri[2].x = x2 - PLAY_REGION_TRIANGLE_SIZE;
3124  tri[2].y = y + PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
3125  dc->DrawPolygon(3, tri);
3126 
3127  r.x = x2 - PLAY_REGION_RECT_WIDTH + 1;
3128  r.y = y - PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
3129  r.width = PLAY_REGION_RECT_WIDTH;
3130  r.height = PLAY_REGION_TRIANGLE_SIZE*2 + 1;
3131  dc->DrawRectangle(r);
3132 
3133  r.x = x1 + PLAY_REGION_TRIANGLE_SIZE;
3135  r.width = std::max(0, x2-x1 - PLAY_REGION_TRIANGLE_SIZE*2);
3136  r.height = PLAY_REGION_RECT_HEIGHT;
3137  dc->DrawRectangle(r);
3138  }
3139  }
3140 }
3141 
3142 void AdornedRulerPanel::ShowContextMenu( MenuChoice choice, const wxPoint *pPosition)
3143 {
3144  wxPoint position;
3145  if(pPosition)
3146  position = *pPosition;
3147  else
3148  {
3149  auto rect = GetRect();
3150  position = { rect.GetLeft() + 1, rect.GetBottom() + 1 };
3151  }
3152 
3153  switch (choice) {
3154  case MenuChoice::QuickPlay:
3155  ShowMenu(position); break;
3156  case MenuChoice::Scrub:
3157  ShowScrubMenu(position); break;
3158  default:
3159  return;
3160  }
3161 
3162  // dismiss and clear Quick-Play indicator
3164 
3165  if (HasCapture())
3166  ReleaseMouse();
3167 }
3168 
3170 {
3171  // Draw AdornedRulerPanel border
3172  AColor::UseThemeColour( dc, clrTrackInfo );
3173  dc->DrawRectangle( mInner );
3174 
3175  if (mShowScrubbing) {
3176  // Let's distinguish the scrubbing area by using a themable
3177  // colour and a line to set it off.
3178  AColor::UseThemeColour(dc, clrScrubRuler, clrTrackPanelText );
3179  wxRect ScrubRect = mScrubZone;
3180  ScrubRect.Inflate( 1,0 );
3181  dc->DrawRectangle(ScrubRect);
3182  }
3183 }
3184 
3186 {
3187  wxRect r = mOuter;
3188  r.width -= RightMargin;
3189  r.height -= BottomMargin;
3190  AColor::BevelTrackInfo( *dc, true, r );
3191 
3192  // Black stroke at bottom
3193  dc->SetPen( *wxBLACK_PEN );
3194  dc->DrawLine( mOuter.x,
3195  mOuter.y + mOuter.height - 1,
3196  mOuter.x + mOuter.width,
3197  mOuter.y + mOuter.height - 1 );
3198 }
3199 
3200 void AdornedRulerPanel::DoDrawMarks(wxDC * dc, bool /*text */ )
3201 {
3202  const double min = Pos2Time(0);
3203  const double hiddenMin = Pos2Time(0, true);
3204  const double max = Pos2Time(mInner.width);
3205  const double hiddenMax = Pos2Time(mInner.width, true);
3206 
3207  mRuler.SetTickColour( theTheme.Colour( clrTrackPanelText ) );
3208  mRuler.SetRange( min, max, hiddenMin, hiddenMax );
3209  mRuler.Draw( *dc );
3210 }
3211 
3213 {
3214  Refresh();
3215 }
3216 
3218 {
3219  // Draw selection
3220  const int p0 = max(1, Time2Pos(mViewInfo->selectedRegion.t0()));
3221  const int p1 = min(mInner.width, Time2Pos(mViewInfo->selectedRegion.t1()));
3222 
3223  dc->SetBrush( wxBrush( theTheme.Colour( clrRulerBackground )) );
3224  dc->SetPen( wxPen( theTheme.Colour( clrRulerBackground )) );
3225 
3226  wxRect r;
3227  r.x = p0;
3228  r.y = mInner.y;
3229  r.width = p1 - p0 - 1;
3230  r.height = mInner.height;
3231  dc->DrawRectangle( r );
3232 }
3233 
3235 {
3236  return ProperRulerHeight + (showScrubBar ? ScrubHeight : 0);
3237 }
3238 
3240 {
3241  mLeftOffset = offset;
3242  mRuler.SetUseZoomInfo(offset, mViewInfo);
3243 }
3244 
3245 // Draws the play/recording position indicator.
3247  (wxDC * dc, wxCoord xx, bool playing, int width, bool scrub, bool seek)
3248 {
3249  ADCChanger changer(dc); // Undo pen and brush changes at function exit
3250 
3251  AColor::IndicatorColor( dc, playing );
3252 
3253  wxPoint tri[ 3 ];
3254  if (seek) {
3255  auto height = IndicatorHeightForWidth(width);
3256  // Make four triangles
3257  const int TriangleWidth = width * 3 / 8;
3258 
3259  // Double-double headed, left-right
3260  auto yy = mShowScrubbing
3261  ? mScrubZone.y
3262  : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
3263  tri[ 0 ].x = xx - IndicatorOffset;
3264  tri[ 0 ].y = yy;
3265  tri[ 1 ].x = xx - IndicatorOffset;
3266  tri[ 1 ].y = yy + height;
3267  tri[ 2 ].x = xx - TriangleWidth;
3268  tri[ 2 ].y = yy + height / 2;
3269  dc->DrawPolygon( 3, tri );
3270 
3271  tri[ 0 ].x -= TriangleWidth;
3272  tri[ 1 ].x -= TriangleWidth;
3273  tri[ 2 ].x -= TriangleWidth;
3274  dc->DrawPolygon( 3, tri );
3275 
3276  tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
3277  tri[ 2 ].x = xx + TriangleWidth;
3278  dc->DrawPolygon( 3, tri );
3279 
3280 
3281  tri[ 0 ].x += TriangleWidth;
3282  tri[ 1 ].x += TriangleWidth;
3283  tri[ 2 ].x += TriangleWidth;
3284  dc->DrawPolygon( 3, tri );
3285  }
3286  else if (scrub) {
3287  auto height = IndicatorHeightForWidth(width);
3288  const int IndicatorHalfWidth = width / 2;
3289 
3290  // Double headed, left-right
3291  auto yy = mShowScrubbing
3292  ? mScrubZone.y
3293  : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
3294  tri[ 0 ].x = xx - IndicatorOffset;
3295  tri[ 0 ].y = yy;
3296  tri[ 1 ].x = xx - IndicatorOffset;
3297  tri[ 1 ].y = yy + height;
3298  tri[ 2 ].x = xx - IndicatorHalfWidth;
3299  tri[ 2 ].y = yy + height / 2;
3300  dc->DrawPolygon( 3, tri );
3301  tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
3302  tri[ 2 ].x = xx + IndicatorHalfWidth;
3303  dc->DrawPolygon( 3, tri );
3304  }
3305  else {
3306  bool pinned = TracksPrefs::GetPinnedHeadPreference();
3307  wxBitmap & bmp = theTheme.Bitmap( pinned ?
3308  (playing ? bmpPlayPointerPinned : bmpRecordPointerPinned) :
3309  (playing ? bmpPlayPointer : bmpRecordPointer)
3310  );
3311  const int IndicatorHalfWidth = bmp.GetWidth() / 2;
3312  dc->DrawBitmap( bmp, xx - IndicatorHalfWidth -1, mInner.y );
3313 #if 0
3314 
3315  // Down pointing triangle
3316  auto height = IndicatorHeightForWidth(width);
3317  const int IndicatorHalfWidth = width / 2;
3318  tri[ 0 ].x = xx - IndicatorHalfWidth;
3319  tri[ 0 ].y = mInner.y;
3320  tri[ 1 ].x = xx + IndicatorHalfWidth;
3321  tri[ 1 ].y = mInner.y;
3322  tri[ 2 ].x = xx;
3323  tri[ 2 ].y = mInner.y + height;
3324  dc->DrawPolygon( 3, tri );
3325 #endif
3326  }
3327 }
3328 
3330 {
3331  if (!mOverlay)
3332  mOverlay = std::make_unique<QuickPlayIndicatorOverlay>(mProject);
3333 
3334  return mOverlay.get();
3335 }
3336 
3338 {
3339  ShowOrHideQuickPlayIndicator(true, repaint_all);
3340 }
3341 
3343 {
3344  ShowOrHideQuickPlayIndicator(false, repaint_all);
3345 }
3346 
3347 // Draws the vertical line and green triangle indicating the Quick Play cursor position.
3348 void AdornedRulerPanel::ShowOrHideQuickPlayIndicator(bool show, bool repaint_all)
3349 {
3350  double latestEnd = std::max(mTracks->GetEndTime(), mProject->GetSel1());
3351  if (!show || (mQuickPlayPos >= latestEnd)) {
3352  GetOverlay()->Update(-1);
3353  }
3354  else {
3355  const int x = Time2Pos(mQuickPlayPos);
3356  bool previewScrub =
3359  GetOverlay()->Update(x, mIsSnapped, previewScrub);
3360  }
3361 
3362  mProject->GetTrackPanel()->DrawOverlays(repaint_all);
3363  DrawOverlays(repaint_all);
3364 }
3365 
3366 void AdornedRulerPanel::SetPlayRegion(double playRegionStart,
3367  double playRegionEnd)
3368 {
3369  // This is called by AudacityProject to make the play region follow
3370  // the current selection. But while the user is selecting a play region
3371  // with the mouse directly in the ruler, changes from outside are blocked.
3372  if (mMouseEventState != mesNone)
3373  return;
3374 
3375  mPlayRegionStart = playRegionStart;
3376  mPlayRegionEnd = playRegionEnd;
3377 
3378  Refresh();
3379 }
3380 
3382 {
3384  ctb->StopPlaying();
3385 
3386  mPlayRegionStart = -1;
3387  mPlayRegionEnd = -1;
3388 
3390 
3391  Refresh();
3392 }
3393 
3394 void AdornedRulerPanel::GetPlayRegion(double* playRegionStart,
3395  double* playRegionEnd)
3396 {
3397  if (mPlayRegionStart >= 0 && mPlayRegionEnd >= 0 &&
3399  {
3400  // swap values to make sure end > start
3401  *playRegionStart = mPlayRegionEnd;
3402  *playRegionEnd = mPlayRegionStart;
3403  } else
3404  {
3405  *playRegionStart = mPlayRegionStart;
3406  *playRegionEnd = mPlayRegionEnd;
3407  }
3408 }
3409 
3410 void AdornedRulerPanel::GetMaxSize(wxCoord *width, wxCoord *height)
3411 {
3412  mRuler.GetMaxSize(width, height);
3413 }
3414 
3415 bool AdornedRulerPanel::s_AcceptsFocus{ false };
3416 
3418  s_AcceptsFocus = true;
3419  return TempAllowFocus{ &s_AcceptsFocus };
3420 }
3421 
3423 {
3424  auto temp = TemporarilyAllowFocus();
3425  SetFocus();
3426 }
bool mLabelEdges
Definition: Ruler.h:229
RulerFormat
Definition: Ruler.h:35
void ShowContextMenu(MenuChoice choice, const wxPoint *pPosition)
Definition: Ruler.cpp:3142
void GetMaxSize(wxCoord *width, wxCoord *height)
Definition: Ruler.cpp:3410
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:3348
void SetLog(bool log)
Definition: Ruler.cpp:199
void Draw(wxDC &dc)
Definition: Ruler.cpp:1323
bool mIsDragging
Definition: Ruler.h:483
A ToolBar that has the main Transport buttons.
bool mQuickPlayEnabled
Definition: Ruler.h:469
AudacityPrefs * gPrefs
Definition: Prefs.cpp:73
void OnSize(wxSizeEvent &event)
Definition: BackedPanel.cpp:70
double mLeftDownClick
Definition: Ruler.h:481
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:1291
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:2215
bool mPlayRegionDragsSelection
Definition: Ruler.h:467
#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:2588
static const wxChar * ScrollingPreferenceKey()
bool mIsSnapped
Definition: Ruler.h:440
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:2158
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:430
wxCursor mCursorHand
Definition: Ruler.h:419
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:423
void UpdateRects()
Definition: Ruler.cpp:2322
std::unique_ptr< SnapManager > mSnapManager
Definition: Ruler.h:439
bool mbMinor
Definition: Ruler.h:234
std::unique_ptr< wxFont > mMinorFont
Definition: Ruler.h:184
double mOldPlayRegionStart
Definition: Ruler.h:445
void OnToggleScrubRuler()
Definition: Ruler.cpp:2890
void OnLockPlayRegion(const CommandContext &context)
Definition: Menus.cpp:9011
void reinit(Integral count, bool initialize=false)
Definition: MemoryX.h:117
ArrayOf< Label > mMajorLabels
Definition: Ruler.h:212
QuickPlayIndicatorOverlay * GetOverlay()
Definition: Ruler.cpp:3329
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:3185
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:727
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:9025
void HandleSnapping()
Definition: Ruler.cpp:3044
void SetNumberScale(const NumberScale *pScale)
Definition: Ruler.cpp:317
void DoDrawMarks(wxDC *dc, bool)
Definition: Ruler.cpp:3200
bool mValid
Definition: Ruler.h:199
void StopPlaying(bool stopStream=true)
void OnContextMenu(wxContextMenuEvent &WXUNUSED(event))
Definition: Ruler.cpp:2906
void OnAutoScroll(wxCommandEvent &evt)
Definition: Ruler.cpp:3065
void OnMouseEvents(wxMouseEvent &evt)
Definition: Ruler.cpp:2393
void OnLockPlayRegion(const CommandContext &context)
void ShowScrubMenu(const wxPoint &pos)
Definition: Ruler.cpp:3012
void DrawSelection()
Definition: Ruler.cpp:3212
ViewInfo *const mViewInfo
Definition: Ruler.h:425
void HandleQPRelease(wxMouseEvent &event)
Definition: Ruler.cpp:2723
wxRect mOuter
Definition: Ruler.h:428
int GetRulerHeight()
Definition: Ruler.h:333
int teBmps
Definition: Theme.h:28
void OnSyncSelToQuickPlay(wxCommandEvent &evt)
Definition: Ruler.cpp:3031
wxSize ImageSize(int iIndex)
Definition: Theme.cpp:1257
std::unique_ptr< QuickPlayIndicatorOverlay > mOverlay
Definition: Ruler.h:485
bool RemoveOverlay(Overlay *pOverlay)
void OnCapture(wxCommandEvent &evt)
Definition: Ruler.cpp:2250
void DoDrawBackground(wxDC *dc)
Definition: Ruler.cpp:3169
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:765
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:2966
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:3417
wxRect mRect
Definition: Ruler.h:172
void Invalidate()
Definition: Ruler.cpp:371
#define safenew
Definition: Audacity.h:230
MouseEventState mMouseEventState
Definition: Ruler.h:479
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:3342
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:443
#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:396
void GetTracksUsableArea(int *width, int *height) const
Definition: TrackPanel.cpp:421
double mQuickPlayPos
Definition: Ruler.h:437
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:2629
void StartQPPlay(bool looped, bool cutPreview)
Definition: Ruler.cpp:2782
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:2381
void DoDrawIndicator(wxDC *dc, wxCoord xx, bool playing, int width, bool scrub, bool seek)
Definition: Ruler.cpp:3247
int format
Definition: ExportPCM.cpp:56
double mMinor
Definition: Ruler.h:191
bool IsScrubbing() const
Definition: Scrubbing.cpp:575
bool mShowScrubbing
Definition: Ruler.h:489
Defines a selected portion of a project.
void SetPlayRegion(double playRegionStart, double playRegionEnd)
Definition: Ruler.cpp:3366
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:3381
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:114
QuickPlayIndicatorOverlay & mPartner
Definition: Ruler.cpp:1767
int mLeftOffset
Definition: Ruler.h:241
AdornedRulerPanel * GetRulerPanel()
Definition: Project.cpp:1432
int IndicatorHeightForWidth(int width)
Definition: Ruler.cpp:1718
~Ruler()
Definition: Ruler.cpp:178
void ShowQuickPlayIndicator(bool repaint_all=false)
Definition: Ruler.cpp:3337
StatusChoice mPrevZone
Definition: Ruler.h:487
int mBottom
Definition: Ruler.h:179
void OnToggleQuickPlay(wxCommandEvent &evt)
Definition: Ruler.cpp:3023
void SetPanelSize()
Definition: Ruler.cpp:2898
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:1979
Fundamental data object of Audacity, placed in the TrackPanel. Classes derived form it include the Wa...
Definition: Track.h:101
bool Seeks() const
Definition: Scrubbing.cpp:628
TrackList * mTracks
Definition: Ruler.h:426
void OnCaptureLost(wxMouseCaptureLostEvent &evt)
Definition: Ruler.cpp:2944
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
void OnPaint(wxPaintEvent &evt)
Definition: Ruler.cpp:2271
void SetUnits(const wxString &units)
Definition: Ruler.cpp:210
int min(int a, int b)
wxRect mScrubZone
Definition: Ruler.h:429
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:2373
bool mTwoTone
Definition: Ruler.h:239
void TP_DisplayStatusMessage(const wxString &msg) override
Definition: Project.cpp:5349
QuickPlayIndicatorOverlay(AudacityProject *project)
Definition: Ruler.cpp:1875
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:398
void OnTogglePinnedState(wxCommandEvent &event)
Definition: Ruler.cpp:2938
wxDC & GetBackingDCForRepaint()
Definition: BackedPanel.cpp:35
#define PLAY_REGION_GLOBAL_OFFSET_Y
Definition: Ruler.cpp:112
void UpdatePrefs()
Definition: Project.cpp:1337
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:442
void AddOverlay(Overlay *pOverlay)
std::pair< wxRect, bool > DoGetRectangle(wxSize size) override
Definition: Ruler.cpp:1897
IMPLEMENT_CLASS(ControlToolBar, ToolBar)
int mLength
Definition: Ruler.h:180
void UpdateStatusBarAndTooltips(StatusChoice choice)
Definition: Ruler.cpp:2845
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:798
_("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:424
double mQuickPlayPosUnsnapped
Definition: Ruler.h:436
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:3086
#define PLAY_REGION_TRIANGLE_SIZE
Definition: Ruler.cpp:109
ControlToolBar * GetControlToolBar()
Definition: Project.cpp:4996
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:2857
bool mTimelineToolTip
Definition: Ruler.h:468
double value
Definition: Ruler.h:203
static bool GetPinnedHeadPreference()
double mHiddenMax
Definition: Ruler.h:188
wxCursor mCursorSizeWE
Definition: Ruler.h:420
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:1908
int mNumMinorMinor
Definition: Ruler.h:215
void OnLockPlayRegion(wxCommandEvent &evt)
Definition: Ruler.cpp:3076
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:2968
std::pair< double, double > Range
Definition: Ruler.h:250
void OnTimelineToolTips(wxCommandEvent &evt)
Definition: Ruler.cpp:3055
void OnSize(wxSizeEvent &evt)
Definition: Ruler.cpp:2309
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:2884
#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:2953
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:448
int GetLeftOffset() const
Definition: TrackPanel.h:287
END_EVENT_TABLE()
void ReCreateButtons()
Definition: Ruler.cpp:2098
double mOldPlayRegionEnd
Definition: Ruler.h:446
double GetSel1() const
Definition: Project.h:205
void OfflimitsPixels(int start, int end)
Definition: Ruler.cpp:333
void DragSelection()
Definition: Ruler.cpp:3038
bool IsPlayRegionLocked()
Definition: Project.h:213
virtual ~QuickPlayIndicatorOverlay()
Definition: Ruler.cpp:1882
static void SetButtonToolTip(AButton &button, const TranslatedInternalString commands[], size_t nCommands)
Definition: ToolBar.cpp:823
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:2366
wxString text
Definition: Ruler.h:206
void SetFocusFromKbd() override
Definition: Ruler.cpp:3422
void GetPlayRegion(double *playRegionStart, double *playRegionEnd)
Definition: Ruler.cpp:3394
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:2911
double mLeftDownClickUnsnapped
Definition: Ruler.h:480
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:1889
void SetLeftOffset(int offset)
Definition: Ruler.cpp:3239
void OnSize(wxSizeEvent &evt)
Definition: Ruler.cpp:1678
void SetCustomMode(bool value)
Definition: Ruler.cpp:1565
void DoDrawSelection(wxDC *dc)
Definition: Ruler.cpp:3217
Makes temporary drawing context changes that you back out of, RAII style.
Definition: AColor.h:42
double mPlayRegionEnd
Definition: Ruler.h:444
void UpdatePrefs()
Definition: Ruler.cpp:2074
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:418
void UpdateStatus()
Definition: AButton.cpp:421