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