1////////////////////////////////////////////////////////////////////////////////
2//
3//  ADOBE SYSTEMS INCORPORATED
4//  Copyright 2003-2007 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////////////////////////////////////////////////////////////////////////////////
11
12package mx.accessibility
13{
14
15import flash.accessibility.Accessibility;
16import flash.accessibility.AccessibilityProperties;
17import flash.events.Event;
18import flash.system.ApplicationDomain;
19
20import mx.accessibility.AccImpl;
21import mx.core.UIComponent;
22import mx.core.mx_internal;
23
24use namespace mx_internal;
25
26/**
27 *  UIComponentAccProps is a subclass of AccessibilityProperties
28 *  for use by various UIComponents.
29 *  It is used to provide accessibility to Form, ToolTip, and Error ToolTip.
30 *
31 *  @langversion 3.0
32 *  @playerversion Flash 9
33 *  @playerversion AIR 1.1
34 *  @productversion Flex 3
35 */
36public class UIComponentAccProps extends AccessibilityProperties
37{
38    include "../core/Version.as";
39
40    //--------------------------------------------------------------------------
41    //
42    //  Class methods
43    //
44    //--------------------------------------------------------------------------
45
46    /**
47     *  Enables accessibility in the UIComponent class.
48     *
49     *  <p>This method is called by application startup code
50     *  that is autogenerated by the MXML compiler.
51     *  Afterwards, when instances of UIComponent are initialized,
52     *  their <code>accessibilityProperties</code> property
53     *  will be set to an instance of this class.</p>
54     *
55     *  @langversion 3.0
56     *  @playerversion Flash 9
57     *  @playerversion AIR 1.1
58     *  @productversion Flex 3
59     */
60    public static function enableAccessibility():void
61    {
62        UIComponent.createAccessibilityImplementation =
63            createAccessibilityImplementation;
64    }
65
66    /**
67     *  @private
68     *  Creates a UIComponent's AccessibilityProperties object.
69     *  This method is called from UIComponent's
70     *  initializeAccessibility() method.
71     */
72    mx_internal static function createAccessibilityImplementation(
73                                            component:UIComponent):void
74    {
75        component.accessibilityProperties =
76            new UIComponentAccProps(component);
77    }
78
79    /**
80     *  @private
81     *  Determines whether a component should avoid producing MSAA information
82     *  directly.  Components that are represented otherwise, such as
83     *  a form field's Required Field indicator (which causes "Required field"
84     *  to be included in the field's accessible name) should be made silent
85     *  in this way.
86     *
87     *  @param component The component to check.
88     *
89     *  @return true if this component should be silent.
90     */
91    mx_internal static function componentShouldBeSilent(component:UIComponent):Boolean
92    {
93        // All tests below require Group to exist and to be under a FormItem.
94        var groupClass:Class = Class(AccImpl.getDefinition(
95            "spark.components.Group", component.moduleFactory
96        ));
97        if (!groupClass)
98            return false;
99        var formItem:UIComponent = AccImpl.findMatchingAncestor(component, AccImpl.isFormItem);
100        if (!formItem)
101            return false;
102
103        // This catches labels for FormItem fields,
104        // and also one object related to the Required Field indicator.
105        // TODO:  We might want to silence all Group components under a FormItem,
106        // but this theory remains to be tested and so is not implemented here.
107        // component.parent should be a FormItem skin (possibly a custom one),
108        // so the parent of that should be the FormItem.
109        // The try/catch block prevents an RTE if
110        // component.parent.parent doesn't exist.
111        try
112        {
113            if (component is groupClass
114            && component.parent.parent === formItem)
115                return true;
116        }
117        catch (e:Error)
118        {
119        }
120
121        // This catches the Required Field graphic itself.
122        // Sought structure: Image in Group in skin in FormItem.
123        var imageClass:Class = Class(AccImpl.getDefinition(
124            "spark.components.Image", component.moduleFactory
125        ));
126        if (!imageClass)
127            return false;
128        try
129        {
130        if (component is imageClass && component.parent is groupClass
131        && component.parent.parent.parent === formItem)
132            return true;
133        }
134        catch (e:Error)
135        {
136        }
137
138        return false;
139    }
140
141    //--------------------------------------------------------------------------
142    //
143    //  Constructor
144    //
145    //--------------------------------------------------------------------------
146
147    /**
148     *  Constructor.
149     *
150     *  @param master The UIComponent instance that this
151     *  AccessibilityProperties instance is making accessible.
152     *
153     *  @langversion 3.0
154     *  @playerversion Flash 9
155     *  @playerversion AIR 1.1
156     *  @productversion Flex 3
157     */
158    public function UIComponentAccProps(component:UIComponent)
159    {
160        super();
161
162        master = component;
163
164        if (component.accessibilityProperties)
165        {
166            silent = component.accessibilityProperties.silent;
167
168            forceSimple = component.accessibilityProperties.forceSimple;
169
170            noAutoLabeling = component.accessibilityProperties.noAutoLabeling;
171
172            if (component.accessibilityProperties.name)
173                name = component.accessibilityProperties.name;
174
175            if (component.accessibilityProperties.description)
176                description = component.accessibilityProperties.description;
177
178            if (component.accessibilityProperties.shortcut)
179                shortcut = component.accessibilityProperties.shortcut;
180        }
181
182        if (AccImpl.getMatchingDefinition(master,
183            AccImpl.getDefinitions("ScrollBar", master.moduleFactory)
184        ))
185        {
186            silent = true;
187            return;
188        }
189
190        if (isFormItemLabel(master))
191        {
192            // TODO:  Why is name set here?  Is this name used somewhere?
193            name = AccImpl.getFormName(master);
194            silent = true;
195            return;
196        }
197
198        // Silence various subparts of Spark forms besides FormItem labels.
199        if (componentShouldBeSilent(component))
200        {
201            // We can't set silent=true or whole FormItems and their fields
202            // will disappear from MSAA, but if we set name="", the Player
203            // will filter out these items for us.
204            name = "";
205            return;
206        }
207
208        // In complex layouts, the text of a FormHeading might not appear
209        // where it should for assistive technology, because the FormHeading's
210        // tabIndex does not also get applied to the text item.
211        // That is fixed here when the FormHeading is being constructed.
212        if (AccImpl.isFormHeading(master) && master.tabIndex > 0)
213        {
214            // Spark-specific solution.
215            try
216            {
217                if (Object(master).labelDisplay.tabIndex == -1)
218                    Object(master).labelDisplay.tabIndex = master.tabIndex;
219            }
220            catch (e:Error)
221            {
222            }
223            // TODO:  Not solved for MX even though the problem does apply there.
224        }
225
226        var formName:String = AccImpl.getFormName(master);
227        if (formName && formName.length != 0)
228            name = formName + name;
229
230        if (master.toolTip && master.toolTip.length != 0)
231            if (!component.accessibilityProperties || (component.accessibilityProperties && !component.accessibilityProperties.name))
232        {
233            oldToolTip = " " + master.toolTip;
234            name += oldToolTip;
235        }
236
237        if (master.errorString && master.errorString.length != 0)
238        {
239            oldErrorString = " " + master.errorString;
240            name += oldErrorString;
241        }
242
243        master.addEventListener("toolTipChanged", eventHandler);
244        master.addEventListener("errorStringChanged", eventHandler);
245    }
246
247    /**
248     *  @private
249     *  Determines whether a component is a formItem label (Spark or MX).
250     *
251     *  @param component The component to check.
252     *
253     *  @return true if this is a label for a formItem.
254     */
255    protected function isFormItemLabel(component:UIComponent):Boolean
256    {
257        // This handles Spark labels on forms.
258        var thisName:String = master.name;
259        if (thisName == "labelDisplay"
260            || thisName == "sequenceLabelDisplay"
261            || thisName == "helpContentGroup"
262            || thisName == "errorTextDisplay"
263        )
264            return true;
265
266        // This handles MX FormItemLabel objects.
267        return Boolean(AccImpl.getMatchingDefinition(master,
268            AccImpl.getDefinitions("FormItemLabel", master.moduleFactory)
269        ));
270    }
271
272    //--------------------------------------------------------------------------
273    //
274    //  Variables
275    //
276    //--------------------------------------------------------------------------
277
278    /**
279     *  @private
280     */
281    private var oldToolTip:String;
282
283    /**
284     *  @private
285     */
286    private var oldErrorString:String;
287
288    //--------------------------------------------------------------------------
289    //
290    //  Properties
291    //
292    //--------------------------------------------------------------------------
293
294    //----------------------------------
295    //  master
296    //----------------------------------
297
298    /**
299     *  A reference to the UIComponent itself.
300     *
301     *  @langversion 3.0
302     *  @playerversion Flash 9
303     *  @playerversion AIR 1.1
304     *  @productversion Flex 3
305     */
306    protected var master:UIComponent;
307
308    //--------------------------------------------------------------------------
309    //
310    //  Event handlers
311    //
312    //--------------------------------------------------------------------------
313
314    /**
315     *  Generic event handler.
316     *  All UIComponentAccProps subclasses must implement this
317     *  to listen for events from its master component.
318     *
319     *  @langversion 3.0
320     *  @playerversion Flash 9
321     *  @playerversion AIR 1.1
322     *  @productversion Flex 3
323     */
324    protected function eventHandler(event:Event):void
325    {
326        var pos:int;
327
328        switch (event.type)
329        {
330            case "errorStringChanged":
331            {
332                if (name && name.length != 0 && oldErrorString)
333                {
334                    pos = name.indexOf(oldErrorString);
335                    if (pos != -1)
336                    {
337                        name = name.substring(0, pos) +
338                               name.substring(pos + oldErrorString.length);
339                    }
340                    oldErrorString = null;
341                }
342
343                if (master.errorString && master.errorString.length != 0)
344                {
345                    if (!name)
346                        name = "";
347
348                    oldErrorString = " " + master.errorString;
349                    name += oldErrorString;
350                }
351
352                Accessibility.updateProperties();
353                break;
354            }
355
356            case "toolTipChanged":
357            {
358                if (name && name.length != 0 && oldToolTip)
359                {
360                    pos = name.indexOf(oldToolTip);
361                    if (pos != -1)
362                    {
363                        name = name.substring(0, pos) +
364                               name.substring(pos + oldToolTip.length);
365                    }
366                    oldToolTip = null;
367                }
368
369                if (master.toolTip && master.toolTip.length != 0)
370                {
371                    if (!master.accessibilityProperties || (master.accessibilityProperties && !master.accessibilityProperties.name))
372                    {
373                        if (!name)
374                            name = "";
375
376                        oldToolTip = " " + master.toolTip;
377                        name += oldToolTip;
378                    }
379                }
380
381                Accessibility.updateProperties();
382                break;
383            }
384        }
385    }
386}
387
388}
389