/*******************************************************************************
 * Copyright (c) 2000, 2016 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.swt.accessibility;

import java.net.*;
import java.util.*;
import java.util.List;

import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.cocoa.*;
import org.eclipse.swt.widgets.*;

/**
 * Instances of this class provide a bridge between application
 * code and assistive technology clients. Many platforms provide
 * default accessible behavior for most widgets, and this class
 * allows that default behavior to be overridden. Applications
 * can get the default Accessible object for a control by sending
 * it <code>getAccessible</code>, and then add an accessible listener
 * to override simple items like the name and help string, or they
 * can add an accessible control listener to override complex items.
 * As a rule of thumb, an application would only want to use the
 * accessible control listener to implement accessibility for a
 * custom control.
 *
 * @see Control#getAccessible
 * @see AccessibleListener
 * @see AccessibleEvent
 * @see AccessibleControlListener
 * @see AccessibleControlEvent
 * @see <a href="https://eclipse.dev/eclipse/swt/snippets/#accessibility">Accessibility snippets</a>
 * @see <a href="https://eclipse.dev/eclipse/swt/">Sample code and further information</a>
 *
 * @since 2.0
 */
public class Accessible {

	static boolean DEBUG = false;

	static final int MAX_RELATION_TYPES = 15;

	static NSString[] baseAttributes = {
		OS.NSAccessibilityRoleAttribute,
		OS.NSAccessibilitySubroleAttribute,
		OS.NSAccessibilityRoleDescriptionAttribute,
		OS.NSAccessibilityHelpAttribute,
		OS.NSAccessibilityFocusedAttribute,
		OS.NSAccessibilityParentAttribute,
		OS.NSAccessibilityChildrenAttribute,
		OS.NSAccessibilityPositionAttribute,
		OS.NSAccessibilitySizeAttribute,
		OS.NSAccessibilityWindowAttribute,
		OS.NSAccessibilityTopLevelUIElementAttribute,
	};

	NSMutableArray attributeNames = null;
	NSMutableArray parameterizedAttributeNames = null;
	NSMutableArray actionNames = null;

	List<AccessibleListener> accessibleListeners;
	List<AccessibleControlListener> accessibleControlListeners;
	List<AccessibleTextListener> accessibleTextListeners;
	List<AccessibleActionListener> accessibleActionListeners;
	List<AccessibleEditableTextListener> accessibleEditableTextListeners;
	List<AccessibleHyperlinkListener> accessibleHyperlinkListeners;
	List<AccessibleTableListener> accessibleTableListeners;
	List<AccessibleTableCellListener> accessibleTableCellListeners;
	List<AccessibleTextExtendedListener> accessibleTextExtendedListeners;
	List<AccessibleValueListener> accessibleValueListeners;
	List<AccessibleAttributeListener> accessibleAttributeListeners;

	Relation relations[] = new Relation[MAX_RELATION_TYPES];
	Accessible parent;
	Control control;
	int currentRole = -1;

	Map<Integer, SWTAccessibleDelegate> childToIdMap = new HashMap<>();
	SWTAccessibleDelegate delegate;

	int index = -1;

	TableAccessibleDelegate tableDelegate;

	/**
	 * Constructs a new instance of this class given its parent.
	 *
	 * @param parent the Accessible parent, which must not be null
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
	 * </ul>
	 *
	 * @see #dispose
	 * @see Control#getAccessible
	 *
	 * @since 3.6
	 */
	public Accessible(Accessible parent) {
		if (parent == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
		this.parent = parent;
		this.control = parent.control;
		delegate = new SWTAccessibleDelegate(this, ACC.CHILDID_SELF);
	}

	/**
	 * @since 3.5
	 * @deprecated
	 */
	@Deprecated
	protected Accessible() {
	}

	Accessible(Control control) {
		this.control = control;
	}

	/**
	 * Invokes platform specific functionality to allocate a new accessible object.
	 * <p>
	 * <b>IMPORTANT:</b> This method is <em>not</em> part of the public
	 * API for <code>Accessible</code>. It is marked public only so that it
	 * can be shared within the packages provided by SWT. It is not
	 * available on all platforms, and should never be called from
	 * application code.
	 * </p>
	 *
	 * @param control the control to get the accessible object for
	 * @return the platform specific accessible object
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 */
	public static Accessible internal_new_Accessible(Control control) {
		return new Accessible(control);
	}

	id accessibleHandle(Accessible accessible) {
		if (accessible.delegate != null) return accessible.delegate;
		if (accessible.control != null) {
			NSView view = accessible.control.view;
			long handle = OS.objc_msgSend(view.id, OS.sel_accessibleHandle);
			return new id(handle);
		}
		return null;
	}

	/**
	 * Adds the listener to the collection of listeners who will
	 * be notified when an accessible client asks for certain strings,
	 * such as name, description, help, or keyboard shortcut. The
	 * listener is notified by sending it one of the messages defined
	 * in the <code>AccessibleListener</code> interface.
	 *
	 * @param listener the listener that should be notified when the receiver
	 * is asked for a name, description, help, or keyboard shortcut string
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleListener
	 * @see #removeAccessibleListener
	 */
	public void addAccessibleListener(AccessibleListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleListeners == null) accessibleListeners = new ArrayList<>();
		accessibleListeners.add(listener);
	}

	/**
	 * Adds the listener to the collection of listeners who will
	 * be notified when an accessible client asks for custom control
	 * specific information. The listener is notified by sending it
	 * one of the messages defined in the <code>AccessibleControlListener</code>
	 * interface.
	 *
	 * @param listener the listener that should be notified when the receiver
	 * is asked for custom control specific information
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleControlListener
	 * @see #removeAccessibleControlListener
	 */
	public void addAccessibleControlListener(AccessibleControlListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleControlListeners == null) accessibleControlListeners = new ArrayList<>();
		accessibleControlListeners.add(listener);
	}

	/**
	 * Adds the listener to the collection of listeners who will
	 * be notified when an accessible client asks for custom text control
	 * specific information. The listener is notified by sending it
	 * one of the messages defined in the <code>AccessibleTextListener</code>
	 * and <code>AccessibleTextExtendedListener</code> interfaces.
	 *
	 * @param listener the listener that should be notified when the receiver
	 * is asked for custom text control specific information
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleTextListener
	 * @see AccessibleTextExtendedListener
	 * @see #removeAccessibleTextListener
	 *
	 * @since 3.0
	 */
	public void addAccessibleTextListener (AccessibleTextListener listener) {
		checkWidget ();
		if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
		if (listener instanceof AccessibleTextExtendedListener) {
			if (accessibleTextExtendedListeners == null) accessibleTextExtendedListeners = new ArrayList<>();
			accessibleTextExtendedListeners.add ((AccessibleTextExtendedListener) listener);
		} else {
			if (accessibleTextListeners == null) accessibleTextListeners = new ArrayList<>();
			accessibleTextListeners.add (listener);
		}
	}

	/**
	 * Adds the listener to the collection of listeners that will be
	 * notified when an accessible client asks for any of the properties
	 * defined in the <code>AccessibleActionListener</code> interface.
	 *
	 * @param listener the listener that should be notified when the receiver
	 * is asked for <code>AccessibleActionListener</code> interface properties
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleActionListener
	 * @see #removeAccessibleActionListener
	 *
	 * @since 3.6
	 */
	public void addAccessibleActionListener(AccessibleActionListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleActionListeners == null) accessibleActionListeners = new ArrayList<>();
		accessibleActionListeners.add(listener);
	}

	/**
	 * Adds the listener to the collection of listeners that will be
	 * notified when an accessible client asks for any of the properties
	 * defined in the <code>AccessibleEditableTextListener</code> interface.
	 *
	 * @param listener the listener that should be notified when the receiver
	 * is asked for <code>AccessibleEditableTextListener</code> interface properties
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleEditableTextListener
	 * @see #removeAccessibleEditableTextListener
	 *
	 * @since 3.7
	 */
	public void addAccessibleEditableTextListener(AccessibleEditableTextListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleEditableTextListeners == null) accessibleEditableTextListeners = new ArrayList<>();
		accessibleEditableTextListeners.add(listener);
	}

	/**
	 * Adds the listener to the collection of listeners that will be
	 * notified when an accessible client asks for any of the properties
	 * defined in the <code>AccessibleHyperlinkListener</code> interface.
	 *
	 * @param listener the listener that should be notified when the receiver
	 * is asked for <code>AccessibleHyperlinkListener</code> interface properties
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleHyperlinkListener
	 * @see #removeAccessibleHyperlinkListener
	 *
	 * @since 3.6
	 */
	public void addAccessibleHyperlinkListener(AccessibleHyperlinkListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleHyperlinkListeners == null) accessibleHyperlinkListeners = new ArrayList<>();
		accessibleHyperlinkListeners.add(listener);
	}

	/**
	 * Adds the listener to the collection of listeners that will be
	 * notified when an accessible client asks for any of the properties
	 * defined in the <code>AccessibleTableListener</code> interface.
	 *
	 * @param listener the listener that should be notified when the receiver
	 * is asked for <code>AccessibleTableListener</code> interface properties
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleTableListener
	 * @see #removeAccessibleTableListener
	 *
	 * @since 3.6
	 */
	public void addAccessibleTableListener(AccessibleTableListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleTableListeners == null) accessibleTableListeners = new ArrayList<>();
		accessibleTableListeners.add(listener);
	}

	/**
	 * Adds the listener to the collection of listeners that will be
	 * notified when an accessible client asks for any of the properties
	 * defined in the <code>AccessibleTableCellListener</code> interface.
	 *
	 * @param listener the listener that should be notified when the receiver
	 * is asked for <code>AccessibleTableCellListener</code> interface properties
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleTableCellListener
	 * @see #removeAccessibleTableCellListener
	 *
	 * @since 3.6
	 */
	public void addAccessibleTableCellListener(AccessibleTableCellListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleTableCellListeners == null) accessibleTableCellListeners = new ArrayList<>();
		accessibleTableCellListeners.add(listener);
	}

	/**
	 * Adds the listener to the collection of listeners that will be
	 * notified when an accessible client asks for any of the properties
	 * defined in the <code>AccessibleValueListener</code> interface.
	 *
	 * @param listener the listener that should be notified when the receiver
	 * is asked for <code>AccessibleValueListener</code> interface properties
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleValueListener
	 * @see #removeAccessibleValueListener
	 *
	 * @since 3.6
	 */
	public void addAccessibleValueListener(AccessibleValueListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleValueListeners == null) accessibleValueListeners = new ArrayList<>();
		accessibleValueListeners.add(listener);
	}

	/**
	 * Adds the listener to the collection of listeners that will be
	 * notified when an accessible client asks for any of the properties
	 * defined in the <code>AccessibleAttributeListener</code> interface.
	 *
	 * @param listener the listener that should be notified when the receiver
	 * is asked for <code>AccessibleAttributeListener</code> interface properties
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleAttributeListener
	 * @see #removeAccessibleAttributeListener
	 *
	 * @since 3.6
	 */
	public void addAccessibleAttributeListener(AccessibleAttributeListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleAttributeListeners == null) accessibleAttributeListeners = new ArrayList<>();
		accessibleAttributeListeners.add(listener);
	}

	void addCGColor(double [] comps, NSMutableAttributedString inAttribString, NSString inAttribute, NSRange inRange) {
		long cgColorSpace = OS.CGColorSpaceCreateDeviceRGB();
		long cgColor = OS.CGColorCreate(cgColorSpace, comps);
		OS.CGColorSpaceRelease(cgColorSpace);
		inAttribString.addAttribute(inAttribute, new id(cgColor), inRange);
		OS.CGColorRelease(cgColor);
	}

	/**
	 * Adds a relation with the specified type and target
	 * to the receiver's set of relations.
	 *
	 * @param type an <code>ACC</code> constant beginning with RELATION_* indicating the type of relation
	 * @param target the accessible that is the target for this relation
	 * @exception IllegalArgumentException ERROR_NULL_ARGUMENT - if the Accessible target is null
	 * @since 3.6
	 */
	public void addRelation(int type, Accessible target) {
		checkWidget();
		if (target == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
		if (relations[type] == null) {
			relations[type] = new Relation(this, type);
		}
		relations[type].addTarget(target);
	}

	int accessibleListenersSize() {
		return accessibleListeners == null ? 0 : accessibleListeners.size();
	}

	int accessibleControlListenersSize() {
		return accessibleControlListeners == null ? 0 : accessibleControlListeners.size();
	}

	int accessibleValueListenersSize() {
		return accessibleValueListeners == null ? 0 : accessibleValueListeners.size();
	}

	int accessibleTextExtendedListenersSize() {
		return accessibleTextExtendedListeners == null ? 0 : accessibleTextExtendedListeners.size();
	}

	int accessibleTextListenersSize() {
		return accessibleTextListeners == null ? 0 : accessibleTextListeners.size();
	}

	int accessibleTableCellListenersSize() {
		return accessibleTableCellListeners == null ? 0 : accessibleTableCellListeners.size();
	}

	int accessibleTableListenersSize() {
		return accessibleTableListeners == null ? 0 : accessibleTableListeners.size();
	}

	int accessibleHyperlinkListenersSize() {
		return accessibleHyperlinkListeners == null ? 0 : accessibleHyperlinkListeners.size();
	}

	int accessibleEditableTextListenersSize() {
		return accessibleEditableTextListeners == null ? 0 : accessibleEditableTextListeners.size();
	}

	int accessibleAttributeListenersSize() {
		return accessibleAttributeListeners == null ? 0 : accessibleAttributeListeners.size();
	}

	int accessibleActionListenersSize() {
		return accessibleActionListeners == null ? 0 : accessibleActionListeners.size();
	}

	void checkRole(int role) {
		// A lightweight control can change its role at any time, so track
		// the current role for the control.  If it changes, reset the attribute list.
		if (role != currentRole) {
			currentRole = role;

			if (attributeNames != null) {
				attributeNames.release();
				attributeNames = null;
			}

			if (parameterizedAttributeNames != null) {
				parameterizedAttributeNames.release();
				parameterizedAttributeNames = null;
			}

			if (actionNames != null) {
				actionNames.release();
				actionNames = null;
			}
		}
	}

	void createTableDelegate() {
		if (tableDelegate == null) {
			tableDelegate = new TableAccessibleDelegate(this);
		}
	}

	id getColumnIndexRangeAttribute(int childID) {
		AccessibleTableCellEvent event = new AccessibleTableCellEvent(this);
		for (int i = 0; i < accessibleTableCellListenersSize(); i++) {
			AccessibleTableCellListener listener = accessibleTableCellListeners.get(i);
			listener.getColumnIndex(event);
			listener.getColumnSpan(event);
		}
		NSRange range = new NSRange();
		range.location = event.index;
		range.length = event.count;
		return NSValue.valueWithRange(range);
	}

	id getRowIndexRangeAttribute(int childID) {
		AccessibleTableCellEvent event = new AccessibleTableCellEvent(this);
		for (int i = 0; i < accessibleTableCellListenersSize(); i++) {
			AccessibleTableCellListener listener = accessibleTableCellListeners.get(i);
			listener.getRowIndex(event);
			listener.getRowSpan(event);
		}
		NSRange range = new NSRange();
		range.location = event.index;
		range.length = event.count;
		return NSValue.valueWithRange(range);
	}

	id getSelectedAttribute(int childID) {
		if (accessibleTableListenersSize() > 0) {
			AccessibleTableEvent event = new AccessibleTableEvent(this);
			event.row = index;
			for (int i = 0; i < accessibleTableListenersSize(); i++) {
				AccessibleTableListener listener = accessibleTableListeners.get(i);
				if (currentRole == ACC.ROLE_ROW)
					listener.isRowSelected(event);
				else
					listener.isColumnSelected(event);
			}
			return NSNumber.numberWithBool(event.isSelected);
		}

		return NSNumber.numberWithBool(false);
	}

	id getIndexAttribute(int childID) {
		return NSNumber.numberWithInt(index);
	}

	id getHeaderAttribute(int childID) {
		id returnValue = null;
		AccessibleTableEvent tableEvent = new AccessibleTableEvent(this);
		for (int i = 0; i < accessibleTableListenersSize(); i++) {
			AccessibleTableListener listener = accessibleTableListeners.get(i);
			listener.getColumnHeader(tableEvent);
		}
		if (tableEvent.accessible != null) returnValue = tableEvent.accessible.delegate;

		return returnValue;
	}

	id getVisibleColumnsAttribute(int childID) {
		if (accessibleTableListenersSize() == 0) return null;
		id returnValue = null;
		AccessibleTableEvent event = new AccessibleTableEvent(this);
		for (int i = 0; i < accessibleTableListenersSize(); i++) {
			AccessibleTableListener listener = accessibleTableListeners.get(i);
			listener.getVisibleColumns(event);
		}
		if (event.accessibles != null) {
			NSMutableArray array = NSMutableArray.arrayWithCapacity(event.accessibles.length);
			Accessible[] accessibles = event.accessibles;
			for (int i = 0; i < accessibles.length; i++) {
				Accessible acc = accessibles[i];
				array.addObject(acc.delegate);
			}
			returnValue = array;
		}
		return returnValue == null ? NSArray.array() : returnValue;
	}

	id getVisibleRowsAttribute(int childID) {
		if (accessibleTableListenersSize() == 0) return null;
		id returnValue = null;
		AccessibleTableEvent event = new AccessibleTableEvent(this);
		for (int i = 0; i < accessibleTableListenersSize(); i++) {
			AccessibleTableListener listener = accessibleTableListeners.get(i);
			listener.getVisibleRows(event);
		}
		if (event.accessibles != null) {
			NSMutableArray array = NSMutableArray.arrayWithCapacity(event.accessibles.length);
			Accessible[] accessibles = event.accessibles;
			for (int i = 0; i < accessibles.length; i++) {
				Accessible acc = accessibles[i];
				array.addObject(acc.delegate);
			}
			returnValue = array;
		}
		return returnValue == null ? NSArray.array() : returnValue;
	}

	id getSelectedRowsAttribute(int childID) {
		if (accessibleTableListenersSize() == 0) return null;
		id returnValue = null;
		AccessibleTableEvent event = new AccessibleTableEvent(this);
		for (int i = 0; i < accessibleTableListenersSize(); i++) {
			AccessibleTableListener listener = accessibleTableListeners.get(i);
			listener.getSelectedRows(event);
		}
		if (event.selected != null) {
			int[] selected = event.selected;
			NSMutableArray array = NSMutableArray.arrayWithCapacity(selected.length);
			for (int i = 0; i < selected.length; i++) {
				event.row = selected[i];
				for (int j = 0; j < accessibleTableListenersSize(); j++) {
					AccessibleTableListener listener = accessibleTableListeners.get(j);
					listener.getRow(event);
				}
				if (event.accessible != null) array.addObject(event.accessible.delegate);
			}
			returnValue = array;
		}
		return returnValue == null ? NSArray.array() : returnValue;
	}


	int getRowCount() {
		AccessibleTableEvent event = new AccessibleTableEvent(this);

		for (int i = 0; i < accessibleTableListenersSize(); i++) {
			AccessibleTableListener listener = accessibleTableListeners.get(i);
			listener.getRowCount(event);
		}

		return event.count;
	}

	id getRowsAttribute(int childID) {
		if (accessibleTableListenersSize() == 0) return null;

		AccessibleTableEvent event = new AccessibleTableEvent(this);
		for (int i = 0; i < accessibleTableListenersSize(); i++) {
			AccessibleTableListener listener = accessibleTableListeners.get(i);
			listener.getRowCount(event);
			listener.getRows(event);
		}

		if (event.accessibles == null) event.accessibles = new Accessible[0];

		if (event.count != event.accessibles.length) {
			createTableDelegate();

			// Rerun the row query now that our accessible is in place.
			for (int i = 0; i < accessibleTableListenersSize(); i++) {
				AccessibleTableListener listener = accessibleTableListeners.get(i);
				listener.getRowCount(event);
				listener.getRows(event);
			}
		}

		NSMutableArray array = NSMutableArray.arrayWithCapacity(event.accessibles.length);
		Object[] rows = event.accessibles;
		for (int i = 0; i < rows.length; i++) {
			Accessible acc = (Accessible) rows[i];
			acc.index = i;
			array.addObject(acc.delegate);
		}
		return array;
	}

	id getSelectedColumnsAttribute(int childID) {
		if (accessibleTableListenersSize() == 0) return null;

		id returnValue = null;
		AccessibleTableEvent event = new AccessibleTableEvent(this);
		for (int i = 0; i < accessibleTableListenersSize(); i++) {
			AccessibleTableListener listener = accessibleTableListeners.get(i);
			listener.getSelectedColumns(event);
		}
		if (event.selected != null) {
			int[] selected = event.selected;
			NSMutableArray array = NSMutableArray.arrayWithCapacity(selected.length);
			for (int i = 0; i < selected.length; i++) {
				event.column = selected[i];
				for (int j = 0; j < accessibleTableListenersSize(); j++) {
					AccessibleTableListener listener = accessibleTableListeners.get(j);
					listener.getColumn(event);
				}
				if (event.accessible != null) array.addObject(event.accessible.delegate);
			}
			returnValue = array;
		}
		return returnValue == null ? NSArray.array() : returnValue;
	}

	int getColumnCount() {
		AccessibleTableEvent event = new AccessibleTableEvent(this);

		for (int i = 0; i < accessibleTableListenersSize(); i++) {
			AccessibleTableListener listener = accessibleTableListeners.get(i);
			listener.getColumnCount(event);
		}

		return event.count;
	}

	id getColumnsAttribute(int childID) {
		if (accessibleTableListenersSize() == 0) return null;

		AccessibleTableEvent event = new AccessibleTableEvent(this);
		for (int i = 0; i < accessibleTableListenersSize(); i++) {
			AccessibleTableListener listener = accessibleTableListeners.get(i);
			listener.getColumnCount(event);
			listener.getColumns(event);
		}

		if (event.accessibles == null) event.accessibles = new Accessible[0];

		if (event.count != event.accessibles.length) {
			createTableDelegate();

			// Rerun the Column query, now that our adapter is in place.
			for (int i = 0; i < accessibleTableListenersSize(); i++) {
				AccessibleTableListener listener = accessibleTableListeners.get(i);
				listener.getColumnCount(event);
				listener.getColumns(event);
			}
		}

		NSMutableArray array = NSMutableArray.arrayWithCapacity(event.accessibles.length);
		Accessible[] accessibles = event.accessibles;
		for (int i = 0; i < accessibles.length; i++) {
			Accessible acc = accessibles[i];
			acc.index = i;
			array.addObject(acc.delegate);
		}
		return array;
	}

	/**
	 * Gets the human-readable description of an action.
	 * <p>
	 * <b>IMPORTANT:</b> This field is <em>not</em> part of the SWT
	 * public API. It is marked public only so that it can be shared
	 * within the packages provided by SWT. It is not available on all
	 * platforms and should never be accessed from application code.
	 * </p>
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 */
	public id internal_accessibilityActionDescription(NSString action, int childID) {
		NSString returnValue = NSString.string();
		String actionName = action.getString();
		if (accessibleActionListenersSize() > 0) {
			AccessibleActionEvent event = new AccessibleActionEvent(this);
			for (int i = 0; i < accessibleActionListenersSize(); i++) {
				AccessibleActionListener listener = accessibleActionListeners.get(i);
				listener.getActionCount(event);
			}
			int index = -1;
			for (int i = 0; i < event.count; i++) {
				event.index = i;
				for (int j = 0; j < accessibleActionListenersSize(); j++) {
					AccessibleActionListener listener = accessibleActionListeners.get(j);
					listener.getName(event);
				}
				if (actionName.equals(event.result)) {
					index = i;
					break;
				}
			}
			if (index != -1) {
				event.index = index;
				event.result = null;
				for (int i = 0; i < accessibleActionListenersSize(); i++) {
					AccessibleActionListener listener = accessibleActionListeners.get(i);
					listener.getDescription(event);
				}
				if (event.result != null) returnValue = NSString.stringWith(event.result);
			}
		}
		return returnValue;
	}

	/**
	 * Gets the array of action names that this object can perform.
	 * <p>
	 * <b>IMPORTANT:</b> This field is <em>not</em> part of the SWT
	 * public API. It is marked public only so that it can be shared
	 * within the packages provided by SWT. It is not available on all
	 * platforms and should never be accessed from application code.
	 * </p>
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 */
	public NSArray internal_accessibilityActionNames(int childID) {
		if (accessibleActionListenersSize() > 0) {
			AccessibleActionEvent event = new AccessibleActionEvent(this);
			for (int i = 0; i < accessibleActionListenersSize(); i++) {
				AccessibleActionListener listener = accessibleActionListeners.get(i);
				listener.getActionCount(event);
			}
			NSMutableArray array = NSMutableArray.arrayWithCapacity(event.count);
			for (int i = 0; i < event.count; i++) {
				event.index = i;
				for (int j = 0; j < accessibleActionListenersSize(); j++) {
					AccessibleActionListener listener = accessibleActionListeners.get(j);
					listener.getName(event);
				}
				array.addObject(NSString.stringWith(event.result));
			}
			return array;
		} else {
			// The supported action list depends on the role played by the control.
			AccessibleControlEvent event = new AccessibleControlEvent(this);
			event.childID = childID;
			event.detail = -1;
			for (int i = 0; i < accessibleControlListenersSize(); i++) {
				AccessibleControlListener listener = accessibleControlListeners.get(i);
				listener.getRole(event);
			}

			// No accessible listener is overriding the role of the control, so let Cocoa return the default set for the control.
			if (event.detail == -1) {
				return null;
			}

			checkRole(event.detail);

			if ((childID == ACC.CHILDID_SELF) && (actionNames != null)) {
				return retainedAutoreleased(actionNames);
			}

			NSMutableArray returnValue = NSMutableArray.arrayWithCapacity(5);

			switch (event.detail) {
				case ACC.ROLE_PUSHBUTTON:
				case ACC.ROLE_RADIOBUTTON:
				case ACC.ROLE_CHECKBUTTON:
				case ACC.ROLE_TABITEM:
				case ACC.ROLE_LINK:
				case ACC.ROLE_CHECKMENUITEM:
				case ACC.ROLE_RADIOMENUITEM:
				case ACC.ROLE_SPLITBUTTON:
					returnValue.addObject(OS.NSAccessibilityPressAction);
					break;
				case ACC.ROLE_COMBOBOX:
					returnValue.addObject(OS.NSAccessibilityConfirmAction);
					break;
				case ACC.ROLE_WINDOW:
				case ACC.ROLE_DIALOG:
//					TODO
//					returnValue.addObject(OS.NSAccessibilityRaiseAction);
					break;
			}


			if (childID == ACC.CHILDID_SELF) {
				actionNames = returnValue;
				actionNames.retain();
				return retainedAutoreleased(actionNames);
			} else {
				// Caller must retain if they want to hold on to it.
				return returnValue;
			}
		}
	}

	/**
	 * Checks to see if the specified attribute can be set by a screen reader or other
	 * assistive service.
	 * <p>
	 * <b>IMPORTANT:</b> This field is <em>not</em> part of the SWT
	 * public API. It is marked public only so that it can be shared
	 * within the packages provided by SWT. It is not available on all
	 * platforms and should never be accessed from application code.
	 * </p>
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 */
	public boolean internal_accessibilityIsAttributeSettable(NSString attribute, int childID) {
		if (accessibleTextExtendedListenersSize() > 0) {
			if (attribute.isEqualToString(OS.NSAccessibilitySelectedTextRangeAttribute)) return true;
			if (attribute.isEqualToString(OS.NSAccessibilityVisibleCharacterRangeAttribute)) return true;
		}
		if (accessibleEditableTextListenersSize() > 0) {
			if (attribute.isEqualToString(OS.NSAccessibilitySelectedTextAttribute)) return true;
		}
		if (accessibleValueListenersSize() > 0) {
			if (attribute.isEqualToString(OS.NSAccessibilityValueAttribute)) return true;
		}
		return false;
	}

	/**
	 * Gets the array of attributes this object supports.
	 * <p>
	 * <b>IMPORTANT:</b> This field is <em>not</em> part of the SWT
	 * public API. It is marked public only so that it can be shared
	 * within the packages provided by SWT. It is not available on all
	 * platforms and should never be accessed from application code.
	 * </p>
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 */
	public NSArray internal_accessibilityAttributeNames(int childID) {
		// The supported attribute set depends on the role played by the control.
		// We may need to add or remove from the base set as needed.
		AccessibleControlEvent event = new AccessibleControlEvent(this);
		event.childID = childID;
		event.detail = -1;
		for (int i = 0; i < accessibleControlListenersSize(); i++) {
			AccessibleControlListener listener = accessibleControlListeners.get(i);
			listener.getRole(event);
		}

		// No accessible listener is overriding the role of the control, so let Cocoa
		// return the default set for the control.
		if (event.detail == -1)
			return null;

		checkRole(event.detail);

		// If the attributes haven't changed return the cached list.
		if (attributeNames != null) return retainedAutoreleased(attributeNames);

		// Set up the base set of attributes.
		NSMutableArray returnValue = NSMutableArray.arrayWithCapacity(baseAttributes.length);

		for (int i = 0; i < baseAttributes.length; i++) {
			returnValue.addObject(baseAttributes[i]);
		}

		switch(event.detail) {
			case ACC.ROLE_CLIENT_AREA:
				break;
			case ACC.ROLE_WINDOW:
				returnValue.addObject(OS.NSAccessibilityTitleAttribute);
//				TODO
//				returnValue.addObject(OS.NSAccessibilityMainAttribute);
//				returnValue.addObject(OS.NSAccessibilityMinimizedAttribute);
				break;
			case ACC.ROLE_MENUBAR:
				returnValue.addObject(OS.NSAccessibilitySelectedChildrenAttribute);
				returnValue.addObject(OS.NSAccessibilityVisibleChildrenAttribute);
				break;
			case ACC.ROLE_MENU:
				returnValue.addObject(OS.NSAccessibilityEnabledAttribute);
				returnValue.addObject(OS.NSAccessibilitySelectedChildrenAttribute);
				returnValue.addObject(OS.NSAccessibilityVisibleChildrenAttribute);
				break;
			case ACC.ROLE_MENUITEM:
				returnValue.addObject(OS.NSAccessibilityEnabledAttribute);
				break;
			case ACC.ROLE_SEPARATOR:
				returnValue.addObject(OS.NSAccessibilityMaxValueAttribute);
				returnValue.addObject(OS.NSAccessibilityMinValueAttribute);
				returnValue.addObject(OS.NSAccessibilityValueAttribute);
				//TODO
//				returnValue.addObject(OS.NSAccessibilityOrientationAttribute);
//				returnValue.addObject(OS.NSAccessibilityPreviousContentsAttribute);
//				returnValue.addObject(OS.NSAccessibilityNextContentsAttribute);
				break;
			case ACC.ROLE_TOOLTIP:
				returnValue.addObject(OS.NSAccessibilityTitleAttribute);
				break;
			case ACC.ROLE_SCROLLBAR:
				returnValue.addObject(OS.NSAccessibilityValueAttribute);
				//TODO
//				returnValue.addObject(OS.NSAccessibilityOrientationAttribute);
				break;
			case ACC.ROLE_DIALOG:
				returnValue.addObject(OS.NSAccessibilityTitleAttribute);
//				TODO
//				returnValue.addObject(OS.NSAccessibilityMainAttribute);
//				returnValue.addObject(OS.NSAccessibilityMinimizedAttribute);
				break;
			case ACC.ROLE_LABEL:
				returnValue.addObject(OS.NSAccessibilityEnabledAttribute);
				returnValue.addObject(OS.NSAccessibilityValueAttribute);
				break;
			case ACC.ROLE_PUSHBUTTON:
				returnValue.addObject(OS.NSAccessibilityEnabledAttribute);
				returnValue.addObject(OS.NSAccessibilityTitleAttribute);
				break;
			case ACC.ROLE_CHECKBUTTON:
			case ACC.ROLE_RADIOBUTTON:
				returnValue.addObject(OS.NSAccessibilityEnabledAttribute);
				returnValue.addObject(OS.NSAccessibilityValueAttribute);
				returnValue.addObject(OS.NSAccessibilityTitleAttribute);
				break;
			case ACC.ROLE_SPLITBUTTON:
				break;
			case ACC.ROLE_COMBOBOX:
				returnValue.addObject(OS.NSAccessibilityEnabledAttribute);
				returnValue.addObject(OS.NSAccessibilityExpandedAttribute);
				returnValue.addObject(OS.NSAccessibilityNumberOfCharactersAttribute);
				returnValue.addObject(OS.NSAccessibilitySelectedTextAttribute);
				returnValue.addObject(OS.NSAccessibilitySelectedTextRangeAttribute);
				returnValue.addObject(OS.NSAccessibilityVisibleCharacterRangeAttribute);
				returnValue.addObject(OS.NSAccessibilityValueAttribute);
				break;
			case ACC.ROLE_TEXT:
			case ACC.ROLE_PARAGRAPH:
			case ACC.ROLE_HEADING:
				returnValue.addObject(OS.NSAccessibilityEnabledAttribute);
				returnValue.addObject(OS.NSAccessibilityNumberOfCharactersAttribute);
				returnValue.addObject(OS.NSAccessibilitySelectedTextAttribute);
				returnValue.addObject(OS.NSAccessibilitySelectedTextRangeAttribute);
				returnValue.addObject(OS.NSAccessibilityInsertionPointLineNumberAttribute);
				returnValue.addObject(OS.NSAccessibilitySelectedTextRangesAttribute);
				returnValue.addObject(OS.NSAccessibilityVisibleCharacterRangeAttribute);
				returnValue.addObject(OS.NSAccessibilityValueAttribute);
				break;
			case ACC.ROLE_TOOLBAR:
				break;
			case ACC.ROLE_LIST:
				returnValue.addObject(OS.NSAccessibilityEnabledAttribute);
				returnValue.addObject(OS.NSAccessibilityColumnsAttribute);
				returnValue.addObject(OS.NSAccessibilitySelectedColumnsAttribute);
				returnValue.addObject(OS.NSAccessibilityRowsAttribute);
				returnValue.addObject(OS.NSAccessibilitySelectedRowsAttribute);
				returnValue.addObject(OS.NSAccessibilityHeaderAttribute);
				returnValue.addObject(OS.NSAccessibilityVisibleRowsAttribute);
				returnValue.addObject(OS.NSAccessibilityVisibleColumnsAttribute);
				returnValue.addObject(OS.NSAccessibilityVisibleChildrenAttribute);
				break;
			case ACC.ROLE_LISTITEM:
				returnValue.addObject(OS.NSAccessibilityValueAttribute);
				break;
			case ACC.ROLE_TABLE:
				returnValue.addObject(OS.NSAccessibilityEnabledAttribute);
				returnValue.addObject(OS.NSAccessibilityColumnsAttribute);
				returnValue.addObject(OS.NSAccessibilitySelectedColumnsAttribute);
				returnValue.addObject(OS.NSAccessibilityVisibleColumnsAttribute);
				returnValue.addObject(OS.NSAccessibilityRowsAttribute);
				returnValue.addObject(OS.NSAccessibilitySelectedRowsAttribute);
				returnValue.addObject(OS.NSAccessibilityVisibleRowsAttribute);
				returnValue.addObject(OS.NSAccessibilityHeaderAttribute);
				break;
			case ACC.ROLE_TABLECELL:
				returnValue.addObject(OS.NSAccessibilityEnabledAttribute);
				returnValue.addObject(OS.NSAccessibilityValueAttribute);
				break;
			case ACC.ROLE_TREE:
				returnValue.addObject(OS.NSAccessibilityColumnsAttribute);
				returnValue.addObject(OS.NSAccessibilitySelectedColumnsAttribute);
				returnValue.addObject(OS.NSAccessibilityRowsAttribute);
				returnValue.addObject(OS.NSAccessibilitySelectedRowsAttribute);
				returnValue.addObject(OS.NSAccessibilityHeaderAttribute);
				returnValue.addObject(OS.NSAccessibilityVisibleRowsAttribute);
				returnValue.addObject(OS.NSAccessibilityVisibleColumnsAttribute);
				break;
			case ACC.ROLE_TREEITEM:
				returnValue.addObject(OS.NSAccessibilityColumnsAttribute);
				returnValue.addObject(OS.NSAccessibilitySelectedColumnsAttribute);
				returnValue.addObject(OS.NSAccessibilityRowsAttribute);
				returnValue.addObject(OS.NSAccessibilitySelectedRowsAttribute);
				returnValue.addObject(OS.NSAccessibilityHeaderAttribute);
				returnValue.addObject(OS.NSAccessibilityVisibleRowsAttribute);
				returnValue.addObject(OS.NSAccessibilityVisibleColumnsAttribute);
			case ACC.ROLE_TABFOLDER:
				returnValue.addObject(OS.NSAccessibilityValueAttribute);
				returnValue.addObject(OS.NSAccessibilityContentsAttribute);
				returnValue.addObject(OS.NSAccessibilityTabsAttribute);
				break;
			case ACC.ROLE_TABITEM:
				returnValue.addObject(OS.NSAccessibilityEnabledAttribute);
				returnValue.addObject(OS.NSAccessibilityValueAttribute);
				returnValue.addObject(OS.NSAccessibilityTitleAttribute);
				break;
			case ACC.ROLE_PROGRESSBAR:
				returnValue.addObject(OS.NSAccessibilityEnabledAttribute);
				returnValue.addObject(OS.NSAccessibilityMaxValueAttribute);
				returnValue.addObject(OS.NSAccessibilityMinValueAttribute);
				returnValue.addObject(OS.NSAccessibilityValueAttribute);
				break;
			case ACC.ROLE_SLIDER:
				returnValue.addObject(OS.NSAccessibilityEnabledAttribute);
				returnValue.addObject(OS.NSAccessibilityMaxValueAttribute);
				returnValue.addObject(OS.NSAccessibilityMinValueAttribute);
				returnValue.addObject(OS.NSAccessibilityValueAttribute);
				//TODO
//				returnValue.addObject(OS.NSAccessibilityOrientationAttribute);
//				increment
//				decrement
				break;
			case ACC.ROLE_LINK:
				//TODO
				returnValue.addObject(OS.NSAccessibilityEnabledAttribute);
//				returnValue.addObject(OS.NSAccessibilityURLAttribute);
//				visited
				break;
			case ACC.ROLE_ALERT:
				break;
			case ACC.ROLE_ANIMATION:
				break;
			case ACC.ROLE_CANVAS:
				break;
			case ACC.ROLE_COLUMN:
				returnValue.removeObject(OS.NSAccessibilityChildrenAttribute);
				returnValue.removeObject(OS.NSAccessibilityFocusedAttribute);
				returnValue.addObject(OS.NSAccessibilityIndexAttribute);
				returnValue.addObject(OS.NSAccessibilitySelectedAttribute);
				returnValue.addObject(OS.NSAccessibilityRowsAttribute);
				returnValue.addObject(OS.NSAccessibilityVisibleRowsAttribute);
				returnValue.addObject(OS.NSAccessibilityHeaderAttribute);
				break;
			case ACC.ROLE_DOCUMENT:
				break;
			case ACC.ROLE_GRAPHIC:
				returnValue.addObject(OS.NSAccessibilityEnabledAttribute);
				returnValue.addObject(OS.NSAccessibilityTitleAttribute);
				returnValue.addObject(OS.NSAccessibilityDescriptionAttribute);
				break;
			case ACC.ROLE_GROUP:
				break;
			case ACC.ROLE_ROW:
				returnValue.addObject(OS.NSAccessibilityVisibleChildrenAttribute);
				returnValue.addObject(OS.NSAccessibilityIndexAttribute);
				returnValue.addObject(OS.NSAccessibilitySelectedAttribute);
				break;
			case ACC.ROLE_SPINBUTTON:
				break;
			case ACC.ROLE_STATUSBAR:
				break;
			case ACC.ROLE_CHECKMENUITEM:
				break;
			case ACC.ROLE_RADIOMENUITEM:
				break;
			case ACC.ROLE_CLOCK:
				break;
			case ACC.ROLE_DATETIME:
				break;
			case ACC.ROLE_CALENDAR:
				break;
			case ACC.ROLE_FOOTER:
				break;
			case ACC.ROLE_HEADER:
				break;
			case ACC.ROLE_FORM:
				break;
			case ACC.ROLE_PAGE:
				break;
			case ACC.ROLE_SECTION:
				break;
		}


		/*
		 * Only report back sub-roles when the SWT role maps to a sub-role.
		 */
		if (event.detail != -1) {
			String osRole = roleToOs(event.detail);
			if (osRole.indexOf(':') == -1)
				returnValue.removeObject(OS.NSAccessibilitySubroleAttribute);
		}

		/*
		 * Children never return their own children, so remove that attribute.
		 */
		if (childID != ACC.CHILDID_SELF) {
			returnValue.removeObject(OS.NSAccessibilityChildrenAttribute);
		}

		if (childID == ACC.CHILDID_SELF) {
			attributeNames = returnValue;
			attributeNames.retain();
			return retainedAutoreleased(attributeNames);
		} else {
			// Caller must retain if necessary.
			return returnValue;
		}
	}

	/**
	 * Returns the value for the specified attribute. Return type depends on the attribute
	 * being queried; see the implementations of the accessor methods for details.
	 * <p>
	 * <b>IMPORTANT:</b> This field is <em>not</em> part of the SWT
	 * public API. It is marked public only so that it can be shared
	 * within the packages provided by SWT. It is not available on all
	 * platforms and should never be accessed from application code.
	 * </p>
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 */
	public id internal_accessibilityAttributeValue(NSString attribute, int childID) {
		if (attribute.isEqualToString(OS.NSAccessibilityRoleAttribute)) return getRoleAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilitySubroleAttribute)) return getSubroleAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityRoleDescriptionAttribute)) return getRoleDescriptionAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityExpandedAttribute)) return getExpandedAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityHelpAttribute)) return getHelpAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityTitleAttribute)) return getTitleAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityValueAttribute)) return getValueAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityMaxValueAttribute)) return getMaxValueAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityMinValueAttribute)) return getMinValueAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityEnabledAttribute)) return getEnabledAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityFocusedAttribute)) return getFocusedAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityParentAttribute)) return getParentAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityChildrenAttribute)) return getChildrenAttribute(childID, false);
		if (attribute.isEqualToString(OS.NSAccessibilityVisibleChildrenAttribute)) return getChildrenAttribute(childID, true);
		if (attribute.isEqualToString(OS.NSAccessibilityContentsAttribute)) return getChildrenAttribute(childID, false);
		// FIXME:  There's no specific API just for tabs, which won't include the buttons (if any.)
		if (attribute.isEqualToString(OS.NSAccessibilityTabsAttribute)) return getTabsAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityWindowAttribute)) return getWindowAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityTopLevelUIElementAttribute)) return getTopLevelUIElementAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityPositionAttribute)) return getPositionAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilitySizeAttribute)) return getSizeAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityDescriptionAttribute)) return getDescriptionAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityNumberOfCharactersAttribute)) return getNumberOfCharactersAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilitySelectedTextAttribute)) return getSelectedTextAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilitySelectedTextRangeAttribute)) return getSelectedTextRangeAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityInsertionPointLineNumberAttribute)) return getInsertionPointLineNumberAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilitySelectedTextRangesAttribute)) return getSelectedTextRangesAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityVisibleCharacterRangeAttribute)) return getVisibleCharacterRangeAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityServesAsTitleForUIElementsAttribute)) return getServesAsTitleForUIElementsAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityTitleUIElementAttribute)) return getTitleUIElementAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityColumnsAttribute)) return getColumnsAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilitySelectedColumnsAttribute)) return getSelectedColumnsAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityRowsAttribute)) return getRowsAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilitySelectedRowsAttribute)) return getSelectedRowsAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityVisibleRowsAttribute)) return getVisibleRowsAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityVisibleColumnsAttribute)) return getVisibleColumnsAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityHeaderAttribute)) return getHeaderAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityIndexAttribute)) return getIndexAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilitySelectedAttribute)) return getSelectedAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityRowIndexRangeAttribute)) return getRowIndexRangeAttribute(childID);
		if (attribute.isEqualToString(OS.NSAccessibilityColumnIndexRangeAttribute)) return getColumnIndexRangeAttribute(childID);

		// If this object don't know how to get the value it's up to the control itself to return an attribute value.
		return null;
	}

	/**
	 * Returns the value of the specified attribute, using the supplied parameter. Return
	 * and parameter types vary depending on the attribute being queried.
	 * <p>
	 * <b>IMPORTANT:</b> This field is <em>not</em> part of the SWT
	 * public API. It is marked public only so that it can be shared
	 * within the packages provided by SWT. It is not available on all
	 * platforms and should never be accessed from application code.
	 * </p>
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 */
	public id internal_accessibilityAttributeValue_forParameter(NSString attribute, id parameter, int childID) {
		if (attribute.isEqualToString(OS.NSAccessibilityStringForRangeParameterizedAttribute)) return getStringForRangeParameterizedAttribute(parameter, childID);
		if (attribute.isEqualToString(OS.NSAccessibilityRangeForLineParameterizedAttribute)) return getRangeForLineParameterizedAttribute(parameter, childID);
		if (attribute.isEqualToString(OS.NSAccessibilityRangeForIndexParameterizedAttribute)) return getRangeForIndexParameterizedAttribute(parameter, childID);
		if (attribute.isEqualToString(OS.NSAccessibilityLineForIndexParameterizedAttribute)) return getLineForIndexParameterizedAttribute(parameter, childID);
		if (attribute.isEqualToString(OS.NSAccessibilityBoundsForRangeParameterizedAttribute)) return getBoundsForRangeParameterizedAttribute(parameter, childID);
		if (attribute.isEqualToString(OS.NSAccessibilityRangeForPositionParameterizedAttribute)) return getRangeForPositionParameterizedAttribute(parameter, childID);
		if (attribute.isEqualToString(OS.NSAccessibilityAttributedStringForRangeParameterizedAttribute)) return getAttributedStringForRangeParameterizedAttribute(parameter, childID);
		if (attribute.isEqualToString(OS.NSAccessibilityStyleRangeForIndexParameterizedAttribute)) return getStyleRangeForIndexAttribute(parameter, childID);
		if (attribute.isEqualToString(OS.NSAccessibilityCellForColumnAndRowParameterizedAttribute)) return getCellForColumnAndRowParameter(parameter, childID);
		return null;
	}

	/**
	 * Returns the UI Element that has the focus. You can assume that the search
	 * for the focus has already been narrowed down to the receiver.
	 * Override this method to do a deeper search with a UIElement -
	 * e.g. a NSMatrix would determine if one of its cells has the focus.
	 * <p>
	 * <b>IMPORTANT:</b> This field is <em>not</em> part of the SWT
	 * public API. It is marked public only so that it can be shared
	 * within the packages provided by SWT. It is not available on all
	 * platforms and should never be accessed from application code.
	 * </p>
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 */
	public id internal_accessibilityFocusedUIElement(int childID) {
		AccessibleControlEvent event = new AccessibleControlEvent(this);
		event.childID = ACC.CHILDID_MULTIPLE; // set to invalid value, to test if the application sets it in getFocus()
		event.accessible = null;
		for (int i = 0; i < accessibleControlListenersSize(); i++) {
			AccessibleControlListener listener = accessibleControlListeners.get(i);
			listener.getFocus(event);
		}

		// The listener did not respond, so let Cocoa figure it out.
		if (event.childID == ACC.CHILDID_MULTIPLE)
			return null;

		/* The application can optionally answer an accessible. */
		if (event.accessible != null) {
			return new id(OS.NSAccessibilityUnignoredAncestor(event.accessible.control.view.id));
		}

		/* Or the application can answer a valid child ID, including CHILDID_SELF and CHILDID_NONE. */
		if (event.childID == ACC.CHILDID_SELF || event.childID == ACC.CHILDID_NONE) {
			return new id(OS.NSAccessibilityUnignoredAncestor(control.view.id));
		}

		return new id(OS.NSAccessibilityUnignoredAncestor(childIDToOs(event.childID).id));
	}

	/**
	 * Returns the deepest descendant of the UIElement hierarchy that contains the point.
	 * You can assume the point has already been determined to lie within the receiver.
	 * Override this method to do deeper hit testing within a UIElement - e.g. a NSMatrix
	 * would test its cells. The point is bottom-left relative screen coordinates.
	 * <p>
	 * <b>IMPORTANT:</b> This field is <em>not</em> part of the SWT
	 * public API. It is marked public only so that it can be shared
	 * within the packages provided by SWT. It is not available on all
	 * platforms and should never be accessed from application code.
	 * </p>
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 */
	public id internal_accessibilityHitTest(NSPoint point, int childID) {
		AccessibleControlEvent event = new AccessibleControlEvent(this);
		event.x = (int) point.x;
		Monitor primaryMonitor = Display.getCurrent().getPrimaryMonitor();
		event.y = (int) (primaryMonitor.getBounds().height - point.y);

		// Set an impossible value to determine if anything responded to the event.
		event.childID = ACC.CHILDID_MULTIPLE;
		for (int i = 0; i < accessibleControlListenersSize(); i++) {
			AccessibleControlListener listener = accessibleControlListeners.get(i);
			listener.getChildAtPoint(event);
		}

		// The listener did not respond, so let Cocoa figure it out.
		if (event.childID == ACC.CHILDID_MULTIPLE && event.accessible == null)
			return null;

		if (event.accessible != null) {
			return new id(OS.NSAccessibilityUnignoredAncestor(event.accessible.delegate.id));
		}

		if (event.childID == ACC.CHILDID_SELF || event.childID == ACC.CHILDID_NONE) {
			return new id(OS.NSAccessibilityUnignoredAncestor(control.view.id));
		}

		return new id(OS.NSAccessibilityUnignoredAncestor(childIDToOs(event.childID).id));
	}

	/**
	 * Return YES if the UIElement doesn't show up to the outside world -
	 * i.e. its parent should return the UIElement's children as its own -
	 * cutting the UIElement out. E.g. NSControls are ignored when they are single-celled.
	 * <p>
	 * <b>IMPORTANT:</b> This field is <em>not</em> part of the SWT
	 * public API. It is marked public only so that it can be shared
	 * within the packages provided by SWT. It is not available on all
	 * platforms and should never be accessed from application code.
	 * </p>
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 */
	public boolean internal_accessibilityIsIgnored(int childID) {
		AccessibleControlEvent event = new AccessibleControlEvent(this);
		event.childID = childID;
		event.detail = -1;
		for (int i = 0; i < accessibleControlListenersSize(); i++) {
			AccessibleControlListener listener = accessibleControlListeners.get(i);
			listener.getRole(event);
		}

		boolean shouldIgnore = (event.detail == -1);

		if (shouldIgnore) {
			shouldIgnore = getTitleAttribute(childID) == null && getHelpAttribute(childID) == null && getDescriptionAttribute(childID) == null;
		}

		return shouldIgnore;
	}

	/**
	 * Return the array of supported attributes that take parameters.
	 * <p>
	 * <b>IMPORTANT:</b> This field is <em>not</em> part of the SWT
	 * public API. It is marked public only so that it can be shared
	 * within the packages provided by SWT. It is not available on all
	 * platforms and should never be accessed from application code.
	 * </p>
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 */
	public NSArray internal_accessibilityParameterizedAttributeNames(int childID) {
		AccessibleControlEvent event = new AccessibleControlEvent(this);
		event.childID = childID;
		event.detail = -1;
		for (int i = 0; i < accessibleControlListenersSize(); i++) {
			AccessibleControlListener listener = accessibleControlListeners.get(i);
			listener.getRole(event);
		}

		// No accessible listener is overriding the role of the control, so let Cocoa
		// return the default set for the control.
		if (event.detail == -1)
			return null;

		checkRole(event.detail);

		if ((childID == ACC.CHILDID_SELF) && (parameterizedAttributeNames != null)) {
			return retainedAutoreleased(parameterizedAttributeNames);
		}

		NSMutableArray returnValue = NSMutableArray.arrayWithCapacity(4);

		switch(event.detail) {
			case ACC.ROLE_TEXT:
			case ACC.ROLE_PARAGRAPH:
			case ACC.ROLE_HEADING:
				returnValue.addObject(OS.NSAccessibilityStringForRangeParameterizedAttribute);
				returnValue.addObject(OS.NSAccessibilityRangeForLineParameterizedAttribute);
				returnValue.addObject(OS.NSAccessibilityRangeForIndexParameterizedAttribute);
				returnValue.addObject(OS.NSAccessibilityLineForIndexParameterizedAttribute);
				returnValue.addObject(OS.NSAccessibilityBoundsForRangeParameterizedAttribute);
				returnValue.addObject(OS.NSAccessibilityRangeForPositionParameterizedAttribute);
				returnValue.addObject(OS.NSAccessibilityAttributedStringForRangeParameterizedAttribute);
				returnValue.addObject(OS.NSAccessibilityStyleRangeForIndexParameterizedAttribute);
				break;
			case ACC.ROLE_TABLE:
				returnValue.addObject(OS.NSAccessibilityCellForColumnAndRowParameterizedAttribute);
				break;
		}

		if (childID == ACC.CHILDID_SELF) {
			parameterizedAttributeNames = returnValue;
			parameterizedAttributeNames.retain();
			return retainedAutoreleased(parameterizedAttributeNames);
		} else {
			// Caller must retain if they want to keep it.
			return returnValue;
		}
	}

	/**
	 * Performs the specified action.
	 * <p>
	 * <b>IMPORTANT:</b> This field is <em>not</em> part of the SWT
	 * public API. It is marked public only so that it can be shared
	 * within the packages provided by SWT. It is not available on all
	 * platforms and should never be accessed from application code.
	 * </p>
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 */
	public boolean internal_accessibilityPerformAction(NSString action, int childID) {
		String actionName = action.getString();
		if (accessibleActionListenersSize() > 0) {
			AccessibleActionEvent event = new AccessibleActionEvent(this);
			for (int i = 0; i < accessibleActionListenersSize(); i++) {
				AccessibleActionListener listener = accessibleActionListeners.get(i);
				listener.getActionCount(event);
			}
			int index = -1;
			for (int i = 0; i < event.count; i++) {
				event.index = i;
				for (int j = 0; j < accessibleActionListenersSize(); j++) {
					AccessibleActionListener listener = accessibleActionListeners.get(j);
					listener.getName(event);
				}
				if (actionName.equals(event.result)) {
					index = i;
					break;
				}
			}
			if (index != -1) {
				event.index = index;
				event.result = null;
				for (int i = 0; i < accessibleActionListenersSize(); i++) {
					AccessibleActionListener listener = accessibleActionListeners.get(i);
					listener.doAction(event);
				}
				return ACC.OK.equals(event.result);
			}
		}
		return false;
	}

	/**
	 * Set the value of the specified attribute to the given value.
	 * Unsupported attributes are ignored.
	 * <p>
	 * <b>IMPORTANT:</b> This field is <em>not</em> part of the SWT
	 * public API. It is marked public only so that it can be shared
	 * within the packages provided by SWT. It is not available on all
	 * platforms and should never be accessed from application code.
	 * </p>
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 */
	public void internal_accessibilitySetValue_forAttribute(id value, NSString attribute, int childId) {
		if (attribute.isEqualToString(OS.NSAccessibilitySelectedTextRangeAttribute)) setSelectedTextRangeAttribute(value, childId);
		if (attribute.isEqualToString(OS.NSAccessibilitySelectedTextAttribute)) setSelectedTextAttribute(value, childId);
		if (attribute.isEqualToString(OS.NSAccessibilityVisibleCharacterRangeAttribute)) setVisibleCharacterRangeAttribute(value, childId);

		if (accessibleValueListenersSize() > 0) {
			AccessibleValueEvent event = new AccessibleValueEvent(this);
			NSNumber number = new NSNumber(value);
			event.value = Double.valueOf(number.doubleValue());
			for (int i = 0; i < accessibleValueListenersSize(); i++) {
				AccessibleValueListener listener = accessibleValueListeners.get(i);
				listener.setCurrentValue(event);
			}
		}
	}

	/**
	 * Disposes of the operating system resources associated with
	 * the receiver, and removes the receiver from its parent's
	 * list of children.
	 * <p>
	 * This method should be called when an accessible that was created
	 * with the public constructor <code>Accessible(Accessible parent)</code>
	 * is no longer needed. You do not need to call this when the receiver's
	 * control is disposed, because all <code>Accessible</code> instances
	 * associated with a control are released when the control is disposed.
	 * It is also not necessary to call this for instances of <code>Accessible</code>
	 * that were retrieved with <code>Control.getAccessible()</code>.
	 * </p>
	 *
	 * @since 3.6
	 */
	public void dispose () {
		if (parent == null) return;
		release(true);
		parent = null;
	}

	/**
	 * Returns the control for this Accessible object.
	 *
	 * @return the receiver's control
	 * @since 3.0
	 */
	public Control getControl() {
		return control;
	}

	/**
	 * Invokes platform specific functionality to dispose an accessible object.
	 * <p>
	 * <b>IMPORTANT:</b> This method is <em>not</em> part of the public
	 * API for <code>Accessible</code>. It is marked public only so that it
	 * can be shared within the packages provided by SWT. It is not
	 * available on all platforms, and should never be called from
	 * application code.
	 * </p>
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 */
	public void internal_dispose_Accessible() {
		release(true);
	}

	id getAttributedStringForRangeParameterizedAttribute(id parameter, int childID) {
		if (accessibleAttributeListenersSize() == 0) return null;

		id stringFragment = getStringForRangeParameterizedAttribute(parameter, childID);
		NSMutableAttributedString attribString = (NSMutableAttributedString)new NSMutableAttributedString().alloc();
		attribString.initWithString(new NSString(stringFragment), null);
		attribString.autorelease();

		// Parameter is an NSRange wrapped in an NSValue.
		NSValue parameterObject = new NSValue(parameter.id);
		NSRange range = parameterObject.rangeValue();

		AccessibleTextAttributeEvent event = new AccessibleTextAttributeEvent(this);

		event.offset = (int) range.location;
		event.start = event.end = -1;

		NSRange attributeRange = new NSRange();

		while (event.offset < range.location + range.length) {
			for (int i = 0; i < accessibleAttributeListenersSize(); i++) {
				AccessibleAttributeListener listener = accessibleAttributeListeners.get(i);
				listener.getTextAttributes(event);
			}

			if (event.start == -1 && event.end == -1) return stringFragment;

			// The returned attributed string must have zero-based attributes.
			attributeRange.location = event.start - range.location;
			attributeRange.length = event.end - event.start;

			// attributeRange.location can be negative if the start of the requested range falls in the middle of a style run.
			// If that happens, clamp to zero and adjust the length by the amount of adjustment.
			if (attributeRange.location < 0) {
				attributeRange.length -= -attributeRange.location;
				attributeRange.location = 0;
			}

			// Likewise, make sure the last attribute set does not run past the end of the requested range.
			if (attributeRange.location + attributeRange.length > range.length) {
				attributeRange.length = range.length - attributeRange.location;
			}

			// Reset the offset so we pick up the next set of attributes that change.
			event.offset = event.end;

			if (event.textStyle != null) {
				TextStyle ts = event.textStyle;
				if (ts.font != null) {
					NSMutableDictionary fontInfoDict = NSMutableDictionary.dictionaryWithCapacity(4);

					NSFont fontUsed = ts.font.handle;
					// Get font name and size from NSFont
					NSString fontName = fontUsed.fontName();
					fontInfoDict.setValue(fontName, OS.NSAccessibilityFontNameKey);
					NSString familyName = fontUsed.familyName();
					fontInfoDict.setValue(familyName, OS.NSAccessibilityFontFamilyKey);
					NSString displayName = fontUsed.displayName();
					fontInfoDict.setValue(displayName, OS.NSAccessibilityVisibleNameKey);
					double fontSize = fontUsed.pointSize();
					fontInfoDict.setValue(NSNumber.numberWithDouble(fontSize), OS.NSAccessibilityFontSizeKey);

					attribString.addAttribute(OS.NSAccessibilityFontTextAttribute, fontInfoDict, attributeRange);
				}

				if (ts.foreground != null) {
					addCGColor(ts.foreground.handle, attribString, OS.NSAccessibilityForegroundColorTextAttribute, attributeRange);
				}

				if (ts.background != null) {
					addCGColor(ts.background.handle, attribString, OS.NSAccessibilityBackgroundColorTextAttribute, attributeRange);
				}

				if (ts.underline) {
					int style = ts.underlineStyle;
					NSString attribute = OS.NSAccessibilityUnderlineTextAttribute;
					NSNumber styleObj = switch (style) {
					case SWT.UNDERLINE_SINGLE -> NSNumber.numberWithInt(OS.kAXUnderlineStyleSingle);
					case SWT.UNDERLINE_DOUBLE -> NSNumber.numberWithInt(OS.kAXUnderlineStyleDouble);
					case SWT.UNDERLINE_SQUIGGLE -> {
						attribute = OS.NSAccessibilityMisspelledTextAttribute;
						yield NSNumber.numberWithBool(true);
					}
					default -> NSNumber.numberWithInt(OS.kAXUnderlineStyleNone);
					};

					attribString.addAttribute(attribute, styleObj, attributeRange);
				}

				if (ts.underlineColor != null) {
					addCGColor(ts.underlineColor.handle, attribString, OS.NSAccessibilityUnderlineColorTextAttribute, attributeRange);
				}

				if (ts.strikeout) {
					attribString.addAttribute(OS.NSAccessibilityStrikethroughTextAttribute, NSNumber.numberWithBool(true), attributeRange);

					if (ts.strikeoutColor != null) {
						addCGColor(ts.strikeoutColor.handle, attribString, OS.NSAccessibilityStrikethroughColorTextAttribute, attributeRange);
					}
				}

				if (ts.data != null) {
					if (ts.data instanceof URL dataAsURL) {
						NSURL linkURL = NSURL.URLWithString(NSString.stringWith(dataAsURL.toExternalForm()));
						attribString.addAttribute(OS.NSAccessibilityLinkTextAttribute, linkURL, attributeRange);
					}
				}
			}
		}

		// Now add the alignment, justification, and indent, if available.
		AccessibleAttributeEvent docAttributes = new AccessibleAttributeEvent(this);
		docAttributes.indent = Integer.MAX_VALUE; // if unchanged no listener filled it in.
		for (int i = 0; i < accessibleAttributeListenersSize(); i++) {
			AccessibleAttributeListener listener = accessibleAttributeListeners.get(i);
			listener.getAttributes(docAttributes);
		}

		if (docAttributes.indent != Integer.MAX_VALUE) {
			NSMutableDictionary paragraphDict = NSMutableDictionary.dictionaryWithCapacity(3);
			// FIXME: Doesn't account for right-to-left text?
			int osAlignment = switch (docAttributes.alignment) {
			case SWT.CENTER -> OS.NSTextAlignmentCenter;
			case SWT.RIGHT -> OS.NSTextAlignmentRight;
			case SWT.LEFT -> OS.NSTextAlignmentLeft;
			default -> OS.NSTextAlignmentLeft;
			};
			paragraphDict.setValue(NSNumber.numberWithInt(osAlignment), NSString.stringWith("AXTextAlignment"));
			range.location = 0;
			attribString.addAttribute(NSString.stringWith("AXParagraphStyle"), paragraphDict, range);
		}

		return attribString;
	}

	id getBoundsForRangeParameterizedAttribute(id parameter, int childID) {
		if (accessibleTextExtendedListenersSize() == 0) return null;

		id returnValue = null;
		NSValue parameterObject = new NSValue(parameter.id);
		NSRange range = parameterObject.rangeValue();
		NSRect rect = new NSRect();
		if (accessibleTextExtendedListenersSize() > 0) {
			AccessibleTextEvent event  = new AccessibleTextEvent(this);
			event.childID = childID;
			event.start = (int)range.location;
			event.end = (int)(range.location + range.length);
			for (int i = 0; i < accessibleTextExtendedListenersSize(); i++) {
				AccessibleTextExtendedListener listener = accessibleTextExtendedListeners.get(i);
				listener.getTextBounds(event);
			}
			rect.x = event.x;

			// Flip y coordinate for Cocoa.
			NSArray screens = NSScreen.screens();
			if (screens == null) return null;

			NSScreen screen = new NSScreen(screens.objectAtIndex(0));
			NSRect frame = screen.frame();
			rect.y = frame.height - event.y - event.height;

			rect.width = event.width;
			rect.height = event.height;
			returnValue = NSValue.valueWithRect(rect);
		} else {
			//FIXME???
			//how to implement with old listener
		}
		return returnValue;
	}

	id getExpandedAttribute(int childID) {
		// TODO: May need to expand the API so the combo box state can be reported.
		return NSNumber.numberWithBool(false);
	}

	id getHelpAttribute (int childID) {
		id returnValue = null;
		AccessibleEvent event = new AccessibleEvent(this);
		event.childID = childID;
		for (int i = 0; i < accessibleListenersSize(); i++) {
			AccessibleListener listener = accessibleListeners.get(i);
			listener.getHelp(event);
		}

		if (event.result != null) {
			returnValue = NSString.stringWith(event.result);
		}

		return returnValue;
	}

	id getRangeForPositionParameterizedAttribute(id parameter, int childID) {
		id returnValue = null;
		NSValue parameterObject = new NSValue(parameter.id);
		NSPoint point = parameterObject.pointValue();
		NSRange range = new NSRange();
		if (accessibleTextExtendedListenersSize() > 0) {
			NSArray screens = NSScreen.screens();
			if (screens == null) return null;

			NSScreen screen = new NSScreen(screens.objectAtIndex(0));
			NSRect frame = screen.frame();
			point.y = frame.height - point.y;
			AccessibleTextEvent event  = new AccessibleTextEvent(this);
			event.childID = childID;
			event.x = (int)point.x;
			event.y = (int)point.y;
			for (int i = 0; i < accessibleTextExtendedListenersSize(); i++) {
				AccessibleTextExtendedListener listener = accessibleTextExtendedListeners.get(i);
				listener.getOffsetAtPoint(event);
			}
			range.location = event.offset;
			range.length = 1;
		} else {
			//FIXME???
			//how to implement with old listener
		}
		returnValue = NSValue.valueWithRange(range);
		return returnValue;
	}

	NSString getRoleAttribute(int childID) {
		NSString returnValue = null;
		AccessibleControlEvent event = new AccessibleControlEvent(this);
		event.childID = childID;
		event.detail = -1;
		for (int i = 0; i < accessibleControlListenersSize(); i++) {
			AccessibleControlListener listener = accessibleControlListeners.get(i);
			listener.getRole(event);
		}
		if (event.detail != -1) {
			String appRole = roleToOs (event.detail);
			int index = appRole.indexOf(':');
			if (index != -1) appRole = appRole.substring(0, index);
			returnValue = NSString.stringWith(appRole);
		}

		return returnValue;
	}

	id getSubroleAttribute (int childID) {
		id returnValue = null;
		AccessibleControlEvent event = new AccessibleControlEvent(this);
		event.childID = childID;
		event.detail = -1;
		for (int i = 0; i < accessibleControlListenersSize(); i++) {
			AccessibleControlListener listener = accessibleControlListeners.get(i);
			listener.getRole(event);
		}
		if (event.detail != -1) {
			String appRole = roleToOs (event.detail);
			int index = appRole.indexOf(':');
			if (index != -1) {
				appRole = appRole.substring(index + 1);
				returnValue = NSString.stringWith(appRole);
			}
		}
		return returnValue;
	}

	id getRoleDescriptionAttribute (int childID) {
		id returnValue = null;
		AccessibleControlEvent event = new AccessibleControlEvent(this);
		event.childID = childID;
		event.detail = -1;
		for (int i = 0; i < accessibleControlListenersSize(); i++) {
			AccessibleControlListener listener = accessibleControlListeners.get(i);
			listener.getRole(event);
		}
		if (event.detail != -1) {
			if (event.detail == ACC.ROLE_TABITEM) {
				returnValue = new NSString(OS.NSAccessibilityRoleDescription (NSString.stringWith("AXTab").id, 0));
			} else {
				String appRole = roleToOs (event.detail);
				String appSubrole = null;
				int index = appRole.indexOf(':');
				if (index != -1) {
					appSubrole = appRole.substring(index + 1);
					appRole = appRole.substring(0, index);
				}
				NSString nsAppRole = NSString.stringWith(appRole);
				NSString nsAppSubrole = null;

				if (appSubrole != null) nsAppSubrole = NSString.stringWith(appSubrole);
				returnValue = new NSString(OS.NSAccessibilityRoleDescription (((nsAppRole != null) ? nsAppRole.id : 0), (nsAppSubrole != null) ? nsAppSubrole.id : 0));
			}
		}
		return returnValue;
	}

	id getTitleAttribute (int childID) {

		id returnValue = null;

		/*
		* Feature of the Macintosh.  The text of a Label is returned in its value,
		* not its title, so ensure that the role is not Label before asking for the title.
		*/
		AccessibleControlEvent roleEvent = new AccessibleControlEvent(this);
		roleEvent.childID = childID;
		roleEvent.detail = -1;
		for (int i = 0; i < accessibleControlListenersSize(); i++) {
			AccessibleControlListener listener = accessibleControlListeners.get(i);
			listener.getRole(roleEvent);
		}
		if (roleEvent.detail != ACC.ROLE_LABEL) {
			AccessibleEvent event = new AccessibleEvent(this);
			event.childID = childID;
			event.result = null;
			for (int i = 0; i < accessibleListenersSize(); i++) {
				AccessibleListener listener = accessibleListeners.get(i);
				listener.getName(event);
			}

			if (event.result != null)
				returnValue = NSString.stringWith(event.result);
		}
		return returnValue;
	}

	id getTitleUIElementAttribute(int childID) {
		id returnValue = null;
		Relation relation = relations[ACC.RELATION_LABELLED_BY];
		if (relation != null) {
			returnValue = relation.getTitleUIElement();
		}
		return returnValue;
	}

	id getValueAttribute (int childID) {
		id returnValue = null;
		if (accessibleValueListenersSize() > 0) {
			AccessibleValueEvent event = new AccessibleValueEvent(this);
			for (int i = 0; i < accessibleValueListenersSize(); i++) {
				AccessibleValueListener listener = accessibleValueListeners.get(i);
				listener.getCurrentValue(event);
			}
			returnValue = NSNumber.numberWithDouble(event.value.doubleValue());
		} else {
			AccessibleControlEvent event = new AccessibleControlEvent(this);
			event.childID = childID;
			event.detail = -1;
			event.result = null; //TODO: could pass the OS value to the app
			for (int i = 0; i < accessibleControlListenersSize(); i++) {
				AccessibleControlListener listener = accessibleControlListeners.get(i);
				listener.getRole(event);
				listener.getValue(event);
			}
			int role = event.detail;
			String value = event.result;

			switch (role) {
				case ACC.ROLE_RADIOBUTTON: // 1 = on, 0 = off
				case ACC.ROLE_CHECKBUTTON: // 1 = checked, 0 = unchecked, 2 = mixed
				case ACC.ROLE_SCROLLBAR: // numeric value representing the position of the scroller
				case ACC.ROLE_SLIDER: // the value associated with the position of the slider thumb
				case ACC.ROLE_PROGRESSBAR: // the value associated with the fill level of the progress bar
					if (value != null) {
						try {
							int number = Integer.parseInt(value);
							returnValue = NSNumber.numberWithBool(number == 0 ? false : true);
						} catch (NumberFormatException ex) {
							if (value.equalsIgnoreCase("true")) {
								returnValue = NSNumber.numberWithBool(true);
							} else if (value.equalsIgnoreCase("false")) {
								returnValue = NSNumber.numberWithBool(false);
							}
						}
					} else {
						returnValue = NSNumber.numberWithBool(false);
					}
					break;
				case ACC.ROLE_TABFOLDER: // the accessibility object representing the currently selected tab item
				case ACC.ROLE_TABITEM:  // 1 = selected, 0 = not selected
					AccessibleControlEvent ace = new AccessibleControlEvent(this);
					ace.childID = -4;
					for (int i = 0; i < accessibleControlListenersSize(); i++) {
						AccessibleControlListener listener = accessibleControlListeners.get(i);
						listener.getSelection(ace);
					}
					if (ace.childID >= ACC.CHILDID_SELF) {
						if (role == ACC.ROLE_TABITEM) {
							returnValue = NSNumber.numberWithBool(ace.childID == childID);
						} else {
							returnValue = new id(OS.NSAccessibilityUnignoredAncestor(childIDToOs(ace.childID).id));
						}
					} else {
						returnValue = NSNumber.numberWithBool(false);
					}
					break;
				case ACC.ROLE_COMBOBOX: // text of the currently selected item
				case ACC.ROLE_TEXT: // text in the text field
				case ACC.ROLE_PARAGRAPH: // text in the text field
				case ACC.ROLE_HEADING: // text in the text field
					if (value != null) returnValue = NSString.stringWith(value);
					break;
				case ACC.ROLE_TABLECELL: // text in the cell
				case ACC.ROLE_LABEL: // text in the label
					/* On a Mac, the 'value' of a label is the same as the 'name' of the label. */
					AccessibleEvent e = new AccessibleEvent(this);
					e.childID = childID;
					e.result = null;
					for (int i = 0; i < accessibleListenersSize(); i++) {
						AccessibleListener listener = accessibleListeners.get(i);
						listener.getName(e);
					}
					if (e.result != null) {
						returnValue = NSString.stringWith(e.result);
					} else {
						if (value != null) returnValue = NSString.stringWith(value);
					}
					returnValue = returnValue == null ? NSString.string() : returnValue;
					break;
			}
		}
		return returnValue;
	}

	id getEnabledAttribute (int childID) {
		AccessibleControlEvent event = new AccessibleControlEvent(this);
		event.detail = ACC.STATE_NORMAL;
		event.childID = childID;
		for (int i = 0; i < accessibleControlListenersSize(); i++) {
			AccessibleControlListener listener = accessibleControlListeners.get(i);
			listener.getState(event);
		}
		boolean enabled = (event.detail & ACC.STATE_DISABLED) == 0;
		if (!enabled && delegate == null) enabled = control.isEnabled();
		return NSNumber.numberWithBool(enabled);
	}

	id getFocusedAttribute (int childID) {
		AccessibleControlEvent event = new AccessibleControlEvent(this);
		event.childID = ACC.CHILDID_MULTIPLE; // set to invalid value, to test if the application sets it in getFocus()
		for (int i = 0; i < accessibleControlListenersSize(); i++) {
			AccessibleControlListener listener = accessibleControlListeners.get(i);
			listener.getFocus(event);
		}

		/* The application can optionally answer an accessible. */
		if (event.accessible != null) {
			boolean hasFocus = (event.accessible.index == childID) && (event.accessible.control == this.control);
			return NSNumber.numberWithBool(hasFocus);
		}

		/* Or the application can answer a valid child ID, including CHILDID_SELF and CHILDID_NONE. */
		if (event.childID == ACC.CHILDID_SELF) {
			return NSNumber.numberWithBool(true);
		}
		if (event.childID == ACC.CHILDID_NONE) {
			return NSNumber.numberWithBool(false);
		}
		if (event.childID != ACC.CHILDID_MULTIPLE) {
			/* Other valid childID. */
			return NSNumber.numberWithBool(event.childID == childID);
		}

		// Invalid childID at this point means the application did not implement getFocus, so
		// let the default handler return the native focus.
		return null;
	}

	id getParentAttribute (int childID) {
		id returnValue = null;
		if (childID == ACC.CHILDID_SELF) {
			if (parent != null) {
				if (parent.delegate != null) {
					returnValue = parent.delegate;
				} else {
					returnValue = new id(OS.NSAccessibilityUnignoredAncestor(accessibleHandle(parent).id));
				}
			} else {
				// Returning null here means 'let Cocoa figure it out.'
				returnValue = null;
			}
		} else {
			returnValue = new id(OS.NSAccessibilityUnignoredAncestor(accessibleHandle(this).id));
		}
		return returnValue;
	}

	id getChildrenAttribute (int childID, boolean visibleOnly) {
		id returnValue = null;
		if (childID == ACC.CHILDID_SELF) {
			// Test for a table first.
			if (currentRole == ACC.ROLE_TABLE) {
				// If the row count differs from the row elements returned,
				// we need to create our own adapter to map the cells onto
				// rows and columns.  The rows and columns attributes determine that for us.
				getRowsAttribute(childID);
				getColumnsAttribute(childID);
			}

			AccessibleControlEvent event = new AccessibleControlEvent(this);
			event.childID = childID;
			event.detail = -1; // set to impossible value to test if app resets
			for (int i = 0; i < accessibleControlListenersSize(); i++) {
				AccessibleControlListener listener = accessibleControlListeners.get(i);
				listener.getChildCount(event);
			}
			int childCount = event.detail;
			event.detail = (visibleOnly ? ACC.VISIBLE : 0);
			if (childCount >= 0) {
				for (int i = 0; i < accessibleControlListenersSize(); i++) {
					AccessibleControlListener listener = accessibleControlListeners.get(i);
					listener.getChildren(event);
				}
				Object[] children = event.children;
				childCount = children != null ? children.length : 0;
				NSMutableArray childArray = NSMutableArray.arrayWithCapacity(childCount);
				for (int i = 0; i < childCount; i++) {
					Object child = children[i];
					if (child instanceof Accessible accessible) {
						if (accessible.delegate != null) {
							childArray.addObject(accessible.delegate);
						} else {
							childArray.addObject(accessibleHandle(accessible));
						}
					} else {
						if (child instanceof Integer) {
							id accChild = childIDToOs(((Integer)child).intValue());
							childArray.addObject(accChild);
						}
					}
				}
				returnValue = new id(OS.NSAccessibilityUnignoredChildren(childArray.id));
			}
		} else {
			// Lightweight children have no children of their own.
			// Don't return null if there are no children -- always return an empty array.
			returnValue = NSArray.array();
		}

		// Returning null here means we want the control itself to determine its children. If the accessible listener
		// implemented getChildCount/getChildren, references to those objects would have been returned above.
		return returnValue;
	}

	id getTabsAttribute (int childID) {
		id returnValue = null;
		if (childID == ACC.CHILDID_SELF) {
			AccessibleControlEvent event = new AccessibleControlEvent(this);
			event.childID = childID;
			event.detail = -1; // set to impossible value to test if app resets
			for (int i = 0; i < accessibleControlListenersSize(); i++) {
				AccessibleControlListener listener = accessibleControlListeners.get(i);
				listener.getChildCount(event);
			}
			if (event.detail > 0) {
				for (int i = 0; i < accessibleControlListenersSize(); i++) {
					AccessibleControlListener listener = accessibleControlListeners.get(i);
					listener.getChildren(event);
				}
				Object [] appChildren = event.children;
				if (appChildren != null && appChildren.length > 0) {
					/* return an NSArray of NSAccessible objects. */
					NSMutableArray childArray = NSMutableArray.arrayWithCapacity(appChildren.length);

					for (int i = 0; i < appChildren.length; i++) {
						Object child = appChildren[i];
						if (child instanceof Integer) {
							int subChildID = ((Integer)child).intValue();
							event.childID = subChildID;
							event.detail = -1;
							for (int j = 0; j < accessibleControlListenersSize(); j++) {
								AccessibleControlListener listener = accessibleControlListeners.get(j);
								listener.getRole(event);
							}

							if (event.detail == ACC.ROLE_TABITEM) {
								id accChild = childIDToOs(((Integer)child).intValue());
								childArray.addObject(accChild);
							}
						} else {
							childArray.addObject(((Accessible)child).control.view);
						}
					}

					returnValue = new id(OS.NSAccessibilityUnignoredChildren(childArray.id));
				}
			}
		} else {
			// Lightweight children have no children of their own.
			// Don't return null if there are no children -- always return an empty array.
			returnValue = NSArray.array();
		}

		// Returning null here means we want the control itself to determine its children. If the accessible listener
		// implemented getChildCount/getChildren, references to those objects would have been returned above.
		return returnValue;
	}

	id getWindowAttribute (int childID) {
		return control.view.window();
	}

	id getTopLevelUIElementAttribute (int childID) {
		return control.view.window();
	}

	id getPositionAttribute (int childID) {
		id returnValue = null;
		AccessibleControlEvent event = new AccessibleControlEvent(this);
		event.childID = childID;
		event.width = -1;

		for (int i = 0; i < accessibleControlListenersSize(); i++) {
			AccessibleControlListener listener = accessibleControlListeners.get(i);
			listener.getLocation(event);
		}

		Monitor primaryMonitor = Display.getCurrent().getPrimaryMonitor();

		NSPoint osPositionAttribute = new NSPoint ();
		if (event.width != -1) {
			// The point returned is the lower-left coordinate of the widget in lower-left relative screen coordinates.
			osPositionAttribute.x = event.x;
			osPositionAttribute.y = primaryMonitor.getBounds().height - event.y - event.height;
			returnValue = NSValue.valueWithPoint(osPositionAttribute);
		} else {
			if (childID != ACC.CHILDID_SELF) {
				Point pt = null;
				Rectangle location = control.getBounds();

				if (control.getParent() != null)
					pt = control.getParent().toDisplay(location.x, location.y);
				else
					pt = ((Shell)control).toDisplay(location.x, location.y);

				osPositionAttribute.x = pt.x;
				osPositionAttribute.y = pt.y;
				returnValue = NSValue.valueWithPoint(osPositionAttribute);
			}
		}

		return returnValue;
	}

	id getSizeAttribute (int childID) {
		id returnValue = null;
		AccessibleControlEvent event = new AccessibleControlEvent(this);
		event.childID = childID;
		event.width = -1;

		for (int i = 0; i < accessibleControlListenersSize(); i++) {
			AccessibleControlListener listener = accessibleControlListeners.get(i);
			listener.getLocation(event);
		}

		NSSize controlSize = new NSSize ();
		if (event.width != -1) {
			controlSize.width = event.width;
			controlSize.height = event.height;
			returnValue = NSValue.valueWithSize(controlSize);
		} else {
			if (childID != ACC.CHILDID_SELF) {
				controlSize.width = controlSize.height = 0;
				returnValue = NSValue.valueWithSize(controlSize);
			}
		}

		return returnValue;
	}

	id getCellForColumnAndRowParameter(id parameter, int childID) {
		id returnValue = null;
		NSArray parameterObject = new NSArray(parameter.id);
		if (parameterObject.count() == 2) {
			AccessibleTableEvent event = new AccessibleTableEvent(this);
			event.column = new NSNumber(parameterObject.objectAtIndex(0)).intValue();
			event.row = new NSNumber(parameterObject.objectAtIndex(1)).intValue();
			for (int i = 0; i < accessibleTableListenersSize(); i++) {
				AccessibleTableListener listener = accessibleTableListeners.get(i);
				listener.getCell(event);
				returnValue = event.accessible.delegate;
			}
		}
		return returnValue;
	}

	id getDescriptionAttribute (int childID) {
		AccessibleEvent event = new AccessibleEvent(this);
		event.childID = childID;
		event.result = null;
		id returnValue = null;
		for (int i = 0; i < accessibleListenersSize(); i++) {
			AccessibleListener listener = accessibleListeners.get(i);
			listener.getDescription(event);
		}

		returnValue = (event.result != null ? NSString.stringWith(event.result) : null);

		// If no description was provided, and this is a composite or canvas, return a blank string
		// -- otherwise, let the Cocoa control handle it.
		if (returnValue == null) {
			if (control instanceof Composite) returnValue = NSString.string();
		}

		return returnValue;
	}

	id getInsertionPointLineNumberAttribute (int childID) {
		id returnValue = null;
		if (accessibleTextExtendedListenersSize() > 0) {
			AccessibleTextEvent event  = new AccessibleTextEvent(this);
			event.childID = childID;
			for (int i = 0; i < accessibleTextExtendedListenersSize(); i++) {
				AccessibleTextExtendedListener listener = accessibleTextExtendedListeners.get(i);
				listener.getCaretOffset(event);
			}
			int caretOffset = event.offset;
			event.start = caretOffset;
			event.end = caretOffset;
			event.count = Integer.MIN_VALUE;
			event.type = ACC.TEXT_BOUNDARY_LINE;
			for (int i = 0; i < accessibleTextExtendedListenersSize(); i++) {
				AccessibleTextExtendedListener listener = accessibleTextExtendedListeners.get(i);
				listener.getText(event);
			}
			returnValue = NSNumber.numberWithInt(Math.max(0, -event.count));
		} else {
			AccessibleControlEvent controlEvent = new AccessibleControlEvent(this);
			controlEvent.childID = childID;
			controlEvent.result = null;
			for (int i = 0; i < accessibleControlListenersSize(); i++) {
				AccessibleControlListener listener = accessibleControlListeners.get(i);
				listener.getValue(controlEvent);
			}
			AccessibleTextEvent textEvent = new AccessibleTextEvent(this);
			textEvent.childID = childID;
			textEvent.offset = -1;
			for (int i = 0; i < accessibleTextListenersSize(); i++) {
				AccessibleTextListener listener = accessibleTextListeners.get(i);
				listener.getCaretOffset(textEvent);
			}
			if (controlEvent.result != null && textEvent.offset != -1) {
				int lineNumber = lineNumberForOffset (controlEvent.result, textEvent.offset);
				returnValue = NSNumber.numberWithInt(lineNumber);
			}
		}
		return returnValue;
	}

	id getLineForIndexParameterizedAttribute (id parameter, int childID) {
		id returnValue = null;
		NSNumber charNumberObj = new NSNumber(parameter.id);
		int charNumber = charNumberObj.intValue();
		if (accessibleTextExtendedListenersSize() > 0) {
			AccessibleTextEvent event = new AccessibleTextEvent(this);
			event.childID = childID;
			event.start = charNumber;
			event.end = charNumber;
			event.count = Integer.MIN_VALUE;
			event.type = ACC.TEXT_BOUNDARY_LINE;
			for (int i = 0; i < accessibleTextExtendedListenersSize(); i++) {
				AccessibleTextExtendedListener listener = accessibleTextExtendedListeners.get(i);
				listener.getText(event);
			}
			returnValue = NSNumber.numberWithInt(Math.max(0, -event.count));
		} else {
			AccessibleControlEvent controlEvent = new AccessibleControlEvent(this);
			controlEvent.childID = childID;
			controlEvent.result = null;
			for (int i = 0; i < accessibleControlListenersSize(); i++) {
				AccessibleControlListener listener = accessibleControlListeners.get(i);
				listener.getValue(controlEvent);
			}
			String text = controlEvent.result;
			if (text != null) returnValue = NSNumber.numberWithInt(lineNumberForOffset(text, charNumber));
		}
		return returnValue;
	}

	id getMaxValueAttribute(int childID) {
		id returnValue = null;
		if (accessibleValueListenersSize() > 0) {
			AccessibleValueEvent event = new AccessibleValueEvent(this);
			for (int i = 0; i < accessibleValueListenersSize(); i++) {
				AccessibleValueListener listener = accessibleValueListeners.get(i);
				listener.getMaximumValue(event);
			}
			returnValue = NSNumber.numberWithDouble(event.value.doubleValue());
		}
		return returnValue;
	}

	id getMinValueAttribute(int childID) {
		id returnValue = null;
		if (accessibleValueListenersSize() > 0) {
			AccessibleValueEvent event = new AccessibleValueEvent(this);
			for (int i = 0; i < accessibleValueListenersSize(); i++) {
				AccessibleValueListener listener = accessibleValueListeners.get(i);
				listener.getMinimumValue(event);
			}
			returnValue = NSNumber.numberWithDouble(event.value.doubleValue());
		}
		return returnValue;
	}

	id getNumberOfCharactersAttribute (int childID) {
		id returnValue = null;
		AccessibleTextEvent event = new AccessibleTextEvent(this);
		event.count = -1;
		for (int i = 0; i < accessibleTextExtendedListenersSize(); i++) {
			AccessibleTextExtendedListener listener = accessibleTextExtendedListeners.get(i);
			listener.getCharacterCount(event);
		}
		if (event.count != -1) {
			AccessibleControlEvent e = new AccessibleControlEvent(this);
			e.childID = ACC.CHILDID_SELF;
			for (int i = 0; i < accessibleControlListenersSize(); i++) {
				AccessibleControlListener listener = accessibleControlListeners.get(i);
				listener.getRole(e);
				listener.getValue(e);
			}
			// TODO: Consider passing the value through for other roles as well (i.e. combo, etc). Keep in sync with get_text.
			event.count = e.detail == ACC.ROLE_TEXT && e.result != null ? e.result.length() : 0;
			returnValue = NSNumber.numberWithInt(event.count);
		}
		return returnValue;
	}

	id getRangeForLineParameterizedAttribute (id parameter, int childID) {
		id returnValue = null;
		// The parameter is an NSNumber with the line number.
		NSNumber lineNumberObj = new NSNumber(parameter.id);
		int lineNumber = lineNumberObj.intValue();
		if (accessibleTextExtendedListenersSize() > 0) {
			AccessibleTextEvent event = new AccessibleTextEvent(this);
			event.childID = childID;
			event.start = event.end = 0;
			event.count = lineNumber;
			event.type = ACC.TEXT_BOUNDARY_LINE;
			for (int i = 0; i < accessibleTextExtendedListenersSize(); i++) {
				AccessibleTextExtendedListener listener = accessibleTextExtendedListeners.get(i);
				listener.getText(event);
			}
			NSRange range = new NSRange();
			range.location = event.start;
			range.length = event.end - event.start;
			returnValue = NSValue.valueWithRange(range);
		} else if (accessibleControlListenersSize() > 0){
			AccessibleControlEvent event = new AccessibleControlEvent(this);
			event.childID = childID;
			event.result = null;
			for (int i = 0; i < accessibleControlListenersSize(); i++) {
				AccessibleControlListener listener = accessibleControlListeners.get(i);
				listener.getValue(event);
			}
			if (event.result != null) {
				NSRange range = rangeForLineNumber (lineNumber, event.result);
				if (range.location != -1) {
					returnValue = NSValue.valueWithRange(range);
				}
			}
		}
		return returnValue;
	}

	id getRangeForIndexParameterizedAttribute (id parameter, int childID) {
		id returnValue = null;
		// The parameter is an NSNumber with the character number.
		NSNumber charNumberObj = new NSNumber(parameter.id);
		int charNumber = charNumberObj.intValue();
		if (accessibleTextExtendedListenersSize() > 0) {
			AccessibleTextEvent event = new AccessibleTextEvent(this);
			event.childID = childID;
			event.start = event.end = 0;
			event.count = charNumber;
			event.type = ACC.TEXT_BOUNDARY_CHAR;
			for (int i = 0; i < accessibleTextExtendedListenersSize(); i++) {
				AccessibleTextExtendedListener listener = accessibleTextExtendedListeners.get(i);
				listener.getText(event);
			}
			NSRange range = new NSRange();
			range.location = event.start;
			range.length = event.end - event.start;
			returnValue = NSValue.valueWithRange(range);
		} else if (accessibleControlListenersSize() > 0) {
//			AccessibleControlEvent event = new AccessibleControlEvent(this);
//			event.childID = childID;
//			event.result = null;
//			for (int i = 0; i < accessibleControlListenersSize(); i++) {
//				AccessibleControlListener listener = accessibleControlListeners.get(i);
//				listener.getValue(event);
//			}
//			if (event.result != null) {
//				NSRange range = rangeForLineNumber (lineNumber, event.result);
//				if (range.location != -1) {
//					returnValue = NSValue.valueWithRange(range);
//				}
//			}
		}
		return returnValue;
	}

	id getSelectedTextAttribute (int childID) {
		id returnValue = NSString.string();
		if (accessibleTextExtendedListenersSize() > 0) {
			AccessibleTextEvent event = new AccessibleTextEvent(this);
			event.childID = childID;
			event.index = 0;
			for (int i = 0; i < accessibleTextExtendedListenersSize(); i++) {
				AccessibleTextExtendedListener listener = accessibleTextExtendedListeners.get(i);
				listener.getSelection(event);
			}
			int start = event.start;
			int end = event.end;
			if (start != end) {
				event.type = ACC.TEXT_BOUNDARY_ALL;
				for (int i = 0; i < accessibleTextExtendedListenersSize(); i++) {
					AccessibleTextExtendedListener listener = accessibleTextExtendedListeners.get(i);
					listener.getText(event);
				}
			}
			String text = event.result;
			if (text != null) returnValue = NSString.stringWith(text);
		} else if (accessibleTextListenersSize() > 0) {
			AccessibleTextEvent event = new AccessibleTextEvent(this);
			event.childID = childID;
			event.offset = -1;
			event.length = -1;
			for (int i = 0; i < accessibleTextListenersSize(); i++) {
				AccessibleTextListener listener = accessibleTextListeners.get(i);
				listener.getSelectionRange(event);
			}
			int offset = event.offset;
			int length = event.length;
			if (offset != -1 && length != -1 && length != 0) {  // TODO: do we need the && length != 0 ?
				AccessibleControlEvent event2 = new AccessibleControlEvent(this);
				event2.childID = event.childID;
				event2.result = null;
				for (int i = 0; i < accessibleControlListenersSize(); i++) {
					AccessibleControlListener listener = accessibleControlListeners.get(i);
					listener.getValue(event2);
				}
				String appValue = event2.result;
				if (appValue != null) {
					returnValue = NSString.stringWith(appValue.substring(offset, offset + length));
				}
			}
		}
		return returnValue;
	}

	id getSelectedTextRangeAttribute (int childID) {
		id returnValue = null;
		if (accessibleTextExtendedListenersSize() > 0) {
			AccessibleTextEvent event = new AccessibleTextEvent(this);
			event.childID = childID;
			event.index = 0;
			for (int i = 0; i < accessibleTextExtendedListenersSize(); i++) {
				AccessibleTextExtendedListener listener = accessibleTextExtendedListeners.get(i);
				listener.getSelection(event);
			}
			NSRange range = new NSRange();
			range.location = event.start;
			range.length = event.end - event.start;
			returnValue = NSValue.valueWithRange(range);
		} else if (accessibleTextExtendedListenersSize() > 0) {
			AccessibleTextEvent event = new AccessibleTextEvent(this);
			event.childID = childID;
			event.offset = -1;
			event.length = 0;
			for (int i = 0; i < accessibleTextListenersSize(); i++) {
				AccessibleTextListener listener = accessibleTextListeners.get(i);
				listener.getSelectionRange(event);
			}
			if (event.offset != -1) {
				NSRange range = new NSRange();
				range.location = event.offset;
				range.length = event.length;
				returnValue = NSValue.valueWithRange(range);
			}
		}
		return returnValue;
	}

	id getServesAsTitleForUIElementsAttribute(int childID) {
		id returnValue = null;
		Relation relation = relations[ACC.RELATION_LABEL_FOR];
		if (relation != null) returnValue = relation.getServesAsTitleForUIElements();
		return returnValue;
	}

	id getStringForRangeParameterizedAttribute (id parameter, int childID) {
		id returnValue = null;

		// Parameter is an NSRange wrapped in an NSValue.
		NSValue parameterObject = new NSValue(parameter.id);
		NSRange range = parameterObject.rangeValue();
		if (accessibleTextExtendedListenersSize() > 0) {
			AccessibleTextEvent event = new AccessibleTextEvent(this);
			event.childID = childID;
			event.start = (int) range.location;
			event.end = (int) (range.location + range.length);
			event.type = ACC.TEXT_BOUNDARY_ALL;
			for (int i = 0; i < accessibleTextExtendedListenersSize(); i++) {
				AccessibleTextExtendedListener listener = accessibleTextExtendedListeners.get(i);
				listener.getText(event);
			}
			if (event.result != null) returnValue = NSString.stringWith(event.result);
		} else if (accessibleControlListenersSize() > 0) {
			AccessibleControlEvent event = new AccessibleControlEvent(this);
			event.childID = childID;
			event.result = null;
			for (int i = 0; i < accessibleControlListenersSize(); i++) {
				AccessibleControlListener listener = accessibleControlListeners.get(i);
				listener.getValue(event);
			}
			String appValue = event.result;

			if (appValue != null) {
				returnValue = NSString.stringWith(appValue.substring((int)range.location, (int)(range.location + range.length)));
			}
		}
		return returnValue;
	}

	id getSelectedTextRangesAttribute (int childID) {
		NSMutableArray returnValue = NSMutableArray.arrayWithCapacity(3);
		if (accessibleTextExtendedListenersSize() > 0) {
			AccessibleTextEvent event = new AccessibleTextEvent(this);
			event.childID = childID;
			for (int i = 0; i < accessibleTextExtendedListenersSize(); i++) {
				AccessibleTextExtendedListener listener = accessibleTextExtendedListeners.get(i);
				listener.getSelectionCount(event);
			}
			if (event.count > 0) {
				for (int i = 0; i < event.count; i++) {
					event.index = i;
					for (int j = 0; j < accessibleTextExtendedListenersSize(); j++) {
						AccessibleTextExtendedListener listener = accessibleTextExtendedListeners.get(j);
						listener.getSelection(event);
					}
					NSRange range = new NSRange();
					range.location = event.start;
					range.length = event.end - event.start;
					returnValue.addObject(NSValue.valueWithRange(range));
				}
			}
		} else if (accessibleTextListenersSize() > 0) {
			AccessibleTextEvent event = new AccessibleTextEvent(this);
			event.childID = childID;
			event.offset = -1;
			event.length = 0;

			for (int i = 0; i < accessibleTextListenersSize(); i++) {
				AccessibleTextListener listener = accessibleTextListeners.get(i);
				listener.getSelectionRange(event);
			}

			if (event.offset != -1) {
				NSRange range = new NSRange();
				range.location = event.offset;
				range.length = event.length;
				returnValue.addObject(NSValue.valueWithRange(range));
			}
		}
		if (returnValue.count() == 0) {
			returnValue.addObject(NSValue.valueWithRange(new NSRange()));
		}
		return returnValue;
	}

	id getStyleRangeForIndexAttribute (id parameter, int childID) {
		if (accessibleAttributeListenersSize() == 0) return null;

		// Parameter is an NSRange wrapped in an NSValue.
		NSNumber parameterObject = new NSNumber(parameter.id);
		int index = parameterObject.intValue();

		AccessibleTextAttributeEvent event = new AccessibleTextAttributeEvent(this);
		event.offset = (int) index;

		// Marker values -- if -1 after calling getTextAttributes, no one implemented it.
		event.start = event.end = -1;

		for (int i = 0; i < accessibleAttributeListenersSize(); i++) {
			AccessibleAttributeListener listener = accessibleAttributeListeners.get(i);
			listener.getTextAttributes(event);
		}

		NSRange range = new NSRange();
		if (event.start == -1 && event.end == -1) {
			range.location = index;
			range.length = 0;
		} else {
			range.location = event.start;
			range.length = event.end - event.start;
		}

		return NSValue.valueWithRange(range);
	}

	id getVisibleCharacterRangeAttribute (int childID) {
		NSRange range = null;
		if (accessibleTextExtendedListenersSize() > 0) {
			AccessibleTextEvent event = new AccessibleTextEvent(this);
			event.childID = childID;
			for (int i=0; i<accessibleTextExtendedListenersSize(); i++) {
				AccessibleTextExtendedListener listener = accessibleTextExtendedListeners.get(i);
				listener.getVisibleRanges(event);
			}
			range = new NSRange();
			range.location = event.start;
			range.length = event.end - event.start;
		} else if (accessibleControlListenersSize() > 0) {
			AccessibleControlEvent event = new AccessibleControlEvent(this);
			event.childID = childID;
			event.result = null;
			for (int i = 0; i < accessibleControlListenersSize(); i++) {
				AccessibleControlListener listener = accessibleControlListeners.get(i);
				listener.getValue(event);
			}
			if (event.result != null) {
				range = new NSRange();
				range.location = 0;
				range.length = event.result.length();
			}
		}

		return (range != null) ? NSValue.valueWithRange(range) : null;
	}

	int lineNumberForOffset (String text, int offset) {
		int lineNumber = 1;
		int length = text.length();
		for (int i = 0; i < offset; i++) {
			switch (text.charAt (i)) {
				case '\r':
					if (i + 1 < length) {
						if (text.charAt (i + 1) == '\n') ++i;
					}
					// FALL THROUGH
				case '\n':
					lineNumber++;
			}
		}
		return lineNumber;
	}

	NSRange rangeForLineNumber (int lineNumber, String text) {
		NSRange range = new NSRange();
		range.location = -1;
		int line = 1;
		int count = 0;
		int length = text.length ();
		for (int i = 0; i < length; i++) {
			if (line == lineNumber) {
				if (count == 0) {
					range.location = i;
				}
				count++;
			}
			if (line > lineNumber) break;
			switch (text.charAt (i)) {
				case '\r':
					if (i + 1 < length && text.charAt (i + 1) == '\n') i++;
					// FALL THROUGH
				case '\n':
					line++;
			}
		}
		range.length = count;
		return range;
	}

	/**
	 * Removes the listener from the collection of listeners who will
	 * be notified when an accessible client asks for certain strings,
	 * such as name, description, help, or keyboard shortcut.
	 *
	 * @param listener the listener that should no longer be notified when the receiver
	 * is asked for a name, description, help, or keyboard shortcut string
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleListener
	 * @see #addAccessibleListener
	 */
	public void removeAccessibleListener(AccessibleListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleListeners != null) {
			accessibleListeners.remove(listener);
			if (accessibleListeners.isEmpty()) accessibleListeners = null;
		}
	}

	/**
	 * Removes the listener from the collection of listeners who will
	 * be notified when an accessible client asks for custom control
	 * specific information.
	 *
	 * @param listener the listener that should no longer be notified when the receiver
	 * is asked for custom control specific information
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleControlListener
	 * @see #addAccessibleControlListener
	 */
	public void removeAccessibleControlListener(AccessibleControlListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleControlListeners != null) {
			accessibleControlListeners.remove(listener);
			if (accessibleControlListeners.isEmpty()) accessibleControlListeners = null;
		}
	}

	/**
	 * Removes the listener from the collection of listeners who will
	 * be notified when an accessible client asks for custom text control
	 * specific information.
	 *
	 * @param listener the listener that should no longer be notified when the receiver
	 * is asked for custom text control specific information
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleTextListener
	 * @see AccessibleTextExtendedListener
	 * @see #addAccessibleTextListener
	 *
	 * @since 3.0
	 */
	public void removeAccessibleTextListener (AccessibleTextListener listener) {
		checkWidget ();
		if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
		if (listener instanceof AccessibleTextExtendedListener) {
			if (accessibleTextExtendedListeners != null) {
				accessibleTextExtendedListeners.remove(listener);
				if (accessibleTextExtendedListeners.isEmpty()) accessibleTextExtendedListeners = null;
			}
		} else {
			if (accessibleTextListeners != null) {
				accessibleTextListeners.remove(listener);
				if (accessibleTextListeners.isEmpty()) accessibleTextListeners = null;
			}
		}
	}

	/**
	 * Removes the listener from the collection of listeners that will be
	 * notified when an accessible client asks for any of the properties
	 * defined in the <code>AccessibleActionListener</code> interface.
	 *
	 * @param listener the listener that should no longer be notified when the receiver
	 * is asked for <code>AccessibleActionListener</code> interface properties
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleActionListener
	 * @see #addAccessibleActionListener
	 *
	 * @since 3.6
	 */
	public void removeAccessibleActionListener(AccessibleActionListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleActionListeners != null) {
			accessibleActionListeners.remove(listener);
			if (accessibleActionListeners.isEmpty()) accessibleActionListeners = null;
		}
	}

	/**
	 * Removes the listener from the collection of listeners that will be
	 * notified when an accessible client asks for any of the properties
	 * defined in the <code>AccessibleEditableTextListener</code> interface.
	 *
	 * @param listener the listener that should no longer be notified when the receiver
	 * is asked for <code>AccessibleEditableTextListener</code> interface properties
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleEditableTextListener
	 * @see #addAccessibleEditableTextListener
	 *
	 * @since 3.7
	 */
	public void removeAccessibleEditableTextListener(AccessibleEditableTextListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleEditableTextListeners != null) {
			accessibleEditableTextListeners.remove(listener);
			if (accessibleEditableTextListeners.isEmpty()) accessibleEditableTextListeners = null;
		}
	}

	/**
	 * Removes the listener from the collection of listeners that will be
	 * notified when an accessible client asks for any of the properties
	 * defined in the <code>AccessibleHyperlinkListener</code> interface.
	 *
	 * @param listener the listener that should no longer be notified when the receiver
	 * is asked for <code>AccessibleHyperlinkListener</code> interface properties
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleHyperlinkListener
	 * @see #addAccessibleHyperlinkListener
	 *
	 * @since 3.6
	 */
	public void removeAccessibleHyperlinkListener(AccessibleHyperlinkListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleHyperlinkListeners != null) {
			accessibleHyperlinkListeners.remove(listener);
			if (accessibleHyperlinkListeners.isEmpty()) accessibleHyperlinkListeners = null;
		}
	}

	/**
	 * Removes the listener from the collection of listeners that will be
	 * notified when an accessible client asks for any of the properties
	 * defined in the <code>AccessibleTableListener</code> interface.
	 *
	 * @param listener the listener that should no longer be notified when the receiver
	 * is asked for <code>AccessibleTableListener</code> interface properties
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleTableListener
	 * @see #addAccessibleTableListener
	 *
	 * @since 3.6
	 */
	public void removeAccessibleTableListener(AccessibleTableListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleTableListeners != null) {
			accessibleTableListeners.remove(listener);
			if (accessibleTableListeners.isEmpty()) accessibleTableListeners = null;
		}
	}

	/**
	 * Removes the listener from the collection of listeners that will be
	 * notified when an accessible client asks for any of the properties
	 * defined in the <code>AccessibleTableCellListener</code> interface.
	 *
	 * @param listener the listener that should no longer be notified when the receiver
	 * is asked for <code>AccessibleTableCellListener</code> interface properties
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleTableCellListener
	 * @see #addAccessibleTableCellListener
	 *
	 * @since 3.6
	 */
	public void removeAccessibleTableCellListener(AccessibleTableCellListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleTableCellListeners != null) {
			accessibleTableCellListeners.remove(listener);
			if (accessibleTableCellListeners.isEmpty()) accessibleTableCellListeners = null;
		}
	}

	/**
	 * Removes the listener from the collection of listeners that will be
	 * notified when an accessible client asks for any of the properties
	 * defined in the <code>AccessibleValueListener</code> interface.
	 *
	 * @param listener the listener that should no longer be notified when the receiver
	 * is asked for <code>AccessibleValueListener</code> interface properties
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleValueListener
	 * @see #addAccessibleValueListener
	 *
	 * @since 3.6
	 */
	public void removeAccessibleValueListener(AccessibleValueListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleValueListeners != null) {
			accessibleValueListeners.remove(listener);
			if (accessibleValueListeners.isEmpty()) accessibleValueListeners = null;
		}
	}

	/**
	 * Removes the listener from the collection of listeners that will be
	 * notified when an accessible client asks for any of the properties
	 * defined in the <code>AccessibleAttributeListener</code> interface.
	 *
	 * @param listener the listener that should no longer be notified when the receiver
	 * is asked for <code>AccessibleAttributeListener</code> interface properties
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see AccessibleAttributeListener
	 * @see #addAccessibleAttributeListener
	 *
	 * @since 3.6
	 */
	public void removeAccessibleAttributeListener(AccessibleAttributeListener listener) {
		checkWidget();
		if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		if (accessibleAttributeListeners != null) {
			accessibleAttributeListeners.remove(listener);
			if (accessibleAttributeListeners.isEmpty()) accessibleAttributeListeners = null;
		}
	}

	/**
	 * Removes the relation with the specified type and target
	 * from the receiver's set of relations.
	 *
	 * @param type an <code>ACC</code> constant beginning with RELATION_* indicating the type of relation
	 * @param target the accessible that is the target for this relation
	 * @exception IllegalArgumentException ERROR_NULL_ARGUMENT - if the Accessible target is null
	 * @since 3.6
	 */
	public void removeRelation(int type, Accessible target) {
		checkWidget();
		if (target == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
		if (relations[type] != null) {
			relations[type].removeTarget(target);
		}
	}

	void release(boolean destroy) {
		if (actionNames != null) actionNames.release();
		actionNames = null;
		if (attributeNames != null) attributeNames.release();
		attributeNames = null;
		if (parameterizedAttributeNames != null) parameterizedAttributeNames.release();
		parameterizedAttributeNames = null;
		if (delegate != null) {
			delegate.internal_dispose_SWTAccessibleDelegate();
			delegate.release();
		}
		delegate = null;
		relations = null;

		if (childToIdMap != null) {
			for (SWTAccessibleDelegate childDelegate : childToIdMap.values()) {
				childDelegate.internal_dispose_SWTAccessibleDelegate();
				childDelegate.release();
			}
			childToIdMap.clear();
			childToIdMap = null;
		}

		if (tableDelegate != null) tableDelegate.release();
	}

	static NSArray retainedAutoreleased(NSArray inObject) {
		id temp = inObject.retain();
		id temp2 = new NSObject(temp.id).autorelease();
		return new NSArray(temp2.id);
	}

	/**
	 * Sends a message with event-specific data to accessible clients
	 * indicating that something has changed within a custom control.
	 *
	 * @param event an <code>ACC</code> constant beginning with EVENT_* indicating the message to send
	 * @param eventData an object containing event-specific data, or null if there is no event-specific data
	 * (eventData is specified in the documentation for individual ACC.EVENT_* constants)
	 *
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see ACC#EVENT_ACTION_CHANGED
	 * @see ACC#EVENT_ATTRIBUTE_CHANGED
	 * @see ACC#EVENT_DESCRIPTION_CHANGED
	 * @see ACC#EVENT_DOCUMENT_LOAD_COMPLETE
	 * @see ACC#EVENT_DOCUMENT_LOAD_STOPPED
	 * @see ACC#EVENT_DOCUMENT_RELOAD
	 * @see ACC#EVENT_HYPERLINK_ACTIVATED
	 * @see ACC#EVENT_HYPERLINK_ANCHOR_COUNT_CHANGED
	 * @see ACC#EVENT_HYPERLINK_END_INDEX_CHANGED
	 * @see ACC#EVENT_HYPERLINK_SELECTED_LINK_CHANGED
	 * @see ACC#EVENT_HYPERLINK_START_INDEX_CHANGED
	 * @see ACC#EVENT_HYPERTEXT_LINK_COUNT_CHANGED
	 * @see ACC#EVENT_HYPERTEXT_LINK_SELECTED
	 * @see ACC#EVENT_LOCATION_CHANGED
	 * @see ACC#EVENT_NAME_CHANGED
	 * @see ACC#EVENT_PAGE_CHANGED
	 * @see ACC#EVENT_SECTION_CHANGED
	 * @see ACC#EVENT_SELECTION_CHANGED
	 * @see ACC#EVENT_STATE_CHANGED
	 * @see ACC#EVENT_TABLE_CAPTION_CHANGED
	 * @see ACC#EVENT_TABLE_CHANGED
	 * @see ACC#EVENT_TABLE_COLUMN_DESCRIPTION_CHANGED
	 * @see ACC#EVENT_TABLE_COLUMN_HEADER_CHANGED
	 * @see ACC#EVENT_TABLE_ROW_DESCRIPTION_CHANGED
	 * @see ACC#EVENT_TABLE_ROW_HEADER_CHANGED
	 * @see ACC#EVENT_TABLE_SUMMARY_CHANGED
	 * @see ACC#EVENT_TEXT_ATTRIBUTE_CHANGED
	 * @see ACC#EVENT_TEXT_CARET_MOVED
	 * @see ACC#EVENT_TEXT_CHANGED
	 * @see ACC#EVENT_TEXT_COLUMN_CHANGED
	 * @see ACC#EVENT_TEXT_SELECTION_CHANGED
	 * @see ACC#EVENT_VALUE_CHANGED
	 *
	 * @since 3.6
	 */
	public void sendEvent(int event, Object eventData) {
		checkWidget();

		id eventSource = accessibleHandle(this);
		if (DEBUG) System.out.println("sendEvent: 0x" + Integer.toHexString(event) + ", data = " + eventData + ", source = " + eventSource);

		switch (event) {
		case ACC.EVENT_TEXT_CHANGED:
		case ACC.EVENT_VALUE_CHANGED:
		case ACC.EVENT_STATE_CHANGED:
		case ACC.EVENT_PAGE_CHANGED:
		case ACC.EVENT_SECTION_CHANGED:
		case ACC.EVENT_ACTION_CHANGED:
		case ACC.EVENT_HYPERLINK_START_INDEX_CHANGED:
		case ACC.EVENT_HYPERLINK_END_INDEX_CHANGED:
		case ACC.EVENT_HYPERLINK_ANCHOR_COUNT_CHANGED:
		case ACC.EVENT_HYPERLINK_SELECTED_LINK_CHANGED:
		case ACC.EVENT_HYPERLINK_ACTIVATED:
		case ACC.EVENT_HYPERTEXT_LINK_COUNT_CHANGED:
		case ACC.EVENT_ATTRIBUTE_CHANGED:
		case ACC.EVENT_TABLE_CAPTION_CHANGED:
		case ACC.EVENT_TABLE_COLUMN_DESCRIPTION_CHANGED:
		case ACC.EVENT_TABLE_COLUMN_HEADER_CHANGED:
		case ACC.EVENT_TABLE_ROW_DESCRIPTION_CHANGED:
		case ACC.EVENT_TABLE_ROW_HEADER_CHANGED:
		case ACC.EVENT_TABLE_SUMMARY_CHANGED:
		case ACC.EVENT_TEXT_ATTRIBUTE_CHANGED:
		case ACC.EVENT_TEXT_COLUMN_CHANGED:
			OS.NSAccessibilityPostNotification(eventSource.id, OS.NSAccessibilityValueChangedNotification.id); break;

		case ACC.EVENT_SELECTION_CHANGED:
			OS.NSAccessibilityPostNotification(eventSource.id, OS.NSAccessibilitySelectedChildrenChangedNotification.id); break;
		case ACC.EVENT_TEXT_SELECTION_CHANGED:
			OS.NSAccessibilityPostNotification(eventSource.id, OS.NSAccessibilitySelectedTextChangedNotification.id); break;
		case ACC.EVENT_LOCATION_CHANGED:
			OS.NSAccessibilityPostNotification(eventSource.id, OS.NSAccessibilityMovedNotification.id); break;
		case ACC.EVENT_NAME_CHANGED:
		case ACC.EVENT_DESCRIPTION_CHANGED:
			OS.NSAccessibilityPostNotification(eventSource.id, OS.NSAccessibilityTitleChangedNotification.id); break;
		case ACC.EVENT_TEXT_CARET_MOVED:
			OS.NSAccessibilityPostNotification(eventSource.id, OS.NSAccessibilitySelectedTextChangedNotification.id); break;
		case ACC.EVENT_TABLE_CHANGED:
			if (tableDelegate != null) {
				tableDelegate.reset();
				getRowsAttribute(ACC.CHILDID_SELF);
				getColumnsAttribute(ACC.CHILDID_SELF);
			}
			if (eventData != null) {
				int[] eventParams = (int[])eventData;
				// Slot 2 of the array is the number of rows that were either added or deleted. If non-zero, fire a notification.
				// Cocoa doesn't have a notification for a change in the number of columns.
				if (eventParams[2] != 0) OS.NSAccessibilityPostNotification(eventSource.id, OS.NSAccessibilityRowCountChangedNotification.id);
			}
			break;

		// None of these correspond to anything in Cocoa.
		case ACC.EVENT_HYPERTEXT_LINK_SELECTED:
		case ACC.EVENT_DOCUMENT_LOAD_COMPLETE:
		case ACC.EVENT_DOCUMENT_LOAD_STOPPED:
		case ACC.EVENT_DOCUMENT_RELOAD:
			break;

		}
	}

	/**
	 * Sends a message with event-specific data and a childID
	 * to accessible clients, indicating that something has changed
	 * within a custom control.
	 *
	 * NOTE: This API is intended for applications that are still using childIDs.
	 * Moving forward, applications should use accessible objects instead of childIDs.
	 *
	 * @param event an <code>ACC</code> constant beginning with EVENT_* indicating the message to send
	 * @param eventData an object containing event-specific data, or null if there is no event-specific data
	 * (eventData is specified in the documentation for individual ACC.EVENT_* constants)
	 * @param childID an identifier specifying a child of the control
	 *
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see ACC#EVENT_DESCRIPTION_CHANGED
	 * @see ACC#EVENT_LOCATION_CHANGED
	 * @see ACC#EVENT_NAME_CHANGED
	 * @see ACC#EVENT_SELECTION_CHANGED
	 * @see ACC#EVENT_STATE_CHANGED
	 * @see ACC#EVENT_TEXT_SELECTION_CHANGED
	 * @see ACC#EVENT_VALUE_CHANGED
	 *
	 * @since 3.8
	 */
	public void sendEvent(int event, Object eventData, int childID) {
		checkWidget();

		id eventSource = childIDToOs(childID);
		if (DEBUG) System.out.println("sendEvent: 0x" + Integer.toHexString(event) + ", data = " + eventData + ", source = " + eventSource);

		switch (event) {
			case ACC.EVENT_VALUE_CHANGED:
			case ACC.EVENT_STATE_CHANGED:
			case ACC.EVENT_SELECTION_CHANGED:
				OS.NSAccessibilityPostNotification(eventSource.id, OS.NSAccessibilitySelectedChildrenChangedNotification.id); break;
			case ACC.EVENT_TEXT_SELECTION_CHANGED:
				OS.NSAccessibilityPostNotification(eventSource.id, OS.NSAccessibilitySelectedTextChangedNotification.id); break;
			case ACC.EVENT_LOCATION_CHANGED:
				OS.NSAccessibilityPostNotification(eventSource.id, OS.NSAccessibilityMovedNotification.id); break;
			case ACC.EVENT_NAME_CHANGED:
			case ACC.EVENT_DESCRIPTION_CHANGED:
				OS.NSAccessibilityPostNotification(eventSource.id, OS.NSAccessibilityTitleChangedNotification.id); break;
		}
	}

	/**
	 * Sends a message to accessible clients that the child selection
	 * within a custom container control has changed.
	 *
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @since 3.0
	 */
	public void selectionChanged () {
		checkWidget();
		id eventSource = accessibleHandle(this);
		if (DEBUG) System.out.println("selectionChanged on " + eventSource);
		if (currentRole == ACC.ROLE_TABLE) {
			OS.NSAccessibilityPostNotification(eventSource.id, OS.NSAccessibilitySelectedRowsChangedNotification.id);
		} else {
			OS.NSAccessibilityPostNotification(eventSource.id, OS.NSAccessibilitySelectedChildrenChangedNotification.id);
		}
	}

	/**
	 * Sends a message to accessible clients indicating that the focus
	 * has changed within a custom control.
	 *
	 * @param childID an identifier specifying a child of the control
	 *
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 */
	public void setFocus(int childID) {
		checkWidget();
		id accessible = childIDToOs(childID);
		if (DEBUG) System.out.println("setFocus on " + accessible);
		OS.NSAccessibilityPostNotification(accessible.id, OS.NSAccessibilityFocusedUIElementChangedNotification.id);
	}

	void setSelectedTextAttribute(id value, int childId) {
		NSString newText = new NSString(value.id);
		int rangeStart = 0;
		id charsValue = getNumberOfCharactersAttribute(childId);
		int rangeEnd = new NSNumber(charsValue).intValue();
		id rangeObj = getSelectedTextRangeAttribute(childId);

		if (rangeObj != null) {
			NSRange range = new NSValue(rangeObj).rangeValue();
			rangeStart = (int)range.location;
			rangeEnd = (int)(range.location + range.length);
		}

		if (accessibleEditableTextListenersSize() > 0) {
			AccessibleEditableTextEvent event = new AccessibleEditableTextEvent(this);
			event.start = rangeStart;
			event.end = rangeEnd;
			event.string = newText.getString();

			for (int i = 0; i < accessibleEditableTextListenersSize(); i++) {
				AccessibleEditableTextListener listener = (AccessibleEditableTextListener) accessibleEditableTextListeners.get(i);
				listener.replaceText(event);
			}
		}
	}

	void setSelectedTextRangeAttribute(id value, int childId) {
		NSRange newRange = new NSValue(value.id).rangeValue();
		if (accessibleTextExtendedListenersSize() > 0) {
			AccessibleTextEvent event = new AccessibleTextEvent(this);
			event.index = 0;
			event.start = (int)newRange.location;
			event.end = (int)(newRange.location + newRange.length);

			for (int i = 0; i < accessibleTextExtendedListenersSize(); i++) {
				AccessibleTextExtendedListener listener = accessibleTextExtendedListeners.get(i);
				listener.removeSelection(event);
				listener.addSelection(event);
			}
		}
	}

	void setVisibleCharacterRangeAttribute(id value, int childId) {
		NSRange newRange = new NSValue(value.id).rangeValue();
		if (accessibleTextExtendedListenersSize() > 0) {
			AccessibleTextEvent event = new AccessibleTextEvent(this);
			event.type = ACC.SCROLL_TYPE_TOP_LEFT;
			event.start = (int)newRange.location;
			event.end = (int)(newRange.location + newRange.length);

			for (int i = 0; i < accessibleTextExtendedListenersSize(); i++) {
				AccessibleTextExtendedListener listener = accessibleTextExtendedListeners.get(i);
				listener.scrollText(event);
			}
		}
	}

	/**
	 * Sends a message to accessible clients that the text
	 * caret has moved within a custom control.
	 *
	 * @param index the new caret index within the control
	 *
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @since 3.0
	 */
	public void textCaretMoved (int index) {
		checkWidget();
		OS.NSAccessibilityPostNotification(control.view.id, OS.NSAccessibilitySelectedTextChangedNotification.id);
	}

	/**
	 * Sends a message to accessible clients that the text
	 * within a custom control has changed.
	 *
	 * @param type the type of change, one of <code>ACC.TEXT_INSERT</code>
	 * or <code>ACC.TEXT_DELETE</code>
	 * @param startIndex the text index within the control where the insertion or deletion begins
	 * @param length the non-negative length in characters of the insertion or deletion
	 *
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @see ACC#TEXT_INSERT
	 * @see ACC#TEXT_DELETE
	 *
	 * @since 3.0
	 */
	public void textChanged (int type, int startIndex, int length) {
		checkWidget();
		OS.NSAccessibilityPostNotification(control.view.id, OS.NSAccessibilityValueChangedNotification.id);
	}

	/**
	 * Sends a message to accessible clients that the text
	 * selection has changed within a custom control.
	 *
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver's control has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver's control</li>
	 * </ul>
	 *
	 * @since 3.0
	 */
	public void textSelectionChanged () {
		checkWidget();
		OS.NSAccessibilityPostNotification(control.view.id, OS.NSAccessibilitySelectedTextChangedNotification.id);
	}

	id childIDToOs(int childID) {
		if (childID == ACC.CHILDID_SELF) {
			return control.view;
		}

		/* Check cache for childID, if found, return corresponding osChildID. */
		SWTAccessibleDelegate childRef = (SWTAccessibleDelegate) childToIdMap.get(Integer.valueOf(childID));

		if (childRef == null) {
			childRef = new SWTAccessibleDelegate(this, childID);
			childToIdMap.put(Integer.valueOf(childID), childRef);
		}

		return childRef;
	}

	NSString concatStringsAsRole(NSString str1, NSString str2) {
		NSString returnValue = str1;
		returnValue = returnValue.stringByAppendingString(NSString.stringWith(":"));
		returnValue = returnValue.stringByAppendingString(str2);
		return returnValue;
	}

	String roleToOs(int role) {
		NSString nsReturnValue = null; //OS.NSAccessibilityUnknownRole;

		switch (role) {
			case ACC.ROLE_CLIENT_AREA: nsReturnValue = OS.NSAccessibilityGroupRole; break;
			case ACC.ROLE_WINDOW: nsReturnValue = OS.NSAccessibilityWindowRole; break;
			case ACC.ROLE_MENUBAR: nsReturnValue = OS.NSAccessibilityMenuBarRole; break;
			case ACC.ROLE_MENU: nsReturnValue = OS.NSAccessibilityMenuRole; break;
			case ACC.ROLE_MENUITEM: nsReturnValue = OS.NSAccessibilityMenuItemRole; break;
			case ACC.ROLE_SEPARATOR: nsReturnValue = OS.NSAccessibilitySplitterRole; break;
			case ACC.ROLE_TOOLTIP: nsReturnValue = OS.NSAccessibilityHelpTagRole; break;
			case ACC.ROLE_SCROLLBAR: nsReturnValue = OS.NSAccessibilityScrollBarRole; break;
			case ACC.ROLE_DIALOG: nsReturnValue = concatStringsAsRole(OS.NSAccessibilityWindowRole, OS.NSAccessibilityDialogSubrole); break;
			case ACC.ROLE_LABEL: nsReturnValue = OS.NSAccessibilityStaticTextRole; break;
			case ACC.ROLE_PUSHBUTTON: nsReturnValue = OS.NSAccessibilityButtonRole; break;
			case ACC.ROLE_CHECKBUTTON: nsReturnValue = OS.NSAccessibilityCheckBoxRole; break;
			case ACC.ROLE_RADIOBUTTON: nsReturnValue = OS.NSAccessibilityRadioButtonRole; break;
			case ACC.ROLE_SPLITBUTTON: nsReturnValue = OS.NSAccessibilityMenuButtonRole; break;
			case ACC.ROLE_COMBOBOX: nsReturnValue = OS.NSAccessibilityComboBoxRole; break;
			case ACC.ROLE_TEXT: {
				int style = control.getStyle();

				if ((style & SWT.MULTI) != 0) {
					nsReturnValue = OS.NSAccessibilityTextAreaRole;
				} else {
					nsReturnValue = OS.NSAccessibilityTextFieldRole;
				}

				break;
			}
			case ACC.ROLE_TOOLBAR: nsReturnValue = OS.NSAccessibilityToolbarRole; break;
			case ACC.ROLE_LIST: nsReturnValue = OS.NSAccessibilityOutlineRole; break;
			case ACC.ROLE_LISTITEM: nsReturnValue = OS.NSAccessibilityStaticTextRole; break;
			case ACC.ROLE_COLUMN: nsReturnValue =  OS.NSAccessibilityColumnRole; break;
			case ACC.ROLE_ROW: nsReturnValue =  concatStringsAsRole(OS.NSAccessibilityRowRole, OS.NSAccessibilityTableRowSubrole); break;
			case ACC.ROLE_TABLE: nsReturnValue = OS.NSAccessibilityTableRole; break;
			case ACC.ROLE_TABLECELL: nsReturnValue = OS.NSAccessibilityStaticTextRole; break;
			case ACC.ROLE_TABLECOLUMNHEADER: nsReturnValue = OS.NSAccessibilityGroupRole; break;
			case ACC.ROLE_TABLEROWHEADER: nsReturnValue = OS.NSAccessibilityGroupRole; break;
			case ACC.ROLE_TREE: nsReturnValue = OS.NSAccessibilityOutlineRole; break;
			case ACC.ROLE_TREEITEM: nsReturnValue = concatStringsAsRole(OS.NSAccessibilityOutlineRole, OS.NSAccessibilityOutlineRowSubrole); break;
			case ACC.ROLE_TABFOLDER: nsReturnValue = OS.NSAccessibilityTabGroupRole; break;
			case ACC.ROLE_TABITEM: nsReturnValue = OS.NSAccessibilityRadioButtonRole; break;
			case ACC.ROLE_PROGRESSBAR: nsReturnValue = OS.NSAccessibilityProgressIndicatorRole; break;
			case ACC.ROLE_SLIDER: nsReturnValue = OS.NSAccessibilitySliderRole; break;
			case ACC.ROLE_LINK: nsReturnValue = OS.NSAccessibilityLinkRole; break;

			//10.6 only -> case ACC.ROLE_CANVAS: nsReturnValue = OS.NSAccessibilityLayoutAreaRole; break;
			case ACC.ROLE_CANVAS: nsReturnValue = OS.NSAccessibilityGroupRole; break;
			case ACC.ROLE_GRAPHIC: nsReturnValue = OS.NSAccessibilityImageRole; break;

			//CLIENT_AREA uses NSAccessibilityGroupRole already
			case ACC.ROLE_GROUP: nsReturnValue = OS.NSAccessibilityGroupRole; break;
			//SPLIT_BUTTON uses NSAccessibilityMenuButtonRole already
			case ACC.ROLE_CHECKMENUITEM: nsReturnValue = OS.NSAccessibilityMenuButtonRole; break;
			case ACC.ROLE_RADIOMENUITEM: nsReturnValue = OS.NSAccessibilityMenuButtonRole; break;
			//don't know the right answer for these:
			case ACC.ROLE_FOOTER:
			case ACC.ROLE_HEADER:
			case ACC.ROLE_FORM:
			case ACC.ROLE_PAGE:
			case ACC.ROLE_SECTION:
				nsReturnValue = OS.NSAccessibilityGroupRole;
				break;
			case ACC.ROLE_HEADING:
			case ACC.ROLE_PARAGRAPH:
				nsReturnValue = OS.NSAccessibilityTextAreaRole;
				break;
			case ACC.ROLE_CLOCK:
			case ACC.ROLE_DATETIME:
			case ACC.ROLE_CALENDAR:
			case ACC.ROLE_ALERT:
			case ACC.ROLE_ANIMATION:
			case ACC.ROLE_DOCUMENT:
			case ACC.ROLE_SPINBUTTON:
			case ACC.ROLE_STATUSBAR:
				nsReturnValue = OS.NSAccessibilityUnknownRole;
		}

		return nsReturnValue.getString();
	}

	/* checkWidget was copied from Widget, and rewritten to work in this package */
	void checkWidget () {
		if (!isValidThread ()) SWT.error (SWT.ERROR_THREAD_INVALID_ACCESS);
		if (control.isDisposed ()) SWT.error (SWT.ERROR_WIDGET_DISPOSED);
	}

	/* isValidThread was copied from Widget, and rewritten to work in this package */
	boolean isValidThread () {
		return control.getDisplay ().getThread () == Thread.currentThread ();
	}

	/**
	 * Adds relationship attributes if needed to the property list.
	 * <p>
	 * <b>IMPORTANT:</b> This method is <em>not</em> part of the SWT
	 * public API. It is marked public only so that it can be shared
	 * within the packages provided by SWT. It is not available on all
	 * platforms and should never be accessed from application code.
	 * </p>
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 */
	public long internal_addRelationAttributes(long defaultAttributes) {
		NSArray attributes = new NSArray(defaultAttributes);
		NSMutableArray returnArray = NSMutableArray.arrayWithCapacity(attributes.count());
		returnArray.addObjectsFromArray(attributes);

		if (getTitleAttribute(ACC.CHILDID_SELF) != null) {
			if (!returnArray.containsObject(OS.NSAccessibilityTitleAttribute)) returnArray.addObject(OS.NSAccessibilityTitleAttribute);
		}

		if (getDescriptionAttribute(ACC.CHILDID_SELF) != null) {
			if (!returnArray.containsObject(OS.NSAccessibilityDescriptionAttribute)) returnArray.addObject(OS.NSAccessibilityDescriptionAttribute);
		}

		// See if this object has a label or is a label for something else. If so, add that to the list.
		if (relations[ACC.RELATION_LABEL_FOR] != null) {
			if (!returnArray.containsObject(OS.NSAccessibilityServesAsTitleForUIElementsAttribute)) returnArray.addObject(OS.NSAccessibilityServesAsTitleForUIElementsAttribute);
			if (!returnArray.containsObject(OS.NSAccessibilityTitleAttribute)) returnArray.addObject(OS.NSAccessibilityTitleAttribute);
		} else {
			returnArray.removeObject(OS.NSAccessibilityServesAsTitleForUIElementsAttribute);
		}

		if (relations[ACC.RELATION_LABELLED_BY] != null) {
			if (!returnArray.containsObject(OS.NSAccessibilityTitleUIElementAttribute)) returnArray.addObject(OS.NSAccessibilityTitleUIElementAttribute);
		} else {
			returnArray.removeObject(OS.NSAccessibilityTitleUIElementAttribute);
		}

		return returnArray.id;
	}

}
