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.skins
13{
14
15import flash.display.DisplayObject;
16import flash.display.DisplayObjectContainer;
17import flash.display.Graphics;
18import flash.display.Loader;
19import flash.display.LoaderInfo;
20import flash.display.Shape;
21import flash.events.ErrorEvent;
22import flash.events.Event;
23import flash.events.IOErrorEvent;
24import flash.geom.Rectangle;
25import flash.net.URLRequest;
26import flash.system.ApplicationDomain;
27import flash.system.LoaderContext;
28import flash.utils.getDefinitionByName;
29import mx.core.EdgeMetrics;
30import mx.core.FlexLoader;
31import mx.core.FlexShape;
32import mx.core.IChildList;
33import mx.core.IContainer;
34import mx.core.IRawChildrenContainer;
35import mx.core.mx_internal;
36import mx.core.IRectangularBorder;
37import mx.managers.ISystemManager;
38import mx.managers.SystemManager;
39import mx.resources.IResourceManager;
40import mx.resources.ResourceManager;
41import mx.styles.ISimpleStyleClient;
42
43use namespace mx_internal;
44
45[ResourceBundle("skins")]
46
47/**
48 *  The RectangularBorder class is an abstract base class for various classes
49 *  that draw rectangular borders around UIComponents.
50 *
51 *  <p>This class implements support for the <code>backgroundImage</code>,
52 *  <code>backgroundSize</code>, and <code>backgroundAttachment</code> styles.</p>
53 */
54public class RectangularBorder extends Border implements IRectangularBorder
55{
56    include "../core/Version.as";
57
58    //--------------------------------------------------------------------------
59    //
60    //  Constructor
61    //
62    //--------------------------------------------------------------------------
63
64    /**
65     *  Constructor.
66     */
67    public function RectangularBorder()
68    {
69        super();
70
71        addEventListener(Event.REMOVED, removedHandler);
72    }
73
74    //--------------------------------------------------------------------------
75    //
76    //  Variables
77    //
78    //--------------------------------------------------------------------------
79
80    /**
81     *  @private
82     *  The value of the backgroundImage style may be either a string
83     *  or a Class pointer. Either way, the value of the backgroundImage
84     *  style is stored here, so that we can detect when it changes.
85     */
86    private var backgroundImageStyle:Object
87
88    /**
89     *  @private
90     *  Original width of background image, before it is scaled.
91     */
92    private var backgroundImageWidth:Number;
93
94    /**
95     *  @private
96     *  Original height of background image, before it is scaled.
97     */
98    private var backgroundImageHeight:Number;
99
100    /**
101     *  @private
102     *  Used for accessing localized Error messages.
103     */
104    private var resourceManager:IResourceManager =
105                                    ResourceManager.getInstance();
106
107    //--------------------------------------------------------------------------
108    //
109    //  Properties
110    //
111    //--------------------------------------------------------------------------
112
113    //----------------------------------
114    //  backgroundImage
115    //----------------------------------
116
117    /**
118     *  The DisplayObject instance that contains the background image, if any.
119     *  This object is a sibling of the RectangularBorder instance.
120     */
121    private var backgroundImage:DisplayObject;
122
123    /**
124     *  Contains <code>true</code> if the RectangularBorder instance
125     *  contains a background image.
126     */
127    public function get hasBackgroundImage():Boolean
128    {
129        return backgroundImage != null;
130    }
131
132    //----------------------------------
133    //  backgroundImageBounds
134    //----------------------------------
135
136    /**
137     *  @private
138     *  Storage for backgroundImageBounds property.
139     */
140    private var _backgroundImageBounds:Rectangle;
141
142    /**
143     *  Rectangular area within which to draw the background image.
144     *
145     *  This can be larger than the dimensions of the border
146     *  if the parent container has scrollable content.
147     *  If this property is null, the border can use
148     *  the parent's size and <code>viewMetrics</code> property to determine its value.
149     */
150    public function get backgroundImageBounds():Rectangle
151    {
152        return _backgroundImageBounds;
153    }
154
155    /**
156     *  @private
157     */
158    public function set backgroundImageBounds(value:Rectangle):void
159    {
160        if (_backgroundImageBounds && value && _backgroundImageBounds.equals(value))
161            return;
162
163        _backgroundImageBounds = value;
164
165        invalidateDisplayList();
166    }
167
168    //--------------------------------------------------------------------------
169    //
170    //  Overridden methods
171    //
172    //--------------------------------------------------------------------------
173
174    /**
175     *  @private
176     */
177    override protected function updateDisplayList(unscaledWidth:Number,
178                                                  unscaledHeight:Number):void
179    {
180        if (!parent)
181            return;
182
183        // If background image has changed, then load new one.
184        var newStyle:Object = getStyle("backgroundImage");
185        if (newStyle != backgroundImageStyle)
186        {
187            // Discard old background image.
188            removedHandler(null);
189
190            backgroundImageStyle = newStyle;
191
192            // The code below looks a lot like Loader.loadContent().
193            var cls:Class;
194
195            // The "as" operator checks to see if newStyle
196            // can be coerced to a Class.
197            if (newStyle && newStyle as Class)
198            {
199                // Load background image given a class pointer
200                cls = Class(newStyle);
201                initBackgroundImage(new cls());
202            }
203            else if (newStyle && newStyle is String)
204            {
205                try
206                {
207                    cls = Class(getDefinitionByName(String(newStyle)));
208                }
209                catch(e:Error)
210                {
211                    // ignore
212                }
213
214                if (cls)
215                {
216                    var newStyleObj:DisplayObject = new cls();
217                    initBackgroundImage(newStyleObj);
218                }
219                else
220                {
221                    // This code is a subset of Loader.loadContent().
222
223                    // Load background image from external URL.
224                    const loader:Loader = new FlexLoader();
225                    loader.contentLoaderInfo.addEventListener(
226                        Event.COMPLETE, completeEventHandler);
227                    loader.contentLoaderInfo.addEventListener(
228                        IOErrorEvent.IO_ERROR, errorEventHandler);
229                    loader.contentLoaderInfo.addEventListener(
230                        ErrorEvent.ERROR, errorEventHandler);
231                    var loaderContext:LoaderContext = new LoaderContext();
232                    loaderContext.applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);
233                    loader.load(new URLRequest(String(newStyle)), loaderContext);
234                }
235            }
236            else if (newStyle)
237            {
238                var message:String = resourceManager.getString(
239                    "skins", "notLoaded", [ newStyle ]);
240                throw new Error(message);
241            }
242        }
243
244        if (backgroundImage)
245            layoutBackgroundImage();
246    }
247
248    //--------------------------------------------------------------------------
249    //
250    //  Methods
251    //
252    //--------------------------------------------------------------------------
253
254    /**
255     *  @private
256     */
257    private function initBackgroundImage(image:DisplayObject):void
258    {
259        backgroundImage = image;
260
261        if (image is Loader)
262        {
263            backgroundImageWidth = Loader(image).contentLoaderInfo.width;
264            backgroundImageHeight = Loader(image).contentLoaderInfo.height;
265        }
266        else
267        {
268            backgroundImageWidth = backgroundImage.width;
269            backgroundImageHeight = backgroundImage.height;
270
271            if (image is ISimpleStyleClient)
272            {
273                // Set the image's styleName to our styleName. We
274                // can't set styleName to this since we aren't an
275                // IStyleClient.
276                ISimpleStyleClient(image).styleName = styleName;
277            }
278        }
279        // To optimize memory use, we've declared RectangularBorder to be a Shape.
280        // As a result, it cannot have any children.
281        // Make the backgroundImage a sibling of this RectangularBorder,
282        // which is positioned just on top of the RectangularBorder.
283        var childrenList:IChildList = parent is IRawChildrenContainer ?
284                                         IRawChildrenContainer(parent).rawChildren :
285                                         IChildList(parent);
286
287        const backgroundMask:Shape = new FlexShape();
288        backgroundMask.name = "backgroundMask";
289        backgroundMask.x = 0;
290        backgroundMask.y = 0;
291        childrenList.addChild(backgroundMask);
292
293        var myIndex:int = childrenList.getChildIndex(this);
294        childrenList.addChildAt(backgroundImage, myIndex + 1);
295
296        backgroundImage.mask = backgroundMask;
297    }
298
299    /**
300     *  Layout the background image.
301     */
302    public function layoutBackgroundImage():void
303    {
304        var p:DisplayObject = parent;
305
306        var bm:EdgeMetrics = p is IContainer ?
307                             IContainer(p).viewMetrics :
308                             borderMetrics;
309
310        var scrollableBk:Boolean =
311            getStyle("backgroundAttachment") != "fixed";
312
313        var sW:Number,
314            sH:Number;
315        if (_backgroundImageBounds)
316        {
317            sW = _backgroundImageBounds.width;
318            sH = _backgroundImageBounds.height;
319        }
320        else
321        {
322            sW = width - bm.left - bm.right;
323            sH = height - bm.top - bm.bottom;
324        }
325
326        // Scale according to backgroundSize.
327        var percentage:Number = getBackgroundSize();
328
329        var sX:Number,
330            sY:Number;
331        if (isNaN(percentage))
332        {
333            sX = 1.0;
334            sY = 1.0;
335        }
336        else
337        {
338            var scale:Number = percentage * 0.01;
339            sX = scale * sW / backgroundImageWidth;
340            sY = scale * sH / backgroundImageHeight;
341        }
342        backgroundImage.scaleX = sX;
343        backgroundImage.scaleY = sY;
344
345        // Center everything.
346        // Use a scrollRect to position and clip the image.
347        var offsetX:Number =
348                Math.round(0.5 * (sW - backgroundImageWidth * sX));
349        var offsetY:Number =
350                Math.round(0.5 * (sH - backgroundImageHeight * sY));
351
352        backgroundImage.x = bm.left;
353        backgroundImage.y = bm.top;
354
355        const backgroundMask:Shape = Shape(backgroundImage.mask);
356        backgroundMask.x = bm.left;
357        backgroundMask.y = bm.top;
358
359        // Adjust offsets by scroll positions.
360        if (scrollableBk && p is IContainer)
361        {
362            offsetX -= IContainer(p).horizontalScrollPosition;
363            offsetY -= IContainer(p).verticalScrollPosition;
364        }
365
366        // Adjust alpha to match backgroundAlpha
367        backgroundImage.alpha = getStyle("backgroundAlpha");
368
369        backgroundImage.x += offsetX;
370        backgroundImage.y += offsetY;
371
372        var maskWidth:Number = width - bm.left - bm.right;
373        var maskHeight:Number = height - bm.top - bm.bottom;
374        if (backgroundMask.width != maskWidth ||
375            backgroundMask.height != maskHeight)
376        {
377            var g:Graphics = backgroundMask.graphics;
378            g.clear();
379            g.beginFill(0xFFFFFF);
380            g.drawRect(0, 0, maskWidth, maskHeight);
381            g.endFill();
382        }
383    }
384
385    /**
386     *  @private
387     */
388    private function getBackgroundSize():Number
389    {
390        var percentage:Number = NaN;
391        var backgroundSize:Object = getStyle("backgroundSize");
392
393        if (backgroundSize && backgroundSize is String)
394        {
395            var index:int = backgroundSize.indexOf("%");
396            if (index != -1)
397                percentage = Number(backgroundSize.substr(0, index));
398        }
399
400        return percentage;
401    }
402
403    //--------------------------------------------------------------------------
404    //
405    //  Event handlers
406    //
407    //--------------------------------------------------------------------------
408
409    /**
410     *  @private
411     */
412    private function errorEventHandler(event:Event):void
413    {
414        // Ignore errors that occure during background image loading.
415    }
416
417    /**
418     *  @private
419     */
420    private function completeEventHandler(event:Event):void
421    {
422        if (!parent)
423            return;
424
425        var target:DisplayObject = DisplayObject(LoaderInfo(event.target).loader);
426        initBackgroundImage(target);
427        layoutBackgroundImage();
428        //  rebroadcast for automation support
429        dispatchEvent(event.clone());
430    }
431
432    /**
433     * Discard old background image.
434     *
435     *  @private
436     */
437    private function removedHandler(event:Event):void
438    {
439        if (backgroundImage)
440        {
441            var childrenList:IChildList = parent is IRawChildrenContainer ?
442                                             IRawChildrenContainer(parent).rawChildren :
443                                             IChildList(parent);
444
445            childrenList.removeChild(backgroundImage.mask);
446            childrenList.removeChild(backgroundImage);
447            backgroundImage = null;
448        }
449    }
450}
451
452}
453