/********************************************************************** Audacity: A Digital Audio Editor TimeTextCtrl.cpp Dominic Mazzoni ********************************************************************//** \class TimeTextCtrl \brief TimeTextCtrl provides the advanced time formatting control used in the status bar of Audacity. The TimeTextCtrl makes use of a format string to specify the exact way that a single time value is split into several fields, such as the hh:mm:ss format. The advantage of this format string is that it is very small and compact, but human-readable and somewhat intuitive, so that it's easy to add new time layouts in the future. It's also designed to make it easier to add i18n support, since the way that times are displayed in different languages could conceivably vary a lot. The time to be formatted is expressed in seconds, so the format string specifies the relationship of each field to the number of seconds. Let's start by considering an example: here's the format string that prints an integer number of seconds in the hour minute second h:m:s format: *:60:60 The "*" is a wildcard, saying that the leftmost field can contain numbers of arbitrary magnitude. The next character, ':', since it is not a digit or a wildcard, is interpreted as a delimiter, and will be displayed between those fields. The next number, 60, indicates that the range of the next field (minutes) is 60. Then there's another ':' delimiter, and finally the last field (seconds) is 60. So, if you give it a number like 3758 (note format it as: 3758 seconds, "*:60:60" -> "1:2:38" Note that 3758 = 1*60*60 + 2*60 + 38. When TimeTextCtrl formats an integer, you can think of its process as working from right to left. Given the value "3758", it fills in the seconds by dividing by 60, sticking the remainder in the seconds field and then passing the quotient to the next field to the left. In order to format a field with leading zeros, simply add a leading zero to that field, like this: 3758 seconds, "*:060:060" -> "1:02:38" In order to format fractions, simply include a field delimiter ending with a decimal point. If the delimiter is simply '.' with nothing else, then the '.' is actually displayed. Otherwise the '.' is dropped, and the other characters in the delimiter are displayed instead. Here's how we'd display hours, minutes, and seconds with three decimal places after the seconds: 3758.5 seconds, "*:060:060.01000" -> "1:02:38.500" Similarly, here's how we'd display the fractional part of seconds as film frames (24 per second) instead of milliseconds: 3758.5 seconds, "*:060:060 and .24 frames" -> "1:02:38 and 12 frames" Note that the decimal '.' is associated with the delimeter, not with the 24. Additionally, the special character '#' can be used in place of a number to represent the current sample rate. Use '0#' to add leading zeros to that field. For example: 3758.5 seconds, "*:060:060+.#samples" -> "1:02:38+22050samples" (Almost) Finally, there is a rule that allows you to change the units into something other than seconds. To do this, put a "|" character on the far right, followed by a number specifying the scaling factor. As an exception to previous rules, decimal points are allowed in the final scaling factor - the period is not interpreted as it would be before the "|" character. (This is fine, because all previous fields must be integers to make sense.) Anyway, if you include a scaling factor after a "|", the time will be multiplied by this factor before it is formatted. For example, to express the current time in NTSC frames (~29.97 fps), you could use the following formatting: 3758.5 seconds, "*.01000 frames|29.97002997" -> "112642.358 frames" Finally there is a further special character that can be used after a "|" and that is "N". This applies special rule for NTSC drop-frame timecode. Summary of format string rules: - The characters '0-9', '*', and '#' are numeric. Any sequence of these characters is treated as defining a new field by specifying its range. All other characters become delimiters between fields. (The one exception is that '.' is treated as numeric after the optional '|'.) - A field with a range of '*', which only makes sense as the leftmost field, means the field should display as large a number as necessary. (Note: this no longer makes sense here and applies to a previous version). - The character '#' represents the current sample rate. - If a field specifier beings with a leading zero, it will be formatted with leading zeros, too - enough to display the maximum value that field can display. So the number 7 in a field specified as '01000' would be formatted as '007'. Bond. James Bond. - Any non-numeric characters before the first field are treated as a prefix, and will be displayed to the left of the first field. - A delimiter ending in '.' is treated specially. All fields after this delimeter are fractional fields, after the decimal point. - The '|' character is treated as a special delimiter. The number to the right of this character (which is allowed to contain a decimal point) is treated as a scaling factor. The time is multiplied by this factor before multiplying. - The special character 'N' after '|' is only used for NTSC drop-frame. *******************************************************************//** \class TimeTextCtrlAx \brief TimeTextCtrlAx gives the TimeTextCtrl Accessibility. *******************************************************************//** \class TimeConverter \brief TimeConverter has all the time conversion and snapping functionality that used to live in TimeTextCtrl. The idea is to have a GUI-less class which can do the conversions, so that we can use it in sanpping without having a window created each time. *//****************************************************************//** \class BuiltinFormatString \brief BuiltinFormatString is a structure used in the TimeTextCtrl and holds both a descriptive name for the string format and a printf inspired style format string, optimised for displaying time in different formats. *//****************************************************************//** \class TimeField \brief TimeField is a class used in the TimeTextCtrl *//****************************************************************//** \class DigitInfo \brief DigitInfo is a class used in the TimeTextCtrl **********************************************************************/ #include "../Audacity.h" #include "../AudacityApp.h" #include "TimeTextCtrl.h" #include "../Theme.h" #include "../AllThemeResources.h" #include "../AColor.h" #include #include #include #include #include #include #include #include #include #ifdef _DEBUG #ifdef _MSC_VER #undef THIS_FILE static char*THIS_FILE= __FILE__; #define new new(_NORMAL_BLOCK, THIS_FILE, __LINE__) #endif #endif #define ID_MENU 9800 // Custom events DEFINE_EVENT_TYPE(EVT_TIMETEXTCTRL_UPDATED) BEGIN_EVENT_TABLE(TimeTextCtrl, wxControl) EVT_ERASE_BACKGROUND(TimeTextCtrl::OnErase) EVT_PAINT(TimeTextCtrl::OnPaint) EVT_CONTEXT_MENU(TimeTextCtrl::OnContext) EVT_MENU_RANGE(ID_MENU, ID_MENU+100, TimeTextCtrl::OnMenu) EVT_MOUSE_EVENTS(TimeTextCtrl::OnMouse) EVT_KEY_DOWN(TimeTextCtrl::OnKeyDown) EVT_SET_FOCUS(TimeTextCtrl::OnFocus) EVT_KILL_FOCUS(TimeTextCtrl::OnFocus) EVT_COMMAND(wxID_ANY, EVT_CAPTURE_KEY, TimeTextCtrl::OnCaptureKey) END_EVENT_TABLE() IMPLEMENT_CLASS(TimeTextCtrl, wxControl) class TimeField { public: TimeField(bool _frac, int _base, int _range, bool _zeropad) { frac = _frac; base = _base; range = _range; zeropad = _zeropad; digits = 0; } bool frac; // is it a fractional field int base; // divide by this (multiply, after decimal point) int range; // then take modulo this int digits; int pos; // Index of this field in the ValueString int fieldX; // x-position of the field on-screen int fieldW; // width of the field on-screen int labelX; // x-position of the label on-screen bool zeropad; wxString label; wxString formatStr; wxString str; void CreateDigitFormatStr() { if (range > 1) digits = (int)ceil(log10(range-1.0)); else digits = 5; // hack: default if (zeropad && range>1) formatStr.Printf(wxT("%%0%dd"), digits); // ex. "%03d" if digits is 3 else { formatStr.Printf(wxT("%%0%dd"), digits); } } }; class DigitInfo { public: DigitInfo(int _field, int _index, int _pos, wxRect _box) { field = _field; index = _index; pos = _pos; digitBox = _box; } int field; // Which field int index; // Index of this digit within the field int pos; // Position in the ValueString wxRect digitBox; }; #include WX_DEFINE_OBJARRAY(TimeFieldArray); WX_DEFINE_OBJARRAY(DigitInfoArray); TimeTextCtrl::TimeTextCtrl(wxWindow *parent, wxWindowID id, wxString formatString, double timeValue, double sampleRate, const wxPoint &pos, const wxSize &size, bool autoPos): wxControl(parent, id, pos, size, wxSUNKEN_BORDER | wxWANTS_CHARS), mTimeValue(timeValue), mFormatString(formatString), mBackgroundBitmap(NULL), mDigitFont(NULL), mLabelFont(NULL), mFocusedDigit(0), mLastField(1), mAutoPos(autoPos) { mConverter.mSampleRate = sampleRate; /* i18n-hint: Name of time display format that shows time in seconds */ BuiltinFormatStrings[0].name = _("seconds"); /* i18n-hint: Format string for displaying time in seconds. Change the comma * in the middle to the 1000s separator for your locale, and the 'seconds' * on the end to the word for seconds. Don't change the numbers. */ BuiltinFormatStrings[0].formatStr = _("01000,01000 seconds"); /* i18n-hint: Name of time display format that shows time in hours, minutes * and seconds */ BuiltinFormatStrings[1].name = _("hh:mm:ss"); /* i18n-hint: Format string for displaying time in hours, minutes and * seconds. Change the 'h' to the abbreviation for hours, 'm' to the * abbreviation for minutes and 's' to the abbreviation for seconds. Don't * change the numbers unless there aren't 60 seconds in a minute in your * locale */ BuiltinFormatStrings[1].formatStr = _("0100 h 060 m 060 s"); /* i18n-hint: Name of time display format that shows time in days, hours, * minutes and seconds */ BuiltinFormatStrings[2].name = _("dd:hh:mm:ss"); /* i18n-hint: Format string for displaying time in days, hours, minutes and * seconds. Change the 'days' to the word for days, 'h' to the abbreviation * for hours, 'm' to the abbreviation for minutes and 's' to the * abbreviation for seconds. Don't change the numbers unless there aren't * 24 hours in a day in your locale */ BuiltinFormatStrings[2].formatStr = _("0100 days 024 h 060 m 060 s"); /* i18n-hint: Name of time display format that shows time in hours, * minutes, seconds and hundredths of a second (1/100 second) */ BuiltinFormatStrings[3].name = _("hh:mm:ss + hundredths"); /* i18n-hint: Format string for displaying time in hours, minutes, seconds * and hundredths of a second. Change the 'h' to the abbreviation for hours, * 'm' to the abbreviation for minutes and 's' to the abbreviation for seconds (the * hundredths are shown as decimal seconds) . Don't change the numbers * unless there aren't 60 minutes in an hour in your locale */ BuiltinFormatStrings[3].formatStr =_("0100 h 060 m 060.0100 s"); /* i18n-hint: Name of time display format that shows time in hours, * minutes, seconds and milliseconds (1/1000 second) */ BuiltinFormatStrings[4].name = _("hh:mm:ss + milliseconds"); /* i18n-hint: Format string for displaying time in hours, minutes, seconds * and milliseconds. Change the 'h' to the abbreviation for hours, 'm' to the * abbreviation for minutes and 's' to the abbreviation for seconds (the * milliseconds are shown as decimal seconds) . Don't change the numbers * unless there aren't 60 minutes in an hour in your locale */ BuiltinFormatStrings[4].formatStr =_("0100 h 060 m 060.01000 s"); /* i18n-hint: Name of time display format that shows time in hours, * minutes, seconds and samples (at the current project sample rate) */ BuiltinFormatStrings[5].name = _("hh:mm:ss + samples"); /* i18n-hint: Format string for displaying time in hours, minutes, seconds * and samples. Change the 'h' to the abbreviation for hours, 'm' to the * abbreviation for minutes, 's' to the abbreviation for seconds and * translate samples . Don't change the numbers * unless there aren't 60 seconds in a minute in your locale */ BuiltinFormatStrings[5].formatStr = _("0100 h 060 m 060 s+.# samples"); /* i18n-hint: Name of time display format that shows time in samples (at the * current project sample rate) */ BuiltinFormatStrings[6].name = _("samples"); /* i18n-hint: Format string for displaying time in samples (lots of samples). * Change the ',' to the 1000s separator for your locale, and translate * samples. If 1000s aren't a base multiple for your number system, then you * can change the numbers to an appropriate one, and put a 0 on the front */ BuiltinFormatStrings[6].formatStr = _("01000,01000,01000 samples|#"); /* i18n-hint: Name of time display format that shows time in hours, minutes, * seconds and frames at 24 frames per second (commonly used for films) */ BuiltinFormatStrings[7].name = _("hh:mm:ss + film frames (24 fps)"); /* i18n-hint: Format string for displaying time in hours, minutes, seconds * and frames at 24 frames per second. Change the 'h' to the abbreviation * for hours, 'm' to the abbreviation for minutes, 's' to the abbreviation * for seconds and translate 'frames' . Don't change the numbers * unless there aren't 60 seconds in a minute in your locale */ BuiltinFormatStrings[7].formatStr = _("0100 h 060 m 060 s+.24 frames"); /* i18n-hint: Name of time display format that shows time in frames (lots of * frames) at 24 frames per second (commonly used for films) */ BuiltinFormatStrings[8].name = _("film frames (24 fps)"); /* i18n-hint: Format string for displaying time in frames at 24 frames per * second. Translate 'frames' and leave the rest alone */ BuiltinFormatStrings[8].formatStr = _("01000,01000 frames|24"); /* i18n-hint: Name of time display format that shows time in hours, minutes, * seconds and frames at NTSC TV drop-frame rate (used for American / * Japanese TV, and very odd) */ BuiltinFormatStrings[9].name = _("hh:mm:ss + NTSC drop frames"); /* i18n-hint: Format string for displaying time in hours, minutes, seconds * and frames with NTSC drop frames. Change the 'h' to the abbreviation * for hours, 'm' to the abbreviation for minutes, 's' to the abbreviation * for seconds and translate 'frames'. Leave the |N alone, it's important! */ BuiltinFormatStrings[9].formatStr = _("0100 h 060 m 060 s+.30 frames|N"); /* i18n-hint: Name of time display format that shows time in hours, minutes, * seconds and frames at NTSC TV non-drop-frame rate (used for American / * Japanese TV, and doesn't quite match wall time */ BuiltinFormatStrings[10].name = _("hh:mm:ss + NTSC non-drop frames"); /* i18n-hint: Format string for displaying time in hours, minutes, seconds * and frames with NTSC drop frames. Change the 'h' to the abbreviation * for hours, 'm' to the abbreviation for minutes, 's' to the abbreviation * for seconds and translate 'frames'. Leave the | .999000999 alone, * the whole things really is slightly off-speed! */ BuiltinFormatStrings[10].formatStr = _("0100 h 060 m 060 s+.030 frames| .999000999"); /* i18n-hint: Name of time display format that shows time in frames at NTSC * TV frame rate (used for American / Japanese TV */ BuiltinFormatStrings[11].name = _("NTSC frames"); /* i18n-hint: Format string for displaying time in frames with NTSC frames. * Translate 'frames' and leave the rest alone. That really is the frame * rate! */ BuiltinFormatStrings[11].formatStr = _("01000,01000 frames|29.97002997"); /* i18n-hint: Name of time display format that shows time in hours, minutes, * seconds and frames at PAL TV frame rate (used for European TV) */ BuiltinFormatStrings[12].name = _("hh:mm:ss + PAL frames (25 fps)"); /* i18n-hint: Format string for displaying time in hours, minutes, seconds * and frames with PAL TV frames. Change the 'h' to the abbreviation * for hours, 'm' to the abbreviation for minutes, 's' to the abbreviation * for seconds and translate 'frames'. Nice simple time code! */ BuiltinFormatStrings[12].formatStr = _("0100 h 060 m 060 s+.25 frames"); /* i18n-hint: Name of time display format that shows time in frames at PAL * TV frame rate (used for European TV */ BuiltinFormatStrings[13].name = _("PAL frames (25 fps)"); /* i18n-hint: Format string for displaying time in frames with NTSC frames. * Translate 'frames' and leave the rest alone. */ BuiltinFormatStrings[13].formatStr = _("01000,01000 frames|25"); /* i18n-hint: Name of time display format that shows time in hours, minutes, * seconds and frames at CD Audio frame rate (75 frames per second) */ BuiltinFormatStrings[14].name = _("hh:mm:ss + CDDA frames (75 fps)"); /* i18n-hint: Format string for displaying time in hours, minutes, seconds * and frames with CD Audio frames. Change the 'h' to the abbreviation * for hours, 'm' to the abbreviation for minutes, 's' to the abbreviation * for seconds and translate 'frames'. */ BuiltinFormatStrings[14].formatStr = _("0100 h 060 m 060 s+.75 frames"); /* i18n-hint: Name of time display format that shows time in frames at CD * Audio frame rate (75 frames per second) */ BuiltinFormatStrings[15].name = _("CDDA frames (75 fps)"); /* i18n-hint: Format string for displaying time in frames with CD Audio * frames. Translate 'frames' and leave the rest alone */ BuiltinFormatStrings[15].formatStr = _("01000,01000 frames|75"); mDigitBoxW = 10; mDigitBoxH = 16; mMenuEnabled = true; mButtonWidth = 9; mConverter.ParseFormatString( mFormatString); Layout(); Fit(); ValueToControls(); //mchinen - aug 15 09 - this seems to put the mTimeValue back to zero, and do nothing else. //ControlsToValue(); #if wxUSE_ACCESSIBILITY SetLabel(wxT("")); SetName(wxT("")); SetAccessible(new TimeTextCtrlAx(this)); #endif } TimeTextCtrl::~TimeTextCtrl() { wxCommandEvent e(EVT_RELEASE_KEYBOARD); e.SetEventObject(this); GetParent()->GetEventHandler()->ProcessEvent(e); if (mBackgroundBitmap) delete mBackgroundBitmap; if (mDigitFont) delete mDigitFont; if (mLabelFont) delete mLabelFont; } // Set the focus to the first (left-most) non-zero digit // If all digits are zero, the right-most position is focused void TimeTextCtrl::UpdateAutoFocus() { if (!mAutoPos) return; mFocusedDigit = 0; while (mFocusedDigit < ((int)mDigits.GetCount() - 1)) { wxChar dgt = mConverter.mValueString[mDigits[mFocusedDigit].pos]; if (dgt != '0') { break; } mFocusedDigit++; } } void TimeTextCtrl::SetFormatString(wxString formatString) { mFormatString = formatString; mConverter.ParseFormatString( mFormatString); Layout(); Fit(); ValueToControls(); ControlsToValue(); UpdateAutoFocus(); } void TimeTextCtrl::SetSampleRate(double sampleRate) { mConverter.mSampleRate = sampleRate; mConverter.ParseFormatString( mFormatString); Layout(); Fit(); ValueToControls(); ControlsToValue(); } void TimeTextCtrl::SetTimeValue(double newTime) { mTimeValue = newTime; ValueToControls(); ControlsToValue(); } void TimeTextCtrl::Increment() { mFocusedDigit = mDigits.GetCount() - 1; Increase(1); } void TimeTextCtrl::Decrement() { mFocusedDigit = mDigits.GetCount() - 1; Decrease(1); } void TimeTextCtrl::EnableMenu(bool enable) { #if wxUSE_TOOLTIPS wxString tip(_("Use right mouse button or context key to change format")); if (enable) SetToolTip(tip); else { wxToolTip *tt = GetToolTip(); if (tt && tt->GetTip() == tip) SetToolTip(NULL); } #endif mMenuEnabled = enable; mButtonWidth = enable ? 9 : 0; Layout(); Fit(); } const double TimeTextCtrl::GetTimeValue() { ControlsToValue(); return mTimeValue; } wxString TimeTextCtrl::GetFormatString() { return mFormatString; } int TimeTextCtrl::GetFormatIndex() { int ndx = 1; int i; for (i = 0; i < TimeTextCtrl::GetNumBuiltins(); i++) { if (mFormatString == TimeTextCtrl::GetBuiltinFormat(i)) { ndx = i; break; } } return ndx; } int TimeTextCtrl::GetNumBuiltins() { return (sizeof(BuiltinFormatStrings) / sizeof(BuiltinFormatStrings[0])); } wxString TimeTextCtrl::GetBuiltinName(const int index) { if (index >= 0 && index < GetNumBuiltins()) return BuiltinFormatStrings[index].name; else return wxT(""); } wxString TimeTextCtrl::GetBuiltinFormat(const int index) { if (index >= 0 && index < GetNumBuiltins()) return BuiltinFormatStrings[index].formatStr; else return wxT(""); } wxString TimeTextCtrl::GetBuiltinFormat(const wxString &name) { int ndx = 1; int i; for (i=0; i= '0' && format[i] <='9') || format[i] == wxT('*') || format[i] == wxT('#')) { numStr += format[i]; if (delimStr != wxT("")) handleDelim = true; } else { delimStr += format[i]; if (numStr != wxT("")) handleNum = true; } if (i == format.Length() - 1) { if (numStr != wxT("")) handleNum = true; if (delimStr != wxT("")) handleDelim = true; } if (handleNum) { bool zeropad = false; long range = 0; if (numStr.Right(1) == wxT("#")) range = (long int)mSampleRate; else if (numStr.Right(1) != wxT("*")) { numStr.ToLong(&range); } if (numStr.GetChar(0)=='0' && numStr.Length()>1) zeropad = true; // Hack: always zeropad zeropad = true; if (inFrac) { int base = fracMult * range; mFields.Add(TimeField(inFrac, base, range, zeropad)); fracMult *= range; numFracFields++; } else { unsigned int j; for(j=0; j 1) delimStr = delimStr.BeforeLast('.'); } if (inFrac) { if (numFracFields == 0) { // Should never happen return; } if (handleNum && numFracFields > 1) mFields[mFields.GetCount()-2].label = delimStr; else mFields[mFields.GetCount()-1].label = delimStr; } else { if (numWholeFields == 0) mPrefix = delimStr; else { mFields[numWholeFields-1].label = delimStr; } } if (goToFrac) inFrac = true; delimStr = wxT(""); } } for(i=0; i GetNumBuiltins()) { event.Skip(); return; } SetFormatString(GetBuiltinFormat(id)); wxCommandEvent e(EVT_TIMETEXTCTRL_UPDATED, GetId()); e.SetInt(id); e.SetString(GetBuiltinName(id)); GetParent()->GetEventHandler()->AddPendingEvent(e); } void TimeTextCtrl::OnContext(wxContextMenuEvent &event) { wxMenu menu; int i; if (!mMenuEnabled) { event.Skip(); return; } SetFocus(); for(i=0; i= mWidth) { wxContextMenuEvent e; OnContext(e); } else if (event.LeftDown()) { SetFocus(); int bestDist = 9999; unsigned int i; mFocusedDigit = 0; for(i=0; i 0 ? (double)event.m_wheelDelta : 120.0) + mScrollRemainder; mScrollRemainder = steps - floor(steps); steps = floor(steps); if (steps < 0.0) { Decrease((int)-steps); Updated(); } else { Increase((int)steps); Updated(); } } } void TimeTextCtrl::OnFocus(wxFocusEvent &event) { wxCommandEvent e(EVT_CAPTURE_KEYBOARD); if (event.GetEventType() == wxEVT_KILL_FOCUS) { e.SetEventType(EVT_RELEASE_KEYBOARD); } e.SetEventObject(this); GetParent()->GetEventHandler()->ProcessEvent(e); Refresh(false); } void TimeTextCtrl::OnCaptureKey(wxCommandEvent &event) { wxKeyEvent *kevent = (wxKeyEvent *)event.GetEventObject(); int keyCode = kevent->GetKeyCode(); // Convert numeric keypad entries. if ((keyCode >= WXK_NUMPAD0) && (keyCode <= WXK_NUMPAD9)) keyCode -= WXK_NUMPAD0 - '0'; switch (keyCode) { case WXK_BACK: case WXK_LEFT: case WXK_RIGHT: case WXK_HOME: case WXK_END: case WXK_UP: case WXK_DOWN: case WXK_TAB: case WXK_RETURN: case WXK_NUMPAD_ENTER: return; default: if (keyCode >= '0' && keyCode <= '9') return; } event.Skip(); return; } void TimeTextCtrl::OnKeyDown(wxKeyEvent &event) { event.Skip(false); int keyCode = event.GetKeyCode(); int digit = mFocusedDigit; if (mFocusedDigit < 0) mFocusedDigit = 0; if (mFocusedDigit >= (int)mDigits.GetCount()) mFocusedDigit = mDigits.GetCount()-1; // Convert numeric keypad entries. if ((keyCode >= WXK_NUMPAD0) && (keyCode <= WXK_NUMPAD9)) keyCode -= WXK_NUMPAD0 - '0'; if (keyCode >= '0' && keyCode <= '9') { mConverter.mValueString[mDigits[mFocusedDigit].pos] = wxChar(keyCode); ControlsToValue(); ValueToControls(); mFocusedDigit = (mFocusedDigit+1)%(mDigits.GetCount()); Updated(); } else if (keyCode == WXK_BACK) { // Moves left, replaces that char with '0', stays there... mFocusedDigit--; mFocusedDigit += mDigits.GetCount(); mFocusedDigit %= mDigits.GetCount(); mConverter.mValueString[mDigits[mFocusedDigit].pos] = '0'; ControlsToValue(); ValueToControls(); Updated(); } else if (keyCode == WXK_LEFT) { mFocusedDigit--; mFocusedDigit += mDigits.GetCount(); mFocusedDigit %= mDigits.GetCount(); Refresh(); } else if (keyCode == WXK_RIGHT) { mFocusedDigit++; mFocusedDigit %= mDigits.GetCount(); Refresh(); } else if (keyCode == WXK_HOME) { mFocusedDigit = 0; Refresh(); } else if (keyCode == WXK_END) { mFocusedDigit = mDigits.GetCount() - 1; Refresh(); } else if (keyCode == WXK_UP) { Increase(1); Updated(); } else if (keyCode == WXK_DOWN) { Decrease(1); Updated(); } else if (keyCode == WXK_TAB) { wxWindow *parent = GetParent(); wxNavigationKeyEvent nevent; nevent.SetWindowChange(event.ControlDown()); nevent.SetDirection(!event.ShiftDown()); nevent.SetEventObject(parent); nevent.SetCurrentFocus(parent); GetParent()->ProcessEvent(nevent); } else if (keyCode == WXK_RETURN || keyCode == WXK_NUMPAD_ENTER) { wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow); wxWindow *def = tlw->GetDefaultItem(); if (def && def->IsEnabled()) { wxCommandEvent cevent(wxEVT_COMMAND_BUTTON_CLICKED, def->GetId()); GetParent()->ProcessEvent(cevent); } } else { event.Skip(); return; } if (digit != mFocusedDigit) { SetFieldFocus(mFocusedDigit); } event.Skip(false); } void TimeTextCtrl::SetFieldFocus(int digit) { #if wxUSE_ACCESSIBILITY mFocusedDigit = digit; mLastField = mDigits[mFocusedDigit].field + 1; // This looks strange (and it is), but it was the only way I could come // up with that allowed Jaws, Window-Eyes, and NVDA to read the control // somewhat the same. See TimeTextCtrlAx below for even more odd looking // hackery. // // If you change SetFieldFocus(), Updated(), or TimeTextCtrlAx, make sure // you test with Jaws, Window-Eyes, and NVDA. GetAccessible()->NotifyEvent(wxACC_EVENT_OBJECT_FOCUS, this, wxOBJID_CLIENT, 0); GetAccessible()->NotifyEvent(wxACC_EVENT_OBJECT_FOCUS, this, wxOBJID_CLIENT, mFocusedDigit + 1); #endif } void TimeTextCtrl::Updated() { wxCommandEvent event(wxEVT_COMMAND_TEXT_UPDATED, GetId()); event.SetEventObject(this); GetEventHandler()->ProcessEvent(event); #if wxUSE_ACCESSIBILITY GetAccessible()->NotifyEvent(wxACC_EVENT_OBJECT_NAMECHANGE, this, wxOBJID_CLIENT, mFocusedDigit + 1); SetFieldFocus(mFocusedDigit); #endif } void TimeTextCtrl::Increase(int steps) { // Slightly messy trick to save us some prefixing. TimeFieldArray & mFields = mConverter.mFields; while(steps > 0) { for(unsigned int i=0; i=mFields[i].pos) && (mDigits[mFocusedDigit].pos 0) { for(unsigned int i=0; i=mFields[i].pos) && (mDigits[mFocusedDigit].pos= 1800) { frames -= 1800; mins++; addMins = frames/1798; frames -= addMins*1798; mins += addMins; secs = frames/30; frames -= secs*30; frames += 2; if( frames >= 30 ) { secs++; frames -= 30; } } else { secs = frames/30; frames -= secs*30; } t_int = mins * 60 + secs; t_frac = frames / 30.; } for(i=0; i 0) value = value % mFields[i].range; } else { value = (t_int / mFields[i].base); if (mFields[i].range > 0) value = value % mFields[i].range; } wxString field = wxString::Format(mFields[i].formatStr, value); mValueString += field; mValueString += mFields[i].label; } } void TimeTextCtrl::ControlsToValue() { mTimeValue = mConverter.ControlsToValue(); } double TimeConverter::ControlsToValue() { unsigned int i; double t = 0.0; for(i=0; i 0 ) { frames += 1800; addMins = mins - 1; } frames += addMins * 1798; t_int -= mins*60; if( mins == 0 ) //first min of a block of 10, don't drop frames 0 and 1 frames += t_int * 30 + t_frac*30.; else { //drop frames 0 and 1 of first seconds of these minutes if( t_int > 0 ) frames += 28 + (t_int-1)*30 + t_frac*30.; else frames += t_frac*30. -2.; } t = frames * 1.001 / 30.; } return t; } #if wxUSE_ACCESSIBILITY TimeTextCtrlAx::TimeTextCtrlAx(TimeTextCtrl *ctrl) : wxWindowAccessible(ctrl) { mCtrl = ctrl; mLastField = -1; mLastDigit = -1; } TimeTextCtrlAx::~TimeTextCtrlAx() { } // Performs the default action. childId is 0 (the action for this object) // or > 0 (the action for a child). // Return wxACC_NOT_SUPPORTED if there is no default action for this // window (e.g. an edit control). wxAccStatus TimeTextCtrlAx::DoDefaultAction(int childId) { return wxACC_NOT_SUPPORTED; } // Retrieves the address of an IDispatch interface for the specified child. // All objects must support this property. wxAccStatus TimeTextCtrlAx::GetChild(int childId, wxAccessible **child) { if (childId == wxACC_SELF) { *child = this; } else { *child = NULL; } return wxACC_OK; } // Gets the number of children. wxAccStatus TimeTextCtrlAx::GetChildCount(int *childCount) { *childCount = mCtrl->mDigits.GetCount(); return wxACC_OK; } // Gets the default action for this object (0) or > 0 (the action for // a child). Return wxACC_OK even if there is no action. actionName // is the action, or the empty string if there is no action. The // retrieved string describes the action that is performed on an // object, not what the object does as a result. For example, a // toolbar button that prints a document has a default action of // "Press" rather than "Prints the current document." wxAccStatus TimeTextCtrlAx::GetDefaultAction(int childId, wxString *actionName) { actionName->Clear(); return wxACC_OK; } // Returns the description for this object or a child. wxAccStatus TimeTextCtrlAx::GetDescription(int childId, wxString *description) { description->Clear(); return wxACC_OK; } // Gets the window with the keyboard focus. // If childId is 0 and child is NULL, no object in // this subhierarchy has the focus. // If this object has the focus, child should be 'this'. wxAccStatus TimeTextCtrlAx::GetFocus(int *childId, wxAccessible **child) { *childId = mCtrl->GetFocusedDigit(); *child = this; return wxACC_OK; } // Returns help text for this object or a child, similar to tooltip text. wxAccStatus TimeTextCtrlAx::GetHelpText(int childId, wxString *helpText) { #if wxUSE_TOOLTIPS wxToolTip *pTip = mCtrl->GetToolTip(); if (pTip) { *helpText = pTip->GetTip(); } return wxACC_OK; #else helpText->Clear(); return wxACC_NOT_SUPPORTED; #endif } // Returns the keyboard shortcut for this object or child. // Return e.g. ALT+K wxAccStatus TimeTextCtrlAx::GetKeyboardShortcut(int childId, wxString *shortcut) { shortcut->Clear(); return wxACC_OK; } // Returns the rectangle for this object (id = 0) or a child element (id > 0). // rect is in screen coordinates. wxAccStatus TimeTextCtrlAx::GetLocation(wxRect & rect, int elementId) { rect = mCtrl->GetRect(); if (elementId != wxACC_SELF) { // rect.x += mCtrl->mFields[elementId - 1].fieldX; // rect.width = mCtrl->mFields[elementId - 1].fieldW; rect = mCtrl->mDigits[elementId - 1].digitBox; } rect.SetPosition(mCtrl->GetParent()->ClientToScreen(rect.GetPosition())); return wxACC_OK; } // Gets the name of the specified object. wxAccStatus TimeTextCtrlAx::GetName(int childId, wxString *name) { // Slightly messy trick to save us some prefixing. TimeFieldArray & mFields = mCtrl->mConverter.mFields; wxString value = mCtrl->GetTimeString(); int field = mCtrl->GetFocusedField(); // Return the entire time string including the control label // when the requested child ID is wxACC_SELF. (Mainly when // the control gets the focus.) if (childId == wxACC_SELF) { *name = mCtrl->GetName(); if (name->IsEmpty()) { *name = mCtrl->GetLabel(); } *name += wxT(" ") + mCtrl->GetTimeString(); } // The user has moved from one field of the time to another so // report the value of the field and the field's label. else if (mLastField != field) { wxString label = mFields[field - 1].label; int cnt = mFields.GetCount(); wxString decimal = wxLocale::GetInfo(wxLOCALE_DECIMAL_POINT, wxLOCALE_CAT_NUMBER); // If the new field is the last field, then check it to see if // it represents fractions of a second. if (field > 1 && field == cnt) { if (mFields[field - 2].label == decimal) { int digits = mFields[field - 1].digits; if (digits == 2) { label = _("centiseconds"); } else if (digits == 3) { label = _("milliseconds"); } } } // If the field following this one represents fractions of a // second then use that label instead of the decimal point. else if (label == decimal && field == cnt - 1) { label = mFields[field].label; } *name = mFields[field - 1].str + wxT(" ") + label + wxT(" ") + mCtrl->GetTimeString().at(mCtrl->mDigits[childId - 1].pos); mLastField = field; mLastDigit = childId; } // The user has moved from one digit to another within a field so // just report the digit under the cursor. else if (mLastDigit != childId) { *name = mCtrl->GetTimeString().at(mCtrl->mDigits[childId - 1].pos); mLastDigit = childId; } // The user has updated the value of a field, so report the field's // value only. else { *name = mFields[field - 1].str; } return wxACC_OK; } // Returns a role constant. wxAccStatus TimeTextCtrlAx::GetRole(int childId, wxAccRole *role) { *role = wxROLE_SYSTEM_STATICTEXT; return wxACC_OK; } // Gets a variant representing the selected children // of this object. // Acceptable values: // - a null variant (IsNull() returns TRUE) // - a list variant (GetType() == wxT("list")) // - an integer representing the selected child element, // or 0 if this object is selected (GetType() == wxT("long")) // - a "void*" pointer to a wxAccessible child object wxAccStatus TimeTextCtrlAx::GetSelections(wxVariant *selections) { return wxACC_NOT_IMPLEMENTED; } // Returns a state constant. wxAccStatus TimeTextCtrlAx::GetState(int childId, long *state) { *state = wxACC_STATE_SYSTEM_FOCUSABLE; *state |= (mCtrl == wxWindow::FindFocus() ? wxACC_STATE_SYSTEM_FOCUSED : 0); return wxACC_OK; } // Returns a localized string representing the value for the object // or child. wxAccStatus TimeTextCtrlAx::GetValue(int childId, wxString *strValue) { return wxACC_NOT_IMPLEMENTED; } #endif // Indentation settings for Vim and Emacs. // Please do not modify past this point. // // Local Variables: // c-basic-offset: 3 // indent-tabs-mode: nil // End: // // vim: et sts=3 sw=3 //