//////////////////////////////////////////////////////////////////////////////// // // 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 spark.components { import flash.display.DisplayObject; import flash.events.Event; import flash.events.IOErrorEvent; import flash.events.SecurityErrorEvent; import flash.events.TimerEvent; import flash.net.URLRequest; import flash.utils.Timer; import mx.controls.listClasses.*; import mx.core.DPIClassification; import mx.core.FlexGlobals; import mx.core.mx_internal; import mx.graphics.BitmapFillMode; import mx.graphics.BitmapScaleMode; import mx.styles.CSSStyleDeclaration; import mx.utils.DensityUtil; import spark.components.supportClasses.StyleableTextField; import spark.core.ContentCache; import spark.core.DisplayObjectSharingMode; import spark.core.IContentLoader; import spark.core.IGraphicElement; import spark.core.IGraphicElementContainer; import spark.core.ISharedDisplayObject; import spark.primitives.BitmapImage; import spark.utils.MultiDPIBitmapSource; use namespace mx_internal; //-------------------------------------- // Styles //-------------------------------------- include "../styles/metadata/GapStyles.as" /** * The delay value before attempting to load the * icon's source if it has not been cached already. * *
The reason a delay is useful is while scrolling around, you * don't necessarily want the image to load up immediately. Instead, * you should wait a certain delay period to make sure the user actually * wants to see this item renderer.
* * @default 500 * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [Style(name="iconDelay", type="Number", format="Time", inherit="no")] /** * Name of the CSS Style declaration to use for the styles for the * message component. * * @default iconItemRendererMessageStyle * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [Style(name="messageStyleName", type="String", inherit="no")] /** * The IconItemRenderer class is a performant item * renderer optimized for mobile devices. * It displays four optional parts for each item in the * list-based control: * *iconField
or
* iconFunction
property.labelField
or labelFunction
property.messageField
or messageFunction
property.decorator
property.To apply CSS styles to the single-line text label, such as font size and color,
* set the styles on the IconItemRenderer class.
* To set styles on the multi-line message, use the messageStyleNameM
style property.
* The following example sets the text styles for both the text label and message:
* <fx:Style> * .myFontStyle { * fontSize: 15; * color: #9933FF; * } * * </fx:Style> * * <s:List id="myList" * width="100%" height="100%" * labelField="firstName"> * <s:itemRenderer> * <fx:Component> * <s:IconItemRenderer messageStyleName="myFontStyle" fontSize="25" * labelField="firstName" * messageField="lastName" * decorator="@Embed(source='assets/logo_small.jpg')"/> * </fx:Component> * </s:itemRenderer> * <s:ArrayCollection> * <fx:Object firstName="Dave" lastName="Duncam" company="Adobe" phone="413-555-1212"/> * <fx:Object firstName="Sally" lastName="Smith" company="Acme" phone="617-555-1491"/> * <fx:Object firstName="Jim" lastName="Jackson" company="Beta" phone="413-555-2345"/> * <fx:Object firstName="Mary" lastName="Moore" company="Gamma" phone="617-555-1899"/> * </s:ArrayCollection> * </s:List> ** * @mxml * *
The <s:IconItemRenderer>
tag inherits all of the tag
* attributes of its superclass and adds the following tag attributes:
* <s:IconItemRenderer * Properties * decorator="" * iconContentLoader="See property description" * iconField="null" * iconFillMode=""scale * iconFunction="null" * iconHeight="NaN" * iconPlaceholder="null" * iconScaleMode="stretch" * iconWidth="NaN" * label="" * labelField="null" * labelFunction="null" * messageField="null" * messageFunction="null" * * Common Styles * horizontalGap="8" * iconDelay="500" * messageStyleName="iconItemRendererMessageStyle" * verticalGap="6" * > ** * @see spark.components.List * @see mx.core.IDataRenderer * @see spark.components.IItemRenderer * @see spark.components.supportClasses.ItemRenderer * @see spark.components.LabelItemRenderer * @includeExample examples/IconItemRendererExample.mxml -noswf * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public class IconItemRenderer extends LabelItemRenderer implements IGraphicElementContainer, ISharedDisplayObject { //-------------------------------------------------------------------------- // // Class constants // //-------------------------------------------------------------------------- /** * @private * Static icon image cache. This is the default for iconContentLoader. */ mx_internal static var _imageCache:ContentCache; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function IconItemRenderer() { super(); if (_imageCache == null) { _imageCache = new ContentCache(); _imageCache.enableCaching = true; _imageCache.maxCacheEntries = 100; } // set default messageDisplay width switch (applicationDPI) { case DPIClassification.DPI_320: { oldUnscaledWidth = 640; break; } case DPIClassification.DPI_240: { oldUnscaledWidth = 480; break; } default: { // default PPI160 oldUnscaledWidth = 320; break; } } } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * @private * Stores the text of the label component. This is calculated in * commitProperties() based on labelFunction, labelField, and label. * *
We can't just use labelDisplay.text because it may contain * a truncated value.
*/ mx_internal var labelText:String = ""; /** * @private * Stores the text of the message component. This is calculated in * commitProperties() based on messageField and messageFunction. * *We can't just use messageDisplay.text because it may contain * a truncated value (Technically we don't truncate message's text * at the moment because it's multi-line text, but in the future * we may not do that, and this feels more consistent with * how we deal with labels, so we still keep this "extra" * variable around even though technically it's not needed).
*/ mx_internal var messageText:String = ""; /** * @private * Since iconDisplay is a GraphicElement, we have to call its lifecycle methods * directly. */ private var iconNeedsValidateProperties:Boolean = false; /** * @private * Since iconDisplay is a GraphicElement, we have to call its lifecycle methods * directly. */ private var iconNeedsValidateSize:Boolean = false; /** * @private * Since iconDisplay is a GraphicElement, we have to call help assign * its display object */ private var iconNeedsDisplayObjectAssignment:Boolean = false; /** * @private * Timer, used to delay setting the source on the icon. */ private var iconSetterDelayTimer:Timer; /** * @private * The source to set iconDisplay to, after waiting an appropriate delay period */ private var iconSourceToLoad:Object; /** * @private * The width of the component on the previous layout manager * pass. This gets set in updateDisplayList() and used in measure() on * the next layout pass. This is so our "guessed width" in measure() * will be as accurate as possible since messageDisplay is multiline and * the messageDisplay height is dependent on the width. * * In the constructor, this is actually set based on the DPI. */ mx_internal var oldUnscaledWidth:Number; /** * @private * Since decoratorDisplay is a GraphicElement, we have to call its lifecycle methods * directly. */ private var decoratorNeedsValidateProperties:Boolean = false; /** * @private * Since decoratorDisplay is a GraphicElement, we have to call its lifecycle methods * directly. */ private var decoratorNeedsValidateSize:Boolean = false; /** * @private * Since decoratorDisplay is a GraphicElement, we have to call help assign * its display object */ private var decoratorNeedsDisplayObjectAssignment:Boolean = false; //-------------------------------------------------------------------------- // // Public Properties: Overridden // //-------------------------------------------------------------------------- /** * @private */ override public function set data(value:Object):void { super.data = value; iconChanged = true; labelChanged = true; messageChanged = true; invalidateProperties(); } /** *If labelFunction
= labelField
= null,
* then use the label
property that gets
* pushed in from the list control.
* However if labelField
is explicitly set to
* ""
(the empty string), then no label appears.
The default value is a static content cache defined on IconItemRenderer * that allows up to 100 entries.
* * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get iconContentLoader():IContentLoader { return _iconContentLoader; } /** * @private */ public function set iconContentLoader(value:IContentLoader):void { if (value == _iconContentLoader) return; _iconContentLoader = value; if (iconDisplay) iconDisplay.contentLoader = _iconContentLoader; } //---------------------------------- // decorator //---------------------------------- /** * @private */ private var _decorator:Object; /** * @private */ private var decoratorChanged:Boolean; /** * @private * The class to use when instantiating the decorator for IconItemRenderer. * This class must extend spark.primitives.BitmapImage. * This property was added for Design View so they can set this to a special * subclass of BitmapImage that knows how to load and resolve resources in Design View. */ mx_internal var decoratorDisplayClass:Class = BitmapImage; /** * The display object component used to * display the decorator for this item renderer. * * @default null * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected var decoratorDisplay:BitmapImage; /** * The decorator icon that appears on the right side * of this item renderer. * *The decorator icon ignores the verticalAlign
style
* and is always centered vertically.
The decorator icon is expected to be an embedded asset. There can * be performance degradation if using external assets.
* * @default "" * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get decorator():Object { return _decorator; } /** * @private */ public function set decorator(value:Object):void { if (value == _decorator) return; _decorator = value; decoratorChanged = true; invalidateProperties(); } //---------------------------------- // labelField //---------------------------------- /** * @private */ private var _labelField:String = null; /** * @private */ private var labelFieldOrFunctionChanged:Boolean; /** * @private */ private var labelChanged:Boolean; /** * The name of the field in the data provider items to display * as the label. * ThelabelFunction
property overrides this property.
*
* If labelFunction
= labelField
= null,
* then use the label
property that gets
* pushed in from the list-based control.
* However if labelField
is explicitly set to
* ""
(the empty string),
* then no label appears.
labelFunction
property overrides
* the labelField
property.
*
* You can supply a labelFunction
that finds the
* appropriate fields and returns a displayable string. The
* labelFunction
is also good for handling formatting and
* localization.
The label function takes a single argument which is the item in * the data provider and returns a String.
** myLabelFunction(item:Object):String* *
If labelFunction
= labelField
= null,
* then use the label
property that gets
* pushed in from the list-based control.
* However if labelField
is explicitly set to
* ""
(the empty string),
* then no label appears.
iconField
is null
, and the item renderer
* does not display an icon.
*
* @default null
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get iconField():String
{
return _iconField;
}
/**
* @private
*/
public function set iconField(value:String):void
{
if (value == _iconField)
return;
_iconField = value;
iconFieldOrFunctionChanged = true;
iconChanged = true;
invalidateProperties();
}
//----------------------------------
// iconFillMode
//----------------------------------
/**
* @private
*/
private var _iconFillMode:String = BitmapFillMode.SCALE;
[Inspectable(category="General", enumeration="clip,repeat,scale", defaultValue="scale")]
/**
* @copy spark.primitives.BitmapImage#fillMode
*
* @default mx.graphics.BitmapFillMode.SCALE
*
* @see mx.graphics.BitmapFillMode
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get iconFillMode():String
{
return _iconFillMode;
}
/**
* @private
*/
public function set iconFillMode(value:String):void
{
if (value == _iconFillMode)
return;
_iconFillMode = value;
if (iconDisplay)
iconDisplay.fillMode = _iconFillMode;
}
//----------------------------------
// iconFunction
//----------------------------------
/**
* @private
*/
private var _iconFunction:Function;
/**
* A user-supplied function to run on each item to determine its icon.
* The iconFunction
property overrides
* the iconField
property.
*
* You can supply an iconFunction
that finds the
* appropriate fields and returns a valid URL or object to be used as
* the icon.
The icon function takes a single argument which is the item in
* the data provider and returns an Object that gets passed to a
* spark.primitives.BitmapImage
object as the source
* property. Icon function can return a valid URL pointing to an image
* or a Class file that represents an image. To see what other types
* of objects can be returned from the icon
* function, see the BitmapImage
's documentation
* myIconFunction(item:Object):Object* * @default null * * @see spark.primitives.BitmapImage#source * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get iconFunction():Function { return _iconFunction; } /** * @private */ public function set iconFunction(value:Function):void { if (value == _iconFunction) return; _iconFunction = value; iconFieldOrFunctionChanged = true; iconChanged = true; invalidateProperties(); } //---------------------------------- // iconHeight //---------------------------------- /** * @private */ private var _iconHeight:Number; /** * The height of the icon. If unspecified, the * intrinsic height of the image is used. * * @default NaN * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get iconHeight():Number { return _iconHeight; } /** * @private */ public function set iconHeight(value:Number):void { if (value == _iconHeight) return; _iconHeight = value; if (iconDisplay) iconDisplay.explicitHeight = _iconHeight; invalidateSize(); invalidateDisplayList(); } //---------------------------------- // iconPlaceholder //---------------------------------- /** * @private */ private var _iconPlaceholder:Object; /** * The icon asset to use while an externally loaded asset is * being downloaded. * *
This asset should be an embedded image and not an externally * loaded image.
* * @default null * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get iconPlaceholder():Object { return _iconPlaceholder; } /** * @private */ public function set iconPlaceholder(value:Object):void { if (value == _iconPlaceholder) return; _iconPlaceholder = value; iconChanged = true; invalidateProperties(); // clear clearOnLoad if necessary if (iconDisplay) iconDisplay.clearOnLoad = (iconPlaceholder == null); } //---------------------------------- // iconScaleMode //---------------------------------- /** * @private */ private var _iconScaleMode:String = BitmapScaleMode.STRETCH; [Inspectable(category="General", enumeration="stretch,letterbox", defaultValue="stretch")] /** * @copy spark.primitives.BitmapImage#scaleMode * * @defaultmx.graphics.BitmapScaleMode.STRETCH
*
* @see mx.graphics.BitmapScaleMode
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get iconScaleMode():String
{
return _iconScaleMode;
}
/**
* @private
*/
public function set iconScaleMode(value:String):void
{
if (value == _iconScaleMode)
return;
_iconScaleMode = value;
if (iconDisplay)
iconDisplay.scaleMode = _iconScaleMode;
}
//----------------------------------
// iconWidth
//----------------------------------
/**
* @private
*/
private var _iconWidth:Number;
/**
* The width of the icon. If unspecified, the
* intrinsic width of the image is used.
*
* @default NaN
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get iconWidth():Number
{
return _iconWidth;
}
/**
* @private
*/
public function set iconWidth(value:Number):void
{
if (value == _iconWidth)
return;
_iconWidth = value;
if (iconDisplay)
iconDisplay.explicitWidth = _iconWidth;
invalidateSize();
invalidateDisplayList();
}
//----------------------------------
// messageField
//----------------------------------
/**
* @private
*/
private var _messageField:String;
/**
* The text component used to
* display the message data of the item renderer.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected var messageDisplay:StyleableTextField;
/**
* @private
*/
private var messageFieldOrFunctionChanged:Boolean;
/**
* @private
*/
private var messageChanged:Boolean;
/**
* The name of the field in the data items to display
* as the message.
* The messageFunction
property overrides this property.
*
* Use the messageStyleName
style to control the
* appearance of the text.
messageFunction
property overrides
* the messageField
property.
*
* You can supply a messageFunction
that finds the
* appropriate fields and returns a displayable string. The
* messageFunction
is also good for handling formatting and
* localization.
The message function takes a single argument which is the item in * the data provider and returns a String.
** myMessageFunction(item:Object):String* * @default null * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get messageFunction():Function { return _messageFunction; } /** * @private */ public function set messageFunction(value:Function):void { if (value == _messageFunction) return; _messageFunction = value; messageFieldOrFunctionChanged = true; messageChanged = true; invalidateProperties(); } //---------------------------------- // redrawRequested //---------------------------------- /** * @private */ private var _redrawRequested:Boolean = false; /** * @inheritDoc * *
We implement this as part of ISharedDisplayObject so the iconDisplay * can share our display object.
* * @langversion 3.0 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function get redrawRequested():Boolean { return _redrawRequested; } /** * @private */ public function set redrawRequested(value:Boolean):void { _redrawRequested = value; } //-------------------------------------------------------------------------- // // IGraphicElementContainer // //-------------------------------------------------------------------------- /** * Notify the host that an element layer has changed. * * TheIGraphicElementHost
must re-evaluates the sequences of
* graphic elements with shared DisplayObjects and may need to re-assign the
* DisplayObjects and redraw the sequences as a result.
*
* Typically the host will perform this in its
* validateProperties()
method.
*
* @param element The element that has changed size.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function invalidateGraphicElementSharing(element:IGraphicElement):void
{
// since the only graphic elements are hooked up to drawing with the background,
// just invalidate display list
if (element == iconDisplay)
iconNeedsDisplayObjectAssignment = true;
else if (element == decoratorDisplay)
decoratorNeedsDisplayObjectAssignment = true;
invalidateProperties();
}
/**
* Notify the host component that an element changed and needs to validate properties.
*
* The IGraphicElementHost
must call the validateProperties()
* method on the IGraphicElement to give it a chance to commit its properties.
*
* Typically the host will validate the elements' properties in its
* validateProperties()
method.
*
* @param element The element that has changed.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function invalidateGraphicElementProperties(element:IGraphicElement):void
{
if (element == iconDisplay)
iconNeedsValidateProperties = true;
else if (element == decoratorDisplay)
decoratorNeedsValidateProperties = true;
invalidateProperties();
}
/**
* Notify the host component that an element size has changed.
*
* The IGraphicElementHost
must call the validateSize()
* method on the IGraphicElement to give it a chance to validate its size.
*
* Typically the host will validate the elements' size in its
* validateSize()
method.
*
* @param element The element that has changed size.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function invalidateGraphicElementSize(element:IGraphicElement):void
{
if (element == iconDisplay)
iconNeedsValidateSize = true;
else if (element == decoratorDisplay)
decoratorNeedsValidateSize = true;
invalidateSize();
}
/**
* Notify the host component that an element has changed and needs to be redrawn.
*
* The IGraphicElementHost
must call the validateDisplayList()
* method on the IGraphicElement to give it a chance to redraw.
*
* Typically the host will validate the elements' display lists in its
* validateDisplayList()
method.
*
* @param element The element that has changed.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function invalidateGraphicElementDisplayList(element:IGraphicElement):void
{
if (element.displayObject is ISharedDisplayObject)
ISharedDisplayObject(element.displayObject).redrawRequested = true;
invalidateDisplayList();
}
//--------------------------------------------------------------------------
//
// Overridden methods: UIComponent
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function createChildren():void
{
super.createChildren();
// create any children you need in here
// iconDisplay, messageDisplay, and decoratorDisplay are created in
// commitProperties() since they are dependent on
// other properties and we don't always create them
// labelText just uses labelElement to display its data
}
/**
* @private
*/
override public function styleChanged(styleName:String):void
{
var allStyles:Boolean = !styleName || styleName == "styleName";
super.styleChanged(styleName);
// if message styles may have changed, let's null out the old
// value and notify messageDisplay
if (allStyles || styleName == "messageStyleName")
{
if (messageDisplay)
{
var messageStyleName:String = getStyle("messageStyleName");
if (messageStyleName)
{
var styleDecl:CSSStyleDeclaration =
styleManager.getMergedStyleDeclaration("." + messageStyleName);
if (styleDecl)
{
messageDisplay.styleDeclaration = styleDecl;
messageDisplay.styleChanged("styleName");
}
}
}
}
}
/**
* @private
*/
override protected function commitProperties():void
{
super.commitProperties();
if (decoratorChanged)
{
decoratorChanged = false;
// let's see if we need to create or remove it
if (decorator && !decoratorDisplay)
{
createDecoratorDisplay();
}
else if (!decorator && decoratorDisplay)
{
destroyDecoratorDisplay();
}
// if we have a decorator display and decoratorChanged was
// set to true, then we should make sure we're pointing to
// the right decorator source to display.
if (decoratorDisplay)
decoratorDisplay.source = decorator;
invalidateSize();
invalidateDisplayList();
}
if (iconFieldOrFunctionChanged)
{
iconFieldOrFunctionChanged = false;
// let's see if we need to create or remove it
if ((iconField || (iconFunction != null)) && !iconDisplay)
{
createIconDisplay();
if (iconDisplay)
attachLoadingListenersToIconDisplay();
}
else if (!(iconField || (iconFunction != null)) && iconDisplay)
{
destroyIconDisplay();
}
invalidateSize();
invalidateDisplayList();
}
if (messageFieldOrFunctionChanged)
{
messageFieldOrFunctionChanged = false;
// let's see if we need to create or remove it
if ((messageField || (messageFunction != null)) && !messageDisplay)
{
createMessageDisplay();
}
else if (!(messageField || (messageFunction != null)) && messageDisplay)
{
destroyMessageDisplay();
}
invalidateSize();
invalidateDisplayList();
}
// label is created in super.createChildren()
if (iconChanged)
{
iconChanged = false;
// we set the icon after a delay for performance benefits and
// so we can cancel the load if we're scrolling fast
// we still grab the source here so we can make sure we don't
// cancel a load when the data is reset (if the source is the same)
// if icon, try setting that
if (iconFunction != null)
{
setIconDisplaySource(iconFunction(data));
}
else if (iconField)
{
try
{
if (iconField in data && data[iconField] != null)
setIconDisplaySource(data[iconField]);
else
setIconDisplaySource(null);
}
catch(e:Error)
{
setIconDisplaySource(null);
}
}
}
if (messageChanged)
{
messageChanged = false;
if (messageFunction != null)
{
messageText = messageFunction(data);
messageDisplay.text = messageText;
}
else if (messageField)
{
try
{
if (messageField in data && data[messageField] != null)
{
messageText = data[messageField];
messageDisplay.text = messageText;
}
else
{
messageText = "";
messageDisplay.text = messageText;
}
}
catch(e:Error)
{
messageText = "";
messageDisplay.text = messageText;
}
}
}
if (labelChanged)
{
labelChanged = false;
// if label, try setting that
if (labelFunction != null)
{
labelText = labelFunction(data);
if (!labelDisplay)
createLabelDisplay();
labelDisplay.text = labelText;
}
else if (labelField) // if labelField is not null or "", then this is a user-set value
{
try
{
if (labelField in data && data[labelField] != null)
{
labelText = data[labelField];
if (!labelDisplay)
createLabelDisplay();
labelDisplay.text = labelText;
}
else
{
labelText = "";
if (!labelDisplay)
createLabelDisplay();
labelDisplay.text = labelText;
}
}
catch(e:Error)
{
labelText = "";
if (!labelDisplay)
createLabelDisplay();
labelDisplay.text = labelText;
}
}
else if (label && labelField === null) // if there's a label and labelField === null, then show label
{
labelText = label;
if (!labelDisplay)
createLabelDisplay();
labelDisplay.text = labelText;
}
else // if labelField === ""
{
// get rid of labelDisplay if present
if (labelDisplay)
destroyLabelDisplay();
}
invalidateSize();
invalidateDisplayList();
}
if (iconNeedsDisplayObjectAssignment)
{
iconNeedsDisplayObjectAssignment = false;
assignDisplayObject(iconDisplay);
}
if (decoratorNeedsDisplayObjectAssignment)
{
decoratorNeedsDisplayObjectAssignment = false;
assignDisplayObject(decoratorDisplay);
}
}
/**
* @private
*/
override public function validateProperties():void
{
super.validateProperties();
// Since IGraphicElement is not ILayoutManagerClient, we need to make sure we
// validate properties of the elements
if (iconNeedsValidateProperties)
{
iconNeedsValidateProperties = false;
if (iconDisplay)
iconDisplay.validateProperties();
}
if (decoratorNeedsValidateProperties)
{
decoratorNeedsValidateProperties = false;
if (decoratorDisplay)
decoratorDisplay.validateProperties();
}
}
/**
* @private
*/
private function assignDisplayObject(bitmapImage:BitmapImage):void
{
if (bitmapImage)
{
// try using this display object first
if (bitmapImage.setSharedDisplayObject(this))
{
bitmapImage.displayObjectSharingMode = DisplayObjectSharingMode.USES_SHARED_OBJECT;
}
else
{
// if we can't use this as the display object, then let's see if
// the icon already has and owns a display object
var ownsDisplayObject:Boolean = (bitmapImage.displayObjectSharingMode != DisplayObjectSharingMode.USES_SHARED_OBJECT);
// If the element doesn't have a DisplayObject or it doesn't own
// the DisplayObject it currently has, then create a new one
var displayObject:DisplayObject = bitmapImage.displayObject;
if (!ownsDisplayObject || !displayObject)
displayObject = bitmapImage.createDisplayObject();
// Add the display object as a child
// Check displayObject for null, some graphic elements
// may choose not to create a DisplayObject during this pass.
if (displayObject)
addChild(displayObject);
bitmapImage.displayObjectSharingMode = DisplayObjectSharingMode.OWNS_UNSHARED_OBJECT;
}
}
}
/**
* @private
* Creates the messageDisplay
component.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function createMessageDisplay():void
{
messageDisplay = StyleableTextField(createInFontContext(StyleableTextField));
messageDisplay.styleName = this;
messageDisplay.editable = false;
messageDisplay.selectable = false;
messageDisplay.multiline = true;
messageDisplay.wordWrap = true;
var messageStyleName:String = getStyle("messageStyleName");
if (messageStyleName)
{
var styleDecl:CSSStyleDeclaration =
styleManager.getMergedStyleDeclaration("." + messageStyleName);
if (styleDecl)
messageDisplay.styleDeclaration = styleDecl;
}
addChild(messageDisplay);
}
/**
* @private
* Destroys the messageDisplay component.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function destroyMessageDisplay():void
{
removeChild(messageDisplay);
messageDisplay = null;
}
/**
* @private
* Creates the iconDisplay component.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function createIconDisplay():void
{
iconDisplay = new iconDisplayClass();
iconDisplay.contentLoader = iconContentLoader;
iconDisplay.fillMode = iconFillMode;
iconDisplay.scaleMode = iconScaleMode;
if (!isNaN(iconWidth))
iconDisplay.explicitWidth = iconWidth;
if (!isNaN(iconHeight))
iconDisplay.explicitHeight = iconHeight;
iconDisplay.parentChanged(this);
iconNeedsDisplayObjectAssignment = true;
}
/**
* @private
* Destroys the iconDisplay component.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function destroyIconDisplay():void
{
// need to remove the display object
var oldDisplayObject:DisplayObject = iconDisplay.displayObject;
if (oldDisplayObject)
{
// If the element created the display object
if (iconDisplay.displayObjectSharingMode != DisplayObjectSharingMode.USES_SHARED_OBJECT &&
oldDisplayObject.parent == this)
{
removeChild(oldDisplayObject);
}
}
iconDisplay.parentChanged(null);
iconDisplay = null;
}
/**
* @private
* Creates the decoratorDisplay component.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function createDecoratorDisplay():void
{
decoratorDisplay = new decoratorDisplayClass();
decoratorDisplay.parentChanged(this);
decoratorNeedsDisplayObjectAssignment = true;
}
/**
* @private
* Destroys the decoratorDisplay component.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function destroyDecoratorDisplay():void
{
// need to remove the display object
var oldDisplayObject:DisplayObject = decoratorDisplay.displayObject;
if (oldDisplayObject)
{
// If the element created the display object
if (decoratorDisplay.displayObjectSharingMode != DisplayObjectSharingMode.USES_SHARED_OBJECT &&
oldDisplayObject.parent == this)
{
removeChild(oldDisplayObject);
}
}
decoratorDisplay.parentChanged(null);
decoratorDisplay = null;
}
/**
* @private
*/
private function attachLoadingListenersToIconDisplay():void
{
if (iconDisplay)
{
iconDisplay.addEventListener(IOErrorEvent.IO_ERROR, iconDisplay_loadErrorHandler, false, 0, true);
iconDisplay.addEventListener(SecurityErrorEvent.SECURITY_ERROR, iconDisplay_loadErrorHandler, false, 0, true);
}
}
/**
* @private
* Method is called when an IOError or SecurityError is dispatched
* while trying to load the icon display. The default implementation
* sets the iconDisplay's source to the iconPlaceholder.
*/
mx_internal function iconDisplay_loadErrorHandler(event:Event):void
{
iconDisplay.source = iconPlaceholder;
}
/**
* @private
*/
private function loadExternalImage(source:Object, iconDelay:Number):void
{
// set iconDisplay's source now to either iconPlaceholder
// or null (if no iconPlaceholder). this is so we don't display the old
// data while we're loading.
iconDisplay.source = iconPlaceholder;
// while we're loading,if iconPlaceholder is set,
// we'll keep that image up while we're loading
// the external content
iconDisplay.clearOnLoad = (iconPlaceholder == null);
if (iconDelay > 0)
{
// set what we're gonna load and start the timer
iconSourceToLoad = source;
if (!iconSetterDelayTimer)
{
iconSetterDelayTimer = new Timer(iconDelay, 1);
iconSetterDelayTimer.addEventListener(TimerEvent.TIMER_COMPLETE, iconSetterDelayTimer_timerCompleteHandler);
}
iconSetterDelayTimer.start();
}
else // iconDelay == 0
{
// load up the image immediately
// need to call validateProperties because we need this iconPlaceholder
// to actually get loaded up since we set iconDisplay.source to a remote
// image on the next line. BitmapImage doesn't actually attempt to load
// up the image (even if it's a locally embedded asset) until commitProperties()
iconDisplay.validateProperties();
iconDisplay.source = source;
}
}
/**
* @private
*/
private function stopLoadingExternalImage():void
{
// stop any asynch operation:
if (iconSetterDelayTimer)
{
iconSourceToLoad = null
iconSetterDelayTimer.stop();
iconSetterDelayTimer.reset();
}
}
/**
* @private
*/
private function setIconDisplaySource(source:Object):void
{
var iconDelay:Number = getStyle("iconDelay");
// if not a string or URL request (or null), load it up immediately
var isExternalSource:Boolean = (source is String || source is URLRequest);
if (!isExternalSource)
{
// get the icon source to find out if it is external or not
if (source is MultiDPIBitmapSource)
{
var app:Object = FlexGlobals.topLevelApplication;
var dpi:Number;
if ("runtimeDPI" in app)
dpi = app["runtimeDPI"];
else
dpi = DensityUtil.getRuntimeDPI();
var multiSource:Object = MultiDPIBitmapSource(source).getSource(dpi);
isExternalSource = (multiSource is String || multiSource is URLRequest);
}
}
// if null or embedded asset do it synchronously
if (!isExternalSource)
{
stopLoadingExternalImage();
// load it up
iconDisplay.source = source;
return;
}
// if it's the same source, don't cancel this load--let it continue
if (iconSourceToLoad == source)
return;
// At this point, we know we can cancel the old asynch operation
// since we're not going to use it anymore
stopLoadingExternalImage();
// we know we're loading external content, check the cache first:
var contentCache:ContentCache = iconContentLoader as ContentCache;
if (contentCache)
{
if (contentCache.getCacheEntry(source))
{
// We know we have this item cached (or atleast have attempted
// to load this item and cache it). Because we're not sure whether
// this item has finished loading or not, let's set the icon's
// source to the placeholder (or null) first so that we can make sure
// we don't leave in a stale image while the load is still happening.
iconDisplay.source = iconPlaceholder;
// while we're loading,if iconPlaceholder is set,
// we'll keep that image up while we're loading
// the external content
iconDisplay.clearOnLoad = (iconPlaceholder == null);
// need to call validateProperties because we need this iconPlaceholder
// to actually get loaded up since we set iconDisplay.source to a remote
// image on the next line. BitmapImage doesn't actually attempt to load
// up the image (even if it's a locally embedded asset) until commitProperties()
iconDisplay.validateProperties();
// now attempt to load up our other image (or grab it from the cache
// if the load had finished already)
iconDisplay.source = source;
return;
}
}
// otherwise, we need to load an external asset and use a Timer
loadExternalImage(source, iconDelay);
}
/**
* @private
*/
private function iconSetterDelayTimer_timerCompleteHandler(event:TimerEvent):void
{
// if we're off-screen, don't do anything
// when we get re-included, our data will be reset as well
if (!includeInLayout)
{
iconSourceToLoad = null;
return;
}
if (iconDisplay)
iconDisplay.source = iconSourceToLoad;
iconSourceToLoad = null;
}
/**
* @private
*/
override public function validateSize(recursive:Boolean = false):void
{
// Since IGraphicElement is not ILayoutManagerClient, we need to make sure we
// validate sizes of the elements, even in cases where recursive==false.
// Validate element size
if (iconNeedsValidateSize)
{
iconNeedsValidateSize = false;
if (iconDisplay)
iconDisplay.validateSize();
}
if (decoratorNeedsValidateSize)
{
decoratorNeedsValidateSize = false;
if (decoratorDisplay)
decoratorDisplay.validateSize();
}
super.validateSize(recursive);
}
/**
* @private
*/
override protected function measure():void
{
// don't call super.measure() because there's no need to do the work that's
// in there--we do it all in here.
//super.measure();
// start them at 0, then go through icon, label, and decorator
// and add to these
var myMeasuredWidth:Number = 0;
var myMeasuredHeight:Number = 0;
var myMeasuredMinWidth:Number = 0;
var myMeasuredMinHeight:Number = 0;
// calculate padding and horizontal gap
// verticalGap is already handled above when there's a label
// and a message since that's the only place verticalGap matters.
// if we handled verticalGap here, it might add it to the icon if
// the icon was the tallest item.
var numHorizontalSections:int = 0;
if (iconDisplay)
numHorizontalSections++;
if (decoratorDisplay)
numHorizontalSections++;
if (labelDisplay || messageDisplay)
numHorizontalSections++;
var paddingAndGapWidth:Number = getStyle("paddingLeft") + getStyle("paddingRight");
if (numHorizontalSections > 0)
paddingAndGapWidth += (getStyle("horizontalGap") * (numHorizontalSections - 1));
var hasLabel:Boolean = labelDisplay && labelDisplay.text != "";
var hasMessage:Boolean = messageDisplay && messageDisplay.text != "";
var verticalGap:Number = (hasLabel && hasMessage) ? getStyle("verticalGap") : 0;
var paddingHeight:Number = getStyle("paddingTop") + getStyle("paddingBottom");
// Icon is on left
var myIconWidth:Number = 0;
var myIconHeight:Number = 0;
if (iconDisplay)
{
myIconWidth = (isNaN(iconWidth) ? getElementPreferredWidth(iconDisplay) : iconWidth);
myIconHeight = (isNaN(iconHeight) ? getElementPreferredHeight(iconDisplay) : iconHeight);
myMeasuredWidth += myIconWidth;
myMeasuredMinWidth += myIconWidth;
myMeasuredHeight = Math.max(myMeasuredHeight, myIconHeight);
myMeasuredMinHeight = Math.max(myMeasuredMinHeight, myIconHeight);
}
// Decorator is up next
var decoratorWidth:Number = 0;
var decoratorHeight:Number = 0;
if (decoratorDisplay)
{
decoratorWidth = getElementPreferredWidth(decoratorDisplay);
decoratorHeight = getElementPreferredHeight(decoratorDisplay);
myMeasuredWidth += decoratorWidth;
myMeasuredMinWidth += decoratorWidth;
myMeasuredHeight = Math.max(myMeasuredHeight, decoratorHeight);
myMeasuredMinHeight = Math.max(myMeasuredHeight, decoratorHeight);
}
// Text is aligned next to icon
var labelWidth:Number = 0;
var labelHeight:Number = 0;
var messageWidth:Number = 0;
var messageHeight:Number = 0;
if (hasLabel)
{
// reset text if it was truncated before.
if (labelDisplay.isTruncated)
labelDisplay.text = labelText;
labelWidth = getElementPreferredWidth(labelDisplay);
labelHeight = getElementPreferredHeight(labelDisplay);
}
if (hasMessage)
{
// now we need to measure messageDisplay's height. Unfortunately, this is tricky and
// is dependent on messageDisplay's width.
// Use the old unscaledWidth width as an estimte for the new one.
// If we are wrong, we'll find out in updateDisplayList()
var messageDisplayEstimatedWidth:Number = oldUnscaledWidth - paddingAndGapWidth - myIconWidth - decoratorWidth;
setElementSize(messageDisplay, messageDisplayEstimatedWidth, NaN);
messageWidth = getElementPreferredWidth(messageDisplay);
messageHeight = getElementPreferredHeight(messageDisplay);
}
myMeasuredWidth += Math.max(labelWidth, messageWidth);
myMeasuredHeight = Math.max(myMeasuredHeight, labelHeight + messageHeight + verticalGap);
myMeasuredWidth += paddingAndGapWidth;
myMeasuredMinWidth += paddingAndGapWidth;
// verticalGap handled in label and message
myMeasuredHeight += paddingHeight;
myMeasuredMinHeight += paddingHeight;
// now set the local variables to the member variables.
measuredWidth = myMeasuredWidth
measuredHeight = myMeasuredHeight;
measuredMinWidth = myMeasuredMinWidth;
measuredMinHeight = myMeasuredMinHeight;
}
/**
* @private
* If we invalidate display list, we need to redraw any graphic elements sharing
* our display object since we call graphics.clear() in super.updateDisplayList()
*/
override public function invalidateDisplayList():void
{
redrawRequested = true;
super.invalidateDisplayList();
}
/**
* @private
*/
override public function validateDisplayList():void
{
super.validateDisplayList();
// Since IGraphicElement is not ILayoutManagerClient, we need to make sure we
// validate properties of the elements
// see if we have an icon that needs to be validated
if (iconDisplay &&
iconDisplay.displayObject is ISharedDisplayObject &&
ISharedDisplayObject(iconDisplay.displayObject).redrawRequested)
{
ISharedDisplayObject(iconDisplay.displayObject).redrawRequested = false;
iconDisplay.validateDisplayList();
// if decoratorDisplay is also using this displayObject than validate
// decoratorDisplay as well
if (decoratorDisplay &&
decoratorDisplay.displayObject is ISharedDisplayObject &&
decoratorDisplay.displayObject == iconDisplay.displayObject)
decoratorDisplay.validateDisplayList();
}
// check just for decoratorDisplay in case it has a different displayObject
// than iconDisplay
if (decoratorDisplay &&
decoratorDisplay.displayObject is ISharedDisplayObject &&
ISharedDisplayObject(decoratorDisplay.displayObject).redrawRequested)
{
ISharedDisplayObject(decoratorDisplay.displayObject).redrawRequested = false;
decoratorDisplay.validateDisplayList();
}
}
/**
* @private
*/
override protected function layoutContents(unscaledWidth:Number,
unscaledHeight:Number):void
{
// no need to call super.layoutContents() since we're changing how it happens here
// start laying out our children now
var iconWidth:Number = 0;
var iconHeight:Number = 0;
var decoratorWidth:Number = 0;
var decoratorHeight:Number = 0;
var hasLabel:Boolean = labelDisplay && labelDisplay.text != "";
var hasMessage:Boolean = messageDisplay && messageDisplay.text != "";
var paddingLeft:Number = getStyle("paddingLeft");
var paddingRight:Number = getStyle("paddingRight");
var paddingTop:Number = getStyle("paddingTop");
var paddingBottom:Number = getStyle("paddingBottom");
var horizontalGap:Number = getStyle("horizontalGap");
var verticalAlign:String = getStyle("verticalAlign");
var verticalGap:Number = (hasLabel && hasMessage) ? getStyle("verticalGap") : 0;
var vAlign:Number;
if (verticalAlign == "top")
vAlign = 0;
else if (verticalAlign == "bottom")
vAlign = 1;
else // if (verticalAlign == "middle")
vAlign = 0.5;
// made "middle" last even though it's most likely so it is the default and if someone
// types "center", then it will still vertically center itself.
var viewWidth:Number = unscaledWidth - paddingLeft - paddingRight;
var viewHeight:Number = unscaledHeight - paddingTop - paddingBottom;
// icon is on the left
if (iconDisplay)
{
// set the icon's position and size
setElementSize(iconDisplay, this.iconWidth, this.iconHeight);
iconWidth = iconDisplay.getLayoutBoundsWidth();
iconHeight = iconDisplay.getLayoutBoundsHeight();
// use vAlign to position the icon.
var iconDisplayY:Number = Math.round(vAlign * (viewHeight - iconHeight)) + paddingTop;
setElementPosition(iconDisplay, paddingLeft, iconDisplayY);
}
// decorator is aligned next to icon
if (decoratorDisplay)
{
decoratorWidth = getElementPreferredWidth(decoratorDisplay);
decoratorHeight = getElementPreferredHeight(decoratorDisplay);
setElementSize(decoratorDisplay, decoratorWidth, decoratorHeight);
// decorator is always right aligned, vertically centered
var decoratorY:Number = Math.round(0.5 * (viewHeight - decoratorHeight)) + paddingTop;
setElementPosition(decoratorDisplay, unscaledWidth - paddingRight - decoratorWidth, decoratorY);
}
// Figure out how much space we have for label and message as well as the
// starting left position
var labelComponentsViewWidth:Number = viewWidth - iconWidth - decoratorWidth;
// don't forget the extra gap padding if these elements exist
if (iconDisplay)
labelComponentsViewWidth -= horizontalGap;
if (decoratorDisplay)
labelComponentsViewWidth -= horizontalGap;
var labelComponentsX:Number = paddingLeft;
if (iconDisplay)
labelComponentsX += iconWidth + horizontalGap;
// calculte the natural height for the label
var labelTextHeight:Number = 0;
if (hasLabel)
{
// reset text if it was truncated before.
if (labelDisplay.isTruncated)
labelDisplay.text = labelText;
// commit styles to make sure it uses updated look
labelDisplay.commitStyles();
labelTextHeight = getElementPreferredHeight(labelDisplay);
}
if (hasMessage)
{
// commit styles to make sure it uses updated look
messageDisplay.commitStyles();
}
// now size and position the elements, 3 different configurations we care about:
// 1) label and message
// 2) label only
// 3) message only
// label display goes on top
// message display goes below
var labelWidth:Number = 0;
var labelHeight:Number = 0;
var messageWidth:Number = 0;
var messageHeight:Number = 0;
if (hasLabel)
{
// handle labelDisplay. it can only be 1 line
// width of label takes up rest of space
// height only takes up what it needs so we can properly place the message
// and make sure verticalAlign is operating on a correct value.
labelWidth = Math.max(labelComponentsViewWidth, 0);
labelHeight = labelTextHeight;
if (labelWidth == 0)
setElementSize(labelDisplay, NaN, 0);
else
setElementSize(labelDisplay, labelWidth, labelHeight);
// attempt to truncate text
labelDisplay.truncateToFit();
}
if (hasMessage)
{
// handle message...because the text is multi-line, measuring and layout
// can be somewhat tricky
messageWidth = Math.max(labelComponentsViewWidth, 0);
// We get called with unscaledWidth = 0 a few times...
// rather than deal with this case normally,
// we can just special-case it later to do something smarter
if (messageWidth == 0)
{
// if unscaledWidth is 0, we want to make sure messageDisplay is invisible.
// we could set messageDisplay's width to 0, but that would cause an extra
// layout pass because of the text reflow logic. Because of that, we
// can just set its height to 0.
setElementSize(messageDisplay, NaN, 0);
}
else
{
// grab old textDisplay height before resizing it
var oldPreferredMessageHeight:Number = getElementPreferredHeight(messageDisplay);
// keep track of oldUnscaledWidth so we have a good guess as to the width
// of the messageDisplay on the next measure() pass
oldUnscaledWidth = unscaledWidth;
// set the width of messageDisplay to messageWidth.
// set the height to oldMessageHeight. If the height's actually wrong,
// we'll invalidateSize() and go through this layout pass again anyways
setElementSize(messageDisplay, messageWidth, oldPreferredMessageHeight);
// grab new messageDisplay height after the messageDisplay has taken its final width
var newPreferredMessageHeight:Number = getElementPreferredHeight(messageDisplay);
// if the resize caused the messageDisplay's height to change (because of
// text reflow), then we need to remeasure ourselves with our new width
if (oldPreferredMessageHeight != newPreferredMessageHeight)
invalidateSize();
messageHeight = newPreferredMessageHeight;
}
// since it's multi-line, no need to truncate
//if (messageDisplay.isTruncated)
// messageDisplay.text = messageText;
//messageDisplay.truncateToFit();
}
else
{
if (messageDisplay)
setElementSize(messageDisplay, 0, 0);
}
// Position the text components now that we know all heights so we can respect verticalAlign style
var totalHeight:Number = 0;
var labelComponentsY:Number = 0;
// Heights used in our alignment calculations. We only care about the "real" ascent
var labelAlignmentHeight:Number = 0;
var messageAlignmentHeight:Number = 0;
if (hasLabel)
labelAlignmentHeight = getElementPreferredHeight(labelDisplay);
if (hasMessage)
messageAlignmentHeight = getElementPreferredHeight(messageDisplay);
totalHeight = labelAlignmentHeight + messageAlignmentHeight + verticalGap;
labelComponentsY = Math.round(vAlign * (viewHeight - totalHeight)) + paddingTop;
if (labelDisplay)
setElementPosition(labelDisplay, labelComponentsX, labelComponentsY);
if (messageDisplay)
{
var messageY:Number = labelComponentsY + labelAlignmentHeight + verticalGap;
setElementPosition(messageDisplay, labelComponentsX, messageY);
}
}
}
}