//////////////////////////////////////////////////////////////////////////////// // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package spark.accessibility { import flash.accessibility.Accessibility; import flash.events.Event; import flash.events.FocusEvent; import mx.accessibility.AccConst; import mx.core.UIComponent; import mx.core.mx_internal; import spark.components.ComboBox; import spark.components.TextInput; import spark.events.IndexChangeEvent; import spark.events.SkinPartEvent; use namespace mx_internal; /** * ComboBoxAccImpl is the accessibility implementation class * for spark.components.ComboBox. * *

When a Spark ComboBox is created, * its accessibilityImplementation property * is set to an instance of this class. * The Flash Player then uses this class to allow MSAA clients * such as screen readers to see and manipulate the ComboBox. * See the mx.accessibility.AccImpl and * flash.accessibility.AccessibilityImplementation classes * for background information about accessibility implementation * classes and MSAA.

* *

Children

* *

The MSAA children of a ComboBox are its editable TextInput * (with childID 1) and its list items (with childIDs 2, 3, ... n). * The number of children is one plus the number of items * in the dataProvider * (not one plus the the number of visible renderers).

* *

As described below, the accessibility of the TextInput * and the list items is managed by the ComboBox; * their accessibilityImplementation * and accessibilityProperties * are ignored by the Flash Player.

* *

Role

* *

The MSAA Role of a ComboBox is ROLE_SYSTEM_COMBOBOX.

* *

The MSAA Role of the editable TextInput is ROLE_SYSTEM_TEXT.

* *

The Role of each list item is ROLE_SYSTEM_LISTITEM.

* *

Name

* *

The MSAA Name of a ComboBox (as well as its editable TextInput) * is, by default, an empty string. * When wrapped in a FormItem element, the Name is the FormItem's label. * To override this behavior, set the ComboBox's * accessibilityName property.

* *

The Name of each list item is determined by the ComboBox's * itemToLabel() method

* *

When the Name of the ComboBox or one of its items changes, * a ComboBox dispatches the MSAA event EVENT_OBJECT_NAMECHANGE * with the proper childID for a list item or 0 for itself.

* *

Description

* *

The MSAA Description of a ComboBox, by default, an empty string, * but you can set the ComboBox's accessibilityDescription * property.

* *

The Description of each list item is the empty string.

* *

State

* *

The MSAA State of a ComboBox is a combination of: *

* *

The State of the editable TextInput is a combination of: *

* *

The State of a list item is a combination of: *

* *

When the State of the ComboBox, its TextInput, * or one of its list items changes, * a ComboBox dispatches the MSAA event EVENT_OBJECT_STATECHANGE * with the proper childID for the TextInput or list item, * or 0 for itself.

* *

Value

* *

The MSAA Value of a ComboBox is the MSAA Name * of the currently selected list item * or the text entered into the TextInput.

* *

The Value of a list item is always the empty string.

* *

When the Value of the ComboBox changes, * a ComboBox dispatches the MSAA event EVENT_OBJECT_VALUECHANGE.

* *

Location

* *

The MSAA Location of a ComboBox, its TextInput, or one of its * list items is its bounding rectangle.

* *

Default Action

* *

Neither the ComboBox or it's TextInput have an MSAA DefaultAction.

* *

The DefaultAction of a list item is "Double Click". * Performing this action will select the item.

* *

Focus

* *

The ComboBox itself can receive focus, as well as its list items * (either while the ComboBox is collapsed or expanded).

* *

Selection

* *

The ComboBox allows a single item to be selected, * in which case an EVENT_OBJECT_SELECTION event is fired.

* * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public class ComboBoxAccImpl extends DropDownListBaseAccImpl { include "../core/Version.as"; //-------------------------------------------------------------------------- // // Class methods // //-------------------------------------------------------------------------- /** * Enables accessibility in the ComboBox class. * *

This method is called by application startup code * that is autogenerated by the MXML compiler. * Afterwards, when instances of ComboBox are initialized, * their accessibilityImplementation property * will be set to an instance of this class.

* * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public static function enableAccessibility():void { ComboBox.createAccessibilityImplementation = createAccessibilityImplementation; } /** * @private * Creates a ComboBox's AccessibilityImplementation object. * This method is called from UIComponent's * initializeAccessibility() method. */ mx_internal static function createAccessibilityImplementation( component:UIComponent):void { component.accessibilityImplementation = new ComboBoxAccImpl(component); } //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @param master The UIComponent instance that this AccImpl instance * is making accessible. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function ComboBoxAccImpl(master:UIComponent) { super(master); // ComboBox has a TextInput as a skin part, // and we need to listen to some of its events. // It may or may not be present when this constructor is called. // If it comes or goes later, we are notified via // "partAdded" and "partRemoved" events. var textInput:TextInput = ComboBox(master).textInput; if (textInput) textInput.addEventListener(Event.CHANGE, textInputChangeHandler); } //-------------------------------------------------------------------------- // // Overridden properties: AccImpl // //-------------------------------------------------------------------------- //---------------------------------- // eventsToHandle //---------------------------------- /** * @private * Array of events that we should listen for from the master component. */ override protected function get eventsToHandle():Array { return super.eventsToHandle.concat([ FocusEvent.FOCUS_IN, SkinPartEvent.PART_ADDED, SkinPartEvent.PART_REMOVED ]); } //-------------------------------------------------------------------------- // // Overridden methods: AccessibilityImplementation // //-------------------------------------------------------------------------- /** * @private * Returns the name of the component. * * @param childID uint. * * @return Name of the component. * * @tiptext Returns the name of the component */ override public function get_accName(childID:uint):String { if (childID <= 1) return super.get_accName(0); else return super.get_accName(childID - 1); } /** * @private * Gets the role for the component. * * @param childID children of the component */ override public function get_accRole(childID:uint):uint { if (childID == 1) return AccConst.ROLE_SYSTEM_TEXT; else if (childID == 0) return super.get_accRole(0); else return super.get_accRole(childID - 1); } /** * @private * IAccessible method for returning the state of the ComboBox. * States are predefined for all the components in MSAA. * Values are assigned to each state. * * @param childID uint * * @return State uint */ override public function get_accState(childID:uint):uint { // let the super class handle the states for list items if (childID > 1) return super.get_accState(childID -1); var accState:uint = super.get_accState(0); // The inner text field has focus and not the combo box itself thus we // need to overwrite the default logic in accImpl here to indicate that // this input field and combo box are focused when they are actually // focused. Set focus for the main component child id 0 or the 1st // child edit field if the component is focused if (master.getFocus() && master.owns(master.getFocus())) { accState |= AccConst.STATE_SYSTEM_FOCUSED; } // Remove the state of collapsed from the child edit field that was // added by the super class for the component and the edit child. // Both are set by the super class because it sees them both as // child id 0 and thus differentiation cannot be performed in the // super class if (childID == 1) accState &= ~AccConst.STATE_SYSTEM_COLLAPSED; return accState; } /** * @private * IAccessible method for returning the Default Action. * * @param childID uint * * @return DefaultAction String */ override public function get_accDefaultAction(childID:uint):String { if (childID <= 1) return null; return super.get_accDefaultAction(childID - 1); } /** * @private * IAccessible method for executing the Default Action. * * @param childID uint */ override public function accDoDefaultAction(childID:uint):void { if (childID > 1) super.accDoDefaultAction(childID -1); } /** * @private * Method to return an array of childIDs. * * @return Array */ override public function getChildIDArray():Array { var childIDArray:Array = super.getChildIDArray(); // 1 for first child edit field childIDArray[childIDArray.length] = childIDArray.length+1; return childIDArray; } /** * @private * IAccessible method for returning the childFocus of the List. * * @param childID uint * * @return focused childID. */ override public function get_accFocus():uint { var index:uint = super.get_accFocus(); if (ComboBox(master).isDropDownOpen) return index > 0 ? index + 1 : 0; else if (master.getFocus() && master.owns(master.getFocus())) return 1; // return child edit field if it is focused return 0; } /** * @private * IAccessible method for returning the bounding box of the ListItem. * * @param childID uint * * @return Location Object */ override public function accLocation(childID:uint):* { if (childID == 0) return super.accLocation(0); else if (childID == 1) return ComboBox(master).textInput; else return super.accLocation(childID - 1); } /** * @private * IAccessible method for selecting an item. * * @param childID uint */ override public function accSelect(selFlag:uint, childID:uint):void { if (childID > 1 ) super.accSelect(selFlag, childID - 1) else if (childID <= 1 && ComboBox(master).textInput) ComboBox(master).setFocus(); } /** * @private * IAccessible method for returning the value of the ComboBox * (which would be the text of the item selected * or the text entered in textInput). * @param childID uint * * @return Value String */ override public function get_accValue(childID:uint):String { var comboBox:ComboBox = ComboBox(master); if (childID <= 1) return comboBox.selectedIndex == -1 ? comboBox.textInput.text :comboBox.selectedItem; return null; } //-------------------------------------------------------------------------- // // Overridden methods: AccImpl // //-------------------------------------------------------------------------- //-------------------------------------------------------------------------- // // Overridden event handlers: AccImpl // //-------------------------------------------------------------------------- /** * @private * Override the generic event handler. * All AccImpl must implement this * to listen for events from its master component. */ override protected function eventHandler(event:Event):void { var textInput:TextInput; switch (event.type) { case "open": { Accessibility.sendEvent(master, 0, AccConst.EVENT_OBJECT_STATECHANGE); var sIndex:uint = ComboBox(master).selectedIndex; if (sIndex >= 0) { // when the combo box is opened, fire an event indicating // which item is selected/focused (focus and selection are the // same for the combo box Accessibility.sendEvent(master, sIndex + 2, AccConst.EVENT_OBJECT_FOCUS); } break; } case "focusIn": { Accessibility.sendEvent(master, 0, AccConst.EVENT_OBJECT_FOCUS); if (master.getFocus() == master && ComboBox(master).textInput) { // when the component receives focus, focus the text input // field of the combobox ComboBox(master).textInput.setFocus(); } break; } case "caretChange": { var index:uint = IndexChangeEvent(event).newIndex; // childIDs are 1 based, a plus two offset is used because the // first child is an edit field and not a list item var childID:uint = index + 2; Accessibility.sendEvent(master, 0, AccConst.EVENT_OBJECT_VALUECHANGE); Accessibility.sendEvent(master, 1, AccConst.EVENT_OBJECT_VALUECHANGE); // This event will fire for the opened and close combo box // normally this only fires on open comboboxes but it is fired // for closed comboboxes to support screen readers if (ComboBox(master).isDropDownOpen && index >=0 && index < ComboBox(master).dataProvider.length) { Accessibility.sendEvent(master, childID, AccConst.EVENT_OBJECT_FOCUS); } break; } case "change": { index = IndexChangeEvent(event).newIndex; if (index >= 0 && ComboBox(master).isDropDownOpen) { // childIDs are 1 based, a plus two offset is used because the // first child is an edit field and not a list item childID = index + 2; Accessibility.sendEvent(master, childID, AccConst.EVENT_OBJECT_SELECTION); } break; } case "close": { if (master.getFocus() && master.owns(master.getFocus()) && ComboBox(master).textInput) { ComboBox(master).textInput.textDisplay.setFocus(); // place the cursor at the beginning of the field, otherwise // text cannot be entered and characters can not be reviewed // without tabbing in and out, or pressing up or down arrows // changing the values ComboBox(master).textInput.selectRange(0,0); Accessibility.sendEvent(master, 1, AccConst.EVENT_OBJECT_FOCUS); } break; } case SkinPartEvent.PART_ADDED: { textInput = ComboBox(master).textInput; if (SkinPartEvent(event).instance == textInput) { textInput.addEventListener(Event.CHANGE, textInputChangeHandler); } break; } case SkinPartEvent.PART_REMOVED: { textInput = ComboBox(master).textInput; if (SkinPartEvent(event).instance == textInput) { textInput.removeEventListener(Event.CHANGE, textInputChangeHandler); } break; } default: { super.eventHandler(event); } } } /** * @private */ private function textInputChangeHandler(event:Event):void { Accessibility.sendEvent(master, 0, AccConst.EVENT_OBJECT_VALUECHANGE); } } }