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