blob: c576e72d7f637686eca544ab339b12431e50e9a1 [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 flashx.textLayout.accessibility
{
import flash.accessibility.Accessibility;
import flash.accessibility.AccessibilityImplementation;
import flash.accessibility.AccessibilityProperties;
import flash.display.DisplayObject;
import flash.events.Event;
import flashx.textLayout.edit.EditingMode;
import flashx.textLayout.edit.ISelectionManager;
import flashx.textLayout.elements.FlowElement;
import flashx.textLayout.elements.FlowLeafElement;
import flashx.textLayout.elements.GlobalSettings;
import flashx.textLayout.elements.ParagraphElement;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.events.CompositionCompleteEvent;
import flashx.textLayout.tlf_internal;
use namespace tlf_internal;
//TODO this is in text_edit... which violates MVC yet again... what to do?
//import flashx.textLayout.events.SelectionEvent;
//TODO handle selectable text when FP implements the new selection API:
// http://frpbugapp.macromedia.com/bugapp/detail.asp?id=217540
// To catch the selection changes reliably, listen for SelectionEvent,
// which is dispatched on the TextFlow whenever the selection changes.
//TODO handle scrolling? might need to expose scrolling in here
//TODO handle hyperlinks? I don't know if MSAA has a concept for this
// (what other text advanced features must be accessible? graphics?)
//TODO what if there is HTML in it? strip it, or read it? we don't have an
// htmlText property, do we?
// TODO Do we want to read the contents of each sprite and stop, even if the
// text flows into other sprite, meaning we read text in taborder; or do we
// want to read the entire model and not worry about the presentation
// (simpler)? Not sure if I can get the contents of each sprite separately.
// TODO TESTING:
// * Test that JAWS reads when setting the focus programmatically.
// * Tests for changing every part of the model programmatically -- role
// and state should update accordingly, visibility, and text contents.
// * Test that setting tabOrder reads as expected. What happens if you set
// tabOrder on multiple flowComposers?
//TODO update this comment after integration
/**
* @private
* The TextAccImpl class adds accessibility for text components.
* This hooks into DisplayObjects when TextFlow.container is set.
*/
public class TextAccImpl extends AccessibilityImplementation
{
//TODO might want to put these constants in a new class if they are
// used anywhere else.
/** Default state */
protected static const STATE_SYSTEM_NORMAL:uint = 0x00000000;
/** Read-only text */
protected static const STATE_SYSTEM_READONLY:uint = 0x00000040;
/** Inivisible text */
//TODO unused, but supported state for text in MSAA
protected static const STATE_SYSTEM_INVISIBLE:uint = 0x00008000;
/** Default role -- read-only, unselectable text. */
protected static const ROLE_SYSTEM_STATICTEXT:uint = 0x29;
/** Editable OR read-only, selectable text. */
protected static const ROLE_SYSTEM_TEXT:uint = 0x2a;
/* When the name changes (name is the text conent in STATICTEXT). */
protected static const EVENT_OBJECT_NAMECHANGE:uint = 0x800c;
/* When the value changes (value is the text content in TEXT). */
protected static const EVENT_OBJECT_VALUECHANGE:uint = 0x800e;
/**
* A reference to the DisplayObject that is hosting accessible text.
*/
//TODO for now this assumes only the first DO in a flow is accessible
// in the future each flow DO should host its own accimpl and read
// the text only for its own box.
//
// Or... perhaps we use getChildIDArray to manage all the text
// flows if they are linked below some master component (but I don't
// think this is the way it will happen).
protected var textContainer:DisplayObject;
/**
* A reference to the TextFlow where our text originates.
*/
protected var textFlow:TextFlow;
/**
* Constructor.
*
* @param textContainer The DisplayObject instance that this
* TextAccImpl instance is making accessible.
* @param textFlow The TextFlow that is hosting the textContainer.
*/
public function TextAccImpl(textCont:DisplayObject, textFlow:TextFlow)
{
super();
this.textContainer = textCont;
this.textFlow = textFlow;
// stub is true when you are NOT providing an acc implementation
// reports to reader as graphic
stub = false;
if (textCont.accessibilityProperties == null)
{
textCont.accessibilityProperties =
new AccessibilityProperties();
}
//TODO
// setup event listeners for text selection and model changes
//textFlow.addEventListener(SelectionEvent.SELECTION_CHANGE,
// eventHandler);
textFlow.addEventListener(CompositionCompleteEvent.COMPOSITION_COMPLETE, eventHandler);
}
public function detachListeners():void
{
textFlow.removeEventListener(CompositionCompleteEvent.COMPOSITION_COMPLETE, eventHandler);
}
/**
* Returns the system role for the text.
*
* @param childID uint.
* @return Role associated with the text.
*/
override public function get_accRole(childID:uint):uint
{
// trace("get_accRole()");
const iManager:ISelectionManager = textFlow.interactionManager;
if (iManager == null)
{
// non-selectable, non-editable text is STATICTEXT
return ROLE_SYSTEM_STATICTEXT
}
else // iManager is an IEditManager and/or ISelectionManager
{
// read-only selectable or editable selectable text are TEXT
return ROLE_SYSTEM_TEXT;
}
}
/**
* Returns the state of the text.
*
* @param childID uint.
* @return Role associated with the text.
*/
override public function get_accState(childID:uint):uint
{
// trace("get_accState()");
const iManager:ISelectionManager = textFlow.interactionManager;
//TODO handle STATE_SYSTEM_INVISIBLE for all cases below
// and add an event to detect changes--does Flash support this?
//TODO handle STATE_SYSTEM_PROTECTED for all cases below
// if vellum gets a concept of password fields, then it needs to
// emit this value if the field is converted to a password;
// otherwise the Flex framework will need to be sure to emit
// this state in a text input component.
// note: focus-related states are handled by the player
if (iManager == null)
{
// non-selectable, non-editable text
return STATE_SYSTEM_READONLY;
}
// must check IEditManager before ISelectionManager (it can be both)
else if (iManager.editingMode == EditingMode.READ_WRITE)
{
// editable selectable text
return STATE_SYSTEM_NORMAL;
}
else // if (iManager instanceof ISelectionManager)
{
// read-only selectable text
return STATE_SYSTEM_READONLY;
}
}
/**
* Returns the name of the text.
*
* @param childID uint.
* @return Name of the text.
*/
override public function get_accName(childID:uint):String
{
// trace("get_accName()");
switch (get_accRole(childID))
{
case ROLE_SYSTEM_STATICTEXT:
{
//TODO this SHOULD come from TextConverter, but then there is a
// circular build dependency since importExport builds
// against model, and it probably violates mvc
//return TextConverter.export(textFlow,
// TextConverter.PLAIN_TEXT_FORMAT);
//TODO this is probably expensive. is there a way to cache
// this and know when dirty?
//TODO look at the generation and determine when it's dirty
return exportToString(textFlow);
}
case ROLE_SYSTEM_TEXT:
default:
return null;
}
}
/**
* Returns the value of the text.
*
* @param childID uint.
* @return Name of the text.
*/
override public function get_accValue(childID:uint):String
{
// trace("get_accValue()");
switch (get_accRole(childID))
{
case ROLE_SYSTEM_TEXT:
{
//TODO this SHOULD come from TextConverter, but then there is a
// circular build dependency since importExport builds
// against model, and it probably violates mvc
//return TextConverter.export(textFlow,
// TextConverter.PLAIN_TEXT_FORMAT);
// TODO this is probably expensive. is there a way to cache
// this and know when dirty?
//TODO look at the generation and determine when it's dirty
return exportToString(textFlow);
}
case ROLE_SYSTEM_STATICTEXT:
default:
return null;
}
}
/**
* Handles COMPOSITION_COMPLETE and SELECTION_CHANGE events,
* updates the MSAA model.
*/
protected function eventHandler(event:Event):void
{
switch (event.type)
{
// This updates the entire accessibility DOM.
// get_accName is probably expensive here, it happens ONLY if
// JAWS is running, otherwise Accessibility.* calls are NOOP.
//
// Event does NOT fire when interactionManager changes; ideally
// we'd want to tell MSAA the role changed, but apparently roles
// are typically static and that's not a supported workflow.
// instead, Flash occasionally polls the displaylist, e.g. when
// you mouseover. calling updateProperties() doesn't necessarily
// trigger role updates (calls to get_acc*()).
case CompositionCompleteEvent.COMPOSITION_COMPLETE:
{
// TODO change childID from 0 if we use getChildIDArray
// otherwise delete this comment
try {
Accessibility.sendEvent(textContainer, 0,
EVENT_OBJECT_NAMECHANGE);
Accessibility.sendEvent(textContainer, 0,
EVENT_OBJECT_VALUECHANGE);
Accessibility.updateProperties();
} catch (e_err:Error) {
// generic error occurred.
// this can happen in the SA player since there is no
// Accessibility implementation.
}
break;
}
//TODO when we have the FP selection APIs
// case SelectionEvent.SELECTION_CHANGE:
// {
// // this is just stubbed code, I don't know what *needs* to
// // be done for SELECTION_CHANGE
// Accessibility.sendEvent(textContainer, 0,
// EVENT_OBJECT_TEXTSELECTIONCHANGED);
// Accessibility.updateProperties();
// break;
// }
}
}
/**
* TODO HACK, remove and refactor.
*
* This is copied from PlainTextExportFilter, which I would prefer to
* access through TextConverter.export(textFlow,
* TextConverter.PLAIN_TEXT_FORMAT);
*
* But, PTEF is in importExport, which builds against text_model,
* which is a circular dependency.
*
* Also, it seems to be adding a trailing newline, which is bad for
* accessibility unless it is really there.
*
* Might want to export and strip out hyphens.
* Move it to the model?
*/
private static function exportToString(source:TextFlow):String
{
var leaf:FlowLeafElement = source.getFirstLeaf();
var rslt:String = "";
var curString:String = "";
var discretionaryHyphen:String = String.fromCharCode(0x00AD);
while (leaf)
{
var p:ParagraphElement = leaf.getParagraph();
while (true)
{
curString = leaf.text;
//split out discretionary hyphen and put string back together
var temparray:Array = curString.split(discretionaryHyphen);
curString = temparray.join("");
rslt += curString;
leaf = leaf.getNextLeaf(p);
if (!leaf)
{
// we want newlines between paragraphs but not at the end
rslt += "\n";
break;
}
}
leaf = p.getLastLeaf().getNextLeaf();
}
return rslt;
}
/**
* The zero-based character index value of the first character in the current selection.
* Components which wish to support inline IME or Accessibility should call into this method.
*
* @return the index of the character at the anchor end of the selection, or <code>-1</code> if no text is selected.
*
* @playerversion Flash 10.0
* @langversion 3.0
*/
public function get selectionActiveIndex():int
{
var selMgr:ISelectionManager = textFlow.interactionManager;
var selIndex:int = -1;
if(selMgr && selMgr.editingMode != EditingMode.READ_ONLY)
{
selIndex = selMgr.activePosition;
}
return selIndex;
}
/**
* The zero-based character index value of the last character in the current selection.
* Components which wish to support inline IME or Accessibility should call into this method.
*
* @return the index of the character at the active end of the selection, or <code>-1</code> if no text is selected.
*
* @playerversion Flash 10.0
* @langversion 3.0
*/
public function get selectionAnchorIndex():int
{
var selMgr:ISelectionManager = textFlow.interactionManager;
var selIndex:int = -1;
if(selMgr && selMgr.editingMode != EditingMode.READ_ONLY)
{
selIndex = selMgr.anchorPosition;
}
return selIndex;
}
/** Enable search index for Ichabod
* Returns the entire text of the TextFlow, or null if search index is not enabled
* @see GlobalSettings.searchIndexEnabled
*/
public function get searchText():String
{
return GlobalSettings.enableSearch ? textFlow.getText() : null;
}
}
}