You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flex.apache.org by ma...@apache.org on 2013/10/08 02:29:19 UTC

[2/8] temp commit move experimental callout to spark

http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/0d6e7b32/frameworks/projects/spark/src/spark/components/Callout.as
----------------------------------------------------------------------
diff --git a/frameworks/projects/spark/src/spark/components/Callout.as b/frameworks/projects/spark/src/spark/components/Callout.as
new file mode 100644
index 0000000..8d81b4a
--- /dev/null
+++ b/frameworks/projects/spark/src/spark/components/Callout.as
@@ -0,0 +1,1669 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.display.DisplayObjectContainer;
+import flash.events.Event;
+import flash.geom.ColorTransform;
+import flash.geom.Matrix;
+import flash.geom.Point;
+import flash.geom.Rectangle;
+
+import mx.core.DPIClassification;
+import mx.core.FlexGlobals;
+import mx.core.ILayoutElement;
+import mx.core.IVisualElement;
+import mx.core.LayoutDirection;
+import mx.core.UIComponent;
+import mx.core.mx_internal;
+import mx.events.ResizeEvent;
+import mx.managers.SystemManager;
+import mx.utils.MatrixUtil;
+import mx.utils.PopUpUtil;
+
+use namespace mx_internal;
+
+//--------------------------------------
+//  Styles
+//--------------------------------------
+
+/**
+ *  Appearance of the <code>contentGroup</code>. 
+ *  Valid MXML values are <code>inset</code>, 
+ *  <code>flat</code>, and <code>none</code>.
+ *
+ *  <p>In ActionScript, you can use the following constants
+ *  to set this property:
+ *  <code>ContentBackgroundAppearance.INSET</code>,
+ *  <code>ContentBackgroundAppearance.FLAT</code> and
+ *  <code>ContentBackgroundAppearance.NONE</code>.</p>
+ *
+ *  @default ContentBackgroundAppearance.INSET
+ *  
+ *  @langversion 3.0
+ *  @playerversion AIR 3
+ *  @productversion Flex 4.6
+ */ 
+[Style(name="contentBackgroundAppearance", type="String", enumeration="inset,flat,none", inherit="no")]
+
+//--------------------------------------
+//  Other metadata
+//--------------------------------------
+
+[IconFile("Callout.png")]
+
+/**
+ *  The Callout container is a SkinnablePopUpContainer that functions as a pop-up
+ *  with additional owner-relative positioning options similar to PopUpAnchor.
+ *  Callout also adds an optional <code>arrow</code> skin part that visually
+ *  displays the direction toward the owner.
+ *
+ *  <p>The following image shows a Callout container labeled 'Settings':</p>
+ *
+ * <p>
+ *  <img src="../../images/ca_calloutViewNav_ca.png" alt="Callout container" />
+ * </p>
+ *
+ *  <p>You can also use the CalloutButton control to open a callout container. 
+ *  The CalloutButton control encapsulates in a single control the callout container 
+ *  and all of the logic necessary to open and close the callout. 
+ *  The CalloutButton control is then said to the be the owner, or host, 
+ *  of the callout.</p>
+ *
+ *  <p>Callout uses the <code>horizontalPosition</code> and
+ *  <code>verticalPosition</code> properties to determine the position of the
+ *  Callout relative to the owner that is specified by the <code>open()</code>
+ *  method. 
+ *  Both properties can be set to <code>CalloutPosition.AUTO</code> which selects a
+ *  position based on the aspect ratio of the screen for the Callout to fit
+ *  with minimal overlap with the owner and and minimal adjustments at the
+ *  screen bounds.</p>
+ *
+ *  <p>Once positioned, the Callout positions the arrow on the side adjacent
+ *  to the owner, centered as close as possible on the horizontal or vertical
+ *  center of the owner as appropriate. The arrow is hidden in cases where
+ *  the Callout position is not adjacent to any edge.</p>
+ *
+ *  <p>You do not create a Callout container as part of the normal layout
+ *  of its parent container.
+ *  Instead, it appears as a pop-up container on top of its parent.
+ *  Therefore, you do not create it directly in the MXML code of your application.</p>
+ *
+ *  <p>Instead, you create is as an MXML component, often in a separate MXML file.
+ *  To show the component create an instance of the MXML component, and
+ *  then call the <code>open()</code> method.
+ *  You can also set the size and position of the component when you open it.</p>
+ *
+ *  <p>To close the component, call the <code>close()</code> method.
+ *  If the pop-up needs to return data to a handler, you can add an event listener for
+ *  the <code>PopUp.CLOSE</code> event, and specify the returned data in
+ *  the <code>close()</code> method.</p>
+ *
+ *  <p>The Callout is initially in its <code>closed</code> skin state.
+ *  When it opens, it adds itself as a pop-up to the PopUpManager,
+ *  and transition to the <code>normal</code> skin state.
+ *  To define open and close animations, use a custom skin with transitions between
+ *  the <code>closed</code> and <code>normal</code> skin states.</p>
+ * 
+ *  <p>Callout changes the default inheritance behavior seen in Flex components 
+ *  and instead, inherits styles from the top-level application. This prevents
+ *  Callout's contents from unintentionally inheriting styles from an owner
+ *  (i.e. Button or TextInput) where the default appearance was desired and
+ *  expected.</p>
+ *
+ *  <p>The Callout container has the following default characteristics:</p>
+ *     <table class="innertable">
+ *     <tr><th>Characteristic</th><th>Description</th></tr>
+ *     <tr><td>Default size</td><td>Large enough to display its children</td></tr>
+ *     <tr><td>Minimum size</td><td>0 pixels</td></tr>
+ *     <tr><td>Maximum size</td><td>10000 pixels wide and 10000 pixels high</td></tr>
+ *     <tr><td>Default skin class</td><td>spark.skins.mobile.CalloutSkin</td></tr>
+ *     </table>
+ *
+ *  @mxml <p>The <code>&lt;s:Callout&gt;</code> tag inherits all of the tag
+ *  attributes of its superclass and adds the following tag attributes:</p>
+ *
+ *  <pre>
+ *  &lt;s:Callout
+ *    <strong>Properties</strong>
+ *    horizontalPosition="auto"
+ *    verticalPosition="auto"
+ *
+ *    <strong>Styles</strong>
+ *    contentBackgroundAppearance="inset"
+ *  /&gt;
+ *  </pre>
+ *
+ *  @see spark.components.CalloutButton
+ *  @see spark.skins.mobile.CalloutSkin
+ *  @see spark.components.ContentBackgroundAppearance
+ *  @see spark.components.CalloutPosition
+ *
+ *  @includeExample examples/CalloutExample.mxml -noswf
+ *
+ *  @langversion 3.0
+ *  @playerversion AIR 3
+ *  @productversion Flex 4.6
+ */
+public class Callout extends SkinnablePopUpContainer
+{
+
+    //--------------------------------------------------------------------------
+    //
+    //  Class constants
+    //
+    //--------------------------------------------------------------------------
+
+    private static var decomposition:Vector.<Number> = new <Number>[0,0,0,0,0];
+
+    //--------------------------------------------------------------------------
+    //
+    //  Constructor
+    //
+    //--------------------------------------------------------------------------
+
+    /**
+     *  Constructor.
+     *
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public function Callout()
+    {
+        super();
+    }
+
+    //--------------------------------------------------------------------------
+    //
+    //  Skin parts
+    //
+    //--------------------------------------------------------------------------
+
+    [Bindable]
+    [SkinPart(required="false")]
+
+    /**
+     *  An optional skin part that visually connects the owner to the
+     *  contentGroup.
+     *
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public var arrow:UIComponent;
+    
+    //--------------------------------------------------------------------------
+    //
+    //  Variables
+    //
+    //--------------------------------------------------------------------------
+    
+    private var invalidatePositionFlag:Boolean = false;
+
+    //--------------------------------------------------------------------------
+    //
+    //  Properties
+    //
+    //--------------------------------------------------------------------------
+
+    //----------------------------------
+    //  horizontalPosition
+    //----------------------------------
+
+    private var _horizontalPosition:String = CalloutPosition.AUTO;
+
+    [Inspectable(category="General", enumeration="before,start,middle,end,after,auto", defaultValue="auto")]
+
+    /**
+     *  Horizontal position of the callout relative to the owner.
+     *
+     *  <p>Possible values are <code>"before"</code>, <code>"start"</code>,
+     *  <code>"middle"</code>, <code>"end"</code>, <code>"after"</code>,
+     *  and <code>"auto"</code> (default).</p>
+     *
+     *  @default CalloutPosition.AUTO
+     *  @see spark.components.CalloutPosition
+     *
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public function get horizontalPosition():String
+    {
+        return _horizontalPosition;
+    }
+
+    /**
+     *  @private
+     */
+    public function set horizontalPosition(value:String):void
+    {
+        if (value == _horizontalPosition)
+            return;
+
+        _horizontalPosition = value;
+
+        invalidatePosition();
+    }
+
+    //----------------------------------
+    //  actualHorizontalPosition
+    //----------------------------------
+
+    private var _actualHorizontalPosition:String;
+
+    /**
+     *  Fully resolved horizontal position after evaluating CalloutPosition.AUTO.
+     * 
+     *  <p>Update this property in <code>commitProperties()</code> when the
+     *  explicit <code>horizontalPosition</code> is CalloutPosition.AUTO. 
+     *  This property must be updated in <code>updatePopUpPosition()</code>
+     *  when attempting to reposition the Callout.</p> 
+     *  
+     *  <p>Subclasses should read this property when computing the <code>arrowDirection</code>,
+     *  the arrow position in <code>updateSkinDisplayList()</code>.</p>
+     *
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    mx_internal function get actualHorizontalPosition():String
+    {
+        if (_actualHorizontalPosition)
+            return _actualHorizontalPosition;
+
+        return horizontalPosition;
+    }
+
+    /**
+     *  @private
+     */
+    mx_internal function set actualHorizontalPosition(value:String):void
+    {
+        _actualHorizontalPosition = value;
+    }
+
+    //----------------------------------
+    //  verticalPosition
+    //----------------------------------
+
+    private var _verticalPosition:String = CalloutPosition.AUTO;
+
+    [Inspectable(category="General", enumeration="before,start,middle,end,after,auto", defaultValue="auto")]
+
+    /**
+     *  Vertical position of the callout relative to the owner.
+     *
+     *  <p>Possible values are <code>"before"</code>, <code>"start"</code>,
+     *  <code>"middle"</code>, <code>"end"</code>, <code>"after"</code>,
+     *  and <code>"auto"</code> (default).</p>
+     *
+     *  @default CalloutPosition.AUTO
+     *  @see spark.components.CalloutPosition
+     *
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public function get verticalPosition():String
+    {
+        return _verticalPosition;
+    }
+
+    /**
+     *  @private
+     */
+    public function set verticalPosition(value:String):void
+    {
+        if (value == _verticalPosition)
+            return;
+
+        _verticalPosition = value;
+
+        invalidatePosition();
+    }
+
+    //----------------------------------
+    //  actualVerticalPosition
+    //----------------------------------
+
+    private var _actualVerticalPosition:String;
+    
+    /**
+     *  Fully resolved vertical position after evaluating CalloutPosition.AUTO.
+     * 
+     *  <p>Update this property in <code>commitProperties()</code> when the
+     *  explicit <code>verticalPosition</code> is CalloutPosition.AUTO. 
+     *  This property must be updated in <code>updatePopUpPosition()</code>
+     *  when attempting to reposition the Callout.</p> 
+     *  
+     *  <p>Subclasses should read this property when computing the <code>arrowDirection</code>,
+     *  the arrow position in <code>updateSkinDisplayList()</code>.</p>
+     *
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    mx_internal function get actualVerticalPosition():String
+    {
+        if (_actualVerticalPosition)
+            return _actualVerticalPosition;
+
+        return verticalPosition;
+    }
+
+    /**
+     *  @private
+     */
+    mx_internal function set actualVerticalPosition(value:String):void
+    {
+        _actualVerticalPosition = value;
+    }
+
+    //----------------------------------
+    //  arrowDirection
+    //----------------------------------
+
+    private var _arrowDirection:String = ArrowDirection.NONE;
+    
+    /**
+     *  @private
+     *  Indicates if arrow direction was flipped automatically.
+     */
+    private var arrowDirectionAdjusted:Boolean = false;
+
+    /**
+     *  A read-only property that indicates the direction from the callout
+     *  towards the owner.
+     *
+     *  <p>This value is computed based on the callout position given by
+     *  <code>horizontalPosition</code> and <code>verticalPosition</code>.
+     *  Exterior and interior positions will point from the callout towards
+     *  the edge of the owner. Corner and absolute center positions are not
+     *  supported and will return a value of <code>"none".</code></p>
+     * 
+     *  @default none
+     *
+     *  @see spark.components.ArrowDirection
+     *
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public function get arrowDirection():String
+    {
+        return _arrowDirection;
+    }
+    
+    /**
+     *  @private
+     *  Invalidate skin when the arrowDirection changes. Dispatches an 
+     *  "arrowDirectionChanged" event when the property is set.
+     */
+    mx_internal function setArrowDirection(value:String):void
+    {
+        if (_arrowDirection == value)
+            return;
+        
+        _arrowDirection = value;
+        
+        // Instead of using skin states for each arrowDirection, the
+        // skin must override commitProperties() and account for
+        // arrowDirection on it's own.
+        skin.invalidateProperties();
+        
+        // adjust margins based on arrow direction
+        switch (arrowDirection)
+        {
+            case ArrowDirection.DOWN:
+            {
+                // Set the marginBottom to zero to place the arrow adjacent to the keyboard
+                softKeyboardEffectMarginBottom = 0;
+                softKeyboardEffectMarginTop = margin;
+                break;
+            }
+            case ArrowDirection.UP:
+            {
+                // Arrow should already be adjacent to the owner or the top of
+                // the screen.
+                softKeyboardEffectMarginTop = 0;
+                softKeyboardEffectMarginBottom = margin;
+                break;
+            }
+            default:
+            {
+                softKeyboardEffectMarginBottom = margin;
+                softKeyboardEffectMarginTop = margin;
+                break;
+            }
+        }
+        
+        if (hasEventListener("arrowDirectionChanged"))
+            dispatchEvent(new Event("arrowDirectionChanged"));
+    }
+    
+    //----------------------------------
+    //  margin
+    //----------------------------------
+    
+    private var _margin:Number = NaN;
+    
+    /**
+     *  @private
+     *  Defines a margin around the Callout to nudge it's position away from the
+     *  edge of the screen.
+     */
+    mx_internal function get margin():Number
+    {
+        if (isNaN(_margin))
+        {
+            var dpi:Number = FlexGlobals.topLevelApplication["applicationDPI"];
+            
+            if (dpi)
+            {
+                switch (dpi)
+                {
+					case DPIClassification.DPI_640:
+					{
+						_margin = 32;
+						break;
+					}
+					case DPIClassification.DPI_480:
+					{
+						_margin = 24;
+						break;
+					}
+                    case DPIClassification.DPI_320:
+                    {
+                        _margin = 16;
+                        break;
+                    }
+					case DPIClassification.DPI_240:
+					{
+						_margin = 12;
+						break;
+					}
+					case DPIClassification.DPI_120:
+					{
+						_margin = 6;
+						break;
+					}
+                    default:
+                    {
+                        // default DPI_160
+                        _margin = 8;
+                        break;
+                    }
+                }
+            }
+            else
+            {
+                _margin = 8;
+            }
+        }
+        
+        return _margin;
+    }
+    
+    private var _explicitMoveForSoftKeyboard:Boolean = false;
+    
+    /**
+     *  @private
+     */
+    override public function get moveForSoftKeyboard():Boolean
+    {
+        // If no explicit setting, then automatically disable move when
+        // pointing up towards the owner.
+        if (!_explicitMoveForSoftKeyboard && 
+            (arrowDirection == ArrowDirection.UP))
+        {
+            return false;
+        }
+        
+        return super.moveForSoftKeyboard;
+    }
+    
+    /**
+     *  @private
+     */
+    override public function set moveForSoftKeyboard(value:Boolean):void
+    {
+        super.moveForSoftKeyboard = value;
+        
+        _explicitMoveForSoftKeyboard = true;
+    }
+    
+    //----------------------------------
+    //  calloutMaxWidth
+    //----------------------------------
+    
+    private var _calloutMaxWidth:Number = NaN;
+    
+    /**
+     *  @private
+     */
+    mx_internal function get calloutMaxWidth():Number
+    {
+        return _calloutMaxWidth;
+    }
+    
+    /**
+     *  @private
+     */
+    mx_internal function set calloutMaxWidth(value:Number):void
+    {
+        if (_calloutMaxWidth == value)
+            return;
+        
+        _calloutMaxWidth = value;
+        
+        invalidateMaxSize();
+    }
+
+    
+    //----------------------------------
+    //  calloutMaxHeight
+    //----------------------------------
+    
+    private var _calloutMaxHeight:Number = NaN;
+    
+    /**
+     *  @private
+     */
+    mx_internal function get calloutMaxHeight():Number
+    {
+        return _calloutMaxHeight;
+    }
+    
+    /**
+     *  @private
+     */
+    mx_internal function set calloutMaxHeight(value:Number):void
+    {
+        if (_calloutMaxHeight == value)
+            return;
+        
+        _calloutMaxHeight = value;
+        
+        invalidateMaxSize();
+    }
+
+
+    //--------------------------------------------------------------------------
+    //
+    //  Overridden methods
+    //
+    //--------------------------------------------------------------------------
+    
+    /**
+     *  @private
+     */
+    override public function get explicitMaxWidth():Number
+    {
+        if (!isNaN(super.explicitMaxWidth))
+            return super.explicitMaxWidth;
+        
+        return calloutMaxWidth;
+    }
+    
+    /**
+     *  @private
+     */
+    override public function get explicitMaxHeight():Number
+    {
+        if (!isNaN(super.explicitMaxHeight))
+            return super.explicitMaxHeight;
+        
+        return calloutMaxHeight;
+    }
+
+    /**
+     *  @private
+     */
+    override protected function commitProperties():void
+    {
+        super.commitProperties();
+        
+        // Do not commit position changes if closed (no owner) or owner was 
+        // removed from the display list.
+        if (!owner || !owner.parent)
+            return;
+        
+        // Compute actual positions when using AUTO
+        commitAutoPosition();
+        
+        // Compute max size based on actual positions
+        commitMaxSize();
+        
+        if (arrow)
+        {
+            // arrowDirection can be set in 2 ways: (1) horizontalPostion/verticalPosition
+            // changes and (2) flipping the axis to fit on screen. 
+            if (!arrowDirectionAdjusted)
+            {
+                // Invalidate only when the arrow direction changes
+                var direction:String = determineArrowPosition(actualHorizontalPosition,
+                    actualVerticalPosition);
+                
+                if (arrowDirection != direction)
+                {
+                    setArrowDirection(direction);
+                    
+                    if (arrow)
+                        arrow.visible = (arrowDirection != ArrowDirection.NONE);
+                }
+            }
+            
+            // Always reset the arrow position
+            invalidateDisplayList();
+        }
+    }
+
+    /**
+     *  @private
+     *  Re-position the pop-up using actualHorizontalPosition and
+     *  actualVerticalPosition. 
+     */
+    override public function updatePopUpPosition():void
+    {
+        if (!owner || !systemManager)
+            return;
+        
+        var popUpPoint:Point = calculatePopUpPosition();
+        var ownerComponent:UIComponent = owner as UIComponent;
+        var concatenatedColorTransform:ColorTransform = 
+            (ownerComponent) ? ownerComponent.$transform.concatenatedColorTransform : null;
+        
+        PopUpUtil.applyPopUpTransform(owner, concatenatedColorTransform,
+                                      systemManager, this, popUpPoint);
+    }
+
+    /**
+     *  @private
+     *
+     *  Cooperative layout
+     *  @see spark.components.supportClasses.TrackBase#partAdded
+     */
+    override protected function partAdded(partName:String, instance:Object):void
+    {
+        super.partAdded(partName, instance);
+
+        if (instance == arrow)
+            arrow.addEventListener(ResizeEvent.RESIZE, arrow_resizeHandler);
+    }
+
+    /**
+     *  @private
+     */
+    override protected function partRemoved(partName:String, instance:Object):void
+    {
+        super.partRemoved(partName, instance);
+
+        if (instance == arrow)
+            arrow.removeEventListener(ResizeEvent.RESIZE, arrow_resizeHandler);
+    }
+
+    /**
+     *  @private
+     */
+    override public function open(owner:DisplayObjectContainer, modal:Boolean=false):void
+    {
+        if (isOpen)
+            return;
+        
+        // reset state
+        invalidatePositionFlag = false;
+        arrowDirectionAdjusted = false;
+
+        // Add to PopUpManager, calls updatePopUpPosition(), and change state
+        super.open(owner, modal);
+        
+        // Reposition the callout when the screen changes
+        var systemManagerParent:SystemManager = this.parent as SystemManager;
+        
+        if (systemManagerParent)
+            systemManagerParent.addEventListener(Event.RESIZE, systemManager_resizeHandler);
+    }
+    
+    /**
+     *  @private
+     */
+    override public function close(commit:Boolean=false, data:*=null):void
+    {
+        if (!isOpen)
+            return;
+        
+        var systemManagerParent:SystemManager = this.parent as SystemManager;
+        
+        if (systemManagerParent)
+            systemManagerParent.removeEventListener(Event.RESIZE, systemManager_resizeHandler);
+        
+        super.close(commit, data);
+    }
+    
+    /**
+     *  @private
+     */
+    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
+    {
+        super.updateDisplayList(unscaledWidth, unscaledHeight);
+        
+        // Callout can be respositioned while open via SystemManager resize or
+        // explicit changes to horizontalPostion and verticalPosition.
+        if (isOpen && invalidatePositionFlag)
+        {
+            updatePopUpPosition();
+            invalidatePositionFlag = false;
+        }
+
+        // Position the arrow
+        updateSkinDisplayList();
+    }
+
+    //--------------------------------------------------------------------------
+    //
+    //  Methods
+    //
+    //--------------------------------------------------------------------------
+    
+    /**
+     *  @private
+     */
+    private function invalidatePosition():void
+    {
+        arrowDirectionAdjusted = false;
+        
+        invalidateProperties();
+        
+        if (isOpen)
+            invalidatePositionFlag = true;
+    }
+    
+    /**
+     *  @private
+     *  Force a new measurement when callout should use it's screen-constrained
+     *  max size.
+     */
+    private function invalidateMaxSize():void
+    {
+        // calloutMaxWidth and calloutMaxHeight don't invalidate 
+        // explicitMaxWidth or explicitMaxHeight. If callout's max size changes
+        // and explicit max sizes aren't set, then invalidate size here so that
+        // callout's max size is applied.
+        if (!canSkipMeasurement() && !isMaxSizeSet)
+            skin.invalidateSize();
+    }
+
+    /**
+     *  Sets the bounds of <code>arrow</code>, whose geometry isn't fully
+     *  specified by the skin's layout.
+     *
+     *  <p>Subclasses can override this method to update the arrow's size,
+     *  position, and visibility, based on the computed
+     *  <code>arrowDirection</code>.</p>
+     *
+     *  <p>By default, this method aligns the arrow on the shorter of either
+     *  the <code>arrow</code> bounds or the <code>owner</code> bounds. This
+     *  implementation assumes that the <code>arrow</code> and the Callout skin
+     *  share the same coordinate space.</p>
+     *
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    protected function updateSkinDisplayList():void
+    {
+        var ownerVisualElement:IVisualElement = owner as IVisualElement;
+
+        // Sanity check to verify owner is still on the display list. If not,
+        // leave the arrow in the current position.
+        if (!arrow || !ownerVisualElement ||
+            (arrowDirection == ArrowDirection.NONE) ||
+            (!ownerVisualElement.parent))
+            return;
+
+        var isStartPosition:Boolean = false;
+        var isMiddlePosition:Boolean = false;
+        var isEndPosition:Boolean = false;
+
+        var position:String = (isArrowVertical) ? actualHorizontalPosition : actualVerticalPosition;
+
+        isStartPosition = (position == CalloutPosition.START);
+        isMiddlePosition = (position == CalloutPosition.MIDDLE);
+        isEndPosition = (position == CalloutPosition.END);
+
+        var isEndOfCallout:Boolean = (arrowDirection == ArrowDirection.DOWN)
+            || (arrowDirection == ArrowDirection.RIGHT);
+
+        var calloutWidth:Number = getLayoutBoundsWidth();
+        var calloutHeight:Number = getLayoutBoundsHeight();
+        var arrowWidth:Number = arrow.getLayoutBoundsWidth();
+        var arrowHeight:Number = arrow.getLayoutBoundsHeight();
+
+        // arrow X/Y in pop-up coordinates
+        var arrowX:Number = 0;
+        var arrowY:Number = 0;
+
+        // Max arrow positions
+        var maxArrowX:Number = calloutWidth - arrowWidth;
+        var maxArrowY:Number = calloutHeight - arrowHeight;
+        
+        // Find the registration point of the owner
+        var sandboxRoot:DisplayObject = systemManager.getSandboxRoot();
+        var regPoint:Point = owner.localToGlobal(new Point());
+        regPoint = sandboxRoot.globalToLocal(regPoint);
+
+        if (isArrowVertical)
+        {
+            // Vertical arrows need horizontal alignment
+            var ownerX:Number = regPoint.x;
+            var ownerVisibleWidth:Number = (ownerVisualElement)
+                ? ownerVisualElement.getLayoutBoundsWidth() : owner.width;
+
+            // Edge cases when start/end of owner is not visible
+            if ((ownerX < 0) && (ownerVisibleWidth < screen.width))
+                ownerVisibleWidth = Math.max(ownerVisibleWidth + ownerX, 0);
+            else if ((ownerX >= 0) && ((ownerX + ownerVisibleWidth) >= screen.width))
+                ownerVisibleWidth = Math.max(screen.width - ownerX, 0);
+
+            ownerVisibleWidth = Math.min(ownerVisibleWidth, screen.width);
+
+            if (calloutWidth <= ownerVisibleWidth)
+            {
+                arrowX = (calloutWidth - arrowWidth) / 2;
+            }
+            else // if (calloutWidth > ownerWidth)
+            {
+                // Center the arrow on the owner
+                arrowX = (ownerVisibleWidth - arrowWidth) / 2;
+                
+                // Add owner offset
+                if (ownerX > 0)
+                    arrowX += Math.abs(ownerX - getLayoutBoundsX());
+                
+                if (ownerX < margin)
+                    arrowX -= (margin - ownerX);
+            }
+            
+            // arrow should not extend past the callout bounds
+            arrowX = Math.max(Math.min(maxArrowX, arrowX), 0);
+
+            // Move the arrow to the bottom of the callout
+            if (isEndOfCallout)
+                arrowY = calloutHeight - arrowHeight;
+        }
+        else
+        {
+            // Horizontal arrows need vertical alignment
+            var ownerY:Number = regPoint.y;
+            var ownerVisibleHeight:Number = (ownerVisualElement)
+                ? ownerVisualElement.getLayoutBoundsHeight() : owner.height;
+
+            // Edge cases when start/end of owner is not visible
+            if ((ownerY < 0) && (ownerVisibleHeight < screen.height))
+                ownerVisibleHeight = Math.max(ownerVisibleHeight + ownerY, 0);
+            else if ((ownerY >= 0) && ((ownerY + ownerVisibleHeight) >= screen.height))
+                ownerVisibleHeight = Math.max(screen.height - ownerY, 0);
+
+            ownerVisibleHeight = Math.min(ownerVisibleHeight, screen.height);
+
+            if (calloutHeight <= ownerVisibleHeight)
+            {
+                arrowY = (calloutHeight - arrowHeight) / 2;
+            }
+            else // if (calloutHeight > ownerHeight)
+            {
+                // Center the arrow on the owner
+                arrowY = (ownerVisibleHeight - arrowHeight) / 2;
+                
+                // Add owner offset
+                if (ownerY > 0)
+                    arrowY += Math.abs(ownerY - getLayoutBoundsY());
+                
+                if (ownerY < margin)
+                    ownerY -= (margin - ownerY);
+            }
+            
+            // arrow should not extend past the callout bounds
+            arrowY = Math.max(Math.min(maxArrowY, arrowY), 0);
+
+            // Move the arrow to the end of the callout
+            if (isEndOfCallout)
+                arrowX = calloutWidth - arrowWidth;
+        }
+
+        arrow.setLayoutBoundsPosition(Math.floor(arrowX), Math.floor(arrowY));
+        arrow.invalidateDisplayList();
+    }
+    
+    /**
+     *  @private
+     * 
+     *  Flip or clear the adjusted position when the callout bounds are outside
+     *  the screen bounds.
+     */
+    mx_internal function adjustCalloutPosition(actualPosition:String, preferredPosition:String,
+                                               calloutStart:Number, calloutEnd:Number,
+                                               screenStart:Number, screenEnd:Number,
+                                               ownerStart:Number, ownerEnd:Number,
+                                               revert:Boolean=false):String
+    {
+        if (!actualPosition)
+            return null;
+        
+        var adjustedPosition:String = null;
+        var calloutSize:Number = calloutEnd - calloutStart;
+        
+        // Exterior space
+        var exteriorSpaceStart:Number = Math.max(0, ownerStart - screenStart);
+        var exteriorSpaceEnd:Number = Math.max(0, ownerEnd - screenEnd);
+        
+        // Fallback to interior positions if using AUTO and callout can not
+        // fit in either exterior positions
+        var useInterior:Boolean = (preferredPosition == CalloutPosition.AUTO) &&
+            (exteriorSpaceStart < calloutSize) &&
+            (exteriorSpaceEnd < calloutSize);
+        var isExterior:Boolean = false;
+        
+        // Flip to opposite position
+        switch (actualPosition)
+        {
+            case CalloutPosition.BEFORE:
+            {
+                isExterior = true;
+                
+                if (calloutStart < screenStart)
+                    adjustedPosition = CalloutPosition.AFTER;
+                
+                break;
+            }
+            case CalloutPosition.AFTER:
+            {
+                isExterior = true;
+                
+                if (calloutEnd > screenEnd)
+                    adjustedPosition = CalloutPosition.BEFORE;
+                
+                break;
+            }
+            case CalloutPosition.END:
+            {
+                if (calloutStart < screenStart)
+                    adjustedPosition = CalloutPosition.START;
+                break;
+            }
+            case CalloutPosition.START:
+            {
+                if (calloutEnd > screenEnd)
+                    adjustedPosition = CalloutPosition.END;
+                break;
+            }
+            // case CalloutPosition.MIDDLE:
+            // Nudge instead of flipping
+        }
+        
+        // Use interior position if exterior flipping was necessary
+        if (useInterior && adjustedPosition && isExterior)
+        {
+            // Choose the exterior position with the most available space.
+            // Note that START grows towards the exterior END and vice versa.
+            adjustedPosition = (exteriorSpaceEnd >= exteriorSpaceStart) ? 
+                CalloutPosition.START : CalloutPosition.END;
+        }
+        
+        // Return null to revert the adjusted position
+        // Otherwise, return the incoming position
+        if (revert)
+            return (adjustedPosition) ? null : actualPosition;
+        
+        // Adjusted position or null if the callout already fits
+        return adjustedPosition;
+    }
+    
+    /**
+     *  @private
+     * 
+     *  Nudge the callout position to fit on screen. Prefer top/left positions
+     *  and allow overflow to get clipped on the bottom/right.
+     */
+    mx_internal function nudgeToFit(calloutStart:Number, calloutEnd:Number,
+                                    screenStart:Number, screenEnd:Number,
+                                    scaleFactor:Number):Number
+    {
+        var position:Number = 0;
+        
+        if (calloutStart < screenStart)
+            position += (screenStart - calloutStart) / scaleFactor;
+        else if (calloutEnd > screenEnd)
+            position -= (calloutEnd - screenEnd) / scaleFactor;
+        
+        return position;
+    }
+
+    /**
+     *  @private
+     *
+     *  Basically the same as PopUpAnchor, but with more position options
+     *  including exterior, interior and corner positions.
+     * 
+     *  Nudging to fit the screen accounts for <code>margin</code> so that
+     *  the Callout is not positioned in the margin.
+     * 
+     *  <code>arrowDirection</code> will change if required for the callout
+     *  to fit.
+     *
+     *  @see #margin
+     */
+    mx_internal function calculatePopUpPosition():Point
+    {
+        // This implementation doesn't handle rotation
+        var sandboxRoot:DisplayObject = systemManager.getSandboxRoot();
+        var matrix:Matrix = MatrixUtil.getConcatenatedMatrix(owner, sandboxRoot);
+
+        var regPoint:Point = new Point();
+
+        if (!matrix)
+            return regPoint;
+        
+        var adjustedHorizontalPosition:String;
+        var adjustedVerticalPosition:String;
+        var calloutBounds:Rectangle = determinePosition(actualHorizontalPosition,
+            actualVerticalPosition, matrix, regPoint);
+        var ownerBounds:Rectangle = owner.getBounds(systemManager.getSandboxRoot());
+
+        // Position the callout in the opposite direction if it
+        // does not fit on the screen.
+        if (screen)
+        {
+            adjustedHorizontalPosition = adjustCalloutPosition(
+                actualHorizontalPosition, horizontalPosition,
+                calloutBounds.left, calloutBounds.right,
+                screen.left, screen.right,
+                ownerBounds.left, ownerBounds.right);
+
+            adjustedVerticalPosition = adjustCalloutPosition(
+                actualVerticalPosition, verticalPosition,
+                calloutBounds.top, calloutBounds.bottom,
+                screen.top, screen.bottom,
+                ownerBounds.top, ownerBounds.bottom);
+        }
+
+        var oldArrowDirection:String = arrowDirection;
+        var actualArrowDirection:String = null;
+        
+        // Reset arrowDirectionAdjusted
+        arrowDirectionAdjusted = false;
+
+        // Get the new registration point based on the adjusted position
+        if ((adjustedHorizontalPosition != null) || (adjustedVerticalPosition != null))
+        {
+            var adjustedRegPoint:Point = new Point();
+            var tempHorizontalPosition:String = (adjustedHorizontalPosition)
+                ? adjustedHorizontalPosition : actualHorizontalPosition;
+            var tempVerticalPosition:String = (adjustedVerticalPosition)
+                ? adjustedVerticalPosition : actualVerticalPosition;
+
+            // Adjust arrow direction after adjusting position
+            actualArrowDirection = determineArrowPosition(tempHorizontalPosition,
+                tempVerticalPosition);
+
+            // All position flips gaurantee an arrowDirection change
+            setArrowDirection(actualArrowDirection);
+            arrowDirectionAdjusted = true;
+
+            if (arrow)
+                arrow.visible = (arrowDirection != ArrowDirection.NONE);
+
+            // Reposition the arrow
+            updateSkinDisplayList();
+
+            var adjustedBounds:Rectangle = determinePosition(tempHorizontalPosition,
+                tempVerticalPosition, matrix, adjustedRegPoint);
+
+            if (screen)
+            {
+                // If we adjusted the position but the callout still doesn't fit,
+                // then revert to the original position.
+                adjustedHorizontalPosition = adjustCalloutPosition(
+                    adjustedHorizontalPosition, horizontalPosition,
+                    adjustedBounds.left, adjustedBounds.right,
+                    screen.left, screen.right,
+                    ownerBounds.left, ownerBounds.right,
+                    true);
+                
+                adjustedVerticalPosition = adjustCalloutPosition(
+                    adjustedVerticalPosition, verticalPosition,
+                    adjustedBounds.top, adjustedBounds.bottom,
+                    screen.top, screen.bottom, 
+                    ownerBounds.top, ownerBounds.bottom,
+                    true);
+            }
+
+            if ((adjustedHorizontalPosition != null) || (adjustedVerticalPosition != null))
+            {
+                regPoint = adjustedRegPoint;
+                calloutBounds = adjustedBounds;
+
+                // Temporarily set actual positions to reposition the arrow
+                if (adjustedHorizontalPosition)
+                    actualHorizontalPosition = adjustedHorizontalPosition;
+
+                if (adjustedVerticalPosition)
+                    actualVerticalPosition = adjustedVerticalPosition;
+
+                // Reposition the arrow with the new actual position
+                updateSkinDisplayList();
+            }
+            else
+            {
+                // Restore previous arrow direction *before* reversing the
+                // adjusted positions
+                setArrowDirection(oldArrowDirection);
+                arrowDirectionAdjusted = false;
+
+                // Reposition the arrow to the original position
+                updateSkinDisplayList();
+            }
+        }
+
+        MatrixUtil.decomposeMatrix(decomposition, matrix, 0, 0);
+        var concatScaleX:Number = decomposition[3];
+        var concatScaleY:Number = decomposition[4];
+
+        // If the callout still doesn't fit, then nudge it
+        // so it is completely on the screen. Make sure to include scale.
+        var screenTop:Number = screen.top;
+        var screenBottom:Number = screen.bottom;
+        var screenLeft:Number = screen.left;
+        var screenRight:Number = screen.right;
+        
+        // Allow zero margin on the the side with the arrow
+        switch (arrowDirection)
+        {
+            case ArrowDirection.UP:
+            {
+                screenBottom -= margin;
+                screenLeft += margin;
+                screenRight -= margin
+                break;
+            }
+            case ArrowDirection.DOWN:
+            {
+                screenTop += margin;
+                screenLeft += margin;
+                screenRight -= margin
+                break;
+            }
+            case ArrowDirection.LEFT:
+            {
+                screenTop += margin;
+                screenBottom -= margin;
+                screenRight -= margin
+                break;
+            }
+            case ArrowDirection.RIGHT:
+            {
+                screenTop += margin;
+                screenBottom -= margin;
+                screenLeft += margin;
+                break;
+            }
+            default:
+            {
+                screenTop += margin;
+                screenBottom -= margin;
+                screenLeft += margin;
+                screenRight -= margin
+                break;
+            }
+        }
+        
+        regPoint.y += nudgeToFit(calloutBounds.top, calloutBounds.bottom,
+            screenTop, screenBottom, concatScaleY);
+        
+        regPoint.x += nudgeToFit(calloutBounds.left, calloutBounds.right,
+            screenLeft, screenRight, concatScaleX);
+
+        // Compute the stage coordinates of the upper,left corner of the PopUp, taking
+        // the postTransformOffsets - which include mirroring - into account.
+        // If we're mirroring, then the implicit assumption that x=left will fail,
+        // so we compensate here.
+
+        if (layoutDirection == LayoutDirection.RTL)
+            regPoint.x += calloutBounds.width;
+        return MatrixUtil.getConcatenatedComputedMatrix(owner, sandboxRoot).transformPoint(regPoint);
+    }
+
+    /**
+     *  @private
+     *  Computes <code>actualHorizontalPosition</code> and/or
+     *  <code>actualVerticalPosition</code> values when using
+     *  <code>CalloutPosition.AUTO</code>. When implementing subclasses of
+     *  Callout, use <code>actualHorizontalPosition</code> and
+     *  <code>actualVerticalPosition</code> to compute
+     *  <code>arrowDirection</code> and positioning in
+     *  <code>updatePopUpPosition()</code> and <code>updateSkinDisplayList()</code>.
+     *
+     *  <p>The default implementation chooses "outer" positions for the callout
+     *  such that the owner is not obscured. Horizontal/Vertical orientation
+     *  relative to the owner choosen based on the aspect ratio.</p>
+     *
+     *  <p>When the aspect ratio is landscape, and the callout can fit to the
+     *  left or right of the owner, <code>actualHorizontalPosition</code> is
+     *  set to <code>CalloutPosition.BEFORE</code> or
+     *  <code>CalloutPosition.AFTER</code> as appropriate.
+     *  <code>actualVerticalPosition</code> is set to
+     *  <code>CalloutPosition.MIDDLE</code> to have the vertical center of the
+     *  callout align to the vertical center of the owner.</p>
+     *
+     *  <p>When the aspect ratio is portrait, and the callout can fit
+     *  above or below the owner, <code>actualVerticalPosition</code> is
+     *  set to <code>CalloutPosition.BEFORE</code> or
+     *  <code>CalloutPosition.AFTER</code> as appropriate.
+     *  <code>actualHorizontalPosition</code> is set to
+     *  <code>CalloutPosition.MIDDLE</code> to have the horizontal center of the
+     *  callout align to the horizontal center of the owner.</p>
+     *
+     *  <p>Subclasses may override to modify automatic positioning behavior.</p>
+     */
+    mx_internal function commitAutoPosition():void
+    {
+        if (!screen || ((horizontalPosition != CalloutPosition.AUTO) &&
+            (verticalPosition != CalloutPosition.AUTO)))
+        {
+            // Use explicit positions instead of AUTO
+            actualHorizontalPosition = null;
+            actualVerticalPosition = null;
+            
+            return;
+        }
+
+        var ownerBounds:Rectangle = owner.getBounds(systemManager.getSandboxRoot());
+
+        // Use aspect ratio to determine vertical/horizontal preference
+        var isLandscape:Boolean = (screen.width > screen.height);
+
+        // Exterior space
+        var exteriorSpaceLeft:Number = Math.max(0, ownerBounds.left);
+        var exteriorSpaceRight:Number = Math.max(0, screen.width - ownerBounds.right);
+        var exteriorSpaceTop:Number = Math.max(0, ownerBounds.top);
+        var exteriorSpaceBottom:Number = Math.max(0, screen.height - ownerBounds.bottom);
+
+        if (verticalPosition != CalloutPosition.AUTO)
+        {
+            // Horizontal auto only
+            switch (verticalPosition)
+            {
+                case CalloutPosition.START:
+                case CalloutPosition.MIDDLE:
+                case CalloutPosition.END:
+                {
+                    actualHorizontalPosition = (exteriorSpaceRight > exteriorSpaceLeft) ? CalloutPosition.AFTER : CalloutPosition.BEFORE;
+                    break;
+                }
+                default:
+                {
+                    actualHorizontalPosition = CalloutPosition.MIDDLE;
+                    break;
+                }
+            }
+            
+            actualVerticalPosition = null;
+        }
+        else if (horizontalPosition != CalloutPosition.AUTO)
+        {
+            // Vertical auto only
+            switch (horizontalPosition)
+            {
+                case CalloutPosition.START:
+                case CalloutPosition.MIDDLE:
+                case CalloutPosition.END:
+                {
+                    actualVerticalPosition = (exteriorSpaceBottom > exteriorSpaceTop) ? CalloutPosition.AFTER : CalloutPosition.BEFORE;
+                    break;
+                }
+                default:
+                {
+                    actualVerticalPosition = CalloutPosition.MIDDLE;
+                    break;
+                }
+            }
+            
+            actualHorizontalPosition = null;
+        }
+        else // if ((verticalPosition == CalloutPosition.AUTO) && (horizontalPosition == CalloutPosition.AUTO))
+        {
+            if (!isLandscape)
+            {
+                // Arrow will be vertical when in portrait
+                actualHorizontalPosition = CalloutPosition.MIDDLE;
+                actualVerticalPosition = (exteriorSpaceBottom > exteriorSpaceTop) ? CalloutPosition.AFTER : CalloutPosition.BEFORE;
+            }
+            else
+            {
+                // Arrow will be horizontal when in landscape
+                actualHorizontalPosition = (exteriorSpaceRight > exteriorSpaceLeft) ? CalloutPosition.AFTER : CalloutPosition.BEFORE;
+                actualVerticalPosition = CalloutPosition.MIDDLE;
+            }
+        }
+    }
+    
+    /**
+     *  @private
+     *  Return true if user-specified max size if set
+     */
+    mx_internal function get isMaxSizeSet():Boolean
+    {
+        var explicitMaxW:Number = super.explicitMaxWidth;
+        var explicitMaxH:Number = super.explicitMaxHeight;
+        
+        return (!isNaN(explicitMaxW) && !isNaN(explicitMaxH));
+    }
+    
+    /**
+     *  @private
+     *  Return the original height if the soft keyboard is active. This height
+     *  is used to stabilize AUTO positioning so that the position is based
+     *  on the original height of the Callout instead of a possibly shorter
+     *  height due to soft keyboard effects.
+     */
+    mx_internal function get calloutHeight():Number
+    {
+        return (isSoftKeyboardEffectActive) ? softKeyboardEffectCachedHeight : getLayoutBoundsHeight();
+    }
+    
+    /**
+     *  @private
+     *  Compute max width and max height. Uses the the owner and screen bounds 
+     *  as well as preferred positions to determine max width and max height  
+     *  for all possible exterior and interior positions.
+     */
+    mx_internal function commitMaxSize():void
+    {
+        var ownerBounds:Rectangle = owner.getBounds(systemManager.getSandboxRoot());
+        var ownerLeft:Number = ownerBounds.left;
+        var ownerRight:Number = ownerBounds.right;
+        var ownerTop:Number = ownerBounds.top;
+        var ownerBottom:Number = ownerBounds.bottom;
+        var maxW:Number;
+        var maxH:Number;
+        
+        switch (actualHorizontalPosition)
+        {
+            case CalloutPosition.MIDDLE:
+            {
+                // Callout matches screen width
+                maxW = screen.width - (margin * 2);
+                break;
+            }
+            case CalloutPosition.START:
+            case CalloutPosition.END:
+            {
+                // Flip left and right when using inner positions
+                ownerLeft = ownerBounds.right;
+                ownerRight = ownerBounds.left;
+                
+                // Fall through
+            }
+            default:
+            {
+                // Maximum is the larger of the actual position or flipped position
+                maxW = Math.max(ownerLeft, screen.right - ownerRight) - margin;
+                break;
+            }
+        }
+        
+        // If preferred position was AUTO, then allow maxWidth to grow to
+        // fit the interior position if the owner is wide
+        if ((horizontalPosition == CalloutPosition.AUTO) &&
+            (ownerBounds.width > maxW))
+            maxW += ownerBounds.width;
+        
+        switch (actualVerticalPosition)
+        {
+            case CalloutPosition.MIDDLE:
+            {
+                // Callout matches screen height
+                maxH = screen.height - (margin * 2);
+                break;
+            }
+            case CalloutPosition.START:
+            case CalloutPosition.END:
+            {
+                // Flip top and bottom when using inner positions
+                ownerTop = ownerBounds.bottom;
+                ownerBottom = ownerBounds.top;
+                
+                // Fall through
+            }
+            default:
+            {
+                // Maximum is the larger of the actual position or flipped position
+                maxH = Math.max(ownerTop, screen.bottom - ownerBottom) - margin;
+                break;
+            }
+        }
+        
+        // If preferred position was AUTO, then allow maxHeight to grow to
+        // fit the interior position if the owner is tall
+        if ((verticalPosition == CalloutPosition.AUTO) && 
+            (ownerBounds.height > maxH))
+            maxH += ownerBounds.height;
+        
+        calloutMaxWidth = maxW;
+        calloutMaxHeight = maxH;
+    }
+
+    /**
+     *  @private
+     */
+    mx_internal function determineArrowPosition(horizontalPos:String, verticalPos:String):String
+    {
+        // Determine arrow direction, outer positions get priority.
+        // Corner positions and center show no arrow
+        var direction:String = ArrowDirection.NONE;
+
+        if (horizontalPos == CalloutPosition.BEFORE)
+        {
+            if ((verticalPos != CalloutPosition.BEFORE)
+                &&  (verticalPos != CalloutPosition.AFTER))
+            {
+                direction = ArrowDirection.RIGHT;
+            }
+        }
+        else if (horizontalPos == CalloutPosition.AFTER)
+        {
+            if ((verticalPos != CalloutPosition.BEFORE)
+                && (verticalPos != CalloutPosition.AFTER))
+            {
+                direction = ArrowDirection.LEFT;
+            }
+        }
+        else if (verticalPos == CalloutPosition.BEFORE)
+        {
+            direction = ArrowDirection.DOWN;
+        }
+        else if (verticalPos == CalloutPosition.AFTER)
+        {
+            direction = ArrowDirection.UP;
+        }
+        else if (horizontalPos == CalloutPosition.START)
+        {
+            direction = ArrowDirection.LEFT;
+        }
+        else if (horizontalPos == CalloutPosition.END)
+        {
+            direction = ArrowDirection.RIGHT;
+        }
+        else if (verticalPos == CalloutPosition.START)
+        {
+            direction = ArrowDirection.UP;
+        }
+        else if (verticalPos == CalloutPosition.END)
+        {
+            direction = ArrowDirection.DOWN;
+        }
+
+        return direction
+    }
+
+    /**
+     *  @private
+     * 
+     *  Uses horizontalPosition and verticalPosition to determine the bounds of
+     *  the callout.
+     */
+    mx_internal function determinePosition(horizontalPos:String, verticalPos:String,
+                                           matrix:Matrix, registrationPoint:Point):Rectangle
+    {
+        var ownerVisualElement:ILayoutElement = owner as ILayoutElement;
+        var ownerWidth:Number = (ownerVisualElement) ? ownerVisualElement.getLayoutBoundsWidth() : owner.width;
+        var ownerHeight:Number = (ownerVisualElement) ? ownerVisualElement.getLayoutBoundsHeight() : owner.height;
+        var calloutWidth:Number = getLayoutBoundsWidth();
+        var calloutHeight:Number = this.calloutHeight;
+
+        switch (horizontalPos)
+        {
+            case CalloutPosition.BEFORE:
+            {
+                // The full width of the callout is before the owner
+                // All arrow directions are ArrowDirection.RIGHT x=(width - arrow.width)
+                registrationPoint.x = -calloutWidth;
+                break;
+            }
+            case CalloutPosition.START:
+            {
+                // ArrowDirection.LEFT is at x=0
+                registrationPoint.x = 0;
+                break;
+            }
+            case CalloutPosition.END:
+            {
+                // The ends of the owner and callout are aligned
+                registrationPoint.x = (ownerWidth - calloutWidth);
+                break;
+            }
+            case CalloutPosition.AFTER:
+            {
+                // The full width of the callout is after the owner
+                // All arrow directions are ArrowDirection.LEFT (x=0)
+                registrationPoint.x = ownerWidth;
+                break;
+            }
+            default: // case CalloutPosition.MIDDLE:
+            {
+                registrationPoint.x = Math.floor((ownerWidth - calloutWidth) / 2);
+                break;
+            }
+        }
+
+        switch (verticalPos)
+        {
+            case CalloutPosition.BEFORE:
+            {
+                // The full height of the callout is before the owner
+                // All arrow directions are ArrowDirection.DOWN y=(height - arrow.height)
+                registrationPoint.y = -calloutHeight;
+                break;
+            }
+            case CalloutPosition.START:
+            {
+                // ArrowDirection.UP is at y=0
+                registrationPoint.y = 0;
+                break;
+            }
+            case CalloutPosition.MIDDLE:
+            {
+                registrationPoint.y = Math.floor((ownerHeight - calloutHeight) / 2);
+                break;
+            }
+            case CalloutPosition.END:
+            {
+                // The ends of the owner and callout are aligned
+                registrationPoint.y = (ownerHeight - calloutHeight);
+                break;
+            }
+            default: //case CalloutPosition.AFTER:
+            {
+                // The full height of the callout is after the owner
+                // All arrow directions are ArrowDirection.UP (y=0)
+                registrationPoint.y = ownerHeight;
+                break;
+            }
+        }
+
+        var topLeft:Point = registrationPoint.clone();
+        var size:Point = MatrixUtil.transformBounds(calloutWidth, calloutHeight, matrix, topLeft);
+        var bounds:Rectangle = new Rectangle();
+        
+        bounds.left = topLeft.x;
+        bounds.top = topLeft.y;
+        bounds.width = size.x;
+        bounds.height = size.y;
+        
+        return bounds;
+    }
+    
+    /**
+     * @private
+     */
+    mx_internal function get isArrowVertical():Boolean
+    {
+        return (arrowDirection == ArrowDirection.UP ||
+                arrowDirection == ArrowDirection.DOWN);
+    }
+
+    //--------------------------------------------------------------------------
+    //
+    //  Event handlers
+    //
+    //--------------------------------------------------------------------------
+
+    /**
+     *  @private
+     */
+    private function arrow_resizeHandler(event:Event):void
+    {
+        updateSkinDisplayList();
+    }
+    
+    /**
+     *  @private
+     */
+    private function systemManager_resizeHandler(event:Event):void
+    {
+        // Remove explicit settings if due to Resize effect
+        softKeyboardEffectResetExplicitSize();
+        
+        // Screen resize might require a new arrow direction and callout position
+        invalidatePosition();
+        
+        if (!isSoftKeyboardEffectActive)
+        {
+            // Force validation and use new screen size only if the keyboard
+            // effect is not active. The stage dimensions may be invalid while 
+            // the soft keyboard is active. See SDK-31860.
+            validateNow();
+        }
+    }
+}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/0d6e7b32/frameworks/projects/spark/src/spark/components/Callout.png
----------------------------------------------------------------------
diff --git a/frameworks/projects/spark/src/spark/components/Callout.png b/frameworks/projects/spark/src/spark/components/Callout.png
new file mode 100644
index 0000000..36bc882
Binary files /dev/null and b/frameworks/projects/spark/src/spark/components/Callout.png differ

http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/0d6e7b32/frameworks/projects/spark/src/spark/components/CalloutButton.as
----------------------------------------------------------------------
diff --git a/frameworks/projects/spark/src/spark/components/CalloutButton.as b/frameworks/projects/spark/src/spark/components/CalloutButton.as
new file mode 100644
index 0000000..7eec462
--- /dev/null
+++ b/frameworks/projects/spark/src/spark/components/CalloutButton.as
@@ -0,0 +1,807 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.events.Event;
+
+import mx.core.ClassFactory;
+import mx.core.IFactory;
+import mx.core.mx_internal;
+import mx.utils.BitFlagUtil;
+
+import spark.components.supportClasses.DropDownController;
+import spark.core.ContainerDestructionPolicy;
+import spark.events.DropDownEvent;
+import spark.events.PopUpEvent;
+import spark.layouts.supportClasses.LayoutBase;
+
+use namespace mx_internal;
+
+//--------------------------------------
+//  Styles
+//--------------------------------------
+
+[Exclude(name="repeatDelay", kind="style")]
+[Exclude(name="repeatInterval", kind="style")]
+
+//--------------------------------------
+//  Events
+//--------------------------------------
+
+/**
+ *  Dispatched when the callout closes for any reason, such when: 
+ *  <ul>
+ *      <li>The callout is programmatically closed.</li>
+ *      <li>The user clicks outside of the callout.</li>
+ *      <li>The user clicks the open button while the callout is 
+ *  displayed.</li>
+ *  </ul>
+ *
+ *  @eventType spark.events.DropDownEvent.CLOSE
+ *  
+ *  @langversion 3.0
+ *  @playerversion AIR 3
+ *  @productversion Flex 4.6
+ */
+[Event(name="close", type="spark.events.DropDownEvent")]
+
+/**
+ *  Dispatched when the user clicks the open button
+ *  to display the callout.  
+ *
+ *  @eventType spark.events.DropDownEvent.OPEN
+ *  
+ *  @langversion 3.0
+ *  @playerversion AIR 3
+ *  @productversion Flex 4.6
+ */
+[Event(name="open", type="spark.events.DropDownEvent")]
+
+//--------------------------------------
+//  Other metadata
+//--------------------------------------
+
+[IconFile("Callout.png")]
+
+[DefaultProperty("calloutContent")]
+
+/**
+ *  The CalloutButton control is a drop down component that defines a button to
+ *  open and close a Callout container. 
+ *  The CalloutButton specifies the layout and child components 
+ *  of the Callout container.
+ *
+ *  <p>The following image shows a Callout container under the CalloutButton 
+ *  labeled 'Open callout':</p>
+ *
+ * <p>
+ *  <img src="../../images/ca_calloutButton_ca.png" alt="Callout button" />
+ * </p>
+ *
+ *  <p>The CalloutButton control uses the spark.components.supportClasses.DropDownController
+ *  class to manage the Callout container. 
+ *  You can access the DropDownController by using the protected 
+ *  <code>CalloutButton.dropDownController</code> property.</p>
+ *
+ *  <p>When the callout is open:</p>
+ *  <ul>
+ *    <li>Clicking the button closes the callout</li>
+ *    <li>Clicking outside of the callout closes the callout.</li>
+ *  </ul>
+ *
+ *  <p>The CalloutButton component has the following default characteristics:</p>
+ *     <table class="innertable">
+ *        <tr>
+ *           <th>Characteristic</th>
+ *           <th>Description</th>
+ *        </tr>
+ *        <tr>
+ *           <td>Default size</td>
+ *           <td>Wide enough to display the text label of the control</td>
+ *        </tr>
+ *        <tr>
+ *           <td>Minimum size</td>
+ *           <td>32 pixels wide and 43 pixels high</td>
+ *        </tr>
+ *        <tr>
+ *           <td>Maximum size</td>
+ *           <td>10000 pixels wide and 10000 pixels high</td>
+ *        </tr>
+ *        <tr>
+ *           <td>Default skin class</td>
+ *           <td>spark.skins.mobile.CalloutButtonSkin</td>
+ *        </tr>
+ *     </table>
+ *
+ *  @mxml
+ *  
+ *  <p>The <code>&lt;s:CalloutButton&gt;</code> tag inherits all of the tag 
+ *  attributes of its superclass and adds the following tag attributes:</p>
+ *  
+ *  <pre>
+ *  &lt;s:CalloutButton
+ *   <strong>Properties</strong>
+ *    calloutDestructionPolicy="auto"
+ *    calloutLayout="BasicLayout"
+ *    horizontalPosition="auto"
+ *    verticalPosition="auto
+ * 
+ *   <strong>Events</strong>
+ *    open="<i>No default</i>"
+ *    close="<i>No default</i>"
+ *      ...
+ *      <i>child tags</i>
+ *      ...
+ *  &lt;/s:CalloutButton&gt;
+ *  </pre>
+ * 
+ *  @see spark.components.Callout
+ *  @see spark.components.Button
+ *  @see spark.components.supportClasses.DropDownController
+ *
+ *  @includeExample examples/CalloutButtonExample.mxml -noswf
+ *  
+ *  @langversion 3.0
+ *  @playerversion AIR 3
+ *  @productversion Flex 4.6
+ */
+public class CalloutButton extends Button
+{
+    //--------------------------------------------------------------------------
+    //
+    //  Class constants
+    //
+    //--------------------------------------------------------------------------
+    
+    /**
+     *  @private
+     */
+    mx_internal static const CALLOUT_CONTENT_PROPERTY_FLAG:uint = 1 << 0;
+    
+    /**
+     *  @private
+     */
+    mx_internal static const CALLOUT_LAYOUT_PROPERTY_FLAG:uint = 1 << 1;
+    
+    /**
+     *  @private
+     */
+    mx_internal static const HORIZONTAL_POSITION_PROPERTY_FLAG:uint = 1 << 2;
+    
+    /**
+     *  @private
+     */
+    mx_internal static const VERTICAL_POSITION_PROPERTY_FLAG:uint = 1 << 3;
+    
+    //--------------------------------------------------------------------------
+    //
+    //  Constructor
+    //
+    //--------------------------------------------------------------------------
+    
+    /**
+     *  Constructor.
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public function CalloutButton()
+    {
+        super();
+        
+        dropDownController = new DropDownController();
+    }
+    
+    //--------------------------------------------------------------------------
+    //
+    //  Skin parts
+    //
+    //--------------------------------------------------------------------------
+    
+    [SkinPart(required="false")]
+    
+    /**
+     *  A skin part that defines the drop-down factory which creates the Callout
+     *  instance.
+     * 
+     *  If <code>dropDown</code> is not defined on the skin, a  
+     *  <code>ClassFactory</code> is created to generate a default Callout
+     *  instance.
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public var dropDown:IFactory;
+    
+    //--------------------------------------------------------------------------
+    //
+    //  Variables
+    //
+    //--------------------------------------------------------------------------
+    
+    /**
+     *  @private
+     *  Several properties are proxied to callout.  However, when callout
+     *  is not around, we need to store values set on CalloutButton.  This object 
+     *  stores those values.  If callout is around, the values are stored 
+     *  on the callout directly.  However, we need to know what values 
+     *  have been set by the developer on the CalloutButton (versus set on 
+     *  the callout or defaults of the callout) as those are values 
+     *  we want to carry around if the callout changes (via a new skin). 
+     *  In order to store this info effeciently, calloutProperties becomes 
+     *  a uint to store a series of BitFlags.  These bits represent whether a 
+     *  property has been explicitely set on this CalloutButton.  When the 
+     *  callout is not around, calloutProperties is a typeless 
+     *  object to store these proxied properties.  When callout is around,
+     *  calloutProperties stores booleans as to whether these properties 
+     *  have been explicitely set or not.
+     */
+    mx_internal var calloutProperties:Object = {};
+    
+    //--------------------------------------------------------------------------
+    //
+    //  Properties proxied to callout
+    //
+    //--------------------------------------------------------------------------
+    
+    //----------------------------------
+    //  calloutContent
+    //---------------------------------- 
+    
+    [ArrayElementType("mx.core.IVisualElement")]
+    
+    /**
+     *  The set of components to include in the Callout's content.
+     *
+     *  @default null
+     *
+     *  @see spark.components.Callout
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public function get calloutContent():Array
+    {
+        if (callout && callout.contentGroup)
+            return callout.contentGroup.getMXMLContent();
+        else
+            return calloutProperties.calloutContent;
+    }
+    
+    /**
+     *  @private
+     */
+    public function set calloutContent(value:Array):void
+    {
+        if (callout)
+        {
+            callout.mxmlContent = value;
+            calloutProperties = BitFlagUtil.update(calloutProperties as uint, 
+                CALLOUT_CONTENT_PROPERTY_FLAG, value != null);
+        }
+        else
+            calloutProperties.calloutContent = value;
+    }
+    
+    //----------------------------------
+    //  calloutLayout
+    //---------------------------------- 
+    
+    /**
+     *  Defines the layout of the Callout container.
+     *
+     *  @default BasicLayout
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public function get calloutLayout():LayoutBase
+    {
+        return (callout)  ? callout.layout : calloutProperties.calloutLayout;
+    }
+    
+    /**
+     *  @private
+     */
+    public function set calloutLayout(value:LayoutBase):void
+    {
+        if (callout)
+        {
+            callout.layout = value;
+            calloutProperties = BitFlagUtil.update(calloutProperties as uint, 
+                CALLOUT_LAYOUT_PROPERTY_FLAG, true);
+        }
+        else
+            calloutProperties.calloutLayout = value;
+    }
+    
+    //----------------------------------
+    //  horizontalPosition
+    //----------------------------------
+    
+    [Inspectable(category="General", enumeration="before,start,middle,end,after,auto", defaultValue="auto")]
+    
+    /**
+     *  @copy spark.components.Callout#horizontalPosition
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public function get horizontalPosition():String
+    {
+        if (callout)
+            return callout.horizontalPosition;
+        
+        return calloutProperties.horizontalPosition;
+    }
+    
+    /**
+     *  @private
+     */
+    public function set horizontalPosition(value:String):void
+    {
+        if (callout)
+        {
+            callout.horizontalPosition = value;
+            calloutProperties = BitFlagUtil.update(calloutProperties as uint, 
+                HORIZONTAL_POSITION_PROPERTY_FLAG, value != null);
+        }
+        else
+            calloutProperties.horizontalPosition = value;
+    }
+    
+    //----------------------------------
+    //  verticalPosition
+    //----------------------------------
+    
+    [Inspectable(category="General", enumeration="before,start,middle,end,after,auto", defaultValue="auto")]
+    
+    /**
+     *  @copy spark.components.Callout#verticalPosition
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public function get verticalPosition():String
+    {
+        if (callout)
+            return callout.verticalPosition;
+        
+        return calloutProperties.verticalPosition;
+    }
+    
+    /**
+     *  @private
+     */
+    public function set verticalPosition(value:String):void
+    {
+        if (callout)
+        {
+            callout.verticalPosition = value;
+            calloutProperties = BitFlagUtil.update(calloutProperties as uint, 
+                VERTICAL_POSITION_PROPERTY_FLAG, value != null);
+        }
+        else
+            calloutProperties.verticalPosition = value;
+    }
+    
+    //--------------------------------------------------------------------------
+    //
+    //  Properties
+    //
+    //--------------------------------------------------------------------------
+    
+    //----------------------------------
+    //  callout
+    //----------------------------------
+    
+    /**
+     *  @private
+     */
+    private var _callout:Callout;
+    
+    [Bindable("calloutChanged")]
+    
+    /**
+     *  The Callout instance created after the <code>DropDownEvent.OPEN</code>
+     *  is fired. The instance is created using the <code>dropDown</code>
+     *  <code>IFactory</code> skin part.
+     * 
+     *  @see #calloutDestructionPolicy
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public function get callout():Callout
+    {
+        return _callout;
+    }
+    
+    /**
+     *  @private
+     */
+    mx_internal function setCallout(value:Callout):void
+    {
+        if (_callout == value)
+            return;
+        
+        _callout = value;
+        
+        if (hasEventListener("calloutChanged"))
+            dispatchEvent(new Event("calloutChanged"));
+    }
+    
+    //----------------------------------
+    //  dropDownController
+    //----------------------------------
+    
+    /**
+     *  @private
+     */
+    private var _dropDownController:DropDownController; 
+    
+    /**
+     *  Instance of the DropDownController class that handles all of the mouse, keyboard 
+     *  and focus user interactions. 
+     * 
+     *  Flex calls the <code>initializeDropDownController()</code> method after 
+     *  the DropDownController instance is created in the constructor.
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    protected function get dropDownController():DropDownController
+    {
+        return _dropDownController;
+    }
+    
+    /**
+     *  @private
+     */
+    protected function set dropDownController(value:DropDownController):void
+    {
+        if (_dropDownController == value)
+            return;
+        
+        _dropDownController = value;
+        
+        _dropDownController.closeOnResize = false;
+        _dropDownController.addEventListener(DropDownEvent.OPEN, dropDownController_openHandler);
+        _dropDownController.addEventListener(DropDownEvent.CLOSE, dropDownController_closeHandler);
+        
+        _dropDownController.openButton = this;
+        
+        if (callout)
+            _dropDownController.dropDown = callout;    
+    }
+    
+    //----------------------------------
+    //  isDropDownOpen
+    //----------------------------------
+    
+    /**
+     *  @copy spark.components.supportClasses.DropDownController#isOpen
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public function get isDropDownOpen():Boolean
+    {
+        if (dropDownController)
+            return dropDownController.isOpen;
+        else
+            return false;
+    }
+    
+    //----------------------------------
+    //  calloutDestructionPolicy
+    //----------------------------------
+    
+    private var _calloutDestructionPolicy:String = ContainerDestructionPolicy.AUTO;
+    
+    [Inspectable(category="General", enumeration="auto,never", defaultValue="auto")]
+    
+    /**
+     *  Defines the destruction policy the callout button uses
+     *  when the callout is closed. 
+     *  If set to <code>"auto"</code>, the button 
+     *  destroys the Callout instance when it is closed.  
+     *  If set to <code>"never"</code>, the Callout container 
+     *  is cached in memory.
+     * 
+     *  @default auto
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public function get calloutDestructionPolicy():String
+    {
+        return _calloutDestructionPolicy;
+    }
+    
+    /**
+     *  @private
+     */
+    public function set calloutDestructionPolicy(value:String):void
+    {
+        if (_calloutDestructionPolicy == value)
+            return;
+        
+        _calloutDestructionPolicy = value;
+        
+        // destroy the callout immediately if currently closed
+        if (!isDropDownOpen &&
+            (calloutDestructionPolicy == ContainerDestructionPolicy.AUTO))
+        {
+            destroyCallout();
+        }
+    }
+    
+    //--------------------------------------------------------------------------
+    //
+    //  Overridden methods
+    //
+    //--------------------------------------------------------------------------
+    
+    /**
+     *  @private
+     */
+    override protected function attachSkin():void
+    {
+        super.attachSkin();
+        
+        // create dropDown if it was not found in the skin
+        if (!dropDown && !("dropDown" in skin))
+            dropDown = new ClassFactory(Callout);
+    }
+    
+    /**
+     *  @private
+     */
+    override protected function partAdded(partName:String, instance:Object):void
+    {
+        super.partAdded(partName, instance);
+        
+        if (partName == "dropDown")
+        {
+            // copy proxied values from calloutProperties (if set) to callout
+            var newCalloutProperties:uint = 0;
+            var calloutInstance:Callout = instance as Callout;
+            
+            if (calloutInstance && dropDownController)
+            {
+                calloutInstance.id = "callout";
+                dropDownController.dropDown = calloutInstance;
+                
+                calloutInstance.addEventListener(PopUpEvent.OPEN, callout_openHandler);
+                calloutInstance.addEventListener(PopUpEvent.CLOSE, callout_closeHandler);
+                
+                if (calloutProperties.calloutContent !== undefined)
+                {
+                    calloutInstance.mxmlContent = calloutProperties.calloutContent;
+                    newCalloutProperties = BitFlagUtil.update(newCalloutProperties, 
+                        CALLOUT_CONTENT_PROPERTY_FLAG, true);
+                }
+                
+                if (calloutProperties.calloutLayout !== undefined)
+                {
+                    calloutInstance.layout = calloutProperties.calloutLayout;
+                    newCalloutProperties = BitFlagUtil.update(newCalloutProperties, 
+                        CALLOUT_LAYOUT_PROPERTY_FLAG, true);
+                }
+                
+                if (calloutProperties.horizontalPosition !== undefined)
+                {
+                    calloutInstance.horizontalPosition = calloutProperties.horizontalPosition;
+                    newCalloutProperties = BitFlagUtil.update(newCalloutProperties, 
+                        HORIZONTAL_POSITION_PROPERTY_FLAG, true);
+                }
+                
+                if (calloutProperties.verticalPosition !== undefined)
+                {
+                    calloutInstance.verticalPosition = calloutProperties.verticalPosition;
+                    newCalloutProperties = BitFlagUtil.update(newCalloutProperties, 
+                        VERTICAL_POSITION_PROPERTY_FLAG, true);
+                }
+                
+                calloutProperties = newCalloutProperties;
+            }
+        }
+    }
+    
+    /**
+     *  @private
+     */
+    override protected function partRemoved(partName:String, instance:Object):void
+    {
+        if (dropDownController && (instance == callout))
+        {
+            dropDownController.dropDown = null;
+        }
+        
+        if (partName == "dropDown")
+        {
+            callout.removeEventListener(PopUpEvent.OPEN, callout_openHandler);
+            callout.removeEventListener(PopUpEvent.CLOSE, callout_closeHandler);
+            
+            // copy proxied values from callout (if explicitely set) to calloutProperties
+            var newCalloutProperties:Object = {};
+            
+            if (BitFlagUtil.isSet(calloutProperties as uint, CALLOUT_CONTENT_PROPERTY_FLAG) &&
+                (callout.contentGroup))
+            {
+                newCalloutProperties.calloutContent = callout.contentGroup.getMXMLContent();
+                callout.contentGroup.mxmlContent = null;
+            }
+            
+            if (BitFlagUtil.isSet(calloutProperties as uint, CALLOUT_LAYOUT_PROPERTY_FLAG))
+            {
+                newCalloutProperties.calloutLayout = callout.layout;
+                callout.layout = null;
+            }
+            
+            if (BitFlagUtil.isSet(calloutProperties as uint, HORIZONTAL_POSITION_PROPERTY_FLAG))
+                newCalloutProperties.horizontalPosition = callout.horizontalPosition;
+            
+            if (BitFlagUtil.isSet(calloutProperties as uint, VERTICAL_POSITION_PROPERTY_FLAG))
+                newCalloutProperties.verticalPosition = callout.verticalPosition;
+            
+            calloutProperties = newCalloutProperties;
+        }
+        
+        super.partRemoved(partName, instance);
+    }
+    
+    //--------------------------------------------------------------------------
+    //
+    //  Methods
+    //
+    //--------------------------------------------------------------------------
+    
+    /**
+     *  Initializes the dropDown and changes the skin state to open. 
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */ 
+    public function openDropDown():void
+    {
+        dropDownController.openDropDown();
+    }
+    
+    /**
+     *  Changes the skin state to normal.
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public function closeDropDown():void
+    {
+        dropDownController.closeDropDown(false);
+    }
+    
+    /**
+     *  @private
+     *  Destroys the callout 
+     */
+    private function destroyCallout():void
+    {
+        removeDynamicPartInstance("dropDown", callout);
+        setCallout(null);
+    }
+    
+    //--------------------------------------------------------------------------
+    //
+    //  Event handlers
+    //
+    //--------------------------------------------------------------------------
+    
+    /**
+     *  @private
+     *  Event handler for the <code>dropDownController</code> 
+     *  <code>DropDownEvent.OPEN</code> event. Creates and opens the Callout.
+     */
+    mx_internal function dropDownController_openHandler(event:DropDownEvent):void
+    {
+        if (!callout)
+            setCallout(createDynamicPartInstance("dropDown") as Callout);
+        
+        if (callout)
+        {
+            // close the callout if the CalloutButton is removed
+            addEventListener(Event.REMOVED_FROM_STAGE, button_removedFromStage);
+            
+            callout.open(this, false);
+        }
+    }
+    
+    /**
+     *  @private
+     *  Event handler for the <code>dropDownController</code> 
+     *  <code>DropDownEvent.CLOSE</code> event. Closes the Callout.
+     */
+    mx_internal function dropDownController_closeHandler(event:DropDownEvent):void
+    {
+        // If the callout was closed directly, then callout could already be
+        // destroyed by calloutDestructionPolicy
+        if (callout)
+        {
+            removeEventListener(Event.REMOVED_FROM_STAGE, button_removedFromStage);
+            
+            // Dispatch the close event after the callout's PopUpEvent.CLOSE fires
+            callout.close();
+        }
+    }
+    
+    /**
+     *  @private
+     */
+    private function callout_openHandler(event:PopUpEvent):void
+    {
+        dispatchEvent(new DropDownEvent(DropDownEvent.OPEN));
+    }
+    
+    /**
+     *  @private
+     */
+    private function callout_closeHandler(event:PopUpEvent):void
+    {
+        // Sanity check. Was callout closed without calling closeDropDown()? 
+        // If so, call closeDropDown directly to remove event listeners. This 
+        // callout_closeHandler will only be called once since the 2nd call
+        // to close() in dropDownController_closeHandler() will not dispatch
+        // another PopUpEvent.CLOSE.
+        if (dropDownController.isOpen)
+            closeDropDown();
+        
+        if (calloutDestructionPolicy == ContainerDestructionPolicy.AUTO)
+            destroyCallout();
+        
+        dispatchEvent(new DropDownEvent(DropDownEvent.CLOSE));
+    }
+    
+    /**
+     *  @private
+     */
+    private function button_removedFromStage(event:Event):void
+    {
+        if (!isDropDownOpen)
+            return;
+        
+        // Hide the callout immediately instead of waiting for the skin
+        // state to transition to "closed"
+        callout.visible = false;
+
+        closeDropDown();
+    }
+}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/0d6e7b32/frameworks/projects/spark/src/spark/components/CalloutPosition.as
----------------------------------------------------------------------
diff --git a/frameworks/projects/spark/src/spark/components/CalloutPosition.as b/frameworks/projects/spark/src/spark/components/CalloutPosition.as
new file mode 100644
index 0000000..a0b6e7d
--- /dev/null
+++ b/frameworks/projects/spark/src/spark/components/CalloutPosition.as
@@ -0,0 +1,96 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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
+{
+
+/**
+ *  The CalloutPosition calss defines the enumeration of 
+ *  horizontal and vertical positions of the Callout component
+ *  relative to the owner.
+ * 
+ *  @see spark.components.Callout
+ *  @see spark.components.Callout#horizontalPosition
+ *  @see spark.components.Callout#verticalPosition
+ * 
+ *  @langversion 3.0
+ *  @playerversion AIR 3
+ *  @productversion Flex 4.6
+ */
+public final class CalloutPosition
+{
+    
+    /**
+     *  Position the trailing edge of the callout before the leading edge of the owner.
+     * 
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public static const BEFORE:String = "before";
+    
+    /**
+     *  Position the leading edge of the callout at the leading edge of the owner.
+     * 
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public static const START:String = "start";
+    
+    /**
+     *  Position the horizontalCenter of the callout to the horizontalCenter of the owner.
+     * 
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public static const MIDDLE:String = "middle";
+    
+    /**
+     *  Position the trailing edge of the callout at the trailing edge of the owner.
+     * 
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public static const END:String = "end";
+    
+    /**
+     *  Position the leading edge of the callout after the trailing edge of the owner.
+     * 
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public static const AFTER:String = "after";
+    
+    /**
+     *  Position the callout on the exterior of the owner where the callout 
+     *  requires the least amount of resizing to fit.
+     * 
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public static const AUTO:String = "auto";
+    
+}
+
+}
\ No newline at end of file