Accessibility: make NumericTextCtrl accessible using Narrator

Problem:
Using the Narrator screen reader on Windows 10, there are a couple of problems:
1. When using the left/right arrow keys, Narrator reads the current field, rather than the digit which is now the focus.
2. Using up/down arrow keys, Narrator is silent.

Fixes:
1. The existing code assumes that NumericTextCtrlAx::GetName() is called only once after left/right arrow is pressed. However, Narrator causes this function to be called more than once. Solution: handle the case where the function is called, and neither the focus or the digits have changed, and use a cached value of the name.

2. If the focus has not changed, then after a focus event, Narrator does not read the name, even if the name has changed. Solution: add a name change event. (The focus event has been retained to keep Window-Eyes happy until we stop supporting it.)

Note:
One of the focus events has been removed from NumericTextCtrl::SetFieldFocus(), as it no longer appears to be necessary.
This commit is contained in:
David Bailes 2019-03-21 13:48:23 +00:00
parent 922e971f2f
commit 069e34df77
2 changed files with 89 additions and 67 deletions

View File

@ -1825,17 +1825,6 @@ void NumericTextCtrl::SetFieldFocus(int digit)
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 NumericTextCtrlAx below for even more odd looking
// hackery.
//
// If you change SetFieldFocus(), Updated(), or NumericTextCtrlAx, 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,
@ -1856,7 +1845,23 @@ void NumericTextCtrl::Updated(bool keyup /* = false */)
#if wxUSE_ACCESSIBILITY
if (!keyup) {
SetFieldFocus(mFocusedDigit);
if (mDigits.size() == 0)
{
mFocusedDigit = 0;
return;
}
// The object_focus event is only needed by Window-Eyes
// and can be removed when we cease to support this screen reader.
GetAccessible()->NotifyEvent(wxACC_EVENT_OBJECT_FOCUS,
this,
wxOBJID_CLIENT,
mFocusedDigit + 1);
GetAccessible()->NotifyEvent(wxACC_EVENT_OBJECT_NAMECHANGE,
this,
wxOBJID_CLIENT,
mFocusedDigit + 1);
}
#endif
}
@ -2018,7 +2023,7 @@ wxAccStatus NumericTextCtrlAx::GetName(int childId, wxString *name)
// Slightly messy trick to save us some prefixing.
std::vector<NumericField> & mFields = mCtrl->mFields;
wxString value = mCtrl->GetString();
wxString ctrlString = mCtrl->GetString();
int field = mCtrl->GetFocusedField();
// Return the entire string including the control label
@ -2037,68 +2042,83 @@ wxAccStatus NumericTextCtrlAx::GetName(int childId, wxString *name)
*name += wxT(" ") +
mCtrl->GetString();
}
// 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.size();
wxString decimal = wxLocale::GetInfo(wxLOCALE_DECIMAL_POINT, wxLOCALE_CAT_NUMBER);
// This case is needed because of the behaviour of Narrator, which
// is different for the other Windows screen readers. After a focus event,
// Narrator causes getName() to be called more than once. However, the code in
// the following else statement assumes that it is executed only once
// when the focus has been moved to another digit. This else if statement
// ensures that this is the case, by using a cached value if nothing
// has changed.
else if (childId == mLastDigit && ctrlString.IsSameAs(mLastCtrlString)) {
*name = mCachedName;
}
else {
// The user has moved from one field of the time to another so
// report the value of the field and the field's label.
if (mLastField != field) {
wxString label = mFields[field - 1].label;
int cnt = mFields.size();
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.
// PRL: click a digit of the control and use left and right arrow keys
// to exercise this code
const bool isTime = (mCtrl->mType == NumericTextCtrl::TIME);
if (field > 1 && field == cnt) {
if (mFields[field - 2].label == decimal) {
int digits = mFields[field - 1].digits;
if (digits == 2) {
if (isTime)
label = _("centiseconds");
else {
// other units
// PRL: does this create translation problems?
label = _("hundredths of ");
label += mFields[field - 1].label;
// If the NEW field is the last field, then check it to see if
// it represents fractions of a second.
// PRL: click a digit of the control and use left and right arrow keys
// to exercise this code
const bool isTime = (mCtrl->mType == NumericTextCtrl::TIME);
if (field > 1 && field == cnt) {
if (mFields[field - 2].label == decimal) {
int digits = mFields[field - 1].digits;
if (digits == 2) {
if (isTime)
label = _("centiseconds");
else {
// other units
// PRL: does this create translation problems?
label = _("hundredths of ");
label += mFields[field - 1].label;
}
}
}
else if (digits == 3) {
if (isTime)
label = _("milliseconds");
else {
// other units
// PRL: does this create translation problems?
label = _("thousandths of ");
label += mFields[field - 1].label;
else if (digits == 3) {
if (isTime)
label = _("milliseconds");
else {
// other units
// PRL: does this create translation problems?
label = _("thousandths of ");
label += mFields[field - 1].label;
}
}
}
}
// 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(", ") + // comma inserts a slight pause
mCtrl->GetString().at(mCtrl->mDigits[childId - 1].pos);
mLastField = field;
mLastDigit = childId;
}
// 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;
// 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->GetString().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 if (field > 0)
{
*name = mFields[field - 1].str;
}
*name = mFields[field - 1].str +
wxT(" ") +
label +
wxT(", ") + // comma inserts a slight pause
mCtrl->GetString().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->GetString().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 if (field > 0)
{
*name = mFields[field - 1].str;
mCachedName = *name;
mLastCtrlString = ctrlString;
}
return wxACC_OK;

View File

@ -346,6 +346,8 @@ private:
NumericTextCtrl *mCtrl;
int mLastField;
int mLastDigit;
wxString mCachedName;
wxString mLastCtrlString;
};
#endif // wxUSE_ACCESSIBILITY