1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-25 08:38:39 +02:00
audacity/src/widgets/TimeTextCtrl.cpp
2010-09-01 22:51:57 +00:00

1673 lines
51 KiB
C++

/**********************************************************************
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 "../Sequence.h" // for sampleCount
#include "../Theme.h"
#include "../AllThemeResources.h"
#include "../AColor.h"
#include <math.h>
#include <wx/dcmemory.h>
#include <wx/font.h>
#include <wx/intl.h>
#include <wx/menu.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/tooltip.h>
#include <wx/toplevel.h>
#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/arrimpl.cpp>
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<TimeTextCtrl::GetNumBuiltins(); i++) {
if (name == TimeTextCtrl::GetBuiltinName(i)) {
ndx = i;
break;
}
}
return TimeTextCtrl::GetBuiltinFormat(ndx);
}
TimeConverter::TimeConverter()
{
mPrefix = wxT("");
mValueTemplate = wxT("");
mValueMask = wxT("");
mValueString = wxT("");
mScalingFactor = 1.0f;
mSampleRate = 1.0f;
mNtscDrop = false;
}
void TimeConverter::ParseFormatString( const wxString & format)
{
mPrefix = wxT("");
mFields.Clear();
mScalingFactor = 1.0;
bool inFrac = false;
int fracMult = 1;
int numWholeFields = 0;
int numFracFields = 0;
wxString numStr;
wxString delimStr;
unsigned int i;
mNtscDrop = false;
for(i=0; i<format.Length(); i++) {
bool handleDelim = false;
bool handleNum = false;
if (format[i] == '|') {
wxString remainder = format.Right(format.Length() - i - 1);
if (remainder == wxT("#"))
mScalingFactor = mSampleRate;
else if (remainder == wxT("N")) {
mNtscDrop = true;
}
else
remainder.ToDouble(&mScalingFactor);
i = format.Length()-1; // force break out of loop
if (delimStr != wxT(""))
handleDelim = true;
if (numStr != wxT(""))
handleNum = true;
}
else if ((format[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<mFields.GetCount(); j++)
mFields[j].base *= range;
mFields.Add(TimeField(inFrac, 1, range, zeropad));
numWholeFields++;
}
numStr = wxT("");
}
if (handleDelim) {
bool goToFrac = false;
if (!inFrac && delimStr[delimStr.Length()-1]=='.') {
goToFrac = true;
if (delimStr.Length() > 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<mFields.GetCount(); i++)
mFields[i].CreateDigitFormatStr();
int pos = 0;
int j;
mValueMask = wxT("");
mValueTemplate = wxT("");
mValueTemplate += mPrefix;
for(j=0; j<(int)mPrefix.Length(); j++)
mValueMask += wxT(".");
pos += mPrefix.Length();
for(i=0; i<mFields.GetCount(); i++) {
mFields[i].pos = pos;
pos += mFields[i].digits;
for(j=0; j<mFields[i].digits; j++) {
mValueTemplate += wxT("0");
mValueMask += wxT("0");
}
pos += mFields[i].label.Length();
mValueTemplate += mFields[i].label;
for(j=0; j<(int)mFields[i].label.Length(); j++)
mValueMask += wxT(".");
}
}
void TimeConverter::PrintDebugInfo()
{
unsigned int i;
printf("%s", (const char *)mPrefix.mb_str());
for(i=0; i<mFields.GetCount(); i++) {
if (mFields[i].frac) {
printf("(t * %d) %% %d '%s' ",
mFields[i].base,
mFields[i].range,
(const char *)mFields[i].label.mb_str());
}
else {
printf("(t / %d) %% %d '%s' ",
mFields[i].base,
mFields[i].range,
(const char *)mFields[i].label.mb_str());
}
}
printf("\n");
}
wxString TimeTextCtrl::GetTimeString()
{
ValueToControls();
return mConverter.mValueString;
}
bool TimeTextCtrl::Layout()
{
unsigned int i, j;
int x, pos;
wxMemoryDC memDC;
if (mBackgroundBitmap) {
delete mBackgroundBitmap;
mBackgroundBitmap = NULL;
}
// Placeholder bitmap so the memDC has something to reference
mBackgroundBitmap = new wxBitmap(1, 1);
memDC.SelectObject(*mBackgroundBitmap);
mDigits.Clear();
mBorderLeft = 1;
mBorderTop = 1;
mBorderRight = 1;
mBorderBottom = 1;
int fontSize = 4;
wxCoord strW, strH;
wxString exampleText = wxT("0");
// Keep making the font bigger until it's too big, then subtract one.
memDC.SetFont(wxFont(fontSize, wxFIXED, wxNORMAL, wxNORMAL));
memDC.GetTextExtent(exampleText, &strW, &strH);
while(strW <= mDigitBoxW && strH <= mDigitBoxH) {
fontSize++;
memDC.SetFont(wxFont(fontSize, wxFIXED, wxNORMAL, wxNORMAL));
memDC.GetTextExtent(exampleText, &strW, &strH);
}
fontSize--;
if (mDigitFont)
delete mDigitFont;
mDigitFont = new wxFont(fontSize, wxFIXED, wxNORMAL, wxNORMAL);
memDC.SetFont(*mDigitFont);
memDC.GetTextExtent(exampleText, &strW, &strH);
mDigitW = strW;
mDigitH = strH;
// The label font should be a little smaller
fontSize--;
if (mLabelFont)
delete mLabelFont;
mLabelFont = new wxFont(fontSize, wxFIXED, wxNORMAL, wxNORMAL);
// Figure out the x-position of each field and label in the box
x = mBorderLeft;
pos = 0;
memDC.SetFont(*mLabelFont);
memDC.GetTextExtent(mConverter.mPrefix, &strW, &strH);
x += strW;
pos += mConverter.mPrefix.Length();
// Slightly messy trick to save us some prefixing.
TimeFieldArray & mFields = mConverter.mFields;
for(i=0; i<mFields.GetCount(); i++) {
mFields[i].fieldX = x;
for(j=0; j<(unsigned int)mFields[i].digits; j++) {
mDigits.Add(DigitInfo(i, j, pos, wxRect(x, mBorderTop,
mDigitBoxW, mDigitBoxH)));
x += mDigitBoxW;
pos++;
}
mFields[i].labelX = x;
memDC.GetTextExtent(mFields[i].label, &strW, &strH);
pos += mFields[i].label.Length();
x += strW;
mFields[i].fieldW = x;
}
mWidth = x + mBorderRight;
mHeight = mDigitBoxH + mBorderTop + mBorderBottom;
// Draw the background bitmap - it contains black boxes where
// all of the digits go and all of the other text
wxBrush Brush;
delete mBackgroundBitmap; // Delete placeholder
mBackgroundBitmap = new wxBitmap(mWidth + mButtonWidth, mHeight);
memDC.SelectObject(*mBackgroundBitmap);
memDC.SetBrush(*wxLIGHT_GREY_BRUSH);
memDC.SetPen(*wxTRANSPARENT_PEN);
memDC.DrawRectangle(0, 0, mWidth + mButtonWidth, mHeight);
int numberBottom = mBorderTop + (mDigitBoxH - mDigitH)/2 + mDigitH;
memDC.GetTextExtent(wxT("0"), &strW, &strH);
int labelTop = numberBottom - strH;
memDC.SetTextForeground(*wxBLACK);
memDC.SetTextBackground(*wxLIGHT_GREY);
memDC.DrawText(mConverter.mPrefix, mBorderLeft, labelTop);
theTheme.SetBrushColour( Brush, clrTimeBack );
memDC.SetBrush(Brush);
memDC.SetBrush(*wxLIGHT_GREY_BRUSH);
for(i=0; i<mDigits.GetCount(); i++)
memDC.DrawRectangle(mDigits[i].digitBox);
memDC.SetBrush( wxNullBrush );
for(i=0; i<mFields.GetCount(); i++)
memDC.DrawText(mFields[i].label,
mFields[i].labelX, labelTop);
if (mMenuEnabled) {
wxRect r(mWidth, 0, mButtonWidth - 1, mHeight - 1);
AColor::Bevel(memDC, true, r);
memDC.SetBrush(*wxBLACK_BRUSH);
memDC.SetPen(*wxBLACK_PEN);
AColor::Arrow(memDC,
mWidth + 1,
(mHeight / 2) - 2,
mButtonWidth - 2);
}
return true;
}
void TimeTextCtrl::Fit()
{
wxSize sz = GetSize();
wxSize csz = GetClientSize();
sz.x = mButtonWidth + mWidth + (sz.x - csz.x);
sz.y = mHeight + (sz.y - csz.y);
SetInitialSize(sz);
}
void TimeTextCtrl::OnErase(wxEraseEvent & event)
{
// Ignore it to prevent flashing
}
void TimeTextCtrl::OnPaint(wxPaintEvent &event)
{
wxPaintDC dc(this);
bool focused = (FindFocus() == this);
dc.DrawBitmap(*mBackgroundBitmap, 0, 0);
wxPen Pen;
wxBrush Brush;
if (focused) {
theTheme.SetPenColour( Pen, clrTimeFontFocus );
dc.SetPen(Pen);
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRectangle(0, 0, mWidth, mHeight);
dc.SetPen( wxNullPen );
}
dc.SetFont(*mDigitFont);
dc.SetTextForeground(theTheme.Colour( clrTimeFont ));
dc.SetTextBackground(theTheme.Colour( clrTimeBack ));
dc.SetPen(*wxTRANSPARENT_PEN);
theTheme.SetBrushColour( Brush , clrTimeBackFocus );
dc.SetBrush( Brush );
int i;
for(i=0; i<(int)mDigits.GetCount(); i++) {
wxRect box = mDigits[i].digitBox;
if (focused && mFocusedDigit == i) {
dc.DrawRectangle(box);
dc.SetTextForeground(theTheme.Colour( clrTimeFontFocus ));
dc.SetTextBackground(theTheme.Colour( clrTimeBackFocus ));
}
int pos = mDigits[i].pos;
wxString digit = mConverter.mValueString.Mid(pos, 1);
int x = box.x + (mDigitBoxW - mDigitW)/2;
int y = box.y + (mDigitBoxH - mDigitH)/2;
dc.DrawText(digit, x, y);
if (focused && mFocusedDigit == i) {
dc.SetTextForeground(theTheme.Colour( clrTimeFont ));
dc.SetTextBackground(theTheme.Colour( clrTimeBack ));
}
}
dc.SetPen( wxNullPen );
dc.SetBrush( wxNullBrush );
}
void TimeTextCtrl::OnMenu(wxCommandEvent &event)
{
int id = event.GetId() - ID_MENU;
if (!mMenuEnabled || id < 0 || id > 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<GetNumBuiltins(); i++) {
menu.AppendCheckItem(ID_MENU+i, GetBuiltinName(i));
if (mFormatString == GetBuiltinFormat(i))
menu.Check(ID_MENU+i, true);
}
PopupMenu(&menu, wxPoint(0, 0));
}
void TimeTextCtrl::OnMouse(wxMouseEvent &event)
{
if (event.LeftDown() && event.GetX() >= mWidth) {
wxContextMenuEvent e;
OnContext(e);
}
else if (event.LeftDown()) {
SetFocus();
int bestDist = 9999;
unsigned int i;
mFocusedDigit = 0;
for(i=0; i<mDigits.GetCount(); i++) {
int dist = abs(event.m_x - (mDigits[i].digitBox.x +
mDigits[i].digitBox.width/2));
if (dist < bestDist) {
mFocusedDigit = i;
bestDist = dist;
}
}
Refresh(false);
}
else if (event.RightDown() && mMenuEnabled) {
wxContextMenuEvent e;
OnContext(e);
}
else if( event.m_wheelRotation != 0 ) {
double steps = event.m_wheelRotation /
(event.m_wheelDelta > 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.GetCount(); i++) {
if( (mDigits[mFocusedDigit].pos>=mFields[i].pos) && (mDigits[mFocusedDigit].pos<mFields[i].pos+mFields[i].digits)) { //it's this field
if(!mConverter.mNtscDrop)
ControlsToValue();
else {
mConverter.mNtscDrop = false;
ControlsToValue();
mConverter.mNtscDrop = true;
}
mTimeValue *= mConverter.mScalingFactor;
double mult = pow(10.,mFields[i].digits-(mDigits[mFocusedDigit].pos-mFields[i].pos)-1);
if (mFields[i].frac)
mTimeValue += mult/(double)mFields[i].base;
else
mTimeValue += mult*(double)mFields[i].base;
if(mConverter.mNtscDrop) {
if( (mTimeValue - (int)mTimeValue)*30 < 2 )
if( (((int)mTimeValue)%60 == 0) && (((int)mTimeValue)%600 != 0) )
mTimeValue = (int)mTimeValue + 2./30.;
}
if(mTimeValue<0.)
mTimeValue=0.;
mTimeValue /= mConverter.mScalingFactor;
if(!mConverter.mNtscDrop)
ValueToControls();
else {
mConverter.mNtscDrop = false;
ValueToControls();
mConverter.mNtscDrop = true;
ControlsToValue();
}
break;
}
}
steps--;
}
ControlsToValue();
}
void TimeTextCtrl::Decrease(int steps)
{
// Slightly messy trick to save us some prefixing.
TimeFieldArray & mFields = mConverter.mFields;
while(steps > 0) {
for(unsigned int i=0; i<mFields.GetCount(); i++) {
if( (mDigits[mFocusedDigit].pos>=mFields[i].pos) && (mDigits[mFocusedDigit].pos<mFields[i].pos+mFields[i].digits)) { //it's this field
if(!mConverter.mNtscDrop)
ControlsToValue();
else {
mConverter.mNtscDrop = false;
ControlsToValue();
mConverter.mNtscDrop = true;
}
mTimeValue *= mConverter.mScalingFactor;
double mult = pow(10.,mFields[i].digits-(mDigits[mFocusedDigit].pos-mFields[i].pos)-1);
if (mFields[i].frac)
mTimeValue -= mult/(double)mFields[i].base;
else
mTimeValue -= mult*(double)mFields[i].base;
if(mConverter.mNtscDrop) {
if( (mTimeValue - (int)mTimeValue)*30 < 2 )
if( (((int)mTimeValue)%60 == 0) && (((int)mTimeValue)%600 != 0) )
mTimeValue = (int)mTimeValue - 1./30.;
}
if(mTimeValue<0.)
mTimeValue=0.;
mTimeValue /= mConverter.mScalingFactor;
if(!mConverter.mNtscDrop)
ValueToControls();
else {
mConverter.mNtscDrop = false;
ValueToControls();
mConverter.mNtscDrop = true;
ControlsToValue();
}
break;
}
}
steps--;
}
ControlsToValue();
}
void TimeTextCtrl::ValueToControls()
{
mConverter.ValueToControls( mTimeValue );
Refresh(false);
}
void TimeConverter::ValueToControls( double RawTime )
{
RawTime = (double)((sampleCount)floor(RawTime * mSampleRate + 0.5)) / mSampleRate; // put on a sample
double theValue = RawTime * mScalingFactor + .000001; // what's this .000001 for?
int t_int = int(theValue);
double t_frac = (theValue - t_int);
unsigned int i;
int tenMins;
int mins;
int addMins;
int secs;
int frames;
mValueString = mPrefix;
if(mNtscDrop) {
frames = (int)(theValue*30./1.001);
tenMins = frames/17982;
frames -= tenMins*17982;
mins = tenMins * 10;
if(frames >= 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<mFields.GetCount(); i++) {
int value;
if (mFields[i].frac) {
value = (int)(t_frac * mFields[i].base);
if (mFields[i].range > 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<mFields.GetCount(); i++) {
long val;
mFields[i].str = mValueString.Mid(mFields[i].pos,
mFields[i].digits);
mFields[i].str.ToLong(&val);
if (mFields[i].frac)
t += (val / (double)mFields[i].base);
else
t += (val * (double)mFields[i].base);
}
t /= mScalingFactor;
if(mNtscDrop) {
int t_int = int(t + .000000001);
double t_frac = (t - t_int);
int tenMins = t_int/600;
double frames = tenMins*17982;
t_int -= tenMins*600;
int mins = t_int/60;
int addMins = 0;
if( mins > 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
//