Finish up edit field accessibility.
This commit is contained in:
1 parent
6f6d4040c4
commit
c5657ee5d4
9 files changed
+361
-39
No files matched your search
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "nvdaController.h"
|
||||
#include "menu_bridge.h"
|
||||
#include "edit_hook.h"
|
||||
|
||||
#include <commctrl.h>
|
||||
#include <gio/gio.h>
|
||||
@@ -28,6 +29,7 @@ std::deque<WineAccessTextEvent> textEvents;
|
||||
bool editReaderActive = false;
|
||||
std::string lastMenuEvent;
|
||||
ULONGLONG lastMenuOwner = 0;
|
||||
HWND lastTextControl = nullptr;
|
||||
|
||||
// Winegcc uses 16-bit wchar_t; Linux libc's wcs* functions expect 32-bit
|
||||
// wchar_t. Use Wine's Unicode APIs or explicit code-unit comparisons.
|
||||
@@ -183,6 +185,9 @@ bool present_accessible_event(const std::string &sourceId,
|
||||
const char *eventType,
|
||||
const ControlDescription &control,
|
||||
const std::string &windowTitle) {
|
||||
// Readers can update their focus owner even when they suppress its output.
|
||||
if (std::string(eventType) == "focus" && sourceId != source_id(lastTextControl, "text"))
|
||||
lastTextControl = nullptr;
|
||||
return call_boolean("PresentAccessibleEvent",
|
||||
g_variant_new(
|
||||
"(sssssssiis)", sourceId.c_str(), eventType,
|
||||
@@ -199,7 +204,13 @@ ControlDescription describe_standard_control(HWND window) {
|
||||
wchar_t label[256] = {};
|
||||
if (!GetClassNameW(window, className, std::size(className)))
|
||||
return control;
|
||||
get_control_text(window, text, std::size(text));
|
||||
const LONG_PTR windowStyle = GetWindowLongPtrW(window, GWL_STYLE);
|
||||
const bool isEdit = lstrcmpW(className, L"Edit") == 0 ||
|
||||
lstrcmpW(className, L"RICHEDIT50W") == 0 ||
|
||||
lstrcmpW(className, L"RichEdit20W") == 0 ||
|
||||
lstrcmpW(className, L"RichEdit20A") == 0;
|
||||
if (!isEdit || !(windowStyle & ES_PASSWORD))
|
||||
get_control_text(window, text, std::size(text));
|
||||
|
||||
if (lstrcmpW(className, L"Button") == 0) {
|
||||
control.name = to_utf8(text);
|
||||
@@ -233,10 +244,6 @@ ControlDescription describe_standard_control(HWND window) {
|
||||
return control;
|
||||
}
|
||||
|
||||
const bool isEdit = lstrcmpW(className, L"Edit") == 0 ||
|
||||
lstrcmpW(className, L"RICHEDIT50W") == 0 ||
|
||||
lstrcmpW(className, L"RichEdit20W") == 0 ||
|
||||
lstrcmpW(className, L"RichEdit20A") == 0;
|
||||
if (isEdit) {
|
||||
find_control_label(window, label, std::size(label));
|
||||
control.name = to_utf8(label);
|
||||
@@ -313,6 +320,10 @@ ControlDescription describe_standard_control(HWND window) {
|
||||
}
|
||||
|
||||
bool present_standard_focus(HWND window) {
|
||||
// Read through the subclass so returning from a menu reports the caret's
|
||||
// line, not the beginning of the document. Never block on the callback.
|
||||
if (editReaderActive && GetPropW(window, EDIT_STATE_PROPERTY))
|
||||
return PostMessageW(window, RegisterWindowMessageW(EDIT_FOCUS_MESSAGE), 0, 0);
|
||||
const ControlDescription control = describe_standard_control(window);
|
||||
if (control.name.empty() && control.role.empty() && control.value.empty() &&
|
||||
control.description.empty())
|
||||
@@ -370,6 +381,9 @@ void present_text_event(const WineAccessTextEvent &event) {
|
||||
if (owner == nullptr || owner != GetAncestor(GetForegroundWindow(), GA_ROOT) ||
|
||||
!GetGUIThreadInfo(GetWindowThreadProcessId(owner, nullptr), &info) || info.hwndFocus != window)
|
||||
return;
|
||||
if ((GetWindowLongPtrW(window, GWL_STYLE) & ES_PASSWORD) &&
|
||||
event.kind != WINE_ACCESS_TEXT_PASSWORD)
|
||||
return;
|
||||
std::string text = to_utf8(event.text);
|
||||
ControlDescription control;
|
||||
if (event.kind == WINE_ACCESS_TEXT_PASSWORD) {
|
||||
@@ -397,7 +411,22 @@ void present_text_event(const WineAccessTextEvent &event) {
|
||||
control.name = text.empty() ? "blank" : text;
|
||||
}
|
||||
}
|
||||
present_accessible_event(source_id(window, "text"), "focus", control, to_utf8(event.title));
|
||||
const bool focus = event.kind == WINE_ACCESS_TEXT_FOCUS || event.kind == WINE_ACCESS_TEXT_PASSWORD;
|
||||
const auto source = source_id(window, "text");
|
||||
const auto title = to_utf8(event.title);
|
||||
// A reader restart can encounter an editor whose subclass already exists.
|
||||
// Re-establish focus before its first action without re-reading the document.
|
||||
if (!focus && lastTextControl != window) {
|
||||
ControlDescription focused;
|
||||
focused.role = GetWindowLongPtrW(window, GWL_STYLE) & ES_READONLY ? "read only edit" : "edit";
|
||||
if (!present_accessible_event(source, "focus", focused, title)) return;
|
||||
}
|
||||
if (event.kind != WINE_ACCESS_TEXT_PASSWORD)
|
||||
control.states = "start=" + std::to_string(event.start) + " end=" +
|
||||
std::to_string(event.end) + " kind=" + std::to_string(event.kind);
|
||||
// Text commands are ordered actions, not transient focus notifications.
|
||||
if (present_accessible_event(source, focus ? "focus" : "text", control, title))
|
||||
lastTextControl = window;
|
||||
}
|
||||
|
||||
LRESULT CALLBACK menu_bridge_proc(HWND window, UINT message, WPARAM wp, LPARAM lp) {
|
||||
@@ -471,9 +500,7 @@ void CALLBACK winevent_callback(HWINEVENTHOOK, DWORD event, HWND window,
|
||||
event != EVENT_OBJECT_VALUECHANGE && event != EVENT_OBJECT_SELECTION &&
|
||||
event != EVENT_OBJECT_STATECHANGE)
|
||||
return;
|
||||
wchar_t className[64] = {};
|
||||
if (editReaderActive && GetClassNameW(window, className, std::size(className)) &&
|
||||
lstrcmpW(className, L"Edit") == 0)
|
||||
if (editReaderActive && GetPropW(window, EDIT_STATE_PROPERTY))
|
||||
return;
|
||||
IAccessible *accessible = nullptr;
|
||||
VARIANT child{};
|
||||
|
||||
Reference in new issue
Block a user