blob: 9e5e732a9718daa557cc1cb6594dec87c82424fe [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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")]
//--------------------------------------
// Styles
//--------------------------------------
/**
* Specifies the delay, in milliseconds, to wait for opening the Callout
* when the button is rolled over.
* If set to <code>NaN</code>, then the Callout opens when clicking the button.
*
* @default NaN
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 3.8
* @productversion Flex 4.11
*/
[Style(name="rollOverOpenDelay", type="Number", inherit="no",theme="spark",minValue="0")]
//--------------------------------------
// 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 on mobile platforms <br/>
* spark.skins.spark.CalloutButtonSkin on desktops</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 Flash 11
* @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 Flash 11
* @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 Flash 11
* @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 Flash 11
* @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 Flash 11
* @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 Flash 11
* @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 Flash 11
* @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 Flash 11
* @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 Flash 11
* @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.rollOverOpenDelay = getStyle("rollOverOpenDelay");
_dropDownController.openButton = this;
if (callout)
_dropDownController.dropDown = callout;
}
/**
* @private
*/
override public function styleChanged(styleProp:String):void
{
super.styleChanged(styleProp);
var allStyles:Boolean = (styleProp == null || styleProp == "styleName");
if (allStyles || styleProp == "rollOverOpenDelay")
{
if (dropDownController)
dropDownController.rollOverOpenDelay = getStyle("rollOverOpenDelay");
}
}
//----------------------------------
// isDropDownOpen
//----------------------------------
/**
* @copy spark.components.supportClasses.DropDownController#isOpen
*
* @langversion 3.0
* @playerversion Flash 11
* @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 Flash 11
* @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 Flash 11
* @playerversion AIR 3
* @productversion Flex 4.6
*/
public function openDropDown():void
{
dropDownController.openDropDown();
}
/**
* Changes the skin state to normal.
*
* @langversion 3.0
* @playerversion Flash 11
* @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();
}
}
}