root/dwt/accessibility/Accessible.d

Revision 313:fcbee1f66cc3, 64.5 kB (checked in by Frank Benoit <benoit@tionex.de>, 4 months ago)

Fix 2 typos

Line 
1 /*******************************************************************************
2  * Copyright (c) 2000, 2008 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  * Port to the D programming language:
11  *     Frank Benoit <benoit@tionex.de>
12  *******************************************************************************/
13 module dwt.accessibility.Accessible;
14
15 import dwt.DWT;
16 import dwt.DWTException;
17 import dwt.internal.ole.win32.COM;
18 import dwt.internal.ole.win32.OAIDL;
19 import dwt.internal.ole.win32.ifs;
20 import dwt.internal.ole.win32.extras;
21 import dwt.internal.win32.OS;
22 import dwt.internal.DWTEventListener;
23 import dwt.ole.win32.OLE;
24 import dwt.ole.win32.Variant;
25 import dwt.widgets.Control;
26 import dwt.widgets.Table;
27 import dwt.widgets.TableItem;
28 import dwt.widgets.Tree;
29 import dwt.widgets.TreeItem;
30 import dwt.widgets.Widget;
31
32 import dwt.accessibility.ACC;
33 import dwt.accessibility.AccessibleControlListener;
34 import dwt.accessibility.AccessibleListener;
35 import dwt.accessibility.AccessibleTextListener;
36 import dwt.accessibility.AccessibleControlEvent;
37 import dwt.accessibility.AccessibleEvent;
38
39 import dwt.dwthelper.utils;
40 import tango.core.Array;
41 import tango.core.Thread;
42
43 /**
44  * Instances of this class provide a bridge between application
45  * code and assistive technology clients. Many platforms provide
46  * default accessible behavior for most widgets, and this class
47  * allows that default behavior to be overridden. Applications
48  * can get the default Accessible object for a control by sending
49  * it <code>getAccessible</code>, and then add an accessible listener
50  * to override simple items like the name and help string, or they
51  * can add an accessible control listener to override complex items.
52  * As a rule of thumb, an application would only want to use the
53  * accessible control listener to implement accessibility for a
54  * custom control.
55  *
56  * @see Control#getAccessible
57  * @see AccessibleListener
58  * @see AccessibleEvent
59  * @see AccessibleControlListener
60  * @see AccessibleControlEvent
61  * @see <a href="http://www.eclipse.org/swt/snippets/#accessibility">Accessibility snippets</a>
62  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
63  *
64  * @since 2.0
65  */
66 public class Accessible {
67     int refCount = 0, enumIndex = 0;
68     _IAccessibleImpl objIAccessible;
69     _IEnumVARIANTImpl objIEnumVARIANT;
70     IAccessible iaccessible;
71     DWTEventListener[] accessibleListeners;
72     DWTEventListener[] accessibleControlListeners;
73     DWTEventListener[] textListeners;
74     Object[] variants;
75     Control control;
76
77     this(Control control) {
78         this.control = control;
79         int /*long*/[] ppvObject = new int /*long*/[1];
80         /* CreateStdAccessibleObject([in] hwnd, [in] idObject, [in] riidInterface, [out] ppvObject).
81          * AddRef has already been called on ppvObject by the callee and must be released by the caller.
82          */
83         HRESULT result = COM.CreateStdAccessibleObject(control.handle, COM.OBJID_CLIENT, &COM.IIDIAccessible, cast(void**)&iaccessible);
84         /* The object needs to be checked, because if the CreateStdAccessibleObject()
85          * symbol is not found, the return value is S_OK.
86          */
87         if (iaccessible is null) return;
88         if (result !is COM.S_OK) OLE.error(OLE.ERROR_CANNOT_CREATE_OBJECT, result);
89
90         objIAccessible = new _IAccessibleImpl(this);
91
92 //PORTING_FIXME: i don't understand this...
93 /+
94         int /*long*/ ppVtable = objIAccessible.ppVtable;
95         int /*long*/[] pVtable = new int /*long*/[1];
96         COM.MoveMemory(pVtable, ppVtable, OS.PTR_SIZEOF);
97         int /*long*/[] funcs = new int /*long*/[28];
98         COM.MoveMemory(funcs, pVtable[0], OS.PTR_SIZEOF * funcs.length);
99         funcs[9] = COM.get_accChild_CALLBACK(funcs[9]);
100         funcs[10] = COM.get_accName_CALLBACK(funcs[10]);
101         funcs[11] = COM.get_accValue_CALLBACK(funcs[11]);
102         funcs[12] = COM.get_accDescription_CALLBACK(funcs[12]);
103         funcs[13] = COM.get_accRole_CALLBACK(funcs[13]);
104         funcs[14] = COM.get_accState_CALLBACK(funcs[14]);
105         funcs[15] = COM.get_accHelp_CALLBACK(funcs[15]);
106         funcs[16] = COM.get_accHelpTopic_CALLBACK(funcs[16]);
107         funcs[17] = COM.get_accKeyboardShortcut_CALLBACK(funcs[17]);
108         funcs[20] = COM.get_accDefaultAction_CALLBACK(funcs[20]);
109         funcs[21] = COM.accSelect_CALLBACK(funcs[21]);
110         funcs[22] = COM.accLocation_CALLBACK(funcs[22]);
111         funcs[23] = COM.accNavigate_CALLBACK(funcs[23]);
112         funcs[25] = COM.accDoDefaultAction_CALLBACK(funcs[25]);
113         funcs[26] = COM.put_accName_CALLBACK(funcs[26]);
114         funcs[27] = COM.put_accValue_CALLBACK(funcs[27]);
115         COM.MoveMemory(pVtable[0], funcs, OS.PTR_SIZEOF * funcs.length);
116 +/
117
118         objIEnumVARIANT = new _IEnumVARIANTImpl(this) ;
119         AddRef();
120     }
121
122     /**
123      * Invokes platform specific functionality to allocate a new accessible object.
124      * <p>
125      * <b>IMPORTANT:</b> This method is <em>not</em> part of the public
126      * API for <code>Accessible</code>. It is marked public only so that it
127      * can be shared within the packages provided by DWT. It is not
128      * available on all platforms, and should never be called from
129      * application code.
130      * </p>
131      *
132      * @param control the control to get the accessible object for
133      * @return the platform specific accessible object
134      */
135     public static Accessible internal_new_Accessible(Control control) {
136         return new Accessible(control);
137     }
138
139     /**
140      * Adds the listener to the collection of listeners who will
141      * be notified when an accessible client asks for certain strings,
142      * such as name, description, help, or keyboard shortcut. The
143      * listener is notified by sending it one of the messages defined
144      * in the <code>AccessibleListener</code> interface.
145      *
146      * @param listener the listener that should be notified when the receiver
147      * is asked for a name, description, help, or keyboard shortcut string
148      *
149      * @exception IllegalArgumentException <ul>
150      *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
151      * </ul>
152      * @exception DWTException <ul>
153      *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
154      *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
155      * </ul>
156      *
157      * @see AccessibleListener
158      * @see #removeAccessibleListener
159      */
160     public void addAccessibleListener(AccessibleListener listener) {
161         checkWidget();
162         if (listener is null) DWT.error(__FILE__, __LINE__, DWT.ERROR_NULL_ARGUMENT);
163         accessibleListeners ~= listener;
164     }
165
166     /**
167      * Adds the listener to the collection of listeners who will
168      * be notified when an accessible client asks for custom control
169      * specific information. The listener is notified by sending it
170      * one of the messages defined in the <code>AccessibleControlListener</code>
171      * interface.
172      *
173      * @param listener the listener that should be notified when the receiver
174      * is asked for custom control specific information
175      *
176      * @exception IllegalArgumentException <ul>
177      *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
178      * </ul>
179      * @exception DWTException <ul>
180      *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
181      *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
182      * </ul>
183      *
184      * @see AccessibleControlListener
185      * @see #removeAccessibleControlListener
186      */
187     public void addAccessibleControlListener(AccessibleControlListener listener) {
188         checkWidget();
189         if (listener is null) DWT.error(__FILE__, __LINE__, DWT.ERROR_NULL_ARGUMENT);
190         accessibleControlListeners ~= listener;
191     }
192
193     /**
194      * Adds the listener to the collection of listeners who will
195      * be notified when an accessible client asks for custom text control
196      * specific information. The listener is notified by sending it
197      * one of the messages defined in the <code>AccessibleTextListener</code>
198      * interface.
199      *
200      * @param listener the listener that should be notified when the receiver
201      * is asked for custom text control specific information
202      *
203      * @exception IllegalArgumentException <ul>
204      *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
205      * </ul>
206      * @exception DWTException <ul>
207      *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
208      *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
209      * </ul>
210      *
211      * @see AccessibleTextListener
212      * @see #removeAccessibleTextListener
213      *
214      * @since 3.0
215      */
216     public void addAccessibleTextListener (AccessibleTextListener listener) {
217         checkWidget ();
218         if (listener is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
219         textListeners ~= listener;
220     }
221
222     /**
223      * Returns the control for this Accessible object.
224      *
225      * @return the receiver's control
226      * @since 3.0
227      */
228     public Control getControl() {
229         return control;
230     }
231
232     /**
233      * Invokes platform specific functionality to dispose an accessible object.
234      * <p>
235      * <b>IMPORTANT:</b> This method is <em>not</em> part of the public
236      * API for <code>Accessible</code>. It is marked public only so that it
237      * can be shared within the packages provided by DWT. It is not
238      * available on all platforms, and should never be called from
239      * application code.
240      * </p>
241      */
242     public void internal_dispose_Accessible() {
243         if (iaccessible !is null) {
244             iaccessible.Release();
245         }
246         iaccessible = null;
247         Release();
248     }
249
250     /**
251      * Invokes platform specific functionality to handle a window message.
252      * <p>
253      * <b>IMPORTANT:</b> This method is <em>not</em> part of the public
254      * API for <code>Accessible</code>. It is marked public only so that it
255      * can be shared within the packages provided by DWT. It is not
256      * available on all platforms, and should never be called from
257      * application code.
258      * </p>
259      */
260     public int /*long*/ internal_WM_GETOBJECT (int /*long*/ wParam, int /*long*/ lParam) {
261         if (objIAccessible is null) return 0;
262         if (lParam is COM.OBJID_CLIENT) {
263             /* LresultFromObject([in] riid, [in] wParam, [in] pAcc)
264              * The argument pAcc is owned by the caller so reference count does not
265              * need to be incremented.
266              */
267             return COM.LresultFromObject(&COM.IIDIAccessible, wParam, cast(IAccessible)objIAccessible);
268         }
269         return 0;
270     }
271
272     /**
273      * Removes the listener from the collection of listeners who will
274      * be notified when an accessible client asks for certain strings,
275      * such as name, description, help, or keyboard shortcut.
276      *
277      * @param listener the listener that should no longer be notified when the receiver
278      * is asked for a name, description, help, or keyboard shortcut string
279      *
280      * @exception IllegalArgumentException <ul>
281      *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
282      * </ul>
283      * @exception DWTException <ul>
284      *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
285      *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
286      * </ul>
287      *
288      * @see AccessibleListener
289      * @see #addAccessibleListener
290      */
291     public void removeAccessibleListener(AccessibleListener listener) {
292         checkWidget();
293         if (listener is null) DWT.error(__FILE__, __LINE__, DWT.ERROR_NULL_ARGUMENT);
294         accessibleListeners.length = accessibleListeners.remove(listener);
295     }
296
297     /**
298      * Removes the listener from the collection of listeners who will
299      * be notified when an accessible client asks for custom control
300      * specific information.
301      *
302      * @param listener the listener that should no longer be notified when the receiver
303      * is asked for custom control specific information
304      *
305      * @exception IllegalArgumentException <ul>
306      *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
307      * </ul>
308      * @exception DWTException <ul>
309      *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
310      *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
311      * </ul>
312      *
313      * @see AccessibleControlListener
314      * @see #addAccessibleControlListener
315      */
316     public void removeAccessibleControlListener(AccessibleControlListener listener) {
317         checkWidget();
318         if (listener is null) DWT.error(__FILE__, __LINE__, DWT.ERROR_NULL_ARGUMENT);
319         accessibleControlListeners.length = accessibleControlListeners.remove(listener);
320     }
321
322     /**
323      * Removes the listener from the collection of listeners who will
324      * be notified when an accessible client asks for custom text control
325      * specific information.
326      *
327      * @param listener the listener that should no longer be notified when the receiver
328      * is asked for custom text control specific information
329      *
330      * @exception IllegalArgumentException <ul>
331      *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
332      * </ul>
333      * @exception DWTException <ul>
334      *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
335      *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
336      * </ul>
337      *
338      * @see AccessibleTextListener
339      * @see #addAccessibleTextListener
340      *
341      * @since 3.0
342      */
343     public void removeAccessibleTextListener (AccessibleTextListener listener) {
344         checkWidget ();
345         if (listener is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
346         textListeners.length = textListeners.remove (listener);
347     }
348
349     /**
350      * Sends a message to accessible clients that the child selection
351      * within a custom container control has changed.
352      *
353      * @exception DWTException <ul>
354      *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
355      *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
356      * </ul>
357      *
358      * @since 3.0
359      */
360     public void selectionChanged () {
361         checkWidget();
362         COM.NotifyWinEvent (COM.EVENT_OBJECT_SELECTIONWITHIN, control.handle, COM.OBJID_CLIENT, COM.CHILDID_SELF);
363     }
364
365     /**
366      * Sends a message to accessible clients indicating that the focus
367      * has changed within a custom control.
368      *
369      * @param childID an identifier specifying a child of the control
370      *
371      * @exception DWTException <ul>
372      *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
373      *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
374      * </ul>
375      */
376     public void setFocus(int childID) {
377         checkWidget();
378         COM.NotifyWinEvent (COM.EVENT_OBJECT_FOCUS, control.handle, COM.OBJID_CLIENT, childIDToOs(childID));
379     }
380
381     /**
382      * Sends a message to accessible clients that the text
383      * caret has moved within a custom control.
384      *
385      * @param index the new caret index within the control
386      *
387      * @exception DWTException <ul>
388      *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
389      *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
390      * </ul>
391      *
392      * @since 3.0
393      */
394     public void textCaretMoved (int index) {
395         checkWidget();
396         COM.NotifyWinEvent (COM.EVENT_OBJECT_LOCATIONCHANGE, control.handle, COM.OBJID_CARET, COM.CHILDID_SELF);
397     }
398
399     /**
400      * Sends a message to accessible clients that the text
401      * within a custom control has changed.
402      *
403      * @param type the type of change, one of <code>ACC.NOTIFY_TEXT_INSERT</code>
404      * or <code>ACC.NOTIFY_TEXT_DELETE</code>
405      * @param startIndex the text index within the control where the insertion or deletion begins
406      * @param length the non-negative length in characters of the insertion or deletion
407      *
408      * @exception DWTException <ul>
409      *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
410      *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
411      * </ul>
412      *
413      * @see ACC#TEXT_INSERT
414      * @see ACC#TEXT_DELETE
415      *
416      * @since 3.0
417      */
418     public void textChanged (int type, int startIndex, int length) {
419         checkWidget();
420         COM.NotifyWinEvent (COM.EVENT_OBJECT_VALUECHANGE, control.handle, COM.OBJID_CLIENT, COM.CHILDID_SELF);
421     }
422
423     /**
424      * Sends a message to accessible clients that the text
425      * selection has changed within a custom control.
426      *
427      * @exception DWTException <ul>
428      *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
429      *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
430      * </ul>
431      *
432      * @since 3.0
433      */
434     public void textSelectionChanged () {
435         checkWidget();
436         // not an MSAA event
437     }
438
439     /* QueryInterface([in] iid, [out] ppvObject)
440      * Ownership of ppvObject transfers from callee to caller so reference count on ppvObject
441      * must be incremented before returning.  Caller is responsible for releasing ppvObject.
442      */
443     HRESULT QueryInterface(REFIID riid, void** ppvObject) {
444         if (iaccessible is null) return COM.CO_E_OBJNOTCONNECTED;
445
446         if (COM.IsEqualGUID(riid, &COM.IIDIUnknown)) {
447             *ppvObject = cast(void*)cast(IUnknown)  objIAccessible;
448             AddRef();
449             return COM.S_OK;
450         }
451
452         if (COM.IsEqualGUID(riid, &COM.IIDIDispatch)) {
453             *ppvObject = cast(void*)cast(IDispatch) objIAccessible;
454             AddRef();
455             return COM.S_OK;
456         }
457
458         if (COM.IsEqualGUID(riid, &COM.IIDIAccessible)) {
459             *ppvObject = cast(void*)cast(IAccessible) objIAccessible;
460             AddRef();
461             return COM.S_OK;
462         }
463
464         if (COM.IsEqualGUID(riid, &COM.IIDIEnumVARIANT)) {
465             *ppvObject = cast(void*)cast(IEnumVARIANT) objIEnumVARIANT;
466             AddRef();
467             enumIndex = 0;
468             return COM.S_OK;
469         }
470
471         HRESULT result = iaccessible.QueryInterface(riid, ppvObject);
472         return result;
473     }
474
475     ULONG AddRef() {
476         refCount++;
477         return refCount;
478     }
479
480     ULONG Release() {
481         refCount--;
482
483         if (refCount is 0) {
484             objIAccessible = null;
485             objIEnumVARIANT = null;
486         }
487         return refCount;
488     }
489
490     HRESULT accDoDefaultAction(VARIANT variant) {
491         if (iaccessible is null) return COM.CO_E_OBJNOTCONNECTED;
492         // Currently, we don't let the application override this. Forward to the proxy.
493         int code = iaccessible.accDoDefaultAction(variant);
494         if (code is COM.E_INVALIDARG) code = COM.S_FALSE; // proxy doesn't know about app childID
495         return code;
496     }
497
498     HRESULT accHitTest(LONG xLeft, LONG yTop, VARIANT* pvarChild) {
499         if (iaccessible is null) return COM.CO_E_OBJNOTCONNECTED;
500         if (accessibleControlListeners.length is 0) {
501             return iaccessible.accHitTest(xLeft, yTop, pvarChild);
502         }
503
504         AccessibleControlEvent event = new AccessibleControlEvent(this);
505         event.childID = ACC.CHILDID_NONE;
506         event.x = xLeft;
507         event.y = yTop;
508         for (int i = 0; i < accessibleControlListeners.length; i++) {
509             AccessibleControlListener listener = cast(AccessibleControlListener) accessibleControlListeners[i];
510             listener.getChildAtPoint(event);
511         }
512         int childID = event.childID;
513         if (childID is ACC.CHILDID_NONE) {
514             return iaccessible.accHitTest(xLeft, yTop, pvarChild);
515         }
516         //TODO - use VARIANT structure
517         pvarChild.vt = COM.VT_I4;
518         pvarChild.lVal = childIDToOs(childID);
519         return COM.S_OK;
520     }
521
522     HRESULT accLocation(LONG* pxLeft, LONG* pyTop, LONG* pcxWidth, LONG* pcyHeight, VARIANT variant) {
523         if (iaccessible is null) return COM.CO_E_OBJNOTCONNECTED;
524         VARIANT* v = &variant;
525         //COM.MoveMemory(v, variant, VARIANT.sizeof);
526         if ((v.vt & 0xFFFF) !is COM.VT_I4) return COM.E_INVALIDARG;
527
528         /* Get the default location from the OS. */
529         int osLeft = 0, osTop = 0, osWidth = 0, osHeight = 0;
530         int code = iaccessible.accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, variant);
531         if (code is COM.E_INVALIDARG) code = COM.S_FALSE; // proxy doesn't know about app childID
532         if (accessibleControlListeners.length is 0) return code;
533         if (code is COM.S_OK) {
534             int[1] pLeft, pTop, pWidth, pHeight;
535             COM.MoveMemory(pLeft.ptr, pxLeft, 4);
536             COM.MoveMemory(pTop.ptr, pyTop, 4);
537             COM.MoveMemory(pWidth.ptr, pcxWidth, 4);
538             COM.MoveMemory(pHeight.ptr, pcyHeight, 4);
539             osLeft = pLeft[0]; osTop = pTop[0]; osWidth = pWidth[0]; osHeight = pHeight[0];
540         }
541
542         AccessibleControlEvent event = new AccessibleControlEvent(this);
543         event.childID = osToChildID(v.lVal);
544         event.x = osLeft;
545         event.y = osTop;
546         event.width = osWidth;
547         event.height = osHeight;
548         for (int i = 0; i < accessibleControlListeners.length; i++) {
549             AccessibleControlListener listener = cast(AccessibleControlListener) accessibleControlListeners[i];
550             listener.getLocation(event);
551         }
552         OS.MoveMemory(pxLeft, &event.x, 4);
553         OS.MoveMemory(pyTop, &event.y, 4);
554         OS.MoveMemory(pcxWidth, &event.width, 4);
555         OS.MoveMemory(pcyHeight, &event.height, 4);
556         return COM.S_OK;
557     }
558
559     HRESULT accNavigate(LONG navDir, VARIANT variant, VARIANT* pvarEndUpAt) {
560         if (iaccessible is null) return COM.CO_E_OBJNOTCONNECTED;
561         // Currently, we don't let the application override this. Forward to the proxy.
562         int code = iaccessible.accNavigate(navDir, variant, pvarEndUpAt);
563         if (code is COM.E_INVALIDARG) code = COM.S_FALSE; // proxy doesn't know about app childID
564         return code;
565     }
566
567     HRESULT accSelect(LONG flagsSelect, VARIANT variant) {
568         if (iaccessible is null) return COM.CO_E_OBJNOTCONNECTED;
569         // Currently, we don't let the application override this. Forward to the proxy.
570         int code = iaccessible.accSelect(flagsSelect, variant);
571         if (code is COM.E_INVALIDARG) code = COM.S_FALSE; // proxy doesn't know about app childID
572         return code;
573     }
574
575     /* get_accChild([in] varChild, [out] ppdispChild)
576      * Ownership of ppdispChild transfers from callee to caller so reference count on ppdispChild
577      * must be incremented before returning.  The caller is responsible for releasing ppdispChild.
578      */
579     HRESULT get_accChild(VARIANT variant, LPDISPATCH* ppdispChild) {
580         if (iaccessible is null) return COM.CO_E_OBJNOTCONNECTED;
581         VARIANT* v = &variant;
582         //COM.MoveMemory(v, variant, VARIANT.sizeof);
583         if ((v.vt & 0xFFFF) !is COM.VT_I4) return COM.E_INVALIDARG;
584         if (accessibleControlListeners.length is 0) {
585             int code = iaccessible.get_accChild(variant, ppdispChild);
586             if (code is COM.E_INVALIDARG) code = COM.S_FALSE; // proxy doesn't know about app childID
587             return code;
588         }
589
590         AccessibleControlEvent event = new AccessibleControlEvent(this);
591         event.childID = osToChildID(v.lVal);
592         for (int i = 0; i < accessibleControlListeners.length; i++) {
593             AccessibleControlListener listener = cast(AccessibleControlListener) accessibleControlListeners[i];
594             listener.getChild(event);
595         }
596         Accessible accessible = event.accessible;
597         if (accessible !is null) {
598             accessible.AddRef();
599             *ppdispChild = accessible.objIAccessible;
600             return COM.S_OK;
601         }
602         return COM.S_FALSE;
603     }
604
605     HRESULT get_accChildCount(LONG* pcountChildren) {
606         if (iaccessible is null) return COM.CO_E_OBJNOTCONNECTED;
607
608         /* Get the default child count from the OS. */
609         int osChildCount = 0;
610         int code = iaccessible.get_accChildCount(pcountChildren);
611         if (accessibleControlListeners.length is 0) return code;
612         if (code is COM.S_OK) {
613             int[1] pChildCount;
614             COM.MoveMemory(pChildCount.ptr, pcountChildren, 4);
615             osChildCount = pChildCount[0];
616         }
617
618         AccessibleControlEvent event = new AccessibleControlEvent(this);
619         event.childID = ACC.CHILDID_SELF;
620         event.detail