1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 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 org.eclipse.swt.accessibility.AccessibleObject;
14 
15 import org.eclipse.swt.internal.accessibility.gtk.ATK;
16 import org.eclipse.swt.internal.gtk.OS;
17 import org.eclipse.swt.internal.LONG;
18 import org.eclipse.swt.accessibility.Accessible;
19 import org.eclipse.swt.accessibility.AccessibleListener;
20 import org.eclipse.swt.accessibility.AccessibleControlListener;
21 import org.eclipse.swt.accessibility.AccessibleTextListener;
22 import org.eclipse.swt.accessibility.AccessibleEvent;
23 import org.eclipse.swt.accessibility.AccessibleControlEvent;
24 import org.eclipse.swt.accessibility.AccessibleTextEvent;
25 import org.eclipse.swt.accessibility.ACC;
26 import org.eclipse.swt.accessibility.AccessibleFactory;
27 import org.eclipse.swt.widgets.Display;
28 import java.lang.all;
29 import java.util.Vector;
30 import java.util.Hashtable;
31 import java.util.Enumeration;
32 version(Tango){
33     //import tango.text.Util;
34 } else { // Phobos
35 }
36 
37 class AccessibleObject {
38     AtkObject* handle;
39     int parentType;
40     int index = -1, id = ACC.CHILDID_SELF;
41     Accessible accessible;
42     AccessibleObject parent;
43     Hashtable children;
44     /*
45     * a lightweight object does not correspond to a concrete gtk widget, but
46     * to a logical child of a widget (eg.- a CTabItem, which is simply drawn)
47     */
48     bool isLightweight = false;
49 
50     static String actionNamePtr;
51     static String descriptionPtr;
52     static String keybindingPtr;
53     static String namePtr;
54     static AccessibleObject[AtkObject*] AccessibleObjects;
55     static /*const*/ ptrdiff_t ATK_ACTION_TYPE;
56     static /*const*/ ptrdiff_t ATK_COMPONENT_TYPE;
57     static /*const*/ ptrdiff_t ATK_HYPERTEXT_TYPE;
58     static /*const*/ ptrdiff_t ATK_SELECTION_TYPE;
59     static /*const*/ ptrdiff_t ATK_TEXT_TYPE;
60     static /*const*/ bool DEBUG;
61     static bool static_this_completed = false;
62 
63     package static void static_this() {
64         if( static_this_completed ) return;
65         DEBUG = Display.DEBUG;
66         ATK_ACTION_TYPE = ATK.g_type_from_name ("AtkAction");
67         ATK_COMPONENT_TYPE = ATK.g_type_from_name ("AtkComponent");
68         ATK_HYPERTEXT_TYPE = ATK.g_type_from_name ("AtkHypertext");
69         ATK_SELECTION_TYPE = ATK.g_type_from_name ("AtkSelection");
70         ATK_TEXT_TYPE = ATK.g_type_from_name ("AtkText");
71         static_this_completed = true;
72     }
73 
74     this (int type, GtkWidget* widget, Accessible accessible, int parentType, bool isLightweight) {
75         children = new Hashtable(9);
76         handle = cast(AtkObject*)ATK.g_object_new (type, null);
77         this.parentType = parentType;
78         ATK.atk_object_initialize (handle, widget);
79         this.accessible = accessible;
80         this.isLightweight = isLightweight;
81         AccessibleObjects[handle] = this;
82         if (DEBUG) getDwtLogger().info( __FILE__, __LINE__, "new AccessibleObject: {}", handle);
83     }
84 
85     void addChild (AccessibleObject child) {
86         children.put(new LONG(cast(int)child.handle), child);
87         child.setParent (this);
88     }
89 
90     package static extern(C) char* atkAction_get_keybinding (void* obj, int index) {
91         auto atkObject = cast(AtkObject*)obj;
92         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkAction_get_keybinding");
93         AccessibleObject object = getAccessibleObject (atkObject);
94         if (object is null) return null;
95         char* parentResult;
96         if (ATK.g_type_is_a (object.parentType, ATK_ACTION_TYPE)) {
97             auto superType = cast(AtkActionIface*)ATK.g_type_interface_peek_parent (ATK.ATK_ACTION_GET_IFACE (object.handle));
98             AtkActionIface* actionIface = superType;
99             if (actionIface.get_keybinding !is null) {
100                 parentResult = actionIface.get_keybinding( object.handle, index );
101             }
102         }
103         AccessibleListener[] listeners = object.getAccessibleListeners ();
104         if (listeners.length is 0) return parentResult;
105 
106         AccessibleEvent event = new AccessibleEvent (object);
107         event.childID = object.id;
108         if (parentResult !is null) {
109             String res = fromStringz( parentResult )._idup();
110             event.result = res;
111         }
112         for (int i = 0; i < listeners.length; i++) {
113             listeners [i].getKeyboardShortcut (event);
114         }
115         if (event.result is null) return parentResult;
116         if (keybindingPtr !is null ) OS.g_free (keybindingPtr.ptr);
117         String name = event.result._idup() ~ '\0';
118         char* p = cast(char*) OS.g_malloc (name.length);
119         keybindingPtr =  p ? cast(String)p[ 0 .. name.length ] : null;
120         return cast(char*)keybindingPtr.ptr;
121     }
122 
123     package static extern(C) char* atkAction_get_name (void* obj, int index) {
124         auto atkObject = cast(AtkObject*)obj;
125         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkAction_get_name");
126         AccessibleObject object = getAccessibleObject (atkObject);
127         if (object is null) return null;
128         char* parentResult;
129         if (ATK.g_type_is_a (object.parentType, ATK_ACTION_TYPE)) {
130             auto actionIface = cast(AtkActionIface*)ATK.g_type_interface_peek_parent (ATK.ATK_ACTION_GET_IFACE (object.handle));
131             if (actionIface.get_name !is null) {
132                 parentResult = actionIface.get_name( object.handle, index);
133             }
134         }
135         AccessibleControlListener[] listeners = object.getControlListeners ();
136         if (listeners.length is 0) return parentResult;
137 
138         AccessibleControlEvent event = new AccessibleControlEvent (object);
139         event.childID = object.id;
140         if (parentResult !is null) {
141             auto res = fromStringz( parentResult );
142             event.result = res._idup();
143         }
144         for (int i = 0; i < listeners.length; i++) {
145             listeners [i].getDefaultAction (event);
146         }
147         if (event.result is null) return parentResult;
148         if (actionNamePtr !is null) OS.g_free (actionNamePtr.ptr);
149 
150         String name = event.result._idup() ~ '\0';
151         auto p = cast(char*)OS.g_malloc (name.length);
152         actionNamePtr =  p ? cast(String)p[ 0 .. name.length ] : null;
153         return cast(char*)actionNamePtr.ptr;
154     }
155 
156     package static extern(C) void atkComponent_get_extents (void* obj, int* x, int* y, int* width, int* height, int coord_type) {
157         auto atkObject = cast(AtkObject*)obj;
158         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkComponent_get_extents");
159         AccessibleObject object = getAccessibleObject (atkObject);
160         if (object is null) return;
161         *x = 0;
162         *y = 0;
163         *width = 0;
164         *height = 0;
165         if (ATK.g_type_is_a (object.parentType, ATK_COMPONENT_TYPE)) {
166             auto componentIface = cast(AtkComponentIface*) ATK.g_type_interface_peek_parent (ATK.ATK_COMPONENT_GET_IFACE (object.handle));
167             if (componentIface.get_extents !is null) {
168                 componentIface.get_extents( object.handle, x, y, width, height, coord_type);
169             }
170         }
171         AccessibleControlListener[] listeners = object.getControlListeners ();
172         if (listeners.length is 0) return;
173 
174         int parentX = *x, parentY = *y;
175         int parentWidth = *width, parentHeight = *height;
176         AccessibleControlEvent event = new AccessibleControlEvent (object);
177         event.childID = object.id;
178         event.x = parentX; event.y = parentY;
179         event.width = parentWidth; event.height = parentHeight;
180         if (coord_type is ATK.ATK_XY_WINDOW) {
181             /* translate control -> display, for filling in event to be dispatched */
182             auto gtkAccessible = ATK.GTK_ACCESSIBLE (object.handle);
183             auto topLevel = ATK.gtk_widget_get_toplevel (gtkAccessible.widget);
184             auto window = OS.GTK_WIDGET_WINDOW (topLevel);
185             int topWindowX, topWindowY;
186             OS.gdk_window_get_origin (window, &topWindowX, &topWindowY);
187             event.x += topWindowX;
188             event.y += topWindowY;
189         }
190         for (int i = 0; i < listeners.length; i++) {
191             listeners [i].getLocation (event);
192         }
193         if (coord_type is ATK.ATK_XY_WINDOW) {
194             /* translate display -> control, for answering to the OS */
195             auto gtkAccessible = ATK.GTK_ACCESSIBLE (object.handle);
196             auto topLevel = ATK.gtk_widget_get_toplevel (gtkAccessible.widget);
197             auto window = OS.GTK_WIDGET_WINDOW (topLevel);
198             int topWindowX, topWindowY;
199             OS.gdk_window_get_origin (window, &topWindowX, &topWindowY);
200             event.x -= topWindowX;
201             event.y -= topWindowY;
202         }
203         *x = event.x;
204         *y = event.y;
205         *width = event.width;
206         *height = event.height;
207         //return 0;
208     }
209 
210     package static extern(C) void atkComponent_get_position (void* obj, int* x, int* y, int coord_type) {
211         auto atkObject = cast(AtkObject*)obj;
212         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkComponent_get_position, object: {} x:{} y:{} coord:{}", atkObject, x, y, coord_type);
213         AccessibleObject object = getAccessibleObject (atkObject);
214         if (object is null) return;
215         *x=0;
216         *y=0;
217         if (ATK.g_type_is_a (object.parentType, ATK_COMPONENT_TYPE)) {
218             auto componentIface = cast(AtkComponentIface*)ATK.g_type_interface_peek_parent (ATK.ATK_COMPONENT_GET_IFACE (object.handle));
219             if (componentIface.get_extents !is null) {
220                 componentIface.get_position( object.handle, x, y, coord_type);
221             }
222         }
223         AccessibleControlListener[] listeners = object.getControlListeners ();
224         if (listeners.length is 0) return;
225 
226         int parentX, parentY;
227         parentX = *x;
228         parentY = *y;
229         AccessibleControlEvent event = new AccessibleControlEvent (object);
230         event.childID = object.id;
231         event.x = parentX; event.y = parentY;
232         if (coord_type is ATK.ATK_XY_WINDOW) {
233             /* translate control -> display, for filling in event to be dispatched */
234             auto gtkAccessible = ATK.GTK_ACCESSIBLE (object.handle);
235             auto topLevel = ATK.gtk_widget_get_toplevel (gtkAccessible.widget);
236             auto window = OS.GTK_WIDGET_WINDOW (topLevel);
237             int topWindowX, topWindowY;
238             OS.gdk_window_get_origin (window, &topWindowX, &topWindowY);
239             event.x += topWindowX;
240             event.y += topWindowY;
241         }
242         for (int i = 0; i < listeners.length; i++) {
243             listeners [i].getLocation (event);
244         }
245         if (coord_type is ATK.ATK_XY_WINDOW) {
246             /* translate display -> control, for answering to the OS */
247             auto gtkAccessible = ATK.GTK_ACCESSIBLE (object.handle);
248             auto topLevel = ATK.gtk_widget_get_toplevel (gtkAccessible.widget);
249             auto window = OS.GTK_WIDGET_WINDOW (topLevel);
250             int topWindowX, topWindowY;
251             OS.gdk_window_get_origin (window, &topWindowX, &topWindowY);
252             event.x -= topWindowX;
253             event.y -= topWindowY;
254         }
255         *x=event.x;
256         *y=event.y;
257         //return 0;
258     }
259 
260     //PORTING_FIXME: what about the coord_type? componentIface.get_size( object.handle, width, height, coord_type);
261     //package static extern(C) void atkComponent_get_size (void* obj, int* width, int* height, int coord_type) {
262     package static extern(C) void atkComponent_get_size (void* obj, int* width, int* height) {
263         auto atkObject = cast(AtkObject*)obj;
264         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkComponent_get_size");
265         AccessibleObject object = getAccessibleObject (atkObject);
266         if (object is null) return;
267         *width=0;
268         *height=0;
269         if (ATK.g_type_is_a (object.parentType, ATK_COMPONENT_TYPE)) {
270             auto componentIface = cast(AtkComponentIface*)ATK.g_type_interface_peek_parent (ATK.ATK_COMPONENT_GET_IFACE (object.handle));
271             if (componentIface.get_extents !is null) {
272                 //PORTING_FIXME: what about the coord_type? componentIface.get_size( object.handle, width, height, coord_type);
273                 componentIface.get_size( object.handle, width, height);
274             }
275         }
276         AccessibleControlListener[] listeners = object.getControlListeners ();
277         if (listeners.length is 0) return;
278 
279         int parentWidth, parentHeight;
280         parentWidth= *width;
281         parentHeight= *height;
282         AccessibleControlEvent event = new AccessibleControlEvent (object);
283         event.childID = object.id;
284         event.width = parentWidth; event.height = parentHeight;
285         for (int i = 0; i < listeners.length; i++) {
286             listeners [i].getLocation (event);
287         }
288         *width=event.width;
289         *height=event.height;
290         //return 0;
291     }
292 
293     package static extern(C) AtkObject* atkComponent_ref_accessible_at_point (void* obj, int x, int y, int coord_type) {
294         auto atkObject = cast(AtkObject*)obj;
295         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkComponent_ref_accessible_at_point");
296         AccessibleObject object = getAccessibleObject (atkObject);
297         if (object is null) return null;
298         AtkObject* parentResult;
299         if (ATK.g_type_is_a (object.parentType, ATK_COMPONENT_TYPE)) {
300             auto componentIface = cast(AtkComponentIface*)ATK.g_type_interface_peek_parent (ATK.ATK_COMPONENT_GET_IFACE (object.handle));
301             if (componentIface.ref_accessible_at_point !is null) {
302                 parentResult = componentIface.ref_accessible_at_point( object.handle, x, y, coord_type);
303             }
304         }
305         AccessibleControlListener[] listeners = object.getControlListeners ();
306         if (listeners.length is 0) return parentResult;
307 
308         AccessibleControlEvent event = new AccessibleControlEvent (object);
309         event.childID = object.id;
310         event.x = x; event.y = y;
311         if (coord_type is ATK.ATK_XY_WINDOW) {
312             /* translate control -> display, for filling in the event to be dispatched */
313             auto gtkAccessible = ATK.GTK_ACCESSIBLE (object.handle);
314             auto topLevel = ATK.gtk_widget_get_toplevel (gtkAccessible.widget);
315             auto window = OS.GTK_WIDGET_WINDOW (topLevel);
316             int topWindowX, topWindowY;
317             OS.gdk_window_get_origin (window, &topWindowX, &topWindowY);
318             event.x += topWindowX;
319             event.y += topWindowY;
320         }
321         for (int i = 0; i < listeners.length; i++) {
322             listeners [i].getChildAtPoint (event);
323         }
324         if (event.childID is object.id) event.childID = ACC.CHILDID_SELF;
325         AccessibleObject accObj = object.getChildByID (event.childID);
326         if (accObj !is null) {
327             if (parentResult !is null) OS.g_object_unref (parentResult);
328             OS.g_object_ref (accObj.handle);
329             return accObj.handle;
330         }
331         return parentResult;
332     }
333 
334     package static extern(C) AtkHyperlink* atkHypertext_get_link (void* obj, int link_index) {
335         auto atkObject = cast(AtkObject*)obj;
336         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkHypertext_get_link");
337         return null;
338     }
339 
340     package static extern(C) int atkHypertext_get_n_links (void* obj) {
341         auto atkObject = cast(AtkObject*)obj;
342         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkHypertext_get_n_links");
343         return 0;   /* read hyperlink's name */
344     }
345 
346     package static extern(C) int atkHypertext_get_link_index (void* obj, int char_index) {
347         auto atkObject = cast(AtkObject*)obj;
348         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkHypertext_get_link_index");
349         return 0;
350     }
351 
352     package static extern(C) char* atkObject_get_description (AtkObject* atkObject) {
353         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkObject_get_description");
354         AccessibleObject object = getAccessibleObject (atkObject);
355         if (object is null) return null;
356         char* parentResult;
357         auto objectClass = cast(AtkObjectClass*)ATK.g_type_class_peek (object.parentType);
358         if (objectClass.get_description !is null) {
359             parentResult = objectClass.get_description(object.handle);
360         }
361         AccessibleListener[] listeners = object.getAccessibleListeners ();
362         if (listeners.length is 0) return parentResult;
363 
364         AccessibleEvent event = new AccessibleEvent (object);
365         event.childID = object.id;
366         if (parentResult !is null) {
367             event.result = fromStringz( parentResult )._idup();
368         }
369         for (int i = 0; i < listeners.length; i++) {
370             listeners [i].getDescription (event);
371         }
372         if (event.result is null) return parentResult;
373         if (descriptionPtr !is null) OS.g_free (descriptionPtr.ptr);
374 
375         String name = event.result._idup() ~ '\0';
376         char* p = cast(char*)OS.g_malloc (name.length);
377         descriptionPtr =  p ? cast(String)p[ 0 .. name.length ] : null;
378         return cast(char*)descriptionPtr.ptr;
379     }
380 
381     package static extern(C) char* atkObject_get_name (AtkObject* atkObject) {
382         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkObject_get_name: {}", atkObject);
383         AccessibleObject object = getAccessibleObject (atkObject);
384         if (object is null) return null;
385         char* parentResult;
386         auto objectClass = cast(AtkObjectClass*)ATK.g_type_class_peek (object.parentType);
387         if (objectClass.get_name !is null) {
388             parentResult = objectClass.get_name( object.handle);
389         }
390         AccessibleListener[] listeners = object.getAccessibleListeners ();
391         if (listeners.length is 0) return parentResult;
392 
393         AccessibleEvent event = new AccessibleEvent (object);
394         event.childID = object.id;
395         if (parentResult !is null) {
396             event.result = fromStringz( parentResult )._idup();
397         }
398         for (int i = 0; i < listeners.length; i++) {
399             listeners [i].getName (event);
400         }
401         if (event.result is null) return parentResult;
402         if (namePtr !is null) OS.g_free (namePtr.ptr);
403         String name = event.result._idup() ~ '\0';
404         char* p = cast(char*)OS.g_malloc (name.length);
405         namePtr =  p ? cast(String)p[ 0 .. name.length ] : null;
406         return cast(char*)namePtr.ptr;
407     }
408 
409     package static extern(C) int atkObject_get_n_children (AtkObject* atkObject) {
410         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkObject_get_n_children: {}", atkObject);
411         AccessibleObject object = getAccessibleObject (atkObject);
412         if (object is null) return 0;
413         int parentResult = 0;
414         auto objectClass = cast(AtkObjectClass*)ATK.g_type_class_peek (object.parentType);
415         if (objectClass.get_n_children !is null) {
416             parentResult = objectClass.get_n_children( object.handle);
417         }
418         AccessibleControlListener[] listeners = object.getControlListeners ();
419         if (listeners.length is 0) return parentResult;
420 
421         AccessibleControlEvent event = new AccessibleControlEvent (object);
422         event.childID = object.id;
423         event.detail = parentResult;
424         for (int i = 0; i < listeners.length; i++) {
425             listeners [i].getChildCount (event);
426         }
427         return event.detail;
428     }
429 
430     package static extern(C) int atkObject_get_index_in_parent (AtkObject* atkObject) {
431         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkObjectCB_get_index_in_parent.  ");
432         AccessibleObject object = getAccessibleObject (atkObject);
433         if (object is null) return 0;
434         if (object.index !is -1) return object.index;
435         auto objectClass = cast(AtkObjectClass*)ATK.g_type_class_peek (object.parentType);
436         if (objectClass.get_index_in_parent is null) return 0;
437         return objectClass.get_index_in_parent(object. handle);
438     }
439 
440     package static extern(C) AtkObject* atkObject_get_parent (AtkObject* atkObject) {
441         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkObject_get_parent: {}", atkObject);
442         AccessibleObject object = getAccessibleObject (atkObject);
443         if (object is null) return null;
444         if (object.parent !is null) return object.parent.handle;
445         auto objectClass = cast(AtkObjectClass*)ATK.g_type_class_peek (object.parentType);
446         if (objectClass.get_parent is null) return null;
447         return objectClass.get_parent( object.handle);
448     }
449 
450     package static extern(C) int atkObject_get_role (AtkObject* atkObject) {
451         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkObject_get_role: {}", atkObject);
452         AccessibleObject object = getAccessibleObject (atkObject);
453         if (object is null) return 0;
454         if (object.getAccessibleListeners ().length !is 0) {
455             AccessibleControlListener[] listeners = object.getControlListeners ();
456             AccessibleControlEvent event = new AccessibleControlEvent (object);
457             event.childID = object.id;
458             event.detail = -1;
459             for (int i = 0; i < listeners.length; i++) {
460                 listeners [i].getRole (event);
461             }
462             if (event.detail !is -1) {
463                 switch (event.detail) {
464                     /* Convert from win32 role values to atk role values */
465                     case ACC.ROLE_CHECKBUTTON: return ATK.ATK_ROLE_CHECK_BOX;
466                     case ACC.ROLE_CLIENT_AREA: return ATK.ATK_ROLE_DRAWING_AREA;
467                     case ACC.ROLE_COMBOBOX: return ATK.ATK_ROLE_COMBO_BOX;
468                     case ACC.ROLE_DIALOG: return ATK.ATK_ROLE_DIALOG;
469                     case ACC.ROLE_LABEL: return ATK.ATK_ROLE_LABEL;
470                     case ACC.ROLE_LINK: return ATK.ATK_ROLE_TEXT;
471                     case ACC.ROLE_LIST: return ATK.ATK_ROLE_LIST;
472                     case ACC.ROLE_LISTITEM: return ATK.ATK_ROLE_LIST_ITEM;
473                     case ACC.ROLE_MENU: return ATK.ATK_ROLE_MENU;
474                     case ACC.ROLE_MENUBAR: return ATK.ATK_ROLE_MENU_BAR;
475                     case ACC.ROLE_MENUITEM: return ATK.ATK_ROLE_MENU_ITEM;
476                     case ACC.ROLE_PROGRESSBAR: return ATK.ATK_ROLE_PROGRESS_BAR;
477                     case ACC.ROLE_PUSHBUTTON: return ATK.ATK_ROLE_PUSH_BUTTON;
478                     case ACC.ROLE_SCROLLBAR: return ATK.ATK_ROLE_SCROLL_BAR;
479                     case ACC.ROLE_SEPARATOR: return ATK.ATK_ROLE_SEPARATOR;
480                     case ACC.ROLE_SLIDER: return ATK.ATK_ROLE_SLIDER;
481                     case ACC.ROLE_TABLE: return ATK.ATK_ROLE_LIST;
482                     case ACC.ROLE_TABLECELL: return ATK.ATK_ROLE_LIST_ITEM;
483                     case ACC.ROLE_TABLECOLUMNHEADER: return ATK.ATK_ROLE_TABLE_COLUMN_HEADER;
484                     case ACC.ROLE_TABLEROWHEADER: return ATK.ATK_ROLE_TABLE_ROW_HEADER;
485                     case ACC.ROLE_TABFOLDER: return ATK.ATK_ROLE_PAGE_TAB_LIST;
486                     case ACC.ROLE_TABITEM: return ATK.ATK_ROLE_PAGE_TAB;
487                     case ACC.ROLE_TEXT: return ATK.ATK_ROLE_TEXT;
488                     case ACC.ROLE_TOOLBAR: return ATK.ATK_ROLE_TOOL_BAR;
489                     case ACC.ROLE_TOOLTIP: return ATK.ATK_ROLE_TOOL_TIP;
490                     case ACC.ROLE_TREE: return ATK.ATK_ROLE_TREE;
491                     case ACC.ROLE_TREEITEM: return ATK.ATK_ROLE_LIST_ITEM;
492                     case ACC.ROLE_RADIOBUTTON: return ATK.ATK_ROLE_RADIO_BUTTON;
493                     case ACC.ROLE_WINDOW: return ATK.ATK_ROLE_WINDOW;
494                     default:
495                 }
496             }
497         }
498         auto objectClass = cast(AtkObjectClass*)ATK.g_type_class_peek (object.parentType);
499         if (objectClass.get_role is null) return 0;
500         return objectClass.get_role( object.handle);
501     }
502 
503     package static extern(C) AtkObject* atkObject_ref_child (AtkObject* atkObject, int index) {
504         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkObject_ref_child: {} of: {}", index, atkObject);
505         AccessibleObject object = getAccessibleObject (atkObject);
506         if (object is null) return null;
507         object.updateChildren ();
508         AccessibleObject accObject = object.getChildByIndex (index);
509         if (accObject !is null) {
510             OS.g_object_ref (accObject.handle);
511             return accObject.handle;
512         }
513         auto objectClass = cast(AtkObjectClass*)ATK.g_type_class_peek (object.parentType);
514         if (objectClass.ref_child is null) return null;
515         return objectClass.ref_child( object.handle, index);
516     }
517 
518     package static extern(C) AtkStateSet * atkObject_ref_state_set (AtkObject* atkObject) {
519         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkObject_ref_state_set");
520         AccessibleObject object = getAccessibleObject (atkObject);
521         if (object is null) return null;
522         AtkStateSet* parentResult;
523         auto objectClass = cast(AtkObjectClass*)ATK.g_type_class_peek (object.parentType);
524         if (objectClass.ref_state_set !is null) {
525             parentResult = objectClass.ref_state_set( object.handle);
526         }
527         AccessibleControlListener[] listeners = object.getControlListeners ();
528         if (listeners.length is 0) return parentResult;
529 
530         auto set = parentResult;
531         AccessibleControlEvent event = new AccessibleControlEvent (object);
532         event.childID = object.id;
533         event.detail = -1;
534         for (int i = 0; i < listeners.length; i++) {
535             listeners [i].getState (event);
536         }
537         if (event.detail !is -1) {
538             /*  Convert from win32 state values to atk state values */
539             int state = event.detail;
540             if ((state & ACC.STATE_BUSY) !is 0) ATK.atk_state_set_add_state (set, ATK.ATK_STATE_BUSY);
541             if ((state & ACC.STATE_CHECKED) !is 0) ATK.atk_state_set_add_state (set, ATK.ATK_STATE_CHECKED);
542             if ((state & ACC.STATE_EXPANDED) !is 0) ATK.atk_state_set_add_state (set, ATK.ATK_STATE_EXPANDED);
543             if ((state & ACC.STATE_FOCUSABLE) !is 0) ATK.atk_state_set_add_state (set, ATK.ATK_STATE_FOCUSABLE);
544             if ((state & ACC.STATE_FOCUSED) !is 0) ATK.atk_state_set_add_state (set, ATK.ATK_STATE_FOCUSED);
545             if ((state & ACC.STATE_HOTTRACKED) !is 0) ATK.atk_state_set_add_state (set, ATK.ATK_STATE_ARMED);
546             if ((state & ACC.STATE_INVISIBLE) is 0) ATK.atk_state_set_add_state (set, ATK.ATK_STATE_VISIBLE);
547             if ((state & ACC.STATE_MULTISELECTABLE) !is 0) ATK.atk_state_set_add_state (set, ATK.ATK_STATE_MULTISELECTABLE);
548             if ((state & ACC.STATE_OFFSCREEN) is 0) ATK.atk_state_set_add_state (set, ATK.ATK_STATE_SHOWING);
549             if ((state & ACC.STATE_PRESSED) !is 0) ATK.atk_state_set_add_state (set, ATK.ATK_STATE_PRESSED);
550             if ((state & ACC.STATE_READONLY) is 0) ATK.atk_state_set_add_state (set, ATK.ATK_STATE_EDITABLE);
551             if ((state & ACC.STATE_SELECTABLE) !is 0) ATK.atk_state_set_add_state (set, ATK.ATK_STATE_SELECTABLE);
552             if ((state & ACC.STATE_SELECTED) !is 0) ATK.atk_state_set_add_state (set, ATK.ATK_STATE_SELECTED);
553             if ((state & ACC.STATE_SIZEABLE) !is 0) ATK.atk_state_set_add_state (set, ATK.ATK_STATE_RESIZABLE);
554             /* Note: STATE_COLLAPSED, STATE_LINKED and STATE_NORMAL have no ATK equivalents */
555         }
556         return set;
557     }
558 
559     package static extern(C) int atkSelection_is_child_selected (void* obj, int index) {
560         auto atkObject = cast(AtkObject*)obj;
561         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkSelection_is_child_selected");
562         AccessibleObject object = getAccessibleObject (atkObject);
563         if (object is null) return 0;
564         int parentResult = 0;
565         if (ATK.g_type_is_a (object.parentType, ATK_SELECTION_TYPE)) {
566             auto selectionIface = cast(AtkSelectionIface*)ATK.g_type_interface_peek_parent (ATK.ATK_SELECTION_GET_IFACE (object.handle));
567             if (selectionIface.is_child_selected !is null) {
568                 parentResult = selectionIface.is_child_selected( object.handle, index);
569             }
570         }
571         AccessibleControlListener[] listeners = object.getControlListeners ();
572         if (listeners.length is 0) return parentResult;
573 
574         AccessibleControlEvent event = new AccessibleControlEvent (object);
575         event.childID = object.id;
576         for (int i = 0; i < listeners.length; i++) {
577             listeners [i].getSelection (event);
578         }
579         AccessibleObject accessibleObject = object.getChildByID (event.childID);
580         if (accessibleObject !is null) {
581             return accessibleObject.index is index ? 1 : 0;
582         }
583         return parentResult;
584     }
585 
586     package static extern(C) AtkObject* atkSelection_ref_selection (void* obj, int index) {
587         auto atkObject = cast(AtkObject*)obj;
588         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkSelection_ref_selection");
589         AccessibleObject object = getAccessibleObject (atkObject);
590         if (object is null) return null;
591         AtkObject* parentResult;
592         if (ATK.g_type_is_a (object.parentType, ATK_SELECTION_TYPE)) {
593             auto selectionIface = cast(AtkSelectionIface*)ATK.g_type_interface_peek_parent (ATK.ATK_SELECTION_GET_IFACE (object.handle));
594             if (selectionIface.ref_selection !is null) {
595                 parentResult = selectionIface.ref_selection( object.handle, index);
596             }
597         }
598         AccessibleControlListener[] listeners = object.getControlListeners ();
599         if (listeners.length is 0) return parentResult;
600 
601         AccessibleControlEvent event = new AccessibleControlEvent (object);
602         event.childID = object.id;
603         for (int i = 0; i < listeners.length; i++) {
604             listeners [i].getSelection (event);
605         }
606         AccessibleObject accObj = object.getChildByID (event.childID);
607         if (accObj !is null) {
608             if (parentResult !is null) OS.g_object_unref (parentResult);
609             OS.g_object_ref (accObj.handle);
610             return accObj.handle;
611         }
612         return parentResult;
613     }
614 
615     package static extern(C) int atkText_get_caret_offset (void* obj) {
616         auto atkObject = cast(AtkObject*)obj;
617         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkText_get_caret_offset");
618         AccessibleObject object = getAccessibleObject (atkObject);
619         if (object is null) return 0;
620         int parentResult = 0;
621         if (ATK.g_type_is_a (object.parentType, ATK_TEXT_TYPE)) {
622             auto textIface = cast(AtkTextIface*)ATK.g_type_interface_peek_parent (ATK.ATK_TEXT_GET_IFACE (object.handle));
623             if (textIface.get_caret_offset !is null) {
624                 parentResult = textIface.get_caret_offset( object.handle);
625             }
626         }
627         AccessibleTextListener[] listeners = object.getTextListeners ();
628         if (listeners.length is 0) return parentResult;
629 
630         AccessibleTextEvent event = new AccessibleTextEvent (object);
631         event.childID = object.id;
632         event.offset = parentResult;
633         for (int i = 0; i < listeners.length; i++) {
634             listeners [i].getCaretOffset (event);
635         }
636         return event.offset;
637     }
638 
639     package static extern(C) uint atkText_get_character_at_offset (void* obj, int offset) {
640         auto atkObject = cast(AtkObject*)obj;
641         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkText_get_character_at_offset");
642         AccessibleObject object = getAccessibleObject (atkObject);
643         if (object is null) return 0;
644         String text = object.getText ();
645         if (text !is null) return text[ offset ]; // TODO
646         if (ATK.g_type_is_a (object.parentType, ATK_TEXT_TYPE)) {
647             auto textIface = cast(AtkTextIface*)ATK.g_type_class_peek (object.parentType);
648             if (textIface.get_character_at_offset !is null) {
649                 return textIface.get_character_at_offset( object.handle, offset);
650             }
651         }
652         return 0;
653     }
654 
655     package static extern(C) int atkText_get_character_count (void* obj) {
656         auto atkObject = cast(AtkObject*)obj;
657         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkText_get_character_count");
658         AccessibleObject object = getAccessibleObject (atkObject);
659         if (object is null) return 0;
660         String text = object.getText ();
661         if (text !is null) return cast(int)/*64bit*/text.length;
662         if (ATK.g_type_is_a (object.parentType, ATK_TEXT_TYPE)) {
663             auto textIface = cast(AtkTextIface*)ATK.g_type_class_peek (object.parentType);
664             if (textIface.get_character_count !is null) {
665                 return textIface.get_character_count( object.handle);
666             }
667         }
668         return 0;
669     }
670 
671     package static extern(C) int atkText_get_n_selections (void* obj) {
672         auto atkObject = cast(AtkObject*)obj;
673         if (DEBUG) getDwtLogger().info( __FILE__, __LINE__, "-->atkText_get_n_selections");
674         AccessibleObject object = getAccessibleObject (atkObject);
675         if (object is null) return 0;
676         int parentResult = 0;
677         if (ATK.g_type_is_a (object.parentType, ATK_TEXT_TYPE)) {
678             auto textIface = cast(AtkTextIface*)ATK.g_type_interface_peek_parent (ATK.ATK_TEXT_GET_IFACE (object.handle));
679             if (textIface.get_n_selections !is null) {
680                 parentResult = textIface.get_n_selections( object.handle);
681             }
682         }
683         AccessibleTextListener[] listeners = object.getTextListeners ();
684         if (listeners.length is 0) return parentResult;
685 
686         AccessibleTextEvent event = new AccessibleTextEvent (object);
687         event.childID = object.id;
688         for (int i = 0; i < listeners.length; i++) {
689             listeners [i].getSelectionRange (event);
690         }
691         return event.length is 0 ? parentResult : 1;
692     }
693 
694     package static extern(C) char* atkText_get_selection (void* obj, int selection_num, int* start_offset, int* end_offset) {
695         auto atkObject = cast(AtkObject*)obj;
696         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkText_get_selection");
697         AccessibleObject object = getAccessibleObject (atkObject);
698         if (object is null) return null;
699         *start_offset=0;
700         *end_offset=0;
701         if (ATK.g_type_is_a (object.parentType, ATK_TEXT_TYPE)) {
702             auto textIface = cast(AtkTextIface*)ATK.g_type_interface_peek_parent (ATK.ATK_TEXT_GET_IFACE (object.handle));
703             if (textIface.get_selection !is null) {
704                 textIface.get_selection( object.handle, selection_num, start_offset, end_offset );
705             }
706         }
707         AccessibleTextListener[] listeners = object.getTextListeners ();
708         if (listeners.length is 0) return null;
709 
710         AccessibleTextEvent event = new AccessibleTextEvent (object);
711         event.childID = object.id;
712         int parentStart;
713         int parentEnd;
714         parentStart= *start_offset;
715         parentEnd= *end_offset;
716         event.offset = parentStart;
717         event.length = (parentEnd - parentStart);
718         for (int i = 0; i < listeners.length; i++) {
719             listeners [i].getSelectionRange (event);
720         }
721         *start_offset = event.offset;
722         *end_offset = event.offset + event.length;
723         return null;
724     }
725 
726     package static extern(C) char* atkText_get_text (void* obj, int start_offset, int end_offset) {
727         auto atkObject = cast(AtkObject*)obj;
728         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkText_get_text: {},{}", start_offset, end_offset);
729         AccessibleObject object = getAccessibleObject (atkObject);
730         if (object is null) return null;
731         String text = object.getText ();
732         if (text.length > 0) {
733             if (end_offset is -1) {
734                 end_offset = cast(int)/*64bit*/text.length;
735             } else {
736                 end_offset = cast(int)/*64bit*/Math.min (end_offset, text.length );
737             }
738             start_offset = Math.min (start_offset, end_offset);
739             text = text[ start_offset .. end_offset ];
740             auto result = cast(char*)OS.g_malloc (text.length+1);
741             result[ 0 .. text.length ] = text;
742             result[ text.length ] = '\0';
743             return result;
744         }
745         return null;
746     }
747 
748     package static extern(C) char* atkText_get_text_after_offset (void* obj, int offset_value, int boundary_type, int* start_offset, int* end_offset) {
749         auto atkObject = cast(AtkObject*)obj;
750         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkText_get_text_after_offset");
751         AccessibleObject object = getAccessibleObject (atkObject);
752         if (object is null) return null;
753         auto offset = offset_value;
754         String text = object.getText ();
755         if (text.length > 0) {
756             int length = cast(int)/*64bit*/text.length ;
757             offset = Math.min (offset, length - 1);
758             int startBounds = offset;
759             int endBounds = offset;
760             switch (boundary_type) {
761                 case ATK.ATK_TEXT_BOUNDARY_CHAR: {
762                     if (length > offset) endBounds++;
763                     break;
764                 }
765                 case ATK.ATK_TEXT_BOUNDARY_WORD_START: {
766                     auto wordStart1 = nextIndexOfChar (text, " !?.\n", offset - 1);
767                     if (wordStart1 is -1) {
768                         startBounds = endBounds = length;
769                         break;
770                     }
771                     wordStart1 = nextIndexOfNotChar (text, " !?.\n", wordStart1);
772                     if (wordStart1 is length) {
773                         startBounds = endBounds = length;
774                         break;
775                     }
776                     startBounds = wordStart1;
777                     auto wordStart2 = nextIndexOfChar (text, " !?.\n", wordStart1);
778                     if (wordStart2 is -1) {
779                         endBounds = length;
780                         break;
781                     }
782                     endBounds = nextIndexOfNotChar (text, " !?.\n", wordStart2);
783                     break;
784                 }
785                 case ATK.ATK_TEXT_BOUNDARY_WORD_END: {
786                     auto previousWordEnd = previousIndexOfNotChar (text, " \n", offset);
787                     if (previousWordEnd is -1 || previousWordEnd !is offset - 1) {
788                         offset = nextIndexOfNotChar (text, " \n", offset);
789                     }
790                     if (offset is -1) {
791                         startBounds = endBounds = length;
792                         break;
793                     }
794                     auto wordEnd1 = nextIndexOfChar (text, " !?.\n", offset);
795                     if (wordEnd1 is -1) {
796                         startBounds = endBounds = length;
797                         break;
798                     }
799                     wordEnd1 = nextIndexOfNotChar (text, "!?.", wordEnd1);
800                     if (wordEnd1 is length) {
801                         startBounds = endBounds = length;
802                         break;
803                     }
804                     startBounds = wordEnd1;
805                     auto wordEnd2 = nextIndexOfNotChar (text, " \n", wordEnd1);
806                     if (wordEnd2 is length) {
807                         startBounds = endBounds = length;
808                         break;
809                     }
810                     wordEnd2 = nextIndexOfChar (text, " !?.\n", wordEnd2);
811                     if (wordEnd2 is -1) {
812                         endBounds = length;
813                         break;
814                     }
815                     endBounds = nextIndexOfNotChar (text, "!?.", wordEnd2);
816                     break;
817                 }
818                 case ATK.ATK_TEXT_BOUNDARY_SENTENCE_START: {
819                     auto previousSentenceEnd = previousIndexOfChar (text, "!?.", offset);
820                     auto previousText = previousIndexOfNotChar (text, " !?.\n", offset);
821                     auto sentenceStart1 = 0;
822                     if (previousSentenceEnd >= previousText) {
823                         sentenceStart1 = nextIndexOfNotChar (text, " !?.\n", offset);
824                     } else {
825                         sentenceStart1 = nextIndexOfChar (text, "!?.", offset);
826                         if (sentenceStart1 is -1) {
827                             startBounds = endBounds = length;
828                             break;
829                         }
830                         sentenceStart1 = nextIndexOfNotChar (text, " !?.\n", sentenceStart1);
831                     }
832                     if (sentenceStart1 is length) {
833                         startBounds = endBounds = length;
834                         break;
835                     }
836                     startBounds = sentenceStart1;
837                     auto sentenceStart2 = nextIndexOfChar (text, "!?.", sentenceStart1);
838                     if (sentenceStart2 is -1) {
839                         endBounds = length;
840                         break;
841                     }
842                     endBounds = nextIndexOfNotChar (text, " !?.\n", sentenceStart2);
843                     break;
844                 }
845                 case ATK.ATK_TEXT_BOUNDARY_SENTENCE_END: {
846                     auto sentenceEnd1 = nextIndexOfChar (text, "!?.", offset);
847                     if (sentenceEnd1 is -1) {
848                         startBounds = endBounds = length;
849                         break;
850                     }
851                     sentenceEnd1 = nextIndexOfNotChar (text, "!?.", sentenceEnd1);
852                     if (sentenceEnd1 is length) {
853                         startBounds = endBounds = length;
854                         break;
855                     }
856                     startBounds = sentenceEnd1;
857                     auto sentenceEnd2 = nextIndexOfNotChar (text, " \n", sentenceEnd1);
858                     if (sentenceEnd2 is length) {
859                         startBounds = endBounds = length;
860                         break;
861                     }
862                     sentenceEnd2 = nextIndexOfChar (text, "!?.", sentenceEnd2);
863                     if (sentenceEnd2 is -1) {
864                         endBounds = length;
865                         break;
866                     }
867                     endBounds = nextIndexOfNotChar (text, "!?.", sentenceEnd2);
868                     break;
869                 }
870                 case ATK.ATK_TEXT_BOUNDARY_LINE_START: {
871                     auto lineStart1 = text.indexOf( '\n' );
872                     if (lineStart1 is -1) {
873                         startBounds = endBounds = length;
874                         break;
875                     }
876                     lineStart1 = nextIndexOfNotChar (text, "\n", lineStart1);
877                     if (lineStart1 is length) {
878                         startBounds = endBounds = length;
879                         break;
880                     }
881                     startBounds = lineStart1;
882                     auto lineStart2 = text.indexOf( '\n' );
883                     if (lineStart2 is -1) {
884                         endBounds = length;
885                         break;
886                     }
887                     lineStart2 = nextIndexOfNotChar (text, "\n", lineStart2);
888                     endBounds = lineStart2;
889                     break;
890                 }
891                 case ATK.ATK_TEXT_BOUNDARY_LINE_END: {
892                     auto lineEnd1 = nextIndexOfChar (text, "\n", offset);
893                     if (lineEnd1 is -1) {
894                         startBounds = endBounds = length;
895                         break;
896                     }
897                     startBounds = lineEnd1;
898                     if (startBounds is length) {
899                         endBounds = length;
900                         break;
901                     }
902                     auto lineEnd2 = nextIndexOfChar (text, "\n", lineEnd1 + 1);
903                     if (lineEnd2 is -1) {
904                         endBounds = length;
905                         break;
906                     }
907                     endBounds = lineEnd2;
908                     break;
909                 }
910                 default:
911             }
912             *start_offset=startBounds;
913             *end_offset=endBounds;
914             text = text[startBounds .. endBounds ];
915             auto result = cast(char*)OS.g_malloc (text.length+1);
916             result[ 0 .. text.length ] = text;
917             result[ text.length ] = '\0';
918             return result;
919         }
920         return null;
921     }
922 
923     package static extern(C) char* atkText_get_text_at_offset (void* obj, int offset_value, int boundary_type, int* start_offset, int* end_offset) {
924         auto atkObject = cast(AtkObject*)obj;
925         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkText_get_text_at_offset: {} start: {} end: {}", offset_value, start_offset, end_offset);
926         AccessibleObject object = getAccessibleObject (atkObject);
927         if (object is null) return null;
928         auto offset = offset_value;
929         String text = object.getText ();
930         if (text.length > 0) {
931             auto length = text.length;
932             offset = cast(int)/*64bit*/Math.min (offset, length - 1);
933             auto startBounds = offset;
934             auto endBounds = offset;
935             switch (boundary_type) {
936                 case ATK.ATK_TEXT_BOUNDARY_CHAR: {
937                     if (length > offset) endBounds++;
938                     break;
939                 }
940                 case ATK.ATK_TEXT_BOUNDARY_WORD_START: {
941                     auto wordStart1 = previousIndexOfNotChar (text, " !?.\n", offset);
942                     if (wordStart1 is -1) {
943                         startBounds = endBounds = 0;
944                         break;
945                     }
946                     wordStart1 = previousIndexOfChar (text, " !?.\n", wordStart1) + 1;
947                     if (wordStart1 is -1) {
948                         startBounds = 0;
949                         break;
950                     }
951                     startBounds = wordStart1;
952                     auto wordStart2 = nextIndexOfChar (text, " !?.\n", wordStart1);
953                     endBounds = nextIndexOfNotChar (text, " !?.\n", wordStart2);
954                     break;
955                 }
956                 case ATK.ATK_TEXT_BOUNDARY_WORD_END: {
957                     auto wordEnd1 = previousIndexOfNotChar (text, "!?.", offset + 1);
958                     wordEnd1 = previousIndexOfChar (text, " !?.\n", wordEnd1);
959                     wordEnd1 = previousIndexOfNotChar (text, " \n", wordEnd1 + 1);
960                     if (wordEnd1 is -1) {
961                         startBounds = endBounds = 0;
962                         break;
963                     }
964                     startBounds = wordEnd1 + 1;
965                     auto wordEnd2 = nextIndexOfNotChar (text, " \n", startBounds);
966                     if (wordEnd2 is length) {
967                         endBounds = startBounds;
968                         break;
969                     }
970                     wordEnd2 = nextIndexOfChar (text, " !?.\n", wordEnd2);
971                     if (wordEnd2 is -1) {
972                         endBounds = startBounds;
973                         break;
974                     }
975                     endBounds = nextIndexOfNotChar (text, "!?.", wordEnd2);
976                     break;
977                 }
978                 case ATK.ATK_TEXT_BOUNDARY_SENTENCE_START: {
979                     auto sentenceStart1 = previousIndexOfNotChar (text, " !?.\n", offset + 1);
980                     if (sentenceStart1 is -1) {
981                         startBounds = endBounds = 0;
982                         break;
983                     }
984                     sentenceStart1 = previousIndexOfChar (text, "!?.", sentenceStart1) + 1;
985                     startBounds = nextIndexOfNotChar (text, " \n", sentenceStart1);
986                     auto sentenceStart2 = nextIndexOfChar (text, "!?.", startBounds);
987                     endBounds = nextIndexOfNotChar (text, " !?.\n", sentenceStart2);
988                     break;
989                 }
990                 case ATK.ATK_TEXT_BOUNDARY_SENTENCE_END: {
991                     auto sentenceEnd1 = previousIndexOfNotChar (text, "!?.", offset + 1);
992                     sentenceEnd1 = previousIndexOfChar (text, "!?.", sentenceEnd1);
993                     sentenceEnd1 = previousIndexOfNotChar (text, " \n", sentenceEnd1 + 1);
994                     if (sentenceEnd1 is -1) {
995                         startBounds = endBounds = 0;
996                         break;
997                     }
998                     startBounds = sentenceEnd1 + 1;
999                     auto sentenceEnd2 = nextIndexOfNotChar (text, " \n", startBounds);
1000                     if (sentenceEnd2 is length) {
1001                         endBounds = startBounds;
1002                         break;
1003                     }
1004                     sentenceEnd2 = nextIndexOfChar (text, "!?.", sentenceEnd2);
1005                     if (sentenceEnd2 is -1) {
1006                         endBounds = startBounds;
1007                         break;
1008                     }
1009                     endBounds = nextIndexOfNotChar (text, "!?.", sentenceEnd2);
1010                     break;
1011                 }
1012                 case ATK.ATK_TEXT_BOUNDARY_LINE_START: {
1013                     startBounds = previousIndexOfChar (text, "\n", offset) + 1;
1014                     auto lineEnd2 = nextIndexOfChar (text, "\n", startBounds);
1015                     if (lineEnd2 < length) lineEnd2++;
1016                     endBounds = lineEnd2;
1017                     break;
1018                 }
1019                 case ATK.ATK_TEXT_BOUNDARY_LINE_END: {
1020                     auto lineEnd1 = previousIndexOfChar (text, "\n", offset);
1021                     if (lineEnd1 is -1) {
1022                         startBounds = endBounds = 0;
1023                         break;
1024                     }
1025                     startBounds = lineEnd1;
1026                     endBounds = nextIndexOfChar (text, "\n", lineEnd1 + 1);
1027                     break;
1028                 }
1029                 default:
1030                     break;
1031             }
1032             *start_offset=startBounds;
1033             *end_offset=endBounds;
1034             text = text[startBounds .. endBounds];
1035             auto result = cast(char*) OS.g_malloc (text.length+1);
1036             result[ 0 .. text.length ] = text;
1037             result[ text.length ] = '\0';
1038             return result;
1039         }
1040         return null;
1041     }
1042 
1043     package static extern(C) char* atkText_get_text_before_offset (void* obj, int offset_value, int boundary_type, int* start_offset, int* end_offset) {
1044         auto atkObject = cast(AtkObject*)obj;
1045         if (DEBUG) getDwtLogger().info (__FILE__, __LINE__, "-->atkText_get_text_before_offset");
1046         AccessibleObject object = getAccessibleObject (atkObject);
1047         if (object is null) return null;
1048         int offset = offset_value;
1049         String text = object.getText ();
1050         if (text.length > 0) {
1051             auto length = text.length;
1052             offset = cast(int)/*64bit*/Math.min (offset, length - 1);
1053             auto startBounds = offset;
1054             auto endBounds = offset;
1055             switch (boundary_type) {
1056                 case ATK.ATK_TEXT_BOUNDARY_CHAR: {
1057                     if (length >= offset && offset > 0) startBounds--;
1058                     break;
1059                 }
1060                 case ATK.ATK_TEXT_BOUNDARY_WORD_START: {
1061                     auto wordStart1 = previousIndexOfChar (text, " !?.\n", offset - 1);
1062                     if (wordStart1 is -1) {
1063                         startBounds = endBounds = 0;
1064                         break;
1065                     }
1066                     auto wordStart2 = previousIndexOfNotChar (text, " !?.\n", wordStart1);
1067                     if (wordStart2 is -1) {
1068                         startBounds = endBounds = 0;
1069                         break;
1070                     }
1071                     endBounds = wordStart1 + 1;
1072                     startBounds = previousIndexOfChar (text, " !?.\n", wordStart2) + 1;
1073                     break;
1074                 }
1075                 case ATK.ATK_TEXT_BOUNDARY_WORD_END: {
1076                     auto wordEnd1 =previousIndexOfChar (text, " !?.\n", offset);
1077                     if (wordEnd1 is -1) {
1078                         startBounds = endBounds = 0;
1079                         break;
1080                     }
1081                     wordEnd1 = previousIndexOfNotChar (text, " \n", wordEnd1 + 1);
1082                     if (wordEnd1 is -1) {
1083                         startBounds = endBounds = 0;
1084                         break;
1085                     }
1086                     endBounds = wordEnd1 + 1;
1087                     auto wordEnd2 = previousIndexOfNotChar (text, " !?.\n", endBounds);
1088                     wordEnd2 = previousIndexOfChar (text, " !?.\n", wordEnd2);
1089                     if (wordEnd2 is -1) {
1090                         startBounds = 0;
1091                         break;
1092                     }
1093                     startBounds = previousIndexOfNotChar (text, " \n", wordEnd2 + 1) + 1;
1094                     break;
1095                 }
1096                 case ATK.ATK_TEXT_BOUNDARY_SENTENCE_START: {
1097                     auto sentenceStart1 = previousIndexOfChar (text, "!?.", offset);
1098                     if (sentenceStart1 is -1) {
1099                         startBounds = endBounds = 0;
1100                         break;
1101                     }
1102                     auto sentenceStart2 = previousIndexOfNotChar (text, "!?.", sentenceStart1);
1103                     if (sentenceStart2 is -1) {
1104                         startBounds = endBounds = 0;
1105                         break;
1106                     }
1107                     endBounds = sentenceStart1 + 1;
1108                     startBounds = previousIndexOfChar (text, "!?.", sentenceStart2) + 1;
1109                     break;
1110                 }
1111                 case ATK.ATK_TEXT_BOUNDARY_SENTENCE_END: {
1112                     auto sentenceEnd1 = previousIndexOfChar (text, "!?.", offset);
1113                     if (sentenceEnd1 is -1) {
1114                         startBounds = endBounds = 0;
1115                         break;
1116                     }
1117                     sentenceEnd1 = previousIndexOfNotChar (text, " \n", sentenceEnd1 + 1);
1118                     if (sentenceEnd1 is -1) {
1119                         startBounds = endBounds = 0;
1120                         break;
1121                     }
1122                     endBounds = sentenceEnd1 + 1;
1123                     auto sentenceEnd2 = previousIndexOfNotChar (text, "!?.", endBounds);
1124                     sentenceEnd2 = previousIndexOfChar (text, "!?.", sentenceEnd2);
1125                     if (sentenceEnd2 is -1) {
1126                         startBounds = 0;
1127                         break;
1128                     }
1129                     startBounds = previousIndexOfNotChar (text, " \n", sentenceEnd2 + 1) + 1;
1130                     break;
1131                 }
1132                 case ATK.ATK_TEXT_BOUNDARY_LINE_START: {
1133                     auto lineStart1 = previousIndexOfChar (text, "\n", offset);
1134                     if (lineStart1 is -1) {
1135                         startBounds = endBounds = 0;
1136                         break;
1137                     }
1138                     endBounds = lineStart1 + 1;
1139                     startBounds = previousIndexOfChar (text, "\n", lineStart1) + 1;
1140                     break;
1141                 }
1142                 case ATK.ATK_TEXT_BOUNDARY_LINE_END: {
1143                     auto lineEnd1 = previousIndexOfChar (text, "\n", offset);
1144                     if (lineEnd1 is -1) {
1145                         startBounds = endBounds = 0;
1146                         break;
1147                     }
1148                     endBounds = lineEnd1;
1149                     startBounds = previousIndexOfChar (text, "\n", lineEnd1);
1150                     if (startBounds is -1) startBounds = 0;
1151                     break;
1152                 }
1153                 default:
1154             }
1155             *start_offset=startBounds;
1156             *end_offset=endBounds;
1157             text = text[startBounds .. endBounds];
1158             auto result = cast(char*)OS.g_malloc (text.length+1);
1159             result[ 0 .. text.length ] = text;
1160             result[ text.length ] = '\0';
1161             return result;
1162         }
1163         return null;
1164     }
1165 
1166     AccessibleListener[] getAccessibleListeners () {
1167         if (accessible is null) return new AccessibleListener [0];
1168         AccessibleListener[] result = accessible.getAccessibleListeners ();
1169         return result !is null ? result : new AccessibleListener [0];
1170     }
1171 
1172     static AccessibleObject getAccessibleObject (AtkObject* atkObject) {
1173         return AccessibleObjects[atkObject];
1174     }
1175 
1176     AccessibleObject getChildByHandle (AtkObject* handle) {
1177         return cast(AccessibleObject) children.get( new LONG(handle) );
1178     }
1179 
1180     AccessibleObject getChildByID (int childId) {
1181         if (childId is ACC.CHILDID_SELF) return this;
1182         Enumeration elements = children.elements ();
1183         while (elements.hasMoreElements ()) {
1184             AccessibleObject object = cast(AccessibleObject) elements.nextElement ();
1185             if (object.id is childId) return object;
1186         }
1187         return null;
1188     }
1189 
1190     AccessibleObject getChildByIndex (int childIndex) {
1191         Enumeration elements = children.elements ();
1192         while (elements.hasMoreElements ()) {
1193             AccessibleObject object = cast(AccessibleObject) elements.nextElement ();
1194             if (object.index is childIndex) return object;
1195         }
1196         return null;
1197     }
1198 
1199     AccessibleControlListener[] getControlListeners () {
1200         if (accessible is null) return new AccessibleControlListener [0];
1201         AccessibleControlListener[] result = accessible.getControlListeners ();
1202         return result !is null ? result : new AccessibleControlListener [0];
1203     }
1204 
1205     String getText () {
1206         char* parentResult;
1207         String parentText = ""; //$NON-NLS-1$
1208         if (ATK.g_type_is_a (parentType, ATK_TEXT_TYPE)) {
1209             auto textIface = cast(AtkTextIface*)ATK.g_type_interface_peek_parent (ATK.ATK_TEXT_GET_IFACE (handle));
1210             int characterCount = 0;
1211             if (textIface.get_character_count !is null) {
1212                 characterCount = textIface.get_character_count( handle);
1213             }
1214             if (characterCount > 0 && textIface.get_text !is null) {
1215                 parentResult = textIface.get_text( handle, 0, characterCount);
1216                 if (parentResult !is null) {
1217                     parentText = fromStringz( parentResult )._idup();
1218                 }
1219             }
1220         }
1221         AccessibleControlListener[] controlListeners = getControlListeners ();
1222         if (controlListeners.length is 0) return parentText;
1223         AccessibleControlEvent event = new AccessibleControlEvent (this);
1224         event.childID = id;
1225         event.result = parentText;
1226         for (int i = 0; i < controlListeners.length; i++) {
1227             controlListeners [i].getValue (event);
1228         }
1229         return event.result;
1230     }
1231 
1232     AccessibleTextListener[] getTextListeners () {
1233         if (accessible is null) return new AccessibleTextListener [0];
1234         AccessibleTextListener[] result = accessible.getTextListeners ();
1235         return result !is null ? result : new AccessibleTextListener [0];
1236     }
1237 
1238     package static extern(C) void gObjectClass_finalize (GObject* atkObject) {
1239         auto superType = ATK.g_type_class_peek_parent (ATK.G_OBJECT_GET_CLASS (cast(GTypeInstance*)atkObject));
1240         auto objectClassStruct = cast(GObjectClass*)ATK.G_OBJECT_CLASS (cast(GTypeClass*)superType);
1241         objectClassStruct.finalize(atkObject);
1242         AccessibleObject object = getAccessibleObject (cast(AtkObject*)atkObject);
1243         if (object !is null) {
1244             AccessibleObjects.remove (cast(AtkObject*)atkObject);
1245             object.release ();
1246         }
1247     }
1248 
1249     static int nextIndexOfChar (String str, String searchChars, int startIndex) {
1250         auto result = cast(int)/*64bit*/str.length;
1251         for (int i = 0; i < searchChars.length; i++) {
1252             char current = searchChars[i];
1253             auto index = str.indexOf( current, startIndex );
1254             if (index !is -1 ) result = Math.min (result, index);
1255         }
1256         return result;
1257     }
1258 
1259     static int nextIndexOfNotChar (String str, String searchChars, int startIndex) {
1260         size_t length = str.length;
1261         auto index = startIndex;
1262         while (index < length) {
1263             char current = str[index];
1264             if ( searchChars.indexOf( current) is -1) break;
1265             index++;
1266         }
1267         return index;
1268     }
1269 
1270     static int previousIndexOfChar (String str, String searchChars, int startIndex) {
1271         int result = -1;
1272         if (startIndex < 0) return result;
1273         str = str[0 .. startIndex];
1274         for (int i = 0; i < searchChars.length ; i++) {
1275             char current = searchChars[i];
1276             auto index = str.lastIndexOf( current);
1277             if (index !is -1 ) result = Math.max (result, index);
1278         }
1279         return result;
1280     }
1281 
1282     static int previousIndexOfNotChar (String str, String searchChars, int startIndex) {
1283         if (startIndex < 0) return -1;
1284         int index = startIndex - 1;
1285         while (index >= 0) {
1286             char current = str[index];
1287             if ( searchChars.indexOf( current) is -1 ) break;
1288             index--;
1289         }
1290         return index;
1291     }
1292 
1293     void release () {
1294         if (DEBUG) getDwtLogger().info( __FILE__, __LINE__, "AccessibleObject.release: {}", handle);
1295         accessible = null;
1296         Enumeration elements = children.elements ();
1297         while (elements.hasMoreElements ()) {
1298             AccessibleObject child = cast(AccessibleObject) elements.nextElement ();
1299             if (child.isLightweight) OS.g_object_unref (child.handle);
1300         }
1301         if (parent !is null) parent.removeChild (this, false);
1302     }
1303 
1304     void removeChild (AccessibleObject child, bool unref) {
1305         children.remove (new LONG (child.handle));
1306         if (unref && child.isLightweight) OS.g_object_unref (child.handle);
1307     }
1308 
1309     void selectionChanged () {
1310         OS.g_signal_emit_by_name0 (handle, ATK.selection_changed.ptr);
1311     }
1312 
1313     void setFocus (int childID) {
1314         updateChildren ();
1315         AccessibleObject accObject = getChildByID (childID);
1316         if (accObject !is null) {
1317             ATK.atk_focus_tracker_notify (accObject.handle);
1318         }
1319     }
1320 
1321     void setParent (AccessibleObject parent) {
1322         this.parent = parent;
1323     }
1324 
1325     void textCaretMoved(int index) {
1326         OS.g_signal_emit_by_name1 (handle, ATK.text_caret_moved.ptr, index);
1327     }
1328 
1329     void textChanged(int type, int startIndex, int length) {
1330         if (type is ACC.TEXT_DELETE) {
1331             OS.g_signal_emit_by_name2 (handle, ATK.text_changed_delete.ptr, startIndex, length);
1332         } else {
1333             OS.g_signal_emit_by_name2 (handle, ATK.text_changed_insert.ptr, startIndex, length);
1334         }
1335     }
1336 
1337     void textSelectionChanged() {
1338         OS.g_signal_emit_by_name0 (handle, ATK.text_selection_changed.ptr);
1339     }
1340 
1341     void updateChildren () {
1342         if (isLightweight) return;
1343         AccessibleControlListener[] listeners = getControlListeners ();
1344         if (listeners.length is 0) return;
1345 
1346         AccessibleControlEvent event = new AccessibleControlEvent (this);
1347         for (int i = 0; i < listeners.length; i++) {
1348             listeners [i].getChildren (event);
1349         }
1350         if (event.children !is null && event.children.length > 0) {
1351             Vector idsToKeep = new Vector (children.size ());
1352             if ( null !is cast(Integer)event.children [0]) {
1353                 /*  an array of child id's (Integers) was answered */
1354                 int parentType = AccessibleFactory.getDefaultParentType ();
1355                 for (int i = 0; i < event.children.length; i++) {
1356                     AccessibleObject object = getChildByIndex (i);
1357                     if (object is null) {
1358                         int childType = AccessibleFactory.getChildType (accessible, i);
1359                         object = new AccessibleObject (childType, null, accessible, parentType, true);
1360                         AccessibleObjects[object.handle] = object;
1361                         addChild (object);
1362                         object.index = i;
1363                     }
1364                     try {
1365                         object.id = (cast(Integer)event.children[i]).intValue ();
1366                     } catch (ClassCastException e) {
1367                         /* a non-ID value was given so don't set the ID */
1368                     }
1369                     idsToKeep.addElement (new LONG (object.handle));
1370                 }
1371             } else {
1372                 /* an array of Accessible children was answered */
1373                 int childIndex = 0;
1374                 for (int i = 0; i < event.children.length; i++) {
1375                     AccessibleObject object = null;
1376                     try {
1377                         object = (cast(Accessible)event.children [i]).accessibleObject;
1378                     } catch (ClassCastException e) {
1379                         /* a non-Accessible value was given so nothing to do here */ 
1380                     }
1381                     if (object !is null) {
1382                         object.index = childIndex++;
1383                         idsToKeep.addElement (new LONG (object.handle));
1384                     }
1385                 }
1386             }
1387             /* remove old children that were not provided as children anymore */
1388             Enumeration ids = children.keys ();
1389             while (ids.hasMoreElements ()) {
1390                 LONG id = cast(LONG)ids.nextElement ();
1391                 if (!idsToKeep.contains (id)) {
1392                     AccessibleObject object = cast(AccessibleObject) children.get (id);
1393                     removeChild (object, true);
1394                 }
1395             }
1396 //            AtkObject*[] idsToKeep = new AtkObject*[]( children.length );
1397 //            idsToKeep.length = 0;
1398 //            if ( null !is (cast(Integer)event.children[0] )) {
1399 //                /*  an array of child id's (Integers) was answered */
1400 //                auto parentType = AccessibleFactory.getDefaultParentType ();
1401 //                for (int i = 0; i < event.children.length; i++) {
1402 //                    AccessibleObject object = getChildByIndex (i);
1403 //                    if (object is null) {
1404 //                        auto childType = AccessibleFactory.getChildType (accessible, i);
1405 //                        object = new AccessibleObject (childType, null, accessible, parentType, true);
1406 //                        AccessibleObjects[object.handle] = object;
1407 //                        addChild (object);
1408 //                        object.index = i;
1409 //                    }
1410 //                    if( auto intChild = cast(Integer)event.children[i] ){
1411 //                        object.id = intChild.intValue ();
1412 //                    }
1413 //                    else {
1414 //                        /* a non-ID value was given so don't set the ID */
1415 //                    }
1416 //                    idsToKeep ~= object.handle;
1417 //                }
1418 //            } else {
1419 //                /* an array of Accessible children was answered */
1420 //                int childIndex = 0;
1421 //                for (int i = 0; i < event.children.length; i++) {
1422 //                    AccessibleObject object = null;
1423 //                    if( auto accChild = cast(Accessible)event.children[i] ){
1424 //                        object = accChild.accessibleObject;
1425 //                    } else {
1426 //                        /* a non-Accessible value was given so nothing to do here */
1427 //                    }
1428 //                    if (object !is null) {
1429 //                        object.index = childIndex++;
1430 //                        idsToKeep ~= object.handle;
1431 //                    }
1432 //                }
1433 //            }
1434 //            /* remove old children that were not provided as children anymore */
1435 //            foreach( id; children.keys ){
1436 //                if ( !tango.core.Array.contains( idsToKeep, id )) {
1437 //                    AccessibleObject object = cast(AccessibleObject) children[id];
1438 //                    removeChild (object, true);
1439 //                }
1440 //            }
1441         }
1442     }
1443 }