blob: 6d049ddb270033faf8ce4d5b2478d6a84edcc43b [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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 mx.accessibility
{
import flash.accessibility.Accessibility;
import flash.accessibility.AccessibilityProperties;
import flash.events.Event;
import flash.system.ApplicationDomain;
import mx.accessibility.AccImpl;
import mx.core.UIComponent;
import mx.core.mx_internal;
use namespace mx_internal;
/**
* UIComponentAccProps is a subclass of AccessibilityProperties
* for use by various UIComponents.
* It is used to provide accessibility to Form, ToolTip, and Error ToolTip.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class UIComponentAccProps extends AccessibilityProperties
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Class methods
//
//--------------------------------------------------------------------------
/**
* Enables accessibility in the UIComponent class.
*
* <p>This method is called by application startup code
* that is autogenerated by the MXML compiler.
* Afterwards, when instances of UIComponent are initialized,
* their <code>accessibilityProperties</code> property
* will be set to an instance of this class.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public static function enableAccessibility():void
{
UIComponent.createAccessibilityImplementation =
createAccessibilityImplementation;
}
/**
* @private
* Creates a UIComponent's AccessibilityProperties object.
* This method is called from UIComponent's
* initializeAccessibility() method.
*/
mx_internal static function createAccessibilityImplementation(
component:UIComponent):void
{
component.accessibilityProperties =
new UIComponentAccProps(component);
}
/**
* @private
* Determines whether a component should avoid producing MSAA information
* directly. Components that are represented otherwise, such as
* a form field's Required Field indicator (which causes "Required field"
* to be included in the field's accessible name) should be made silent
* in this way.
*
* @param component The component to check.
*
* @return true if this component should be silent.
*/
mx_internal static function componentShouldBeSilent(component:UIComponent):Boolean
{
// All tests below require Group to exist and to be under a FormItem.
var groupClass:Class = Class(AccImpl.getDefinition(
"spark.components.Group", component.moduleFactory
));
if (!groupClass)
return false;
var formItem:UIComponent = AccImpl.findMatchingAncestor(component, AccImpl.isFormItem);
if (!formItem)
return false;
// This catches labels for FormItem fields,
// and also one object related to the Required Field indicator.
// TODO: We might want to silence all Group components under a FormItem,
// but this theory remains to be tested and so is not implemented here.
// component.parent should be a FormItem skin (possibly a custom one),
// so the parent of that should be the FormItem.
// The try/catch block prevents an RTE if
// component.parent.parent doesn't exist.
try
{
if (component is groupClass
&& component.parent.parent === formItem)
return true;
}
catch (e:Error)
{
}
// This catches the Required Field graphic itself.
// Sought structure: Image in Group in skin in FormItem.
var imageClass:Class = Class(AccImpl.getDefinition(
"spark.components.Image", component.moduleFactory
));
if (!imageClass)
return false;
try
{
if (component is imageClass && component.parent is groupClass
&& component.parent.parent.parent === formItem)
return true;
}
catch (e:Error)
{
}
return false;
}
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @param master The UIComponent instance that this
* AccessibilityProperties instance is making accessible.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function UIComponentAccProps(component:UIComponent)
{
super();
master = component;
if (component.accessibilityProperties)
{
silent = component.accessibilityProperties.silent;
forceSimple = component.accessibilityProperties.forceSimple;
noAutoLabeling = component.accessibilityProperties.noAutoLabeling;
if (component.accessibilityProperties.name)
name = component.accessibilityProperties.name;
if (component.accessibilityProperties.description)
description = component.accessibilityProperties.description;
if (component.accessibilityProperties.shortcut)
shortcut = component.accessibilityProperties.shortcut;
}
if (AccImpl.getMatchingDefinition(master,
AccImpl.getDefinitions("ScrollBar", master.moduleFactory)
))
{
silent = true;
return;
}
if (isFormItemLabel(master))
{
// TODO: Why is name set here? Is this name used somewhere?
name = AccImpl.getFormName(master);
silent = true;
return;
}
// Silence various subparts of Spark forms besides FormItem labels.
if (componentShouldBeSilent(component))
{
// We can't set silent=true or whole FormItems and their fields
// will disappear from MSAA, but if we set name="", the Player
// will filter out these items for us.
name = "";
return;
}
// In complex layouts, the text of a FormHeading might not appear
// where it should for assistive technology, because the FormHeading's
// tabIndex does not also get applied to the text item.
// That is fixed here when the FormHeading is being constructed.
if (AccImpl.isFormHeading(master) && master.tabIndex > 0)
{
// Spark-specific solution.
try
{
if (Object(master).labelDisplay.tabIndex == -1)
Object(master).labelDisplay.tabIndex = master.tabIndex;
}
catch (e:Error)
{
}
// TODO: Not solved for MX even though the problem does apply there.
}
var formName:String = AccImpl.getFormName(master);
if (formName && formName.length != 0)
name = formName + name;
if (master.toolTip && master.toolTip.length != 0)
if (!component.accessibilityProperties || (component.accessibilityProperties && !component.accessibilityProperties.name))
{
oldToolTip = " " + master.toolTip;
name += oldToolTip;
}
if (master.errorString && master.errorString.length != 0)
{
oldErrorString = " " + master.errorString;
name += oldErrorString;
}
master.addEventListener("toolTipChanged", eventHandler);
master.addEventListener("errorStringChanged", eventHandler);
}
/**
* @private
* Determines whether a component is a formItem label (Spark or MX).
*
* @param component The component to check.
*
* @return true if this is a label for a formItem.
*/
protected function isFormItemLabel(component:UIComponent):Boolean
{
// This handles Spark labels on forms.
var thisName:String = master.name;
if (thisName == "labelDisplay"
|| thisName == "sequenceLabelDisplay"
|| thisName == "helpContentGroup"
|| thisName == "errorTextDisplay"
)
return true;
// This handles MX FormItemLabel objects.
return Boolean(AccImpl.getMatchingDefinition(master,
AccImpl.getDefinitions("FormItemLabel", master.moduleFactory)
));
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
*/
private var oldToolTip:String;
/**
* @private
*/
private var oldErrorString:String;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// master
//----------------------------------
/**
* A reference to the UIComponent itself.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var master:UIComponent;
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
/**
* Generic event handler.
* All UIComponentAccProps subclasses must implement this
* to listen for events from its master component.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function eventHandler(event:Event):void
{
var pos:int;
switch (event.type)
{
case "errorStringChanged":
{
if (name && name.length != 0 && oldErrorString)
{
pos = name.indexOf(oldErrorString);
if (pos != -1)
{
name = name.substring(0, pos) +
name.substring(pos + oldErrorString.length);
}
oldErrorString = null;
}
if (master.errorString && master.errorString.length != 0)
{
if (!name)
name = "";
oldErrorString = " " + master.errorString;
name += oldErrorString;
}
Accessibility.updateProperties();
break;
}
case "toolTipChanged":
{
if (name && name.length != 0 && oldToolTip)
{
pos = name.indexOf(oldToolTip);
if (pos != -1)
{
name = name.substring(0, pos) +
name.substring(pos + oldToolTip.length);
}
oldToolTip = null;
}
if (master.toolTip && master.toolTip.length != 0)
{
if (!master.accessibilityProperties || (master.accessibilityProperties && !master.accessibilityProperties.name))
{
if (!name)
name = "";
oldToolTip = " " + master.toolTip;
name += oldToolTip;
}
}
Accessibility.updateProperties();
break;
}
}
}
}
}