Audacity  2.2.2
NumericTextCtrl.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  NumericTextCtrl.cpp
6 
7  Dominic Mazzoni
8 
9 
10 ********************************************************************//****************************************************************//****************************************************************//****************************************************************/
168 #include "../Audacity.h"
169 #include "NumericTextCtrl.h"
170 #include "audacity/Types.h"
171 #include "../Theme.h"
172 #include "../AllThemeResources.h"
173 #include "../AColor.h"
174 #include "../Project.h"
175 #include "../TranslatableStringArray.h"
176 
177 #include <algorithm>
178 #include <math.h>
179 #include <limits>
180 
181 #include <wx/wx.h>
182 #include <wx/dcmemory.h>
183 #include <wx/font.h>
184 #include <wx/intl.h>
185 #include <wx/menu.h>
186 #include <wx/sizer.h>
187 #include <wx/stattext.h>
188 #include <wx/tooltip.h>
189 #include <wx/toplevel.h>
190 
191 //
192 // ----------------------------------------------------------------------------
193 // BuiltinFormatString Struct
194 // ----------------------------------------------------------------------------
195 //
200 {
201  wxString name;
202  wxString formatStr;
203 };
204 
205 //
206 // ----------------------------------------------------------------------------
207 // UntranslatedBuiltinFormatString Struct
208 // ----------------------------------------------------------------------------
209 //
214 {
215  wxString name;
216  wxString formatStr;
217 
219  {
220  return { wxGetTranslation( name ), wxGetTranslation( formatStr ) };
221  }
222 };
223 
224 //
225 // ----------------------------------------------------------------------------
226 // NumericField Class
227 // ----------------------------------------------------------------------------
228 //
230 {
231 public:
232  NumericField(bool _frac, int _base, int _range, bool _zeropad)
233  {
234  frac = _frac;
235  base = _base;
236  range = _range;
237  zeropad = _zeropad;
238  digits = 0;
239  }
240  NumericField( const NumericField & ) = default;
241  NumericField &operator = ( const NumericField & ) = default;
242  //NumericField( NumericField && ) = default;
243  //NumericField &operator = ( NumericField && ) = default;
245  {
246  if (range > 1)
247  digits = (int)ceil(log10(range-1.0));
248  else
249  digits = 5; // hack: default
250  if (zeropad && range>1)
251  formatStr.Printf(wxT("%%0%dd"), digits); // ex. "%03d" if digits is 3
252  else {
253  formatStr.Printf(wxT("%%0%dd"), digits);
254  }
255  }
256  bool frac; // is it a fractional field
257  int base; // divide by this (multiply, after decimal point)
258  int range; // then take modulo this
259  int digits;
260  int pos; // Index of this field in the ValueString
261  int fieldX; // x-position of the field on-screen
262  int fieldW; // width of the field on-screen
263  int labelX; // x-position of the label on-screen
264  bool zeropad;
265  wxString label;
266  wxString formatStr;
267  wxString str;
268 };
269 
270 //
271 // ----------------------------------------------------------------------------
272 // DigitInfo Class
273 // ----------------------------------------------------------------------------
274 //
276 {
277 public:
278  DigitInfo(int _field, int _index, int _pos, wxRect _box)
279  {
280  field = _field;
281  index = _index;
282  pos = _pos;
283  digitBox = _box;
284  }
285  int field; // Which field
286  int index; // Index of this digit within the field
287  int pos; // Position in the ValueString
288  wxRect digitBox;
289 };
290 
291 namespace {
292 
293 const std::vector<BuiltinFormatString> &TimeConverterFormats() {
294 
299 static const UntranslatedBuiltinFormatString TimeConverterFormats_[] = {
300  {
301  /* i18n-hint: Name of time display format that shows time in seconds */
302  XO("seconds"),
303  /* i18n-hint: Format string for displaying time in seconds. Change the comma
304  * in the middle to the 1000s separator for your locale, and the 'seconds'
305  * on the end to the word for seconds. Don't change the numbers. */
306  XO("01000,01000 seconds")
307  },
308 
309  {
310  /* i18n-hint: Name of time display format that shows time in hours, minutes
311  * and seconds */
312  XO("hh:mm:ss"),
313  /* i18n-hint: Format string for displaying time in hours, minutes and
314  * seconds. Change the 'h' to the abbreviation for hours, 'm' to the
315  * abbreviation for minutes and 's' to the abbreviation for seconds. Don't
316  * change the numbers unless there aren't 60 seconds in a minute in your
317  * locale */
318  XO("0100 h 060 m 060 s")
319  },
320 
321  {
322  /* i18n-hint: Name of time display format that shows time in days, hours,
323  * minutes and seconds */
324  XO("dd:hh:mm:ss"),
325  /* i18n-hint: Format string for displaying time in days, hours, minutes and
326  * seconds. Change the 'days' to the word for days, 'h' to the abbreviation
327  * for hours, 'm' to the abbreviation for minutes and 's' to the
328  * abbreviation for seconds. Don't change the numbers unless there aren't
329  * 24 hours in a day in your locale */
330  XO("0100 days 024 h 060 m 060 s")
331  },
332 
333  {
334  /* i18n-hint: Name of time display format that shows time in hours,
335  * minutes, seconds and hundredths of a second (1/100 second) */
336  XO("hh:mm:ss + hundredths"),
337  /* i18n-hint: Format string for displaying time in hours, minutes, seconds
338  * and hundredths of a second. Change the 'h' to the abbreviation for hours,
339  * 'm' to the abbreviation for minutes and 's' to the abbreviation for seconds
340  * (the hundredths are shown as decimal seconds) . Don't change the numbers
341  * unless there aren't 60 minutes in an hour in your locale */
342  XO("0100 h 060 m 060.0100 s")
343  },
344 
345  {
346  /* i18n-hint: Name of time display format that shows time in hours,
347  * minutes, seconds and milliseconds (1/1000 second) */
348  XO("hh:mm:ss + milliseconds"),
349  /* i18n-hint: Format string for displaying time in hours, minutes, seconds
350  * and milliseconds. Change the 'h' to the abbreviation for hours, 'm' to the
351  * abbreviation for minutes and 's' to the abbreviation for seconds (the
352  * milliseconds are shown as decimal seconds) . Don't change the numbers
353  * unless there aren't 60 minutes in an hour in your locale */
354  XO("0100 h 060 m 060.01000 s")
355  },
356 
357  {
358  /* i18n-hint: Name of time display format that shows time in hours,
359  * minutes, seconds and samples (at the current project sample rate) */
360  XO("hh:mm:ss + samples"),
361  /* i18n-hint: Format string for displaying time in hours, minutes, seconds
362  * and samples. Change the 'h' to the abbreviation for hours, 'm' to the
363  * abbreviation for minutes, 's' to the abbreviation for seconds and
364  * translate samples . Don't change the numbers
365  * unless there aren't 60 seconds in a minute in your locale */
366  XO("0100 h 060 m 060 s+.# samples")
367  },
368 
369  {
370  /* i18n-hint: Name of time display format that shows time in samples (at the
371  * current project sample rate). For example the number of a sample at 1
372  * second into a recording at 44.1KHz would be 44,100.
373  */
374  XO("samples"),
375  /* i18n-hint: Format string for displaying time in samples (lots of samples).
376  * Change the ',' to the 1000s separator for your locale, and translate
377  * samples. If 1000s aren't a base multiple for your number system, then you
378  * can change the numbers to an appropriate one, and put a 0 on the front */
379  XO("01000,01000,01000 samples|#")
380  },
381 
382  {
383  /* i18n-hint: Name of time display format that shows time in hours, minutes,
384  * seconds and frames at 24 frames per second (commonly used for films) */
385  XO("hh:mm:ss + film frames (24 fps)"),
386  /* i18n-hint: Format string for displaying time in hours, minutes, seconds
387  * and frames at 24 frames per second. Change the 'h' to the abbreviation
388  * for hours, 'm' to the abbreviation for minutes, 's' to the abbreviation
389  * for seconds and translate 'frames' . Don't change the numbers
390  * unless there aren't 60 seconds in a minute in your locale */
391  XO("0100 h 060 m 060 s+.24 frames")
392  },
393 
394  {
395  /* i18n-hint: Name of time display format that shows time in frames (lots of
396  * frames) at 24 frames per second (commonly used for films) */
397  XO("film frames (24 fps)"),
398  /* i18n-hint: Format string for displaying time in frames at 24 frames per
399  * second. Change the comma
400  * in the middle to the 1000s separator for your locale,
401  * translate 'frames' and leave the rest alone */
402  XO("01000,01000 frames|24")
403  },
404 
405  {
406  /* i18n-hint: Name of time display format that shows time in hours, minutes,
407  * seconds and frames at NTSC TV drop-frame rate (used for American /
408  * Japanese TV, and very odd) */
409  XO("hh:mm:ss + NTSC drop frames"),
410  /* i18n-hint: Format string for displaying time in hours, minutes, seconds
411  * and frames with NTSC drop frames. Change the 'h' to the abbreviation
412  * for hours, 'm' to the abbreviation for minutes, 's' to the abbreviation
413  * for seconds and translate 'frames'. Leave the |N alone, it's important! */
414  XO("0100 h 060 m 060 s+.30 frames|N")
415  },
416 
417  {
418  /* i18n-hint: Name of time display format that shows time in hours, minutes,
419  * seconds and frames at NTSC TV non-drop-frame rate (used for American /
420  * Japanese TV, and doesn't quite match wall time */
421  XO("hh:mm:ss + NTSC non-drop frames"),
422  /* i18n-hint: Format string for displaying time in hours, minutes, seconds
423  * and frames with NTSC drop frames. Change the 'h' to the abbreviation
424  * for hours, 'm' to the abbreviation for minutes, 's' to the abbreviation
425  * for seconds and translate 'frames'. Leave the | .999000999 alone,
426  * the whole things really is slightly off-speed! */
427  XO("0100 h 060 m 060 s+.030 frames| .999000999")
428  },
429 
430  {
431  /* i18n-hint: Name of time display format that shows time in frames at NTSC
432  * TV frame rate (used for American / Japanese TV */
433  XO("NTSC frames"),
434  /* i18n-hint: Format string for displaying time in frames with NTSC frames.
435  * Change the comma
436  * in the middle to the 1000s separator for your locale,
437  * translate 'frames' and leave the rest alone. That really is the frame
438  * rate! */
439  XO("01000,01000 frames|29.97002997")
440  },
441 
442  {
443  /* i18n-hint: Name of time display format that shows time in hours, minutes,
444  * seconds and frames at PAL TV frame rate (used for European TV) */
445  XO("hh:mm:ss + PAL frames (25 fps)"),
446  /* i18n-hint: Format string for displaying time in hours, minutes, seconds
447  * and frames with PAL TV frames. Change the 'h' to the abbreviation
448  * for hours, 'm' to the abbreviation for minutes, 's' to the abbreviation
449  * for seconds and translate 'frames'. Nice simple time code! */
450  XO("0100 h 060 m 060 s+.25 frames")
451  },
452 
453  {
454  /* i18n-hint: Name of time display format that shows time in frames at PAL
455  * TV frame rate (used for European TV) */
456  XO("PAL frames (25 fps)"),
457  /* i18n-hint: Format string for displaying time in frames with NTSC frames.
458  * Change the comma
459  * in the middle to the 1000s separator for your locale,
460  * translate 'frames' and leave the rest alone. */
461  XO("01000,01000 frames|25")
462  },
463 
464  {
465  /* i18n-hint: Name of time display format that shows time in hours, minutes,
466  * seconds and frames at CD Audio frame rate (75 frames per second) */
467  XO("hh:mm:ss + CDDA frames (75 fps)"),
468  /* i18n-hint: Format string for displaying time in hours, minutes, seconds
469  * and frames with CD Audio frames. Change the 'h' to the abbreviation
470  * for hours, 'm' to the abbreviation for minutes, 's' to the abbreviation
471  * for seconds and translate 'frames'. */
472  XO("0100 h 060 m 060 s+.75 frames")
473  },
474 
475  {
476  /* i18n-hint: Name of time display format that shows time in frames at CD
477  * Audio frame rate (75 frames per second) */
478  XO("CDDA frames (75 fps)"),
479  /* i18n-hint: Format string for displaying time in frames with CD Audio
480  * frames. Change the comma
481  * in the middle to the 1000s separator for your locale,
482  * translate 'frames' and leave the rest alone */
483  XO("01000,01000 frames|75")
484  },
485 };
486 
487 class FormatsArray final
488  : public TranslatableArray< std::vector< BuiltinFormatString > >
489 {
490  void Populate() override
491  {
492  for (auto &format : TimeConverterFormats_)
493  mContents.push_back( format.Translate() );
494  }
495 };
496 
497 static FormatsArray theArray;
498 return theArray.Get();
499 
500 } // end function
501 
502 const std::vector<BuiltinFormatString> &FrequencyConverterFormats() {
503 
508 static const UntranslatedBuiltinFormatString FrequencyConverterFormats_[] = {
509  /* i18n-hint: Name of display format that shows frequency in hertz */
510  {
511  XO("Hz"),
512  /* i18n-hint: Format string for displaying frequency in hertz. Change
513  * the decimal point for your locale. Don't change the numbers. */
514  XO("0100000.0100 Hz")
515  },
516 
517  {
518  XO("kHz"),
519  /* i18n-hint: Format string for displaying frequency in kilohertz. Change
520  * the decimal point for your locale. Don't change the numbers. */
521  XO("01000.01000 kHz|0.001")
522  },
523 };
524 
525 class FormatsArray final
526  : public TranslatableArray< std::vector< BuiltinFormatString > >
527 {
528  void Populate() override
529  {
530  for (auto &format : FrequencyConverterFormats_)
531  mContents.push_back( format.Translate() );
532  }
533 };
534 
535 static FormatsArray theArray;
536 return theArray.Get();
537 
538 } // end function
539 
540 const std::vector<BuiltinFormatString> &BandwidthConverterFormats() {
541 
546 static const UntranslatedBuiltinFormatString BandwidthConverterFormats_[] = {
547  {
548  /* i18n-hint: Name of display format that shows log of frequency
549  * in octaves */
550  XO("octaves"),
551  /* i18n-hint: Format string for displaying log of frequency in octaves.
552  * Change the decimal points for your locale. Don't change the numbers. */
553  // Scale factor is 1 / ln (2)
554  XO("100.01000 octaves|1.442695041")
555  },
556 
557  {
558  /* i18n-hint: Name of display format that shows log of frequency
559  * in semitones and cents */
560  XO("semitones + cents"),
561  /* i18n-hint: Format string for displaying log of frequency in semitones
562  * and cents.
563  * Change the decimal points for your locale. Don't change the numbers. */
564  // Scale factor is 12 / ln (2)
565  XO("1000 semitones .0100 cents|17.312340491")
566  },
567 
568  {
569  /* i18n-hint: Name of display format that shows log of frequency
570  * in decades */
571  XO("decades"),
572  /* i18n-hint: Format string for displaying log of frequency in decades.
573  * Change the decimal points for your locale. Don't change the numbers. */
574  // Scale factor is 1 / ln (10)
575  XO("10.01000 decades|0.434294482")
576  },
577 };
578 
579 
580 class FormatsArray final
581  : public TranslatableArray< std::vector< BuiltinFormatString > >
582 {
583  void Populate() override
584  {
585  for (auto &format : BandwidthConverterFormats_)
586  mContents.push_back( format.Translate() );
587  }
588 };
589 
590 static FormatsArray theArray;
591 return theArray.Get();
592 
593 } // end function
594 
595  const std::vector<BuiltinFormatString> &ChooseBuiltinFormatStrings
597  {
598  switch (type) {
599  default:
601  return TimeConverterFormats();
603  return FrequencyConverterFormats();
605  return BandwidthConverterFormats();
606  }
607  }
608 }
609 
610 //
611 // ----------------------------------------------------------------------------
612 // NumericConverter Class
613 // ----------------------------------------------------------------------------
614 //
616  const wxString & formatName,
617  double value,
618  double sampleRate)
619  : mBuiltinFormatStrings( ChooseBuiltinFormatStrings( type ) )
620 {
621  ResetMinValue();
622  ResetMaxValue();
623  mInvalidValue = -1.0;
624 
625  mDefaultNdx = 0;
626 
627  mType = type;
628 
629  if (type == NumericConverter::TIME )
630  mDefaultNdx = 4; // Default to "hh:mm:ss + milliseconds".
631 
633 
634  mPrefix = wxT("");
635  mValueTemplate = wxT("");
636  mValueMask = wxT("");
637  mValueString = wxT("");
638 
639  mScalingFactor = 1.0f;
640  mSampleRate = 1.0f;
641  mNtscDrop = false;
642 
643  mFocusedDigit = 0;
644 
645  mValue = value; // used in SetSampleRate, reassigned later
646 
647  SetSampleRate(sampleRate);
648  SetFormatName(formatName);
649  SetValue(value); // mValue got overridden to -1 in ControlsToValue(), reassign
650 }
651 
653 {
654  mPrefix = wxT("");
655  mFields.clear();
656  mDigits.clear();
657  mScalingFactor = 1.0;
658 
659  bool inFrac = false;
660  int fracMult = 1;
661  int numWholeFields = 0;
662  int numFracFields = 0;
663  wxString numStr;
664  wxString delimStr;
665  unsigned int i;
666 
667  mNtscDrop = false;
668  for(i=0; i<format.Length(); i++) {
669  bool handleDelim = false;
670  bool handleNum = false;
671 
672  if (format[i] == '|') {
673  wxString remainder = format.Right(format.Length() - i - 1);
674 
675  if (remainder == wxT("#"))
677  else if (remainder == wxT("N")) {
678  mNtscDrop = true;
679  }
680  else
681  remainder.ToDouble(&mScalingFactor);
682  i = format.Length()-1; // force break out of loop
683  if (delimStr != wxT(""))
684  handleDelim = true;
685  if (numStr != wxT(""))
686  handleNum = true;
687  }
688  else if ((format[i] >= '0' && format[i] <='9') ||
689  format[i] == wxT('*') || format[i] == wxT('#')) {
690  numStr += format[i];
691  if (delimStr != wxT(""))
692  handleDelim = true;
693  }
694  else {
695  delimStr += format[i];
696  if (numStr != wxT(""))
697  handleNum = true;
698  }
699 
700  if (i == format.Length() - 1) {
701  if (numStr != wxT(""))
702  handleNum = true;
703  if (delimStr != wxT(""))
704  handleDelim = true;
705  }
706 
707  if (handleNum) {
708  bool zeropad = false;
709  long range = 0;
710 
711  if (numStr.Right(1) == wxT("#"))
712  range = (long int)mSampleRate;
713  else if (numStr.Right(1) != wxT("*")) {
714  numStr.ToLong(&range);
715  }
716  if (numStr.GetChar(0)=='0' && numStr.Length()>1)
717  zeropad = true;
718 
719  // Hack: always zeropad
720  zeropad = true;
721 
722  if (inFrac) {
723  int base = fracMult * range;
724  mFields.push_back(NumericField(inFrac, base, range, zeropad));
725  fracMult *= range;
726  numFracFields++;
727  }
728  else {
729  unsigned int j;
730  for(j=0; j<mFields.size(); j++)
731  mFields[j].base *= range;
732  mFields.push_back(NumericField(inFrac, 1, range, zeropad));
733  numWholeFields++;
734  }
735  numStr = wxT("");
736  }
737 
738  if (handleDelim) {
739  bool goToFrac = false;
740 
741  if (!inFrac && delimStr[delimStr.Length()-1]=='.') {
742  goToFrac = true;
743  if (delimStr.Length() > 1)
744  delimStr = delimStr.BeforeLast('.');
745  }
746 
747  if (inFrac) {
748  if (numFracFields == 0) {
749  // Should never happen
750  return;
751  }
752  if (handleNum && numFracFields > 1)
753  mFields[mFields.size()-2].label = delimStr;
754  else
755  mFields[mFields.size()-1].label = delimStr;
756  }
757  else {
758  if (numWholeFields == 0)
759  mPrefix = delimStr;
760  else {
761  mFields[numWholeFields-1].label = delimStr;
762  }
763  }
764 
765  if (goToFrac)
766  inFrac = true;
767  delimStr = wxT("");
768  }
769  }
770 
771  for(i = 0; i < mFields.size(); i++) {
772  mFields[i].CreateDigitFormatStr();
773  }
774 
775  int pos = 0;
776  int j;
777  mValueMask = wxT("");
778  mValueTemplate = wxT("");
779 
781  for(j=0; j<(int)mPrefix.Length(); j++)
782  mValueMask += wxT(".");
783  pos += mPrefix.Length();
784 
785  for(i = 0; i < mFields.size(); i++) {
786  mFields[i].pos = pos;
787 
788  for(j=0; j<mFields[i].digits; j++) {
789  mDigits.push_back(DigitInfo(i, j, pos, wxRect()));
790  mValueTemplate += wxT("0");
791  mValueMask += wxT("0");
792  pos++;
793  }
794 
795  pos += mFields[i].label.Length();
796  mValueTemplate += mFields[i].label;
797  for(j=0; j<(int)mFields[i].label.Length(); j++)
798  mValueMask += wxT(".");
799  }
800 }
801 
803 {
804  unsigned int i;
805 
806  wxPrintf("%s", (const char *)mPrefix.mb_str());
807 
808  for(i = 0; i < mFields.size(); i++) {
809  if (mFields[i].frac) {
810  wxPrintf("(t * %d) %% %d '%s' ",
811  mFields[i].base,
812  mFields[i].range,
813  (const char *)mFields[i].label.mb_str());
814 
815  }
816  else {
817  wxPrintf("(t / %d) %% %d '%s' ",
818  mFields[i].base,
819  mFields[i].range,
820  (const char *)mFields[i].label.mb_str());
821  }
822  }
823 
824  wxPrintf("\n");
825 }
826 
828 {
829 }
830 
832 {
834 }
835 
836 void NumericConverter::ValueToControls(double rawValue, bool nearest /* = true */)
837 {
838  //rawValue = 4.9995f; Only for testing!
839  if (mType == TIME)
840  rawValue =
841  floor(rawValue * mSampleRate + (nearest ? 0.5f : 0.0f))
842  / mSampleRate; // put on a sample
843  double theValue =
844  rawValue * mScalingFactor
845  // PRL: what WAS this .000001 for? Nobody could explain.
846  // + .000001
847  ;
848  sampleCount t_int;
849  bool round = true;
850  // We round on the last field. If we have a fractional field we round using it.
851  // Otherwise we round to nearest integer.
852  for(unsigned int i = 0; i < mFields.size(); i++) {
853  if (mFields[i].frac)
854  round = false;
855  }
856  if (theValue < 0)
857  t_int = -1;
858  else if(round)
859  t_int = sampleCount(theValue + (nearest ? 0.5f : 0.0f));
860  else
861  {
862  wxASSERT( mFields.back().frac );
863  theValue += (nearest ? 0.5f : 0.0f) / mFields.back().base;
864  t_int = sampleCount(theValue);
865  }
866  double t_frac;
867  if (theValue < 0)
868  t_frac = -1;
869  else
870  t_frac = (theValue - t_int.as_double() );
871  unsigned int i;
872  int tenMins;
873  int mins;
874  int addMins;
875  int secs;
876  int frames;
877 
879 
880  if(mNtscDrop && theValue >= 0) {
881  frames = (int)(theValue*30./1.001 + (nearest ? 0.5f : 0.0f));
882  tenMins = frames/17982;
883  frames -= tenMins*17982;
884  mins = tenMins * 10;
885  if(frames >= 1800) {
886  frames -= 1800;
887  mins++;
888  addMins = frames/1798;
889  frames -= addMins*1798;
890  mins += addMins;
891  secs = frames/30;
892  frames -= secs*30;
893  frames += 2;
894  if( frames >= 30 ) {
895  secs++;
896  frames -= 30;
897  }
898  }
899  else {
900  secs = frames/30;
901  frames -= secs*30;
902  }
903  t_int = mins * 60 + secs;
904  t_frac = frames / 30.;
905  }
906 
907  for(i = 0; i < mFields.size(); i++) {
908  int value = -1;
909 
910  if (mFields[i].frac) {
911  // JKC: This old code looks bogus to me.
912  // The rounding is not propogating to earlier fields in the frac case.
913  //value = (int)(t_frac * mFields[i].base + 0.5); // +0.5 as rounding required
914  // I did the rounding earlier.
915  if (t_frac >= 0)
916  value = (int)(t_frac * mFields[i].base);
917  // JKC: TODO: Find out what the range is supposed to do.
918  // It looks bogus too.
919  //if (mFields[i].range > 0)
920  // value = value % mFields[i].range;
921  }
922  else {
923  if (t_int >= 0) {
924  // UNSAFE_SAMPLE_COUNT_TRUNCATION
925  // truncation danger!
926  value = (t_int.as_long_long() / mFields[i].base);
927  if (mFields[i].range > 0)
928  value = value % mFields[i].range;
929  }
930  }
931 
932  wxString field;
933  if (value < 0) {
934  for (int ii = 0; ii < mFields[i].digits; ++ii)
935  field += wxT("-");
936  }
937  else
938  field = wxString::Format(mFields[i].formatStr, value);
939  mValueString += field;
940  mValueString += mFields[i].label;
941  }
942 }
943 
945 {
946  unsigned int i;
947  double t = 0.0;
948 
949  if (mFields.size() > 0 &&
950  mValueString.Mid(mFields[0].pos, 1) == wxChar('-')) {
952  return;
953  }
954 
955  for(i = 0; i < mFields.size(); i++) {
956  long val;
957  mFields[i].str = mValueString.Mid(mFields[i].pos,
958  mFields[i].digits);
959  mFields[i].str.ToLong(&val);
960  if (mFields[i].frac)
961  t += (val / (double)mFields[i].base);
962  else
963  t += (val * (double)mFields[i].base);
964  }
965 
966  t /= mScalingFactor;
967  if(mNtscDrop) {
968  int t_int = (int)(t + .000000001);
969  double t_frac = (t - t_int);
970  int tenMins = t_int/600;
971  double frames = tenMins*17982;
972  t_int -= tenMins*600;
973  int mins = t_int/60;
974  int addMins = 0;
975  if( mins > 0 ) {
976  frames += 1800;
977  addMins = mins - 1;
978  }
979  frames += addMins * 1798;
980  t_int -= mins*60;
981  if( mins == 0 ) //first min of a block of 10, don't drop frames 0 and 1
982  frames += t_int * 30 + t_frac*30.;
983  else { //drop frames 0 and 1 of first seconds of these minutes
984  if( t_int > 0 )
985  frames += 28 + (t_int-1)*30 + t_frac*30.;
986  else
987  frames += t_frac*30. -2.;
988  }
989  t = frames * 1.001 / 30.;
990  }
991 
992  mValue = std::max(mMinValue, std::min(mMaxValue, t));
993 }
994 
995 void NumericConverter::SetFormatName(const wxString & formatName)
996 {
997  SetFormatString(GetBuiltinFormat(formatName));
998 }
999 
1000 void NumericConverter::SetFormatString(const wxString & formatString)
1001 {
1002  mFormatString = formatString;
1004  ValueToControls();
1005  ControlsToValue();
1006 }
1007 
1008 void NumericConverter::SetSampleRate(double sampleRate)
1009 {
1010  mSampleRate = sampleRate;
1012  ValueToControls();
1013  ControlsToValue();
1014 }
1015 
1016 void NumericConverter::SetValue(double newValue)
1017 {
1018  mValue = newValue;
1019  ValueToControls();
1020  ControlsToValue();
1021 }
1022 
1023 void NumericConverter::SetMinValue(double minValue)
1024 {
1025  mMinValue = minValue;
1026  if (mMaxValue < minValue)
1027  mMaxValue = minValue;
1028  if (mValue < minValue)
1029  SetValue(minValue);
1030 }
1031 
1033 {
1034  mMinValue = 0.0;
1035 }
1036 
1037 void NumericConverter::SetMaxValue(double maxValue)
1038 {
1039  mMaxValue = maxValue;
1040  if (mMinValue > maxValue) {
1041  mMinValue = maxValue;
1042  }
1043  if (mValue > maxValue)
1044  SetValue(maxValue);
1045 }
1046 
1048 {
1049  mMaxValue = std::numeric_limits<double>::max();
1050 }
1051 
1053 {
1054  ControlsToValue();
1055  return mValue;
1056 }
1057 
1059 {
1060  return mFormatString;
1061 }
1062 
1064 {
1065  // int ndx = 1;
1066  int ndx = std::min(1, GetNumBuiltins() - 1);
1067  int i;
1068 
1069  for (i = 0; i < GetNumBuiltins(); i++) {
1070  if (mFormatString == GetBuiltinFormat(i)) {
1071  ndx = i;
1072  break;
1073  }
1074  }
1075 
1076  return ndx;
1077 }
1078 
1080 {
1081  return mNBuiltins;
1082 }
1083 
1084 wxString NumericConverter::GetBuiltinName(const int index)
1085 {
1086  if (index >= 0 && index < GetNumBuiltins())
1087  return mBuiltinFormatStrings[index].name;
1088 
1089  return wxEmptyString;
1090 }
1091 
1092 wxString NumericConverter::GetBuiltinFormat(const int index)
1093 {
1094  if (index >= 0 && index < GetNumBuiltins())
1095  return mBuiltinFormatStrings[index].formatStr;
1096 
1097  return wxEmptyString;
1098 }
1099 
1100 wxString NumericConverter::GetBuiltinFormat(const wxString &name)
1101 {
1102  int ndx = mDefaultNdx;
1103  int i;
1104 
1105  for (i = 0; i < GetNumBuiltins(); i++) {
1106  if (name == GetBuiltinName(i)) {
1107  ndx = i;
1108  break;
1109  }
1110  }
1111 
1112  return GetBuiltinFormat(ndx);
1113 }
1114 
1116 {
1117  ValueToControls();
1118 
1119  return mValueString;
1120 }
1121 
1123 {
1124  mFocusedDigit = mDigits.size() - 1;
1125  Adjust(1, 1);
1126 }
1127 
1129 {
1130  mFocusedDigit = mDigits.size() - 1;
1131  Adjust(1, -1);
1132 }
1133 
1134 void NumericConverter::Adjust(int steps, int dir)
1135 {
1136  // It is possible and "valid" for steps to be zero if a
1137  // high precision device is being used and wxWidgets supports
1138  // reporting a higher precision...Mac wx3 does.
1139  if (steps == 0)
1140  return;
1141 
1142  wxASSERT(dir == -1 || dir == 1);
1143  wxASSERT(steps > 0);
1144  if (steps < 0)
1145  steps = -steps;
1146 
1147  while (steps != 0)
1148  {
1149  for (size_t i = 0; i < mFields.size(); i++)
1150  {
1151  if ((mDigits[mFocusedDigit].pos >= mFields[i].pos) &&
1152  (mDigits[mFocusedDigit].pos < mFields[i].pos + mFields[i].digits))
1153  { //it's this field
1154  if (!mNtscDrop)
1155  {
1156  ControlsToValue();
1157  }
1158  else
1159  {
1160  mNtscDrop = false;
1161  ControlsToValue();
1162  mNtscDrop = true;
1163  }
1164 
1165  if (mValue < 0)
1166  mValue = 0;
1167 
1169 
1170  double mult = pow(10., mFields[i].digits - (mDigits[mFocusedDigit].pos - mFields[i].pos) - 1);
1171  if (mFields[i].frac)
1172  {
1173  mValue += ((mult / (double)mFields[i].base) * dir);
1174  }
1175  else
1176  {
1177  mValue += ((mult * (double)mFields[i].base) * dir);
1178  }
1179 
1180  if (mNtscDrop)
1181  {
1182  if ((mValue - (int)mValue) * 30 < 2)
1183  {
1184  if ((((int)mValue) % 60 == 0) && (((int)mValue) % 600 != 0))
1185  {
1186  mValue = (int)mValue + (dir > 0 ? 2. : -1.) / 30.;
1187  }
1188  }
1189  }
1190 
1191  if (mValue < 0.)
1192  {
1193  mValue = 0.;
1194  }
1195 
1196  mValue = std::max(mMinValue, std::min(mMaxValue, mValue));
1197 
1199 
1200  if (!mNtscDrop)
1201  {
1202  ValueToControls();
1203  }
1204  else
1205  {
1206  mNtscDrop = false;
1207  ValueToControls();
1208  mNtscDrop = true;
1209  ControlsToValue();
1210  }
1211  break;
1212  }
1213  }
1214  steps--;
1215  }
1216 
1217  ControlsToValue();
1218 }
1219 
1220 #define ID_MENU 9800
1221 
1222 // Custom events
1223 
1224 DEFINE_EVENT_TYPE(EVT_TIMETEXTCTRL_UPDATED)
1225 DEFINE_EVENT_TYPE(EVT_FREQUENCYTEXTCTRL_UPDATED)
1226 DEFINE_EVENT_TYPE(EVT_BANDWIDTHTEXTCTRL_UPDATED)
1227 
1228 BEGIN_EVENT_TABLE(NumericTextCtrl, wxControl)
1229  EVT_ERASE_BACKGROUND(NumericTextCtrl::OnErase)
1230  EVT_PAINT(NumericTextCtrl::OnPaint)
1231  EVT_CONTEXT_MENU(NumericTextCtrl::OnContext)
1232  EVT_MOUSE_EVENTS(NumericTextCtrl::OnMouse)
1233  EVT_KEY_DOWN(NumericTextCtrl::OnKeyDown)
1234  EVT_KEY_UP(NumericTextCtrl::OnKeyUp)
1235  EVT_SET_FOCUS(NumericTextCtrl::OnFocus)
1236  EVT_KILL_FOCUS(NumericTextCtrl::OnFocus)
1237  EVT_COMMAND(wxID_ANY, EVT_CAPTURE_KEY, NumericTextCtrl::OnCaptureKey)
1239 
1240 IMPLEMENT_CLASS(NumericTextCtrl, wxControl)
1241 
1242 NumericTextCtrl::NumericTextCtrl(wxWindow *parent, wxWindowID id,
1243  NumericConverter::Type type,
1244  const wxString &formatName,
1245  double timeValue,
1246  double sampleRate,
1247  const Options &options,
1248  const wxPoint &pos,
1249  const wxSize &size):
1250  wxControl(parent, id, pos, size, wxSUNKEN_BORDER | wxWANTS_CHARS),
1251  NumericConverter(type, formatName, timeValue, sampleRate),
1252  mBackgroundBitmap{},
1253  mDigitFont{},
1254  mLabelFont{},
1255  mLastField(1),
1256  mAutoPos(options.autoPos)
1257  , mType(type)
1258 {
1259  mAllowInvalidValue = false;
1260 
1261  mDigitBoxW = 10;
1262  mDigitBoxH = 16;
1263 
1264  mReadOnly = options.readOnly;
1265  mMenuEnabled = options.menuEnabled;
1266  mButtonWidth = 9;
1267 
1268  Layout();
1269  Fit();
1270  ValueToControls();
1271  //PRL -- would this fix the following?
1272  //ValueToControls();
1273 
1274  //mchinen - aug 15 09 - this seems to put the mValue back to zero, and do nothing else.
1275  //ControlsToValue();
1276 
1277  mScrollRemainder = 0.0;
1278 
1279 #if wxUSE_ACCESSIBILITY
1280  SetLabel(wxT(""));
1281  SetName(wxT(""));
1282  SetAccessible(safenew NumericTextCtrlAx(this));
1283 #endif
1284 
1285  if (options.hasInvalidValue)
1286  SetInvalidValue( options.invalidValue );
1287 
1288  if (!options.format.empty())
1289  SetFormatString( options.format );
1290 
1291  if (options.hasValue)
1292  SetValue( options.value );
1293 }
1294 
1296 {
1297 }
1298 
1299 // Set the focus to the first (left-most) non-zero digit
1300 // If all digits are zero, the right-most position is focused
1301 // If all digits are hyphens (invalid), the left-most position is focused
1303 {
1304  if (!mAutoPos)
1305  return;
1306 
1307  mFocusedDigit = 0;
1308  while (mFocusedDigit < ((int)mDigits.size() - 1)) {
1309  wxChar dgt = mValueString[mDigits[mFocusedDigit].pos];
1310  if (dgt != '0') {
1311  break;
1312  }
1313  mFocusedDigit++;
1314  }
1315 }
1316 
1317 void NumericTextCtrl::SetFormatName(const wxString & formatName)
1318 {
1319  SetFormatString(GetBuiltinFormat(formatName));
1320 }
1321 
1322 void NumericTextCtrl::SetFormatString(const wxString & formatString)
1323 {
1324  NumericConverter::SetFormatString(formatString);
1325  Layout();
1326  Fit();
1327  ValueToControls();
1328  ControlsToValue();
1329  UpdateAutoFocus();
1330 }
1331 
1332 void NumericTextCtrl::SetSampleRate(double sampleRate)
1333 {
1334  NumericConverter::SetSampleRate(sampleRate);
1335  Layout();
1336  Fit();
1337  ValueToControls();
1338  ControlsToValue();
1339 }
1340 
1341 void NumericTextCtrl::SetValue(double newValue)
1342 {
1343  NumericConverter::SetValue(newValue);
1344  ValueToControls();
1345  ControlsToValue();
1346 }
1347 
1348 void NumericTextCtrl::SetReadOnly(bool readOnly)
1349 {
1350  mReadOnly = readOnly;
1351 }
1352 
1354 {
1355 #if wxUSE_TOOLTIPS
1356  wxString tip(_("(Use context menu to change format.)"));
1357  if (enable)
1358  SetToolTip(tip);
1359  else {
1360  wxToolTip *tt = GetToolTip();
1361  if (tt && tt->GetTip() == tip)
1362  SetToolTip(NULL);
1363  }
1364 #endif
1365  mMenuEnabled = enable;
1366  mButtonWidth = enable ? 9 : 0;
1367  Layout();
1368  Fit();
1369 }
1370 
1371 void NumericTextCtrl::SetInvalidValue(double invalidValue)
1372 {
1373  const bool wasInvalid = mAllowInvalidValue && (mValue == mInvalidValue);
1374  mAllowInvalidValue = true;
1375  mInvalidValue = invalidValue;
1376  if (wasInvalid)
1377  SetValue(invalidValue);
1378 }
1379 
1381 {
1382  unsigned int i, j;
1383  int x, pos;
1384 
1385  wxMemoryDC memDC;
1386 
1387  // Placeholder bitmap so the memDC has something to reference
1388  mBackgroundBitmap = std::make_unique<wxBitmap>(1, 1);
1389  memDC.SelectObject(*mBackgroundBitmap);
1390 
1391  mDigits.clear();
1392 
1393  mBorderLeft = 1;
1394  mBorderTop = 1;
1395  mBorderRight = 1;
1396  mBorderBottom = 1;
1397 
1398  int fontSize = 4;
1399  wxCoord strW, strH;
1400  wxString exampleText = wxT("0");
1401 
1402  // Keep making the font bigger until it's too big, then subtract one.
1403  memDC.SetFont(wxFont(fontSize, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
1404  memDC.GetTextExtent(exampleText, &strW, &strH);
1405  while (strW <= mDigitBoxW && strH <= mDigitBoxH) {
1406  fontSize++;
1407  memDC.SetFont(wxFont(fontSize, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
1408  memDC.GetTextExtent(exampleText, &strW, &strH);
1409  }
1410  fontSize--;
1411 
1412  mDigitFont = std::make_unique<wxFont>(fontSize, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
1413  memDC.SetFont(*mDigitFont);
1414  memDC.GetTextExtent(exampleText, &strW, &strH);
1415  mDigitW = strW;
1416  mDigitH = strH;
1417 
1418  // The label font should be a little smaller
1419  fontSize--;
1420  mLabelFont = std::make_unique<wxFont>(fontSize, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
1421 
1422  // Figure out the x-position of each field and label in the box
1423  x = mBorderLeft;
1424  pos = 0;
1425 
1426  memDC.SetFont(*mLabelFont);
1427  memDC.GetTextExtent(mPrefix, &strW, &strH);
1428  x += strW;
1429  pos += mPrefix.Length();
1430 
1431  for(i = 0; i < mFields.size(); i++) {
1432  mFields[i].fieldX = x;
1433  for(j=0; j<(unsigned int)mFields[i].digits; j++) {
1434  mDigits.push_back(DigitInfo(i, j, pos, wxRect(x, mBorderTop,
1435  mDigitBoxW, mDigitBoxH)));
1436  x += mDigitBoxW;
1437  pos++;
1438  }
1439 
1440  mFields[i].labelX = x;
1441  memDC.GetTextExtent(mFields[i].label, &strW, &strH);
1442  pos += mFields[i].label.Length();
1443  x += strW;
1444  mFields[i].fieldW = x;
1445  }
1446 
1447  mWidth = x + mBorderRight;
1449 
1450  // Draw the background bitmap - it contains black boxes where
1451  // all of the digits go and all of the other text
1452 
1453  wxBrush Brush;
1454 
1455  mBackgroundBitmap = std::make_unique<wxBitmap>(mWidth + mButtonWidth, mHeight);
1456  memDC.SelectObject(*mBackgroundBitmap);
1457 
1458  theTheme.SetBrushColour( Brush, clrTimeHours );
1459  memDC.SetBrush(Brush);
1460  memDC.SetPen(*wxTRANSPARENT_PEN);
1461  memDC.DrawRectangle(0, 0, mWidth + mButtonWidth, mHeight);
1462 
1463  int numberBottom = mBorderTop + (mDigitBoxH - mDigitH)/2 + mDigitH;
1464 
1465  memDC.GetTextExtent(wxT("0"), &strW, &strH);
1466  int labelTop = numberBottom - strH;
1467 
1468  memDC.SetTextForeground(theTheme.Colour( clrTimeFont ));
1469  memDC.SetTextBackground(theTheme.Colour( clrTimeBack ));
1470  memDC.DrawText(mPrefix, mBorderLeft, labelTop);
1471 
1472  theTheme.SetBrushColour( Brush, clrTimeBack );
1473  memDC.SetBrush(Brush);
1474  //memDC.SetBrush(*wxLIGHT_GREY_BRUSH);
1475  for(i = 0; i < mDigits.size(); i++)
1476  memDC.DrawRectangle(mDigits[i].digitBox);
1477  memDC.SetBrush( wxNullBrush );
1478 
1479  for(i = 0; i < mFields.size(); i++)
1480  memDC.DrawText(mFields[i].label,
1481  mFields[i].labelX, labelTop);
1482 
1483  if (mMenuEnabled) {
1484  wxRect r(mWidth, 0, mButtonWidth - 1, mHeight - 1);
1485  AColor::Bevel(memDC, true, r);
1486  memDC.SetBrush(*wxBLACK_BRUSH);
1487  memDC.SetPen(*wxBLACK_PEN);
1488  AColor::Arrow(memDC,
1489  mWidth + 1,
1490  (mHeight / 2) - 2,
1491  mButtonWidth - 2);
1492  }
1493  return true;
1494 }
1495 
1497 {
1498  wxSize sz = GetSize();
1499  wxSize csz = GetClientSize();
1500 
1501  sz.x = mButtonWidth + mWidth + (sz.x - csz.x);
1502  sz.y = mHeight + (sz.y - csz.y);
1503 
1504  SetInitialSize(sz);
1505 }
1506 
1507 void NumericTextCtrl::OnErase(wxEraseEvent & WXUNUSED(event))
1508 {
1509  // Ignore it to prevent flashing
1510 }
1511 
1512 void NumericTextCtrl::OnPaint(wxPaintEvent & WXUNUSED(event))
1513 {
1514  wxPaintDC dc(this);
1515  bool focused = (FindFocus() == this);
1516 
1517  dc.DrawBitmap(*mBackgroundBitmap, 0, 0);
1518 
1519  wxPen Pen;
1520  wxBrush Brush;
1521  if (focused) {
1522  theTheme.SetPenColour( Pen, clrTimeFontFocus );
1523  dc.SetPen(Pen);
1524  dc.SetBrush(*wxTRANSPARENT_BRUSH);
1525  dc.DrawRectangle(0, 0, mWidth, mHeight);
1526  dc.SetPen( wxNullPen );
1527  }
1528 
1529  dc.SetFont(*mDigitFont);
1530  dc.SetTextForeground(theTheme.Colour( clrTimeFont ));
1531  dc.SetTextBackground(theTheme.Colour( clrTimeBack ));
1532 
1533  dc.SetPen(*wxTRANSPARENT_PEN);
1534  theTheme.SetBrushColour( Brush , clrTimeBackFocus );
1535  dc.SetBrush( Brush );
1536 
1537  int i;
1538  for(i = 0; i < (int)mDigits.size(); i++) {
1539  wxRect box = mDigits[i].digitBox;
1540  if (focused && mFocusedDigit == i) {
1541  dc.DrawRectangle(box);
1542  dc.SetTextForeground(theTheme.Colour( clrTimeFontFocus ));
1543  dc.SetTextBackground(theTheme.Colour( clrTimeBackFocus ));
1544  }
1545  int pos = mDigits[i].pos;
1546  wxString digit = mValueString.Mid(pos, 1);
1547  int x = box.x + (mDigitBoxW - mDigitW)/2;
1548  int y = box.y + (mDigitBoxH - mDigitH)/2;
1549  dc.DrawText(digit, x, y);
1550  if (focused && mFocusedDigit == i) {
1551  dc.SetTextForeground(theTheme.Colour( clrTimeFont ));
1552  dc.SetTextBackground(theTheme.Colour( clrTimeBack ));
1553  }
1554  }
1555  dc.SetPen( wxNullPen );
1556  dc.SetBrush( wxNullBrush );
1557 }
1558 
1559 void NumericTextCtrl::OnContext(wxContextMenuEvent &event)
1560 {
1561  wxMenu menu;
1562  int i;
1563 
1564  if (!mMenuEnabled) {
1565  event.Skip();
1566  return;
1567  }
1568 
1569  SetFocus();
1570 
1571  int currentSelection = -1;
1572  for (i = 0; i < GetNumBuiltins(); i++) {
1573  menu.AppendRadioItem(ID_MENU + i, GetBuiltinName(i));
1574  if (mFormatString == GetBuiltinFormat(i)) {
1575  menu.Check(ID_MENU + i, true);
1576  currentSelection = i;
1577  }
1578  }
1579 
1580  PopupMenu(&menu, wxPoint(0, 0));
1581 
1582  // This used to be in an EVT_MENU() event handler, but GTK
1583  // is sensitive to what is done within the handler if the
1584  // user happens to check the first menuitem and then is
1585  // moving down the menu when the ...CTRL_UPDATED event
1586  // handler kicks in.
1587  for (i = 0; i < GetNumBuiltins(); i++) {
1588  if (menu.IsChecked(ID_MENU + i) && i != currentSelection) {
1590 
1591  int eventType = 0;
1592  switch (mType) {
1594  eventType = EVT_TIMETEXTCTRL_UPDATED;
1595  break;
1597  eventType = EVT_FREQUENCYTEXTCTRL_UPDATED;
1598  break;
1600  eventType = EVT_BANDWIDTHTEXTCTRL_UPDATED;
1601  break;
1602  default:
1603  wxASSERT(false);
1604  break;
1605  }
1606 
1607  wxCommandEvent e(eventType, GetId());
1608  e.SetInt(i);
1609  e.SetString(GetBuiltinName(i));
1610  GetParent()->GetEventHandler()->AddPendingEvent(e);
1611  }
1612  }
1613 
1614 }
1615 
1616 void NumericTextCtrl::OnMouse(wxMouseEvent &event)
1617 {
1618  if (event.LeftDown() && event.GetX() >= mWidth) {
1619  wxContextMenuEvent e;
1620  OnContext(e);
1621  }
1622  else if (event.LeftDown()) {
1623  SetFocus();
1624 
1625  int bestDist = 9999;
1626  unsigned int i;
1627 
1628  mFocusedDigit = 0;
1629  for(i = 0; i < mDigits.size(); i++) {
1630  int dist = abs(event.m_x - (mDigits[i].digitBox.x +
1631  mDigits[i].digitBox.width/2));
1632  if (dist < bestDist) {
1633  mFocusedDigit = i;
1634  bestDist = dist;
1635  }
1636  }
1637 
1638  Refresh(false);
1639  }
1640  else if (event.RightDown() && mMenuEnabled) {
1641  wxContextMenuEvent e;
1642  OnContext(e);
1643  }
1644  else if(!mReadOnly && event.m_wheelRotation != 0 ) {
1645  double steps = event.m_wheelRotation /
1646  (event.m_wheelDelta > 0 ? (double)event.m_wheelDelta : 120.0) +
1648  mScrollRemainder = steps - floor(steps);
1649  steps = floor(steps);
1650 
1651  Adjust((int)fabs(steps), steps < 0.0 ? -1 : 1);
1652  Updated();
1653  }
1654 }
1655 
1656 void NumericTextCtrl::OnFocus(wxFocusEvent &event)
1657 {
1658  if (event.GetEventType() == wxEVT_KILL_FOCUS) {
1660  }
1661  else {
1663  }
1664 
1665  Refresh(false);
1666 }
1667 
1668 void NumericTextCtrl::OnCaptureKey(wxCommandEvent &event)
1669 {
1670  wxKeyEvent *kevent = (wxKeyEvent *)event.GetEventObject();
1671  int keyCode = kevent->GetKeyCode();
1672 
1673  // Convert numeric keypad entries.
1674  if ((keyCode >= WXK_NUMPAD0) && (keyCode <= WXK_NUMPAD9))
1675  keyCode -= WXK_NUMPAD0 - '0';
1676 
1677  switch (keyCode)
1678  {
1679  case WXK_BACK:
1680  case WXK_LEFT:
1681  case WXK_RIGHT:
1682  case WXK_HOME:
1683  case WXK_END:
1684  case WXK_UP:
1685  case WXK_DOWN:
1686  case WXK_TAB:
1687  case WXK_RETURN:
1688  case WXK_NUMPAD_ENTER:
1689  case WXK_DELETE:
1690  return;
1691 
1692  default:
1693  if (keyCode >= '0' && keyCode <= '9')
1694  return;
1695  }
1696 
1697  event.Skip();
1698 
1699  return;
1700 }
1701 
1702 void NumericTextCtrl::OnKeyUp(wxKeyEvent &event)
1703 {
1704  int keyCode = event.GetKeyCode();
1705 
1706  event.Skip(true);
1707 
1708  if ((keyCode >= WXK_NUMPAD0) && (keyCode <= WXK_NUMPAD9))
1709  keyCode -= WXK_NUMPAD0 - '0';
1710 
1711  if ((keyCode >= '0' && keyCode <= '9') ||
1712  (keyCode == WXK_DELETE) ||
1713  (keyCode == WXK_BACK) ||
1714  (keyCode == WXK_UP) ||
1715  (keyCode == WXK_DOWN)) {
1716  Updated(true);
1717  }
1718 }
1719 
1720 void NumericTextCtrl::OnKeyDown(wxKeyEvent &event)
1721 {
1722  if (mDigits.size() == 0)
1723  {
1724  mFocusedDigit = 0;
1725  return;
1726  }
1727 
1728  event.Skip(false);
1729 
1730  int keyCode = event.GetKeyCode();
1731  int digit = mFocusedDigit;
1732 
1733  if (mFocusedDigit < 0)
1734  mFocusedDigit = 0;
1735  if (mFocusedDigit >= (int)mDigits.size())
1736  mFocusedDigit = mDigits.size() - 1;
1737 
1738  // Convert numeric keypad entries.
1739  if ((keyCode >= WXK_NUMPAD0) && (keyCode <= WXK_NUMPAD9))
1740  keyCode -= WXK_NUMPAD0 - '0';
1741 
1742  if (!mReadOnly && (keyCode >= '0' && keyCode <= '9')) {
1743  int digitPosition = mDigits[mFocusedDigit].pos;
1744  if (mValueString[digitPosition] == wxChar('-')) {
1745  mValue = std::max(mMinValue, std::min(mMaxValue, 0.0));
1746  ValueToControls();
1747  // Beware relocation of the string
1748  digitPosition = mDigits[mFocusedDigit].pos;
1749  }
1750  mValueString[digitPosition] = wxChar(keyCode);
1751  ControlsToValue();
1752  Refresh();// Force an update of the control. [Bug 1497]
1753  ValueToControls();
1754  mFocusedDigit = (mFocusedDigit + 1) % (mDigits.size());
1755  Updated();
1756  }
1757 
1758  else if (!mReadOnly && keyCode == WXK_DELETE) {
1759  if (mAllowInvalidValue)
1761  }
1762 
1763  else if (!mReadOnly && keyCode == WXK_BACK) {
1764  // Moves left, replaces that char with '0', stays there...
1765  mFocusedDigit--;
1766  mFocusedDigit += mDigits.size();
1767  mFocusedDigit %= mDigits.size();
1768  wxString::reference theDigit = mValueString[mDigits[mFocusedDigit].pos];
1769  if (theDigit != wxChar('-'))
1770  theDigit = '0';
1771  ControlsToValue();
1772  Refresh();// Force an update of the control. [Bug 1497]
1773  ValueToControls();
1774  Updated();
1775  }
1776 
1777  else if (keyCode == WXK_LEFT) {
1778  mFocusedDigit--;
1779  mFocusedDigit += mDigits.size();
1780  mFocusedDigit %= mDigits.size();
1781  Refresh();
1782  }
1783 
1784  else if (keyCode == WXK_RIGHT) {
1785  mFocusedDigit++;
1786  mFocusedDigit %= mDigits.size();
1787  Refresh();
1788  }
1789 
1790  else if (keyCode == WXK_HOME) {
1791  mFocusedDigit = 0;
1792  Refresh();
1793  }
1794 
1795  else if (keyCode == WXK_END) {
1796  mFocusedDigit = mDigits.size() - 1;
1797  Refresh();
1798  }
1799 
1800  else if (!mReadOnly && keyCode == WXK_UP) {
1801  Adjust(1, 1);
1802  Updated();
1803  }
1804 
1805  else if (!mReadOnly && keyCode == WXK_DOWN) {
1806  Adjust(1, -1);
1807  Updated();
1808  }
1809 
1810  else if (keyCode == WXK_TAB) {
1811 #if defined(__WXMSW__)
1812  // Using Navigate() on Windows, rather than the following code causes
1813  // bug 1542
1814  wxWindow* parent = GetParent();
1815  wxNavigationKeyEvent nevent;
1816  nevent.SetWindowChange(event.ControlDown());
1817  nevent.SetDirection(!event.ShiftDown());
1818  nevent.SetEventObject(parent);
1819  nevent.SetCurrentFocus(parent);
1820  GetParent()->GetEventHandler()->ProcessEvent(nevent);
1821 #else
1822  Navigate(event.ShiftDown()
1823  ? wxNavigationKeyEvent::IsBackward
1824  : wxNavigationKeyEvent::IsForward);
1825 #endif
1826  }
1827 
1828  else if (keyCode == WXK_RETURN || keyCode == WXK_NUMPAD_ENTER) {
1829  wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
1830  wxWindow *def = tlw->GetDefaultItem();
1831  if (def && def->IsEnabled()) {
1832  wxCommandEvent cevent(wxEVT_COMMAND_BUTTON_CLICKED,
1833  def->GetId());
1834  GetParent()->GetEventHandler()->ProcessEvent(cevent);
1835  }
1836  }
1837 
1838  else {
1839  event.Skip();
1840  return;
1841  }
1842 
1843  if (digit != mFocusedDigit) {
1845  }
1846 }
1847 
1849 {
1850 #if wxUSE_ACCESSIBILITY
1851  if (mDigits.size() == 0)
1852  {
1853  mFocusedDigit = 0;
1854  return;
1855  }
1856  mFocusedDigit = digit;
1857  mLastField = mDigits[mFocusedDigit].field + 1;
1858 
1859  // This looks strange (and it is), but it was the only way I could come
1860  // up with that allowed Jaws, Window-Eyes, and NVDA to read the control
1861  // somewhat the same. See NumericTextCtrlAx below for even more odd looking
1862  // hackery.
1863  //
1864  // If you change SetFieldFocus(), Updated(), or NumericTextCtrlAx, make sure
1865  // you test with Jaws, Window-Eyes, and NVDA.
1866  GetAccessible()->NotifyEvent(wxACC_EVENT_OBJECT_FOCUS,
1867  this,
1868  wxOBJID_CLIENT,
1869  0);
1870  GetAccessible()->NotifyEvent(wxACC_EVENT_OBJECT_FOCUS,
1871  this,
1872  wxOBJID_CLIENT,
1873  mFocusedDigit + 1);
1874 #endif
1875 }
1876 
1877 void NumericTextCtrl::Updated(bool keyup /* = false */)
1878 {
1879  wxCommandEvent event(wxEVT_COMMAND_TEXT_UPDATED, GetId());
1880 
1881  // This will give listeners the ability to do tasks when the
1882  // update has been completed, like when the UP ARROW has been
1883  // held down and is finally released.
1884  event.SetInt(keyup);
1885  event.SetEventObject(this);
1886  GetEventHandler()->ProcessEvent(event);
1887 
1888 #if wxUSE_ACCESSIBILITY
1889  if (!keyup) {
1890  GetAccessible()->NotifyEvent(wxACC_EVENT_OBJECT_NAMECHANGE,
1891  this,
1892  wxOBJID_CLIENT,
1893  mFocusedDigit + 1);
1895  }
1896 #endif
1897 }
1898 
1900 {
1901  const wxString previousValueString = mValueString;
1903  if (mValueString != previousValueString) {
1904  // Doing this only when needed is an optimization.
1905  // NumerixTextCtrls are used in the selection bar at the bottom
1906  // of Audacity, and are updated at high frequency through
1907  // SetValue() when Audacity is playing. This consumes a
1908  // significant amount of CPU. Typically, when a track is
1909  // playing, only one of the NumericTextCtrl actually changes
1910  // (the audio position). We save CPU by updating the control
1911  // only when needed.
1912  Refresh(false);
1913  }
1914 }
1915 
1916 
1918 {
1920 }
1921 
1922 #if wxUSE_ACCESSIBILITY
1923 
1924 NumericTextCtrlAx::NumericTextCtrlAx(NumericTextCtrl *ctrl)
1925 : wxWindowAccessible(ctrl)
1926 {
1927  mCtrl = ctrl;
1928  mLastField = -1;
1929  mLastDigit = -1;
1930 }
1931 
1932 NumericTextCtrlAx::~NumericTextCtrlAx()
1933 {
1934 }
1935 
1936 // Performs the default action. childId is 0 (the action for this object)
1937 // or > 0 (the action for a child).
1938 // Return wxACC_NOT_SUPPORTED if there is no default action for this
1939 // window (e.g. an edit control).
1940 wxAccStatus NumericTextCtrlAx::DoDefaultAction(int WXUNUSED(childId))
1941 {
1942  return wxACC_NOT_SUPPORTED;
1943 }
1944 
1945 // Retrieves the address of an IDispatch interface for the specified child.
1946 // All objects must support this property.
1947 wxAccStatus NumericTextCtrlAx::GetChild(int childId, wxAccessible **child)
1948 {
1949  if (childId == wxACC_SELF) {
1950  *child = this;
1951  }
1952  else {
1953  *child = NULL;
1954  }
1955 
1956  return wxACC_OK;
1957 }
1958 
1959 // Gets the number of children.
1960 wxAccStatus NumericTextCtrlAx::GetChildCount(int *childCount)
1961 {
1962  *childCount = mCtrl->mDigits.size();
1963 
1964  return wxACC_OK;
1965 }
1966 
1967 // Gets the default action for this object (0) or > 0 (the action for
1968 // a child). Return wxACC_OK even if there is no action. actionName
1969 // is the action, or the empty string if there is no action. The
1970 // retrieved string describes the action that is performed on an
1971 // object, not what the object does as a result. For example, a
1972 // toolbar button that prints a document has a default action of
1973 // "Press" rather than "Prints the current document."
1974 wxAccStatus NumericTextCtrlAx::GetDefaultAction(int WXUNUSED(childId), wxString *actionName)
1975 {
1976  actionName->Clear();
1977 
1978  return wxACC_OK;
1979 }
1980 
1981 // Returns the description for this object or a child.
1982 wxAccStatus NumericTextCtrlAx::GetDescription(int WXUNUSED(childId), wxString *description)
1983 {
1984  description->Clear();
1985 
1986  return wxACC_OK;
1987 }
1988 
1989 // Gets the window with the keyboard focus.
1990 // If childId is 0 and child is NULL, no object in
1991 // this subhierarchy has the focus.
1992 // If this object has the focus, child should be 'this'.
1993 wxAccStatus NumericTextCtrlAx::GetFocus(int *childId, wxAccessible **child)
1994 {
1995  *childId = mCtrl->GetFocusedDigit();
1996  *child = this;
1997 
1998  return wxACC_OK;
1999 }
2000 
2001 // Returns help text for this object or a child, similar to tooltip text.
2002 wxAccStatus NumericTextCtrlAx::GetHelpText(int WXUNUSED(childId), wxString *helpText)
2003 {
2004 // removed help text, as on balance it's more of an irritation than useful
2005 #if 0 // was #if wxUSE_TOOLTIPS
2006  wxToolTip *pTip = mCtrl->GetToolTip();
2007  if (pTip) {
2008  *helpText = pTip->GetTip();
2009  }
2010 
2011  return wxACC_OK;
2012 #else
2013  helpText->Clear();
2014 
2015  return wxACC_NOT_SUPPORTED;
2016 #endif
2017 }
2018 
2019 // Returns the keyboard shortcut for this object or child.
2020 // Return e.g. ALT+K
2021 wxAccStatus NumericTextCtrlAx::GetKeyboardShortcut(int WXUNUSED(childId), wxString *shortcut)
2022 {
2023  shortcut->Clear();
2024 
2025  return wxACC_OK;
2026 }
2027 
2028 // Returns the rectangle for this object (id = 0) or a child element (id > 0).
2029 // rect is in screen coordinates.
2030 wxAccStatus NumericTextCtrlAx::GetLocation(wxRect & rect, int elementId)
2031 {
2032  if ((elementId != wxACC_SELF) &&
2033  // We subtract 1, below, and need to avoid neg index to mDigits.
2034  (elementId > 0))
2035  {
2036 // rect.x += mCtrl->mFields[elementId - 1].fieldX;
2037 // rect.width = mCtrl->mFields[elementId - 1].fieldW;
2038  rect = mCtrl->mDigits[elementId - 1].digitBox;
2039  rect.SetPosition(mCtrl->ClientToScreen(rect.GetPosition()));
2040  }
2041  else
2042  {
2043  rect = mCtrl->GetRect();
2044  rect.SetPosition(mCtrl->GetParent()->ClientToScreen(rect.GetPosition()));
2045  }
2046 
2047  return wxACC_OK;
2048 }
2049 
2050 // Gets the name of the specified object.
2051 wxAccStatus NumericTextCtrlAx::GetName(int childId, wxString *name)
2052 {
2053  // Slightly messy trick to save us some prefixing.
2054  std::vector<NumericField> & mFields = mCtrl->mFields;
2055 
2056  wxString value = mCtrl->GetString();
2057  int field = mCtrl->GetFocusedField();
2058 
2059  // Return the entire string including the control label
2060  // when the requested child ID is wxACC_SELF. (Mainly when
2061  // the control gets the focus.)
2062  if ((childId == wxACC_SELF) ||
2063  // We subtract 1 from childId in the other cases below, and
2064  // need to avoid neg index to mDigits, so funnel into this clause.
2065  (childId < 1))
2066  {
2067  *name = mCtrl->GetName();
2068  if (name->IsEmpty()) {
2069  *name = mCtrl->GetLabel();
2070  }
2071 
2072  *name += wxT(" ") +
2073  mCtrl->GetString();
2074  }
2075  // The user has moved from one field of the time to another so
2076  // report the value of the field and the field's label.
2077  else if (mLastField != field) {
2078  wxString label = mFields[field - 1].label;
2079  int cnt = mFields.size();
2080  wxString decimal = wxLocale::GetInfo(wxLOCALE_DECIMAL_POINT, wxLOCALE_CAT_NUMBER);
2081 
2082  // If the NEW field is the last field, then check it to see if
2083  // it represents fractions of a second.
2084  // PRL: click a digit of the control and use left and right arrow keys
2085  // to exercise this code
2086  const bool isTime = (mCtrl->mType == NumericTextCtrl::TIME);
2087  if (field > 1 && field == cnt) {
2088  if (mFields[field - 2].label == decimal) {
2089  int digits = mFields[field - 1].digits;
2090  if (digits == 2) {
2091  if (isTime)
2092  label = _("centiseconds");
2093  else {
2094  // other units
2095  // PRL: does this create translation problems?
2096  label = _("hundredths of ");
2097  label += mFields[field - 1].label;
2098  }
2099  }
2100  else if (digits == 3) {
2101  if (isTime)
2102  label = _("milliseconds");
2103  else {
2104  // other units
2105  // PRL: does this create translation problems?
2106  label = _("thousandths of ");
2107  label += mFields[field - 1].label;
2108  }
2109  }
2110  }
2111  }
2112  // If the field following this one represents fractions of a
2113  // second then use that label instead of the decimal point.
2114  else if (label == decimal && field == cnt - 1) {
2115  label = mFields[field].label;
2116  }
2117 
2118  *name = mFields[field - 1].str +
2119  wxT(" ") +
2120  label +
2121  wxT(", ") + // comma inserts a slight pause
2122  mCtrl->GetString().at(mCtrl->mDigits[childId - 1].pos);
2123  mLastField = field;
2124  mLastDigit = childId;
2125  }
2126  // The user has moved from one digit to another within a field so
2127  // just report the digit under the cursor.
2128  else if (mLastDigit != childId) {
2129  *name = mCtrl->GetString().at(mCtrl->mDigits[childId - 1].pos);
2130  mLastDigit = childId;
2131  }
2132  // The user has updated the value of a field, so report the field's
2133  // value only.
2134  else if (field > 0)
2135  {
2136  *name = mFields[field - 1].str;
2137  }
2138 
2139  return wxACC_OK;
2140 }
2141 
2142 // Returns a role constant.
2143 wxAccStatus NumericTextCtrlAx::GetRole(int WXUNUSED(childId), wxAccRole *role)
2144 {
2145  *role = wxROLE_SYSTEM_STATICTEXT;
2146  return wxACC_OK;
2147 }
2148 
2149 // Gets a variant representing the selected children
2150 // of this object.
2151 // Acceptable values:
2152 // - a null variant (IsNull() returns TRUE)
2153 // - a list variant (GetType() == wxT("list"))
2154 // - an integer representing the selected child element,
2155 // or 0 if this object is selected (GetType() == wxT("long"))
2156 // - a "void*" pointer to a wxAccessible child object
2157 wxAccStatus NumericTextCtrlAx::GetSelections(wxVariant * WXUNUSED(selections))
2158 {
2159  return wxACC_NOT_IMPLEMENTED;
2160 }
2161 
2162 // Returns a state constant.
2163 wxAccStatus NumericTextCtrlAx::GetState(int WXUNUSED(childId), long *state)
2164 {
2165  *state = wxACC_STATE_SYSTEM_FOCUSABLE;
2166  *state |= (mCtrl == wxWindow::FindFocus() ? wxACC_STATE_SYSTEM_FOCUSED : 0);
2167 
2168  return wxACC_OK;
2169 }
2170 
2171 // Returns a localized string representing the value for the object
2172 // or child.
2173 wxAccStatus NumericTextCtrlAx::GetValue(int WXUNUSED(childId), wxString * WXUNUSED(strValue))
2174 {
2175  return wxACC_NOT_IMPLEMENTED;
2176 }
2177 
2178 #endif
2179 
void SetFormatName(const wxString &formatName)
void SetFormatString(const wxString &formatString)
AUDACITY_DLL_API Theme theTheme
Definition: Theme.cpp:209
void Adjust(int steps, int dir)
DigitInfo(int _field, int _index, int _pos, wxRect _box)
EVT_COMMAND(wxID_ANY, EVT_FREQUENCYTEXTCTRL_UPDATED, LabelDialog::OnFreqUpdate) LabelDialog
Definition: LabelDialog.cpp:88
std::vector< NumericField > mFields
std::unique_ptr< wxFont > mDigitFont
static void CaptureKeyboard(wxWindow *handler)
Definition: Project.cpp:5783
std::unique_ptr< wxBitmap > mBackgroundBitmap
void SetFormatName(const wxString &formatName)
static void Arrow(wxDC &dc, wxCoord x, wxCoord y, int width, bool down=true)
Definition: AColor.cpp:96
void OnContext(wxContextMenuEvent &event)
NumericTextCtrlAx gives the NumericTextCtrl Accessibility.
DEFINE_EVENT_TYPE(EVT_OPEN_AUDIO_FILE)
Custom events.
void SetReadOnly(bool readOnly=true)
wxString label
Definition: Tags.cpp:727
void SetSampleRate(double sampleRate)
void OnKeyDown(wxKeyEvent &event)
std::unique_ptr< wxFont > mLabelFont
wxString GetFormatString()
void EnableMenu(bool enable=true)
NumericConverter(Type type, const wxString &formatName=wxEmptyString, double value=0.0f, double sampleRate=1.0f)
#define XO(s)
Definition: Internat.h:30
void SetFormatString(const wxString &formatString)
void CreateDigitFormatStr()
struct to hold a formatting control string and its untranslated name Used in an array to hold the bui...
std::vector< DigitInfo > mDigits
virtual ~NumericConverter()
#define safenew
Definition: Audacity.h:223
NumericField is a class used in NumericTextCtrl.
virtual void ControlsToValue()
void SetMaxValue(double maxValue)
void ValueToControls() override
void OnMouse(wxMouseEvent &event)
int format
Definition: ExportPCM.cpp:56
NumericConverter provides the advanced formatting control used in the selection bar of Audacity...
static void Bevel(wxDC &dc, bool up, const wxRect &r)
Definition: AColor.cpp:202
void SetBrushColour(wxBrush &Brush, int iIndex)
Definition: Theme.cpp:1215
NumericField(bool _frac, int _base, int _range, bool _zeropad)
wxEVT_COMMAND_TEXT_UPDATED
Definition: Nyquist.cpp:109
DigitInfo is a class used in NumericTextCtrl.
int min(int a, int b)
void OnErase(wxEraseEvent &event)
void Updated(bool keyup=false)
void ControlsToValue() override
#define ID_MENU
void SetValue(double newValue)
wxString GetBuiltinFormat(const int index)
BuiltinFormatString Translate() const
void SetMinValue(double minValue)
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom"))), OnMoveTrack) void TrackMenuTable::OnSetName(wxCommandEvent &)
static void ReleaseKeyboard(wxWindow *handler)
Definition: Project.cpp:5794
IMPLEMENT_CLASS(ControlToolBar, ToolBar)
void OnCaptureKey(wxCommandEvent &event)
void SetInvalidValue(double invalidValue)
const wxChar * name
Definition: Distortion.cpp:94
void Fit() override
bool Layout() override
const std::vector< BuiltinFormatString > & mBuiltinFormatStrings
wxString GetBuiltinName(const int index)
wxColour & Colour(int iIndex)
Definition: Theme.cpp:1208
NumericField & operator=(const NumericField &)=default
END_EVENT_TABLE()
void SetValue(double newValue)
virtual void Populate()=0
NumericConverter::Type mType
void OnKeyUp(wxKeyEvent &event)
virtual void ValueToControls()
virtual void ParseFormatString(const wxString &format)
struct to hold a formatting control string and its user facing name Used in an array to hold the buil...
virtual ~NumericTextCtrl()
void SetSampleRate(double sampleRate)
void SetPenColour(wxPen &Pen, int iIndex)
Definition: Theme.cpp:1221
void OnPaint(wxPaintEvent &event)
void OnFocus(wxFocusEvent &event)