OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include <atlbase.h> | |
6 #include <atlcom.h> | |
7 | |
8 #include <vector> | |
9 | |
10 #include "views/accessibility/native_view_accessibility_win.h" | |
11 | |
12 #include "third_party/iaccessible2/ia2_api_all.h" | |
13 #include "ui/base/accessibility/accessible_text_utils.h" | |
14 #include "ui/base/accessibility/accessible_view_state.h" | |
15 #include "ui/base/view_prop.h" | |
16 #include "views/widget/native_widget_win.h" | |
17 #include "views/widget/widget.h" | |
18 | |
19 using ui::AccessibilityTypes; | |
20 | |
21 // static | |
22 long NativeViewAccessibilityWin::next_unique_id_ = 1; | |
23 | |
24 // static | |
25 scoped_refptr<NativeViewAccessibilityWin> NativeViewAccessibilityWin::Create( | |
26 views::View* view) { | |
27 CComObject<NativeViewAccessibilityWin>* instance = NULL; | |
28 HRESULT hr = CComObject<NativeViewAccessibilityWin>::CreateInstance( | |
29 &instance); | |
30 DCHECK(SUCCEEDED(hr)); | |
31 instance->set_view(view); | |
32 return scoped_refptr<NativeViewAccessibilityWin>(instance); | |
33 } | |
34 | |
35 NativeViewAccessibilityWin::NativeViewAccessibilityWin() | |
36 : view_(NULL), | |
37 unique_id_(next_unique_id_++) { | |
38 } | |
39 | |
40 NativeViewAccessibilityWin::~NativeViewAccessibilityWin() { | |
41 } | |
42 | |
43 // TODO(ctguil): Handle case where child View is not contained by parent. | |
44 STDMETHODIMP NativeViewAccessibilityWin::accHitTest( | |
45 LONG x_left, LONG y_top, VARIANT* child) { | |
46 if (!child) | |
47 return E_INVALIDARG; | |
48 | |
49 if (!view_) | |
50 return E_FAIL; | |
51 | |
52 gfx::Point point(x_left, y_top); | |
53 views::View::ConvertPointToView(NULL, view_, &point); | |
54 | |
55 if (!view_->HitTest(point)) { | |
56 // If containing parent is not hit, return with failure. | |
57 child->vt = VT_EMPTY; | |
58 return S_FALSE; | |
59 } | |
60 | |
61 views::View* view = view_->GetEventHandlerForPoint(point); | |
62 if (view == view_) { | |
63 // No child hit, return parent id. | |
64 child->vt = VT_I4; | |
65 child->lVal = CHILDID_SELF; | |
66 } else { | |
67 child->vt = VT_DISPATCH; | |
68 child->pdispVal = view->GetNativeViewAccessible(); | |
69 child->pdispVal->AddRef(); | |
70 } | |
71 return S_OK; | |
72 } | |
73 | |
74 HRESULT NativeViewAccessibilityWin::accDoDefaultAction(VARIANT var_id) { | |
75 if (!IsValidId(var_id)) | |
76 return E_INVALIDARG; | |
77 | |
78 // The object does not support the method. This value is returned for | |
79 // controls that do not perform actions, such as edit fields. | |
80 return DISP_E_MEMBERNOTFOUND; | |
81 } | |
82 | |
83 STDMETHODIMP NativeViewAccessibilityWin::accLocation( | |
84 LONG* x_left, LONG* y_top, LONG* width, LONG* height, VARIANT var_id) { | |
85 if (!IsValidId(var_id) || !x_left || !y_top || !width || !height) | |
86 return E_INVALIDARG; | |
87 | |
88 if (!view_) | |
89 return E_FAIL; | |
90 | |
91 if (!view_->bounds().IsEmpty()) { | |
92 *width = view_->width(); | |
93 *height = view_->height(); | |
94 gfx::Point topleft(view_->bounds().origin()); | |
95 views::View::ConvertPointToScreen( | |
96 view_->parent() ? view_->parent() : view_, &topleft); | |
97 *x_left = topleft.x(); | |
98 *y_top = topleft.y(); | |
99 } else { | |
100 return E_FAIL; | |
101 } | |
102 return S_OK; | |
103 } | |
104 | |
105 STDMETHODIMP NativeViewAccessibilityWin::accNavigate( | |
106 LONG nav_dir, VARIANT start, VARIANT* end) { | |
107 if (start.vt != VT_I4 || !end) | |
108 return E_INVALIDARG; | |
109 | |
110 if (!view_) | |
111 return E_FAIL; | |
112 | |
113 switch (nav_dir) { | |
114 case NAVDIR_FIRSTCHILD: | |
115 case NAVDIR_LASTCHILD: { | |
116 if (start.lVal != CHILDID_SELF) { | |
117 // Start of navigation must be on the View itself. | |
118 return E_INVALIDARG; | |
119 } else if (!view_->has_children()) { | |
120 // No children found. | |
121 return S_FALSE; | |
122 } | |
123 | |
124 // Set child_id based on first or last child. | |
125 int child_id = 0; | |
126 if (nav_dir == NAVDIR_LASTCHILD) | |
127 child_id = view_->child_count() - 1; | |
128 | |
129 views::View* child = view_->child_at(child_id); | |
130 end->vt = VT_DISPATCH; | |
131 end->pdispVal = child->GetNativeViewAccessible(); | |
132 end->pdispVal->AddRef(); | |
133 return S_OK; | |
134 } | |
135 case NAVDIR_LEFT: | |
136 case NAVDIR_UP: | |
137 case NAVDIR_PREVIOUS: | |
138 case NAVDIR_RIGHT: | |
139 case NAVDIR_DOWN: | |
140 case NAVDIR_NEXT: { | |
141 // Retrieve parent to access view index and perform bounds checking. | |
142 views::View* parent = view_->parent(); | |
143 if (!parent) { | |
144 return E_FAIL; | |
145 } | |
146 | |
147 if (start.lVal == CHILDID_SELF) { | |
148 int view_index = parent->GetIndexOf(view_); | |
149 // Check navigation bounds, adjusting for View child indexing (MSAA | |
150 // child indexing starts with 1, whereas View indexing starts with 0). | |
151 if (!IsValidNav(nav_dir, view_index, -1, | |
152 parent->child_count() - 1)) { | |
153 // Navigation attempted to go out-of-bounds. | |
154 end->vt = VT_EMPTY; | |
155 return S_FALSE; | |
156 } else { | |
157 if (IsNavDirNext(nav_dir)) { | |
158 view_index += 1; | |
159 } else { | |
160 view_index -=1; | |
161 } | |
162 } | |
163 | |
164 views::View* child = parent->child_at(view_index); | |
165 end->pdispVal = child->GetNativeViewAccessible(); | |
166 end->vt = VT_DISPATCH; | |
167 end->pdispVal->AddRef(); | |
168 return S_OK; | |
169 } else { | |
170 // Check navigation bounds, adjusting for MSAA child indexing (MSAA | |
171 // child indexing starts with 1, whereas View indexing starts with 0). | |
172 if (!IsValidNav(nav_dir, start.lVal, 0, parent->child_count() + 1)) { | |
173 // Navigation attempted to go out-of-bounds. | |
174 end->vt = VT_EMPTY; | |
175 return S_FALSE; | |
176 } else { | |
177 if (IsNavDirNext(nav_dir)) { | |
178 start.lVal += 1; | |
179 } else { | |
180 start.lVal -= 1; | |
181 } | |
182 } | |
183 | |
184 HRESULT result = this->get_accChild(start, &end->pdispVal); | |
185 if (result == S_FALSE) { | |
186 // Child is a leaf. | |
187 end->vt = VT_I4; | |
188 end->lVal = start.lVal; | |
189 } else if (result == E_INVALIDARG) { | |
190 return E_INVALIDARG; | |
191 } else { | |
192 // Child is not a leaf. | |
193 end->vt = VT_DISPATCH; | |
194 } | |
195 } | |
196 break; | |
197 } | |
198 default: | |
199 return E_INVALIDARG; | |
200 } | |
201 // Navigation performed correctly. Global return for this function, if no | |
202 // error triggered an escape earlier. | |
203 return S_OK; | |
204 } | |
205 | |
206 STDMETHODIMP NativeViewAccessibilityWin::get_accChild(VARIANT var_child, | |
207 IDispatch** disp_child) { | |
208 if (var_child.vt != VT_I4 || !disp_child) | |
209 return E_INVALIDARG; | |
210 | |
211 if (!view_) | |
212 return E_FAIL; | |
213 | |
214 LONG child_id = V_I4(&var_child); | |
215 | |
216 if (child_id == CHILDID_SELF) { | |
217 // Remain with the same dispatch. | |
218 return S_OK; | |
219 } | |
220 | |
221 views::View* child_view = NULL; | |
222 if (child_id > 0) { | |
223 int child_id_as_index = child_id - 1; | |
224 if (child_id_as_index < view_->child_count()) { | |
225 // Note: child_id is a one based index when indexing children. | |
226 child_view = view_->child_at(child_id_as_index); | |
227 } else { | |
228 // Attempt to retrieve a child view with the specified id. | |
229 child_view = view_->GetViewByID(child_id); | |
230 } | |
231 } else { | |
232 // Negative values are used for events fired using the view's | |
233 // NativeWidgetWin. | |
234 views::NativeWidgetWin* widget = static_cast<views::NativeWidgetWin*>( | |
235 view_->GetWidget()->native_widget()); | |
236 child_view = widget->GetAccessibilityViewEventAt(child_id); | |
237 } | |
238 | |
239 if (!child_view) { | |
240 // No child found. | |
241 *disp_child = NULL; | |
242 return E_FAIL; | |
243 } | |
244 | |
245 *disp_child = child_view->GetNativeViewAccessible(); | |
246 (*disp_child)->AddRef(); | |
247 return S_OK; | |
248 } | |
249 | |
250 STDMETHODIMP NativeViewAccessibilityWin::get_accChildCount(LONG* child_count) { | |
251 if (!child_count || !view_) | |
252 return E_INVALIDARG; | |
253 | |
254 if (!view_) | |
255 return E_FAIL; | |
256 | |
257 *child_count = view_->child_count(); | |
258 return S_OK; | |
259 } | |
260 | |
261 STDMETHODIMP NativeViewAccessibilityWin::get_accDefaultAction( | |
262 VARIANT var_id, BSTR* def_action) { | |
263 if (!IsValidId(var_id) || !def_action) | |
264 return E_INVALIDARG; | |
265 | |
266 if (!view_) | |
267 return E_FAIL; | |
268 | |
269 ui::AccessibleViewState state; | |
270 view_->GetAccessibleState(&state); | |
271 string16 temp_action = state.default_action; | |
272 | |
273 if (!temp_action.empty()) { | |
274 *def_action = SysAllocString(temp_action.c_str()); | |
275 } else { | |
276 return S_FALSE; | |
277 } | |
278 | |
279 return S_OK; | |
280 } | |
281 | |
282 STDMETHODIMP NativeViewAccessibilityWin::get_accDescription( | |
283 VARIANT var_id, BSTR* desc) { | |
284 if (!IsValidId(var_id) || !desc) | |
285 return E_INVALIDARG; | |
286 | |
287 if (!view_) | |
288 return E_FAIL; | |
289 | |
290 string16 temp_desc; | |
291 | |
292 view_->GetTooltipText(gfx::Point(), &temp_desc); | |
293 if (!temp_desc.empty()) { | |
294 *desc = SysAllocString(temp_desc.c_str()); | |
295 } else { | |
296 return S_FALSE; | |
297 } | |
298 | |
299 return S_OK; | |
300 } | |
301 | |
302 STDMETHODIMP NativeViewAccessibilityWin::get_accFocus(VARIANT* focus_child) { | |
303 if (!focus_child) | |
304 return E_INVALIDARG; | |
305 | |
306 if (!view_) | |
307 return E_FAIL; | |
308 | |
309 views::FocusManager* focus_manager = view_->GetFocusManager(); | |
310 views::View* focus = focus_manager ? focus_manager->GetFocusedView() : NULL; | |
311 if (focus == view_) { | |
312 // This view has focus. | |
313 focus_child->vt = VT_I4; | |
314 focus_child->lVal = CHILDID_SELF; | |
315 } else if (focus && view_->Contains(focus)) { | |
316 // Return the child object that has the keyboard focus. | |
317 focus_child->pdispVal = focus->GetNativeViewAccessible(); | |
318 focus_child->pdispVal->AddRef(); | |
319 return S_OK; | |
320 } else { | |
321 // Neither this object nor any of its children has the keyboard focus. | |
322 focus_child->vt = VT_EMPTY; | |
323 } | |
324 return S_OK; | |
325 } | |
326 | |
327 STDMETHODIMP NativeViewAccessibilityWin::get_accKeyboardShortcut( | |
328 VARIANT var_id, BSTR* acc_key) { | |
329 if (!IsValidId(var_id) || !acc_key) | |
330 return E_INVALIDARG; | |
331 | |
332 if (!view_) | |
333 return E_FAIL; | |
334 | |
335 ui::AccessibleViewState state; | |
336 view_->GetAccessibleState(&state); | |
337 string16 temp_key = state.keyboard_shortcut; | |
338 | |
339 if (!temp_key.empty()) { | |
340 *acc_key = SysAllocString(temp_key.c_str()); | |
341 } else { | |
342 return S_FALSE; | |
343 } | |
344 | |
345 return S_OK; | |
346 } | |
347 | |
348 STDMETHODIMP NativeViewAccessibilityWin::get_accName( | |
349 VARIANT var_id, BSTR* name) { | |
350 if (!IsValidId(var_id) || !name) | |
351 return E_INVALIDARG; | |
352 | |
353 if (!view_) | |
354 return E_FAIL; | |
355 | |
356 // Retrieve the current view's name. | |
357 ui::AccessibleViewState state; | |
358 view_->GetAccessibleState(&state); | |
359 string16 temp_name = state.name; | |
360 if (!temp_name.empty()) { | |
361 // Return name retrieved. | |
362 *name = SysAllocString(temp_name.c_str()); | |
363 } else { | |
364 // If view has no name, return S_FALSE. | |
365 return S_FALSE; | |
366 } | |
367 | |
368 return S_OK; | |
369 } | |
370 | |
371 STDMETHODIMP NativeViewAccessibilityWin::get_accParent( | |
372 IDispatch** disp_parent) { | |
373 if (!disp_parent) | |
374 return E_INVALIDARG; | |
375 | |
376 if (!view_) | |
377 return E_FAIL; | |
378 | |
379 views::View* parent_view = view_->parent(); | |
380 | |
381 if (!parent_view) { | |
382 // This function can get called during teardown of WidetWin so we | |
383 // should bail out if we fail to get the HWND. | |
384 if (!view_->GetWidget() || !view_->GetWidget()->GetNativeView()) { | |
385 *disp_parent = NULL; | |
386 return S_FALSE; | |
387 } | |
388 | |
389 // For a View that has no parent (e.g. root), point the accessible parent | |
390 // to the default implementation, to interface with Windows' hierarchy | |
391 // and to support calls from e.g. WindowFromAccessibleObject. | |
392 HRESULT hr = | |
393 ::AccessibleObjectFromWindow(view_->GetWidget()->GetNativeView(), | |
394 OBJID_WINDOW, IID_IAccessible, | |
395 reinterpret_cast<void**>(disp_parent)); | |
396 | |
397 if (!SUCCEEDED(hr)) { | |
398 *disp_parent = NULL; | |
399 return S_FALSE; | |
400 } | |
401 | |
402 return S_OK; | |
403 } | |
404 | |
405 *disp_parent = parent_view->GetNativeViewAccessible(); | |
406 (*disp_parent)->AddRef(); | |
407 return S_OK; | |
408 } | |
409 | |
410 STDMETHODIMP NativeViewAccessibilityWin::get_accRole( | |
411 VARIANT var_id, VARIANT* role) { | |
412 if (!IsValidId(var_id) || !role) | |
413 return E_INVALIDARG; | |
414 | |
415 if (!view_) | |
416 return E_FAIL; | |
417 | |
418 ui::AccessibleViewState state; | |
419 view_->GetAccessibleState(&state); | |
420 role->vt = VT_I4; | |
421 role->lVal = MSAARole(state.role); | |
422 return S_OK; | |
423 } | |
424 | |
425 STDMETHODIMP NativeViewAccessibilityWin::get_accState( | |
426 VARIANT var_id, VARIANT* state) { | |
427 // This returns MSAA states. See also the IAccessible2 interface | |
428 // get_states(). | |
429 | |
430 if (!IsValidId(var_id) || !state) | |
431 return E_INVALIDARG; | |
432 | |
433 if (!view_) | |
434 return E_FAIL; | |
435 | |
436 state->vt = VT_I4; | |
437 | |
438 // Retrieve all currently applicable states of the parent. | |
439 SetState(state, view_); | |
440 | |
441 // Make sure that state is not empty, and has the proper type. | |
442 if (state->vt == VT_EMPTY) | |
443 return E_FAIL; | |
444 | |
445 return S_OK; | |
446 } | |
447 | |
448 STDMETHODIMP NativeViewAccessibilityWin::get_accValue( | |
449 VARIANT var_id, BSTR* value) { | |
450 if (!IsValidId(var_id) || !value) | |
451 return E_INVALIDARG; | |
452 | |
453 if (!view_) | |
454 return E_FAIL; | |
455 | |
456 // Retrieve the current view's value. | |
457 ui::AccessibleViewState state; | |
458 view_->GetAccessibleState(&state); | |
459 string16 temp_value = state.value; | |
460 | |
461 if (!temp_value.empty()) { | |
462 // Return value retrieved. | |
463 *value = SysAllocString(temp_value.c_str()); | |
464 } else { | |
465 // If view has no value, fall back into the default implementation. | |
466 *value = NULL; | |
467 return E_NOTIMPL; | |
468 } | |
469 | |
470 return S_OK; | |
471 } | |
472 | |
473 // IAccessible functions not supported. | |
474 | |
475 STDMETHODIMP NativeViewAccessibilityWin::get_accSelection(VARIANT* selected) { | |
476 if (selected) | |
477 selected->vt = VT_EMPTY; | |
478 return E_NOTIMPL; | |
479 } | |
480 | |
481 STDMETHODIMP NativeViewAccessibilityWin::accSelect( | |
482 LONG flagsSelect, VARIANT var_id) { | |
483 return E_NOTIMPL; | |
484 } | |
485 | |
486 STDMETHODIMP NativeViewAccessibilityWin::get_accHelp( | |
487 VARIANT var_id, BSTR* help) { | |
488 if (help) | |
489 *help = NULL; | |
490 return E_NOTIMPL; | |
491 } | |
492 | |
493 STDMETHODIMP NativeViewAccessibilityWin::get_accHelpTopic( | |
494 BSTR* help_file, VARIANT var_id, LONG* topic_id) { | |
495 if (help_file) { | |
496 *help_file = NULL; | |
497 } | |
498 if (topic_id) { | |
499 *topic_id = static_cast<LONG>(-1); | |
500 } | |
501 return E_NOTIMPL; | |
502 } | |
503 | |
504 STDMETHODIMP NativeViewAccessibilityWin::put_accName( | |
505 VARIANT var_id, BSTR put_name) { | |
506 // Deprecated. | |
507 return E_NOTIMPL; | |
508 } | |
509 | |
510 STDMETHODIMP NativeViewAccessibilityWin::put_accValue( | |
511 VARIANT var_id, BSTR put_val) { | |
512 // Deprecated. | |
513 return E_NOTIMPL; | |
514 } | |
515 | |
516 // | |
517 // IAccessible2 | |
518 // | |
519 | |
520 STDMETHODIMP NativeViewAccessibilityWin::role(LONG* role) { | |
521 if (!view_) | |
522 return E_FAIL; | |
523 | |
524 if (!role) | |
525 return E_INVALIDARG; | |
526 | |
527 ui::AccessibleViewState state; | |
528 view_->GetAccessibleState(&state); | |
529 *role = MSAARole(state.role); | |
530 return S_OK; | |
531 } | |
532 | |
533 STDMETHODIMP NativeViewAccessibilityWin::get_states(AccessibleStates* states) { | |
534 // This returns IAccessible2 states, which supplement MSAA states. | |
535 // See also the MSAA interface get_accState. | |
536 | |
537 if (!view_) | |
538 return E_FAIL; | |
539 | |
540 if (!states) | |
541 return E_INVALIDARG; | |
542 | |
543 ui::AccessibleViewState state; | |
544 view_->GetAccessibleState(&state); | |
545 | |
546 // There are only a couple of states we need to support | |
547 // in IAccessible2. If any more are added, we may want to | |
548 // add a helper function like MSAAState. | |
549 *states = IA2_STATE_OPAQUE; | |
550 if (state.state & AccessibilityTypes::STATE_EDITABLE) | |
551 *states |= IA2_STATE_EDITABLE; | |
552 | |
553 return S_OK; | |
554 } | |
555 | |
556 STDMETHODIMP NativeViewAccessibilityWin::get_uniqueID(LONG* unique_id) { | |
557 if (!view_) | |
558 return E_FAIL; | |
559 | |
560 if (!unique_id) | |
561 return E_INVALIDARG; | |
562 | |
563 *unique_id = unique_id_; | |
564 return S_OK; | |
565 } | |
566 | |
567 STDMETHODIMP NativeViewAccessibilityWin::get_windowHandle(HWND* window_handle) { | |
568 if (!view_) | |
569 return E_FAIL; | |
570 | |
571 if (!window_handle) | |
572 return E_INVALIDARG; | |
573 | |
574 *window_handle = view_->GetWidget()->GetNativeView(); | |
575 return S_OK; | |
576 } | |
577 | |
578 // | |
579 // IAccessibleText | |
580 // | |
581 | |
582 STDMETHODIMP NativeViewAccessibilityWin::get_nCharacters(LONG* n_characters) { | |
583 if (!view_) | |
584 return E_FAIL; | |
585 | |
586 if (!n_characters) | |
587 return E_INVALIDARG; | |
588 | |
589 string16 text = TextForIAccessibleText(); | |
590 *n_characters = static_cast<LONG>(text.size()); | |
591 return S_OK; | |
592 } | |
593 | |
594 STDMETHODIMP NativeViewAccessibilityWin::get_caretOffset(LONG* offset) { | |
595 if (!view_) | |
596 return E_FAIL; | |
597 | |
598 if (!offset) | |
599 return E_INVALIDARG; | |
600 | |
601 ui::AccessibleViewState state; | |
602 view_->GetAccessibleState(&state); | |
603 *offset = static_cast<LONG>(state.selection_end); | |
604 return S_OK; | |
605 } | |
606 | |
607 STDMETHODIMP NativeViewAccessibilityWin::get_nSelections(LONG* n_selections) { | |
608 if (!view_) | |
609 return E_FAIL; | |
610 | |
611 if (!n_selections) | |
612 return E_INVALIDARG; | |
613 | |
614 ui::AccessibleViewState state; | |
615 view_->GetAccessibleState(&state); | |
616 if (state.selection_start != state.selection_end) | |
617 *n_selections = 1; | |
618 else | |
619 *n_selections = 0; | |
620 return S_OK; | |
621 } | |
622 | |
623 STDMETHODIMP NativeViewAccessibilityWin::get_selection(LONG selection_index, | |
624 LONG* start_offset, | |
625 LONG* end_offset) { | |
626 if (!view_) | |
627 return E_FAIL; | |
628 | |
629 if (!start_offset || !end_offset || selection_index != 0) | |
630 return E_INVALIDARG; | |
631 | |
632 ui::AccessibleViewState state; | |
633 view_->GetAccessibleState(&state); | |
634 *start_offset = static_cast<LONG>(state.selection_start); | |
635 *end_offset = static_cast<LONG>(state.selection_end); | |
636 return S_OK; | |
637 } | |
638 | |
639 STDMETHODIMP NativeViewAccessibilityWin::get_text(LONG start_offset, | |
640 LONG end_offset, | |
641 BSTR* text) { | |
642 if (!view_) | |
643 return E_FAIL; | |
644 | |
645 ui::AccessibleViewState state; | |
646 view_->GetAccessibleState(&state); | |
647 string16 text_str = TextForIAccessibleText(); | |
648 LONG len = static_cast<LONG>(text_str.size()); | |
649 | |
650 if (start_offset == IA2_TEXT_OFFSET_LENGTH) { | |
651 start_offset = len; | |
652 } else if (start_offset == IA2_TEXT_OFFSET_CARET) { | |
653 start_offset = static_cast<LONG>(state.selection_end); | |
654 } | |
655 if (end_offset == IA2_TEXT_OFFSET_LENGTH) { | |
656 end_offset = static_cast<LONG>(text_str.size()); | |
657 } else if (end_offset == IA2_TEXT_OFFSET_CARET) { | |
658 end_offset = static_cast<LONG>(state.selection_end); | |
659 } | |
660 | |
661 // The spec allows the arguments to be reversed. | |
662 if (start_offset > end_offset) { | |
663 LONG tmp = start_offset; | |
664 start_offset = end_offset; | |
665 end_offset = tmp; | |
666 } | |
667 | |
668 // The spec does not allow the start or end offsets to be out or range; | |
669 // we must return an error if so. | |
670 if (start_offset < 0) | |
671 return E_INVALIDARG; | |
672 if (end_offset > len) | |
673 return E_INVALIDARG; | |
674 | |
675 string16 substr = text_str.substr(start_offset, end_offset - start_offset); | |
676 if (substr.empty()) | |
677 return S_FALSE; | |
678 | |
679 *text = SysAllocString(substr.c_str()); | |
680 DCHECK(*text); | |
681 return S_OK; | |
682 } | |
683 | |
684 STDMETHODIMP NativeViewAccessibilityWin::get_textAtOffset( | |
685 LONG offset, | |
686 enum IA2TextBoundaryType boundary_type, | |
687 LONG* start_offset, LONG* end_offset, | |
688 BSTR* text) { | |
689 if (!start_offset || !end_offset || !text) | |
690 return E_INVALIDARG; | |
691 | |
692 // The IAccessible2 spec says we don't have to implement the "sentence" | |
693 // boundary type, we can just let the screenreader handle it. | |
694 if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) { | |
695 *start_offset = 0; | |
696 *end_offset = 0; | |
697 *text = NULL; | |
698 return S_FALSE; | |
699 } | |
700 | |
701 const string16& text_str = TextForIAccessibleText(); | |
702 | |
703 *start_offset = FindBoundary( | |
704 text_str, boundary_type, offset, ui::BACKWARDS_DIRECTION); | |
705 *end_offset = FindBoundary( | |
706 text_str, boundary_type, offset, ui::FORWARDS_DIRECTION); | |
707 return get_text(*start_offset, *end_offset, text); | |
708 } | |
709 | |
710 STDMETHODIMP NativeViewAccessibilityWin::get_textBeforeOffset( | |
711 LONG offset, | |
712 enum IA2TextBoundaryType boundary_type, | |
713 LONG* start_offset, LONG* end_offset, | |
714 BSTR* text) { | |
715 if (!start_offset || !end_offset || !text) | |
716 return E_INVALIDARG; | |
717 | |
718 // The IAccessible2 spec says we don't have to implement the "sentence" | |
719 // boundary type, we can just let the screenreader handle it. | |
720 if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) { | |
721 *start_offset = 0; | |
722 *end_offset = 0; | |
723 *text = NULL; | |
724 return S_FALSE; | |
725 } | |
726 | |
727 const string16& text_str = TextForIAccessibleText(); | |
728 | |
729 *start_offset = FindBoundary( | |
730 text_str, boundary_type, offset, ui::BACKWARDS_DIRECTION); | |
731 *end_offset = offset; | |
732 return get_text(*start_offset, *end_offset, text); | |
733 } | |
734 | |
735 STDMETHODIMP NativeViewAccessibilityWin::get_textAfterOffset( | |
736 LONG offset, | |
737 enum IA2TextBoundaryType boundary_type, | |
738 LONG* start_offset, LONG* end_offset, | |
739 BSTR* text) { | |
740 if (!start_offset || !end_offset || !text) | |
741 return E_INVALIDARG; | |
742 | |
743 // The IAccessible2 spec says we don't have to implement the "sentence" | |
744 // boundary type, we can just let the screenreader handle it. | |
745 if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) { | |
746 *start_offset = 0; | |
747 *end_offset = 0; | |
748 *text = NULL; | |
749 return S_FALSE; | |
750 } | |
751 | |
752 const string16& text_str = TextForIAccessibleText(); | |
753 | |
754 *start_offset = offset; | |
755 *end_offset = FindBoundary( | |
756 text_str, boundary_type, offset, ui::FORWARDS_DIRECTION); | |
757 return get_text(*start_offset, *end_offset, text); | |
758 } | |
759 | |
760 STDMETHODIMP NativeViewAccessibilityWin::get_offsetAtPoint( | |
761 LONG x, LONG y, enum IA2CoordinateType coord_type, LONG* offset) { | |
762 if (!view_) | |
763 return E_FAIL; | |
764 | |
765 if (!offset) | |
766 return E_INVALIDARG; | |
767 | |
768 // We don't support this method, but we have to return something | |
769 // rather than E_NOTIMPL or screen readers will complain. | |
770 *offset = 0; | |
771 return S_OK; | |
772 } | |
773 | |
774 // | |
775 // IServiceProvider methods. | |
776 // | |
777 | |
778 STDMETHODIMP NativeViewAccessibilityWin::QueryService( | |
779 REFGUID guidService, REFIID riid, void** object) { | |
780 if (!view_) | |
781 return E_FAIL; | |
782 | |
783 if (guidService == IID_IAccessible || | |
784 guidService == IID_IAccessible2 || | |
785 guidService == IID_IAccessibleText) { | |
786 return QueryInterface(riid, object); | |
787 } | |
788 | |
789 *object = NULL; | |
790 return E_FAIL; | |
791 } | |
792 | |
793 // | |
794 // Static methods. | |
795 // | |
796 | |
797 int32 NativeViewAccessibilityWin::MSAAEvent(AccessibilityTypes::Event event) { | |
798 switch (event) { | |
799 case AccessibilityTypes::EVENT_ALERT: | |
800 return EVENT_SYSTEM_ALERT; | |
801 case AccessibilityTypes::EVENT_FOCUS: | |
802 return EVENT_OBJECT_FOCUS; | |
803 case AccessibilityTypes::EVENT_MENUSTART: | |
804 return EVENT_SYSTEM_MENUSTART; | |
805 case AccessibilityTypes::EVENT_MENUEND: | |
806 return EVENT_SYSTEM_MENUEND; | |
807 case AccessibilityTypes::EVENT_MENUPOPUPSTART: | |
808 return EVENT_SYSTEM_MENUPOPUPSTART; | |
809 case AccessibilityTypes::EVENT_MENUPOPUPEND: | |
810 return EVENT_SYSTEM_MENUPOPUPEND; | |
811 case AccessibilityTypes::EVENT_NAME_CHANGED: | |
812 return EVENT_OBJECT_NAMECHANGE; | |
813 case AccessibilityTypes::EVENT_TEXT_CHANGED: | |
814 return EVENT_OBJECT_VALUECHANGE; | |
815 case AccessibilityTypes::EVENT_SELECTION_CHANGED: | |
816 return IA2_EVENT_TEXT_CARET_MOVED; | |
817 case AccessibilityTypes::EVENT_VALUE_CHANGED: | |
818 return EVENT_OBJECT_VALUECHANGE; | |
819 default: | |
820 // Not supported or invalid event. | |
821 NOTREACHED(); | |
822 return -1; | |
823 } | |
824 } | |
825 | |
826 int32 NativeViewAccessibilityWin::MSAARole(AccessibilityTypes::Role role) { | |
827 switch (role) { | |
828 case AccessibilityTypes::ROLE_ALERT: | |
829 return ROLE_SYSTEM_ALERT; | |
830 case AccessibilityTypes::ROLE_APPLICATION: | |
831 return ROLE_SYSTEM_APPLICATION; | |
832 case AccessibilityTypes::ROLE_BUTTONDROPDOWN: | |
833 return ROLE_SYSTEM_BUTTONDROPDOWN; | |
834 case AccessibilityTypes::ROLE_BUTTONMENU: | |
835 return ROLE_SYSTEM_BUTTONMENU; | |
836 case AccessibilityTypes::ROLE_CHECKBUTTON: | |
837 return ROLE_SYSTEM_CHECKBUTTON; | |
838 case AccessibilityTypes::ROLE_COMBOBOX: | |
839 return ROLE_SYSTEM_COMBOBOX; | |
840 case AccessibilityTypes::ROLE_DIALOG: | |
841 return ROLE_SYSTEM_DIALOG; | |
842 case AccessibilityTypes::ROLE_GRAPHIC: | |
843 return ROLE_SYSTEM_GRAPHIC; | |
844 case AccessibilityTypes::ROLE_GROUPING: | |
845 return ROLE_SYSTEM_GROUPING; | |
846 case AccessibilityTypes::ROLE_LINK: | |
847 return ROLE_SYSTEM_LINK; | |
848 case AccessibilityTypes::ROLE_LOCATION_BAR: | |
849 return ROLE_SYSTEM_GROUPING; | |
850 case AccessibilityTypes::ROLE_MENUBAR: | |
851 return ROLE_SYSTEM_MENUBAR; | |
852 case AccessibilityTypes::ROLE_MENUITEM: | |
853 return ROLE_SYSTEM_MENUITEM; | |
854 case AccessibilityTypes::ROLE_MENUPOPUP: | |
855 return ROLE_SYSTEM_MENUPOPUP; | |
856 case AccessibilityTypes::ROLE_OUTLINE: | |
857 return ROLE_SYSTEM_OUTLINE; | |
858 case AccessibilityTypes::ROLE_OUTLINEITEM: | |
859 return ROLE_SYSTEM_OUTLINEITEM; | |
860 case AccessibilityTypes::ROLE_PAGETAB: | |
861 return ROLE_SYSTEM_PAGETAB; | |
862 case AccessibilityTypes::ROLE_PAGETABLIST: | |
863 return ROLE_SYSTEM_PAGETABLIST; | |
864 case AccessibilityTypes::ROLE_PANE: | |
865 return ROLE_SYSTEM_PANE; | |
866 case AccessibilityTypes::ROLE_PROGRESSBAR: | |
867 return ROLE_SYSTEM_PROGRESSBAR; | |
868 case AccessibilityTypes::ROLE_PUSHBUTTON: | |
869 return ROLE_SYSTEM_PUSHBUTTON; | |
870 case AccessibilityTypes::ROLE_RADIOBUTTON: | |
871 return ROLE_SYSTEM_RADIOBUTTON; | |
872 case AccessibilityTypes::ROLE_SCROLLBAR: | |
873 return ROLE_SYSTEM_SCROLLBAR; | |
874 case AccessibilityTypes::ROLE_SEPARATOR: | |
875 return ROLE_SYSTEM_SEPARATOR; | |
876 case AccessibilityTypes::ROLE_STATICTEXT: | |
877 return ROLE_SYSTEM_STATICTEXT; | |
878 case AccessibilityTypes::ROLE_TEXT: | |
879 return ROLE_SYSTEM_TEXT; | |
880 case AccessibilityTypes::ROLE_TITLEBAR: | |
881 return ROLE_SYSTEM_TITLEBAR; | |
882 case AccessibilityTypes::ROLE_TOOLBAR: | |
883 return ROLE_SYSTEM_TOOLBAR; | |
884 case AccessibilityTypes::ROLE_WINDOW: | |
885 return ROLE_SYSTEM_WINDOW; | |
886 case AccessibilityTypes::ROLE_CLIENT: | |
887 default: | |
888 // This is the default role for MSAA. | |
889 return ROLE_SYSTEM_CLIENT; | |
890 } | |
891 } | |
892 | |
893 int32 NativeViewAccessibilityWin::MSAAState(AccessibilityTypes::State state) { | |
894 // This maps MSAA states for get_accState(). See also the IAccessible2 | |
895 // interface get_states(). | |
896 | |
897 int32 msaa_state = 0; | |
898 if (state & AccessibilityTypes::STATE_CHECKED) | |
899 msaa_state |= STATE_SYSTEM_CHECKED; | |
900 if (state & AccessibilityTypes::STATE_COLLAPSED) | |
901 msaa_state |= STATE_SYSTEM_COLLAPSED; | |
902 if (state & AccessibilityTypes::STATE_DEFAULT) | |
903 msaa_state |= STATE_SYSTEM_DEFAULT; | |
904 if (state & AccessibilityTypes::STATE_EXPANDED) | |
905 msaa_state |= STATE_SYSTEM_EXPANDED; | |
906 if (state & AccessibilityTypes::STATE_HASPOPUP) | |
907 msaa_state |= STATE_SYSTEM_HASPOPUP; | |
908 if (state & AccessibilityTypes::STATE_HOTTRACKED) | |
909 msaa_state |= STATE_SYSTEM_HOTTRACKED; | |
910 if (state & AccessibilityTypes::STATE_INVISIBLE) | |
911 msaa_state |= STATE_SYSTEM_INVISIBLE; | |
912 if (state & AccessibilityTypes::STATE_LINKED) | |
913 msaa_state |= STATE_SYSTEM_LINKED; | |
914 if (state & AccessibilityTypes::STATE_OFFSCREEN) | |
915 msaa_state |= STATE_SYSTEM_OFFSCREEN; | |
916 if (state & AccessibilityTypes::STATE_PRESSED) | |
917 msaa_state |= STATE_SYSTEM_PRESSED; | |
918 if (state & AccessibilityTypes::STATE_PROTECTED) | |
919 msaa_state |= STATE_SYSTEM_PROTECTED; | |
920 if (state & AccessibilityTypes::STATE_READONLY) | |
921 msaa_state |= STATE_SYSTEM_READONLY; | |
922 if (state & AccessibilityTypes::STATE_SELECTED) | |
923 msaa_state |= STATE_SYSTEM_SELECTED; | |
924 if (state & AccessibilityTypes::STATE_FOCUSED) | |
925 msaa_state |= STATE_SYSTEM_FOCUSED; | |
926 if (state & AccessibilityTypes::STATE_UNAVAILABLE) | |
927 msaa_state |= STATE_SYSTEM_UNAVAILABLE; | |
928 return msaa_state; | |
929 } | |
930 | |
931 // | |
932 // Private methods. | |
933 // | |
934 | |
935 bool NativeViewAccessibilityWin::IsNavDirNext(int nav_dir) const { | |
936 return (nav_dir == NAVDIR_RIGHT || | |
937 nav_dir == NAVDIR_DOWN || | |
938 nav_dir == NAVDIR_NEXT); | |
939 } | |
940 | |
941 bool NativeViewAccessibilityWin::IsValidNav( | |
942 int nav_dir, int start_id, int lower_bound, int upper_bound) const { | |
943 if (IsNavDirNext(nav_dir)) { | |
944 if ((start_id + 1) > upper_bound) { | |
945 return false; | |
946 } | |
947 } else { | |
948 if ((start_id - 1) <= lower_bound) { | |
949 return false; | |
950 } | |
951 } | |
952 return true; | |
953 } | |
954 | |
955 bool NativeViewAccessibilityWin::IsValidId(const VARIANT& child) const { | |
956 // View accessibility returns an IAccessible for each view so we only support | |
957 // the CHILDID_SELF id. | |
958 return (VT_I4 == child.vt) && (CHILDID_SELF == child.lVal); | |
959 } | |
960 | |
961 void NativeViewAccessibilityWin::SetState( | |
962 VARIANT* msaa_state, views::View* view) { | |
963 // Ensure the output param is initialized to zero. | |
964 msaa_state->lVal = 0; | |
965 | |
966 // Default state; all views can have accessibility focus. | |
967 msaa_state->lVal |= STATE_SYSTEM_FOCUSABLE; | |
968 | |
969 if (!view) | |
970 return; | |
971 | |
972 if (!view->IsEnabled()) | |
973 msaa_state->lVal |= STATE_SYSTEM_UNAVAILABLE; | |
974 if (!view->IsVisible()) | |
975 msaa_state->lVal |= STATE_SYSTEM_INVISIBLE; | |
976 if (view->IsHotTracked()) | |
977 msaa_state->lVal |= STATE_SYSTEM_HOTTRACKED; | |
978 if (view->HasFocus()) | |
979 msaa_state->lVal |= STATE_SYSTEM_FOCUSED; | |
980 | |
981 // Add on any view-specific states. | |
982 ui::AccessibleViewState view_state; | |
983 view->GetAccessibleState(&view_state); | |
984 msaa_state->lVal |= MSAAState(view_state.state); | |
985 } | |
986 | |
987 string16 NativeViewAccessibilityWin::TextForIAccessibleText() { | |
988 ui::AccessibleViewState state; | |
989 view_->GetAccessibleState(&state); | |
990 if (state.role == AccessibilityTypes::ROLE_TEXT) | |
991 return state.value; | |
992 else | |
993 return state.name; | |
994 } | |
995 | |
996 void NativeViewAccessibilityWin::HandleSpecialTextOffset( | |
997 const string16& text, LONG* offset) { | |
998 if (*offset == IA2_TEXT_OFFSET_LENGTH) { | |
999 *offset = static_cast<LONG>(text.size()); | |
1000 } else if (*offset == IA2_TEXT_OFFSET_CARET) { | |
1001 get_caretOffset(offset); | |
1002 } | |
1003 } | |
1004 | |
1005 ui::TextBoundaryType NativeViewAccessibilityWin::IA2TextBoundaryToTextBoundary( | |
1006 IA2TextBoundaryType ia2_boundary) { | |
1007 switch(ia2_boundary) { | |
1008 case IA2_TEXT_BOUNDARY_CHAR: return ui::CHAR_BOUNDARY; | |
1009 case IA2_TEXT_BOUNDARY_WORD: return ui::WORD_BOUNDARY; | |
1010 case IA2_TEXT_BOUNDARY_LINE: return ui::LINE_BOUNDARY; | |
1011 case IA2_TEXT_BOUNDARY_SENTENCE: return ui::SENTENCE_BOUNDARY; | |
1012 case IA2_TEXT_BOUNDARY_PARAGRAPH: return ui::PARAGRAPH_BOUNDARY; | |
1013 case IA2_TEXT_BOUNDARY_ALL: return ui::ALL_BOUNDARY; | |
1014 default: | |
1015 NOTREACHED(); | |
1016 return ui::CHAR_BOUNDARY; | |
1017 } | |
1018 } | |
1019 | |
1020 LONG NativeViewAccessibilityWin::FindBoundary( | |
1021 const string16& text, | |
1022 IA2TextBoundaryType ia2_boundary, | |
1023 LONG start_offset, | |
1024 ui::TextBoundaryDirection direction) { | |
1025 HandleSpecialTextOffset(text, &start_offset); | |
1026 ui::TextBoundaryType boundary = IA2TextBoundaryToTextBoundary(ia2_boundary); | |
1027 std::vector<int32> line_breaks; | |
1028 return ui::FindAccessibleTextBoundary( | |
1029 text, line_breaks, boundary, start_offset, direction); | |
1030 } | |
OLD | NEW |