1//////////////////////////////////////////////////////////////////////////////// 2// 3// ADOBE SYSTEMS INCORPORATED 4// Copyright 2007-2010 Adobe Systems Incorporated 5// All Rights Reserved. 6// 7// NOTICE: Adobe permits you to use, modify, and distribute this file 8// in accordance with the terms of the license agreement accompanying it. 9// 10//////////////////////////////////////////////////////////////////////////////// 11package flashx.textLayout.accessibility 12{ 13 import flash.accessibility.Accessibility; 14 import flash.accessibility.AccessibilityImplementation; 15 import flash.accessibility.AccessibilityProperties; 16 import flash.display.DisplayObject; 17 import flash.events.Event; 18 19 import flashx.textLayout.edit.EditingMode; 20 import flashx.textLayout.edit.ISelectionManager; 21 import flashx.textLayout.elements.FlowElement; 22 import flashx.textLayout.elements.FlowLeafElement; 23 import flashx.textLayout.elements.GlobalSettings; 24 import flashx.textLayout.elements.ParagraphElement; 25 import flashx.textLayout.elements.TextFlow; 26 import flashx.textLayout.events.CompositionCompleteEvent; 27 import flashx.textLayout.tlf_internal; 28 29 use namespace tlf_internal; 30 //TODO this is in text_edit... which violates MVC yet again... what to do? 31 //import flashx.textLayout.events.SelectionEvent; 32 33 //TODO handle selectable text when FP implements the new selection API: 34 // http://frpbugapp.macromedia.com/bugapp/detail.asp?id=217540 35 // To catch the selection changes reliably, listen for SelectionEvent, 36 // which is dispatched on the TextFlow whenever the selection changes. 37 38 //TODO handle scrolling? might need to expose scrolling in here 39 40 //TODO handle hyperlinks? I don't know if MSAA has a concept for this 41 // (what other text advanced features must be accessible? graphics?) 42 //TODO what if there is HTML in it? strip it, or read it? we don't have an 43 // htmlText property, do we? 44 45 // TODO Do we want to read the contents of each sprite and stop, even if the 46 // text flows into other sprite, meaning we read text in taborder; or do we 47 // want to read the entire model and not worry about the presentation 48 // (simpler)? Not sure if I can get the contents of each sprite separately. 49 50 // TODO TESTING: 51 // * Test that JAWS reads when setting the focus programmatically. 52 // * Tests for changing every part of the model programmatically -- role 53 // and state should update accordingly, visibility, and text contents. 54 // * Test that setting tabOrder reads as expected. What happens if you set 55 // tabOrder on multiple flowComposers? 56 57 //TODO update this comment after integration 58 /** 59 * @private 60 * The TextAccImpl class adds accessibility for text components. 61 * This hooks into DisplayObjects when TextFlow.container is set. 62 */ 63 public class TextAccImpl extends AccessibilityImplementation 64 { 65 //TODO might want to put these constants in a new class if they are 66 // used anywhere else. 67 68 /** Default state */ 69 protected static const STATE_SYSTEM_NORMAL:uint = 0x00000000; 70 71 /** Read-only text */ 72 protected static const STATE_SYSTEM_READONLY:uint = 0x00000040; 73 74 /** Inivisible text */ 75 //TODO unused, but supported state for text in MSAA 76 protected static const STATE_SYSTEM_INVISIBLE:uint = 0x00008000; 77 78 /** Default role -- read-only, unselectable text. */ 79 protected static const ROLE_SYSTEM_STATICTEXT:uint = 0x29; 80 81 /** Editable OR read-only, selectable text. */ 82 protected static const ROLE_SYSTEM_TEXT:uint = 0x2a; 83 84 /* When the name changes (name is the text conent in STATICTEXT). */ 85 protected static const EVENT_OBJECT_NAMECHANGE:uint = 0x800c; 86 87 /* When the value changes (value is the text content in TEXT). */ 88 protected static const EVENT_OBJECT_VALUECHANGE:uint = 0x800e; 89 90 /** 91 * A reference to the DisplayObject that is hosting accessible text. 92 */ 93 //TODO for now this assumes only the first DO in a flow is accessible 94 // in the future each flow DO should host its own accimpl and read 95 // the text only for its own box. 96 // 97 // Or... perhaps we use getChildIDArray to manage all the text 98 // flows if they are linked below some master component (but I don't 99 // think this is the way it will happen). 100 protected var textContainer:DisplayObject; 101 102 /** 103 * A reference to the TextFlow where our text originates. 104 */ 105 protected var textFlow:TextFlow; 106 107 /** 108 * Constructor. 109 * 110 * @param textContainer The DisplayObject instance that this 111 * TextAccImpl instance is making accessible. 112 * @param textFlow The TextFlow that is hosting the textContainer. 113 */ 114 public function TextAccImpl(textCont:DisplayObject, textFlow:TextFlow) 115 { 116 super(); 117 118 this.textContainer = textCont; 119 this.textFlow = textFlow; 120 121 // stub is true when you are NOT providing an acc implementation 122 // reports to reader as graphic 123 stub = false; 124 125 if (textCont.accessibilityProperties == null) 126 { 127 textCont.accessibilityProperties = 128 new AccessibilityProperties(); 129 } 130 131 //TODO 132 // setup event listeners for text selection and model changes 133 //textFlow.addEventListener(SelectionEvent.SELECTION_CHANGE, 134 // eventHandler); 135 textFlow.addEventListener(CompositionCompleteEvent.COMPOSITION_COMPLETE, eventHandler); 136 } 137 138 public function detachListeners():void 139 { 140 textFlow.removeEventListener(CompositionCompleteEvent.COMPOSITION_COMPLETE, eventHandler); 141 } 142 143 /** 144 * Returns the system role for the text. 145 * 146 * @param childID uint. 147 * @return Role associated with the text. 148 */ 149 override public function get_accRole(childID:uint):uint 150 { 151 // trace("get_accRole()"); 152 153 const iManager:ISelectionManager = textFlow.interactionManager; 154 if (iManager == null) 155 { 156 // non-selectable, non-editable text is STATICTEXT 157 return ROLE_SYSTEM_STATICTEXT 158 } 159 else // iManager is an IEditManager and/or ISelectionManager 160 { 161 // read-only selectable or editable selectable text are TEXT 162 return ROLE_SYSTEM_TEXT; 163 } 164 } 165 166 /** 167 * Returns the state of the text. 168 * 169 * @param childID uint. 170 * @return Role associated with the text. 171 */ 172 override public function get_accState(childID:uint):uint 173 { 174 // trace("get_accState()"); 175 176 const iManager:ISelectionManager = textFlow.interactionManager; 177 178 //TODO handle STATE_SYSTEM_INVISIBLE for all cases below 179 // and add an event to detect changes--does Flash support this? 180 181 //TODO handle STATE_SYSTEM_PROTECTED for all cases below 182 // if vellum gets a concept of password fields, then it needs to 183 // emit this value if the field is converted to a password; 184 // otherwise the Flex framework will need to be sure to emit 185 // this state in a text input component. 186 187 // note: focus-related states are handled by the player 188 189 if (iManager == null) 190 { 191 // non-selectable, non-editable text 192 return STATE_SYSTEM_READONLY; 193 } 194 // must check IEditManager before ISelectionManager (it can be both) 195 else if (iManager.editingMode == EditingMode.READ_WRITE) 196 { 197 // editable selectable text 198 return STATE_SYSTEM_NORMAL; 199 } 200 else // if (iManager instanceof ISelectionManager) 201 { 202 // read-only selectable text 203 return STATE_SYSTEM_READONLY; 204 } 205 } 206 207 /** 208 * Returns the name of the text. 209 * 210 * @param childID uint. 211 * @return Name of the text. 212 */ 213 override public function get_accName(childID:uint):String 214 { 215 // trace("get_accName()"); 216 217 switch (get_accRole(childID)) 218 { 219 case ROLE_SYSTEM_STATICTEXT: 220 { 221 //TODO this SHOULD come from TextConverter, but then there is a 222 // circular build dependency since importExport builds 223 // against model, and it probably violates mvc 224 //return TextConverter.export(textFlow, 225 // TextConverter.PLAIN_TEXT_FORMAT); 226 227 //TODO this is probably expensive. is there a way to cache 228 // this and know when dirty? 229 //TODO look at the generation and determine when it's dirty 230 return exportToString(textFlow); 231 } 232 233 case ROLE_SYSTEM_TEXT: 234 default: 235 return null; 236 } 237 } 238 239 /** 240 * Returns the value of the text. 241 * 242 * @param childID uint. 243 * @return Name of the text. 244 */ 245 override public function get_accValue(childID:uint):String 246 { 247 // trace("get_accValue()"); 248 249 switch (get_accRole(childID)) 250 { 251 case ROLE_SYSTEM_TEXT: 252 { 253 //TODO this SHOULD come from TextConverter, but then there is a 254 // circular build dependency since importExport builds 255 // against model, and it probably violates mvc 256 //return TextConverter.export(textFlow, 257 // TextConverter.PLAIN_TEXT_FORMAT); 258 259 // TODO this is probably expensive. is there a way to cache 260 // this and know when dirty? 261 //TODO look at the generation and determine when it's dirty 262 return exportToString(textFlow); 263 } 264 265 case ROLE_SYSTEM_STATICTEXT: 266 default: 267 return null; 268 } 269 } 270 271 /** 272 * Handles COMPOSITION_COMPLETE and SELECTION_CHANGE events, 273 * updates the MSAA model. 274 */ 275 protected function eventHandler(event:Event):void 276 { 277 switch (event.type) 278 { 279 // This updates the entire accessibility DOM. 280 // get_accName is probably expensive here, it happens ONLY if 281 // JAWS is running, otherwise Accessibility.* calls are NOOP. 282 // 283 // Event does NOT fire when interactionManager changes; ideally 284 // we'd want to tell MSAA the role changed, but apparently roles 285 // are typically static and that's not a supported workflow. 286 // instead, Flash occasionally polls the displaylist, e.g. when 287 // you mouseover. calling updateProperties() doesn't necessarily 288 // trigger role updates (calls to get_acc*()). 289 case CompositionCompleteEvent.COMPOSITION_COMPLETE: 290 { 291 // TODO change childID from 0 if we use getChildIDArray 292 // otherwise delete this comment 293 try { 294 Accessibility.sendEvent(textContainer, 0, 295 EVENT_OBJECT_NAMECHANGE); 296 Accessibility.sendEvent(textContainer, 0, 297 EVENT_OBJECT_VALUECHANGE); 298 Accessibility.updateProperties(); 299 } catch (e_err:Error) { 300 // generic error occurred. 301 // this can happen in the SA player since there is no 302 // Accessibility implementation. 303 } 304 break; 305 } 306 307 //TODO when we have the FP selection APIs 308 // case SelectionEvent.SELECTION_CHANGE: 309 // { 310 // // this is just stubbed code, I don't know what *needs* to 311 // // be done for SELECTION_CHANGE 312 // Accessibility.sendEvent(textContainer, 0, 313 // EVENT_OBJECT_TEXTSELECTIONCHANGED); 314 // Accessibility.updateProperties(); 315 // break; 316 // } 317 } 318 } 319 320 /** 321 * TODO HACK, remove and refactor. 322 * 323 * This is copied from PlainTextExportFilter, which I would prefer to 324 * access through TextConverter.export(textFlow, 325 * TextConverter.PLAIN_TEXT_FORMAT); 326 * 327 * But, PTEF is in importExport, which builds against text_model, 328 * which is a circular dependency. 329 * 330 * Also, it seems to be adding a trailing newline, which is bad for 331 * accessibility unless it is really there. 332 * 333 * Might want to export and strip out hyphens. 334 * Move it to the model? 335 */ 336 private static function exportToString(source:TextFlow):String 337 { 338 var leaf:FlowLeafElement = source.getFirstLeaf(); 339 var rslt:String = ""; 340 var curString:String = ""; 341 var discretionaryHyphen:String = String.fromCharCode(0x00AD); 342 while (leaf) 343 { 344 var p:ParagraphElement = leaf.getParagraph(); 345 while (true) 346 { 347 curString = leaf.text; 348 349 //split out discretionary hyphen and put string back together 350 var temparray:Array = curString.split(discretionaryHyphen); 351 curString = temparray.join(""); 352 353 rslt += curString; 354 leaf = leaf.getNextLeaf(p); 355 if (!leaf) 356 { 357 // we want newlines between paragraphs but not at the end 358 rslt += "\n"; 359 break; 360 } 361 } 362 leaf = p.getLastLeaf().getNextLeaf(); 363 } 364 return rslt; 365 } 366 367 /** 368 * The zero-based character index value of the first character in the current selection. 369 * Components which wish to support inline IME or Accessibility should call into this method. 370 * 371 * @return the index of the character at the anchor end of the selection, or <code>-1</code> if no text is selected. 372 * 373 * @playerversion Flash 10.0 374 * @langversion 3.0 375 */ 376 public function get selectionActiveIndex():int 377 { 378 var selMgr:ISelectionManager = textFlow.interactionManager; 379 var selIndex:int = -1; 380 if(selMgr && selMgr.editingMode != EditingMode.READ_ONLY) 381 { 382 selIndex = selMgr.activePosition; 383 } 384 385 return selIndex; 386 } 387 388 /** 389 * The zero-based character index value of the last character in the current selection. 390 * Components which wish to support inline IME or Accessibility should call into this method. 391 * 392 * @return the index of the character at the active end of the selection, or <code>-1</code> if no text is selected. 393 * 394 * @playerversion Flash 10.0 395 * @langversion 3.0 396 */ 397 public function get selectionAnchorIndex():int 398 { 399 var selMgr:ISelectionManager = textFlow.interactionManager; 400 var selIndex:int = -1; 401 if(selMgr && selMgr.editingMode != EditingMode.READ_ONLY) 402 { 403 selIndex = selMgr.anchorPosition; 404 } 405 406 return selIndex; 407 } 408 409 /** Enable search index for Ichabod 410 * Returns the entire text of the TextFlow, or null if search index is not enabled 411 * @see GlobalSettings.searchIndexEnabled 412 */ 413 public function get searchText():String 414 { 415 return GlobalSettings.enableSearch ? textFlow.getText() : null; 416 } 417 418 } 419} 420 421