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