| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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.display.Stage; |
| import flash.events.Event; |
| import flash.events.MouseEvent; |
| import flash.events.SoftKeyboardEvent; |
| import flash.events.SoftKeyboardTrigger; |
| import flash.events.TimerEvent; |
| import flash.geom.Rectangle; |
| import flash.utils.Timer; |
| |
| import mx.core.EventPriority; |
| import mx.core.FlexGlobals; |
| import mx.core.FlexVersion; |
| import mx.core.mx_internal; |
| import mx.effects.IEffect; |
| import mx.effects.Parallel; |
| import mx.events.EffectEvent; |
| import mx.events.FlexEvent; |
| import mx.events.SandboxMouseEvent; |
| import mx.managers.PopUpManager; |
| import mx.managers.SystemManager; |
| import mx.styles.StyleProtoChain; |
| |
| import spark.effects.Move; |
| import spark.effects.Resize; |
| import spark.effects.animation.Animation; |
| import spark.effects.easing.IEaser; |
| import spark.effects.easing.Power; |
| import spark.events.PopUpEvent; |
| |
| use namespace mx_internal; |
| |
| //-------------------------------------- |
| // Events |
| //-------------------------------------- |
| |
| /** |
| * Dispatched by the container when it's opened and ready for user interaction. |
| * |
| * <p>This event is dispatched when the container switches from the |
| * <code>closed</code> to <code>normal</code> skin state and the transition |
| * to that state completes.</p> |
| * |
| * <p>Note: As of Flex 4.6, SkinnablePopUp container inherits its styles |
| * from the stage and not its owner.</p> |
| * |
| * @eventType spark.events.PopUpEvent.OPEN |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| [Event(name="open", type="spark.events.PopUpEvent")] |
| |
| /** |
| * Dispatched by the container when it's closed. |
| * |
| * <p>This event is dispatched when the container switches from the |
| * <code>normal</code> to <code>closed</code> skin state and |
| * the transition to that state completes.</p> |
| * |
| * <p>The event provides a mechanism to pass commit information from |
| * the container to an event listener. |
| * One typical usage scenario is building a multiple-choice dialog with a |
| * cancel button. |
| * When a valid option is selected, you close the pop up |
| * with a call to the <code>close()</code> method, passing |
| * <code>true</code> to the <code>commit</code> parameter and optionally passing in |
| * any relevant data. |
| * When the SkinnablePopUpContainer has completed closing, |
| * it dispatches this event. |
| * Then, in the event listener, you can check the <code>commit</code> |
| * property and perform the appropriate action. </p> |
| * |
| * @eventType spark.events.PopUpEvent.CLOSE |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| [Event(name="close", type="spark.events.PopUpEvent")] |
| |
| //-------------------------------------- |
| // Styles |
| //-------------------------------------- |
| |
| /** |
| * Duration of the soft keyboard move and resize effect in milliseconds. |
| * |
| * @default 150 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11 |
| * @playerversion AIR 3.1 |
| * @productversion Flex 4.6 |
| */ |
| [Style(name="softKeyboardEffectDuration", type="Number", format="Time", inherit="no", minValue="0.0")] |
| |
| //-------------------------------------- |
| // States |
| //-------------------------------------- |
| |
| /** |
| * The closed state. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| [SkinState("closed")] |
| |
| /** |
| * The SkinnablePopUpContainer class is a SkinnableContainer that functions as a pop-up. |
| * One typical use for a SkinnablePopUpContainer container is to open a simple window |
| * in an application, such as an alert window, to indicate that the user must perform some action. |
| * |
| * <p>You do not create a SkinnablePopUpContainer container as part of the normal layout |
| * of its parent container. |
| * Instead, it appears as a pop-up window 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 SkinnablePopUpContainer 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>The SkinnablePopUpContainer 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.spark.SkinnablePopUpContainerSkin</td></tr> |
| * </table> |
| * |
| * @mxml <p>The <code><s:SkinnablePopUpContainer></code> tag inherits all of the tag |
| * attributes of its superclass and adds the following tag attributes:</p> |
| * |
| * <pre> |
| * <s:SkinnablePopUpContainer |
| * <strong>Events</strong> |
| * close="<i>No default</i>" |
| * open="<i>No default</i>" |
| * /> |
| * </pre> |
| * |
| * @see spark.skins.spark.SkinnablePopUpContainerSkin |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| public class SkinnablePopUpContainer extends SkinnableContainer |
| { |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| public function SkinnablePopUpContainer() |
| { |
| super(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Storage for the close event while waiting for the close transition to |
| * complete before dispatching it. |
| * |
| * @private |
| */ |
| private var closeEvent:PopUpEvent; |
| |
| /** |
| * Track whether the container is added to the PopUpManager. |
| * |
| * @private |
| */ |
| private var addedToPopUpManager:Boolean = false; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Soft Keyboard Effect Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * The current soft keyboard effect instance. This field is only set in |
| * cases where the position and size are not snapped during (a) initial |
| * activation and (b) deactivation. |
| */ |
| private var softKeyboardEffect:IEffect; |
| |
| /** |
| * @private |
| * Original pop-up y-position. |
| */ |
| private var softKeyboardEffectCachedYPosition:Number; |
| |
| /** |
| * @private |
| * Indicates a soft keyboard event was received but the effect is delayed |
| * while waiting for a mouseDown and mouseUp event sequence. |
| */ |
| private var softKeyboardEffectPendingEventType:String = null; |
| |
| /** |
| * @private |
| * Number of milliseconds to wait for a mouseDown and mouseUp event |
| * sequence before playing the deactivate effect. |
| */ |
| mx_internal var softKeyboardEffectPendingEffectDelay:Number = 100; |
| |
| /** |
| * @private |
| * Timer used for awaiting a mouseDown event after a pending soft keyboard |
| * effect. |
| */ |
| private var softKeyboardEffectPendingEventTimer:Timer; |
| |
| /** |
| * @private |
| * Flag when orientationChanging is dispatched and orientationChange has |
| * not been received. |
| */ |
| private var softKeyboardEffectOrientationChanging:Boolean = false; |
| |
| /** |
| * @private |
| * Flag when orientation change handlers are installed to suppress |
| * excess soft keyboard effects during orientation change on iOS. |
| */ |
| private var softKeyboardEffectOrientationHandlerAdded:Boolean = false; |
| |
| /** |
| * @private |
| * Flag when mouse and orientation listeners are installed before |
| * the soft keyboard activate effect is played. |
| */ |
| private var softKeyboardStateChangeListenersInstalled:Boolean; |
| |
| /** |
| * @private |
| * Flag when resize listers are installed after ACTIVATE and uninstalled |
| * just before DEACTIVATE. |
| */ |
| private var resizeListenerInstalled:Boolean = false; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // isOpen |
| //---------------------------------- |
| |
| /** |
| * Storage for the isOpen property. |
| * |
| * @private |
| */ |
| private var _isOpen:Boolean = false; |
| |
| [Inspectable(category="General", defaultValue="false")] |
| |
| /** |
| * Contains <code>true</code> when the container is open and is currently showing as a pop-up. |
| * |
| * @see #open |
| * @see #close |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| public function get isOpen():Boolean |
| { |
| return _isOpen; |
| } |
| |
| /** |
| * Updates the isOpen flag to be reflected in the skin state |
| * without actually popping up the container through the PopUpManager. |
| * |
| * @private |
| */ |
| mx_internal function setIsOpen(value:Boolean):void |
| { |
| // NOTE: DesignView relies on this API, consult tooling before making changes. |
| _isOpen = value; |
| invalidateSkinState(); |
| } |
| |
| //---------------------------------- |
| // resizeForSoftKeyboard |
| //---------------------------------- |
| |
| private var _resizeForSoftKeyboard:Boolean = true; |
| |
| /** |
| * Enables resizing the pop-up when the soft keyboard |
| * on a mobile device is active. |
| * |
| * @default true |
| * |
| * @langversion 3.0 |
| * @playerversion AIR 3 |
| * @productversion Flex 4.6 |
| */ |
| public function get resizeForSoftKeyboard():Boolean |
| { |
| return _resizeForSoftKeyboard; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set resizeForSoftKeyboard(value:Boolean):void |
| { |
| if (_resizeForSoftKeyboard == value) |
| return; |
| |
| _resizeForSoftKeyboard = value; |
| } |
| |
| //---------------------------------- |
| // moveForSoftKeyboard |
| //---------------------------------- |
| |
| private var _moveForSoftKeyboard:Boolean = true; |
| |
| /** |
| * Enables moving the pop-up when the soft keyboard |
| * on a mobile device is active. |
| * |
| * @default true |
| * |
| * @langversion 3.0 |
| * @playerversion AIR 3 |
| * @productversion Flex 4.6 |
| */ |
| public function get moveForSoftKeyboard():Boolean |
| { |
| return _moveForSoftKeyboard; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set moveForSoftKeyboard(value:Boolean):void |
| { |
| if (_moveForSoftKeyboard == value) |
| return; |
| |
| _moveForSoftKeyboard = value; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Opens the container as a pop-up, and switches the skin state from |
| * <code>closed</code> to <code>normal</code>. |
| * After and transitions finish playing, it dispatches the |
| * <code>FlexEvent.OPEN</code> event. |
| * |
| * @param owner The owner of the container. |
| * The popup appears over this component. The owner must not be descendant |
| * of this container. |
| * |
| * @param modal Whether the container should be modal. |
| * A modal container takes all keyboard and mouse input until it is closed. |
| * A nonmodal container allows other components to accept input while the pop-up window is open. |
| * |
| * @see #close |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| public function open(owner:DisplayObjectContainer, modal:Boolean = false):void |
| { |
| if (isOpen) |
| return; |
| |
| closeEvent = null; // Clear any pending close event |
| this.owner = owner; |
| |
| // We track whether we've been added to the PopUpManager to handle the |
| // scenario of state transition interrupton. For example we may be playing |
| // a close transition and be interrupted with a call to open() in which |
| // case we wouldn't have removed the container from the PopUpManager since |
| // the close transition never reached its end. |
| if (!addedToPopUpManager) |
| { |
| addedToPopUpManager = true; |
| |
| // This will create the skin and attach it |
| PopUpManager.addPopUp(this, owner, modal); |
| |
| updatePopUpPosition(); |
| } |
| |
| // Change state *after* we pop up, as the skin needs to go be in the initial "closed" |
| // state while being created above in order for transitions to detect state change and play. |
| _isOpen = true; |
| invalidateSkinState(); |
| if (skin) |
| skin.addEventListener(FlexEvent.STATE_CHANGE_COMPLETE, stateChangeComplete_handler); |
| else |
| stateChangeComplete_handler(null); // Call directly |
| } |
| |
| /** |
| * Positions the pop-up after the pop-up is added to PopUpManager but |
| * before any state transitions occur. The base implementation of open() |
| * calls updatePopUpPosition() immediately after the pop-up is added. |
| * |
| * This method may also be called at any time to update the pop-up's |
| * position. Pop-ups that are positioned relative to their owner should |
| * call this method after position or size changes occur on the owner or |
| * it's ancestors. |
| * |
| * @langversion 3.0 |
| * @playerversion AIR 3 |
| * @productversion Flex 4.6 |
| */ |
| public function updatePopUpPosition():void |
| { |
| // subclasses will implement custom positioning |
| // e.g. PopUpManager.centerPopUp() |
| } |
| |
| /** |
| * Changes the current skin state to <code>closed</code>, waits until any state transitions |
| * finish playing, dispatches a <code>PopUpEvent.CLOSE</code> event, |
| * and then removes the container from the PopUpManager. |
| * |
| * <p>Use the <code>close()</code> method of the SkinnablePopUpContainer container |
| * to pass data back to the main application from the pop up. |
| * One typical usage scenario is building a dialog with a cancel button. |
| * When a valid option is selected in the dialog box, you close the dialog |
| * with a call to the <code>close()</code> method, passing |
| * <code>true</code> to the <code>commit</code> parameter and optionally passing |
| * any relevant data. |
| * When the SkinnablePopUpContainer has completed closing, |
| * it dispatch the <code>close</code> event. |
| * In the event listener for the <code>close</code> event, you can check |
| * the <code>commit</code> parameter and perform the appropriate actions. </p> |
| * |
| * @param commit Specifies if the return data should be committed by the application. |
| * The value of this argument is written to the <code>commit</code> property of |
| * the <code>PopUpEvent</code> event object. |
| * |
| * @param data Specifies any data returned to the application. |
| * The value of this argument is written to the <code>data</code> property of |
| * the <code>PopUpEvent</code> event object. |
| * |
| * @see #open |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| public function close(commit:Boolean = false, data:* = undefined):void |
| { |
| if (!isOpen) |
| return; |
| |
| // We will dispatch the event later, when the close transition is complete. |
| closeEvent = new PopUpEvent(PopUpEvent.CLOSE, false, false, commit, data); |
| |
| // Change state |
| _isOpen = false; |
| invalidateSkinState(); |
| |
| if (skin) |
| skin.addEventListener(FlexEvent.STATE_CHANGE_COMPLETE, stateChangeComplete_handler); |
| else |
| stateChangeComplete_handler(null); // Call directly |
| } |
| |
| /** |
| * Called by the soft keyboard <code>activate</code> and <code>deactive</code> event handlers, |
| * this method is responsible for creating the Spark effect played on the pop-up. |
| * |
| * This method may be overridden by subclasses. By default, it |
| * creates a parellel move and resize effect on the pop-up. |
| * |
| * @param yTo The new y-coordinate of the pop-up. |
| * |
| * @param height The new height of the pop-up. |
| * |
| * @return An IEffect instance serving as the move and/or resize transition |
| * for the pop-up. This effect is played after the soft keyboard is |
| * activated or deactivated. |
| * |
| * @langversion 3.0 |
| * @playerversion AIR 3 |
| * @productversion Flex 4.6 |
| */ |
| protected function createSoftKeyboardEffect(yTo:Number, heightTo:Number):IEffect |
| { |
| var move:Move; |
| var resize:Resize; |
| var easer:IEaser = new Power(0, 5); |
| |
| if (yTo != this.y) |
| { |
| move = new Move(); |
| move.target = this; |
| move.yTo = yTo; |
| move.disableLayout = true; |
| move.easer = easer; |
| } |
| |
| if (heightTo != this.height) |
| { |
| resize = new Resize(); |
| resize.target = this; |
| resize.heightTo = heightTo; |
| resize.disableLayout = true; |
| resize.easer = easer; |
| } |
| |
| if (move && resize) |
| { |
| var parallel:Parallel = new Parallel(); |
| parallel.addChild(move); |
| parallel.addChild(resize); |
| |
| return parallel; |
| } |
| else if (move || resize) |
| { |
| return (move) ? move : resize; |
| } |
| |
| return null; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // mx_internal properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------------- |
| // softKeyboardEffectCachedExplicitHeight |
| //---------------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _softKeyboardEffectExplicitHeightFlag:Boolean = false; |
| |
| /** |
| * @private |
| * Flag when explicitHeight is set when the soft keyboard effect is |
| * active. Use this to distinguish explicitHeight changes due to the |
| * resizeForSoftKeyboard setting. When true, we prevent the original |
| * cached height from being modified. |
| */ |
| mx_internal function get softKeyboardEffectExplicitHeightFlag():Boolean |
| { |
| return _softKeyboardEffectExplicitHeightFlag; |
| } |
| |
| private function setSoftKeyboardEffectExplicitHeightFlag(value:Boolean):void |
| { |
| _softKeyboardEffectExplicitHeightFlag = value; |
| } |
| |
| //---------------------------------------- |
| // softKeyboardEffectCachedExplicitWidth |
| //---------------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _softKeyboardEffectExplicitWidthFlag:Boolean = false; |
| |
| /** |
| * @private |
| * Flag when explicitWidth is set when the soft keyboard effect is |
| * active. Use this to distinguish explicitWidth changes due to the |
| * resizeForSoftKeyboard setting. |
| */ |
| mx_internal function get softKeyboardEffectExplicitWidthFlag():Boolean |
| { |
| return _softKeyboardEffectExplicitWidthFlag; |
| } |
| |
| private function setSoftKeyboardEffectExplicitWidthFlag(value:Boolean):void |
| { |
| _softKeyboardEffectExplicitWidthFlag = value; |
| } |
| |
| //---------------------------------- |
| // softKeyboardEffectCachedHeight |
| //---------------------------------- |
| |
| private var _softKeyboardEffectCachedHeight:Number; |
| |
| /** |
| * @private |
| * The original pop-up height to restore to when the soft keyboard is |
| * deactivated. If an explicitHeight was defined at activation, use it. |
| * If not, then use explicitMaxHeight or measuredHeight. |
| */ |
| mx_internal function get softKeyboardEffectCachedHeight():Number |
| { |
| var heightTo:Number = _softKeyboardEffectCachedHeight; |
| |
| if (!softKeyboardEffectExplicitHeightFlag) |
| { |
| if (!isNaN(explicitMaxHeight) && (measuredHeight > explicitMaxHeight)) |
| heightTo = explicitMaxHeight; |
| else |
| heightTo = measuredHeight; |
| } |
| |
| return heightTo; |
| } |
| |
| /** |
| * @private |
| */ |
| private function setSoftKeyboardEffectCachedHeight(value:Number):void |
| { |
| // Only allow changes to the cached height if it was not set explicitly |
| // prior to and/or during the soft keyboard effect. |
| if (!softKeyboardEffectExplicitHeightFlag) |
| _softKeyboardEffectCachedHeight = value; |
| } |
| |
| //---------------------------------- |
| // isSoftKeyboardEffectActive |
| //---------------------------------- |
| |
| private var _isSoftKeyboardEffectActive:Boolean; |
| |
| /** |
| * @private |
| * Returns true if the soft keyboard is active and the pop-up is moved |
| * and/or resized. |
| */ |
| mx_internal function get isSoftKeyboardEffectActive():Boolean |
| { |
| return _isSoftKeyboardEffectActive; |
| } |
| |
| //---------------------------------- |
| // marginTop |
| //---------------------------------- |
| |
| private var _marginTop:Number = 0; |
| |
| /** |
| * @private |
| * Defines a margin at the top of the screen where the pop-up cannot be |
| * resized or moved to. |
| */ |
| mx_internal function get softKeyboardEffectMarginTop():Number |
| { |
| return _marginTop; |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function set softKeyboardEffectMarginTop(value:Number):void |
| { |
| _marginTop = value; |
| } |
| |
| //---------------------------------- |
| // marginBottom |
| //---------------------------------- |
| |
| private var _marginBottom:Number = 0; |
| |
| /** |
| * @private |
| * Defines a margin at the bottom of the screen where the pop-up cannot be |
| * resized or moved to. |
| */ |
| mx_internal function get softKeyboardEffectMarginBottom():Number |
| { |
| return _marginBottom; |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function set softKeyboardEffectMarginBottom(value:Number):void |
| { |
| _marginBottom = value; |
| } |
| |
| //---------------------------------- |
| // isMouseDown |
| //---------------------------------- |
| |
| private var _isMouseDown:Boolean = false; |
| |
| /** |
| * @private |
| */ |
| private function get isMouseDown():Boolean |
| { |
| return _isMouseDown; |
| } |
| |
| /** |
| * @private |
| */ |
| private function set isMouseDown(value:Boolean):void |
| { |
| _isMouseDown = value; |
| |
| // Attempt to play a pending effect |
| playPendingEffect(true); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Force callout inheritance chain to start at the style root. |
| */ |
| override mx_internal function initProtoChain():void |
| { |
| // Maintain backwards compatibility of popup style inheritance |
| if (FlexVersion.compatibilityVersion < FlexVersion.VERSION_4_6) |
| super.initProtoChain(); |
| else |
| StyleProtoChain.initProtoChain(this, false); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function getCurrentSkinState():String |
| { |
| // The states are: |
| // "normal" |
| // "disabled" |
| // "closed" |
| |
| var state:String = super.getCurrentSkinState(); |
| if (!isOpen) |
| return state == "normal" ? "closed" : state; |
| return state; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Event handlers |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Play the soft keyboard effect. |
| */ |
| private function startEffect(event:Event):void |
| { |
| removeEventListener(Event.ENTER_FRAME, startEffect); |
| |
| // Abort the effect if the pop-up is closed or closing. The state |
| // transition handler will restore the original size of the pop-up. |
| if (!isOpen || !softKeyboardEffect) |
| return; |
| |
| // Clear the cached positions when the deactivate effect is complete. |
| softKeyboardEffect.addEventListener(EffectEvent.EFFECT_END, softKeyboardEffectCleanup); |
| softKeyboardEffect.addEventListener(EffectEvent.EFFECT_STOP, softKeyboardEffectCleanup); |
| |
| // Install mouse delay and orientation change listeners now. |
| // explicitHeight listener is installed after the effect completes. |
| if (isSoftKeyboardEffectActive) |
| installSoftKeyboardStateChangeListeners(); |
| |
| // Force the master clock of the animation engine to update its |
| // current time so that the overhead of creating the effect is not |
| // included in our animation interpolation. See SDK-27793 |
| Animation.pulse(); |
| softKeyboardEffect.play(); |
| } |
| |
| /** |
| * @private |
| * |
| * Called when we have completed transitioning to opened/closed state. |
| */ |
| private function stateChangeComplete_handler(event:Event):void |
| { |
| // We get called directly with null if there's no skin to listen to. |
| if (event) |
| event.target.removeEventListener(FlexEvent.STATE_CHANGE_COMPLETE, stateChangeComplete_handler); |
| |
| // Check for soft keyboard support |
| var topLevelApp:Application = FlexGlobals.topLevelApplication as Application; |
| var softKeyboardEffectEnabled:Boolean = (topLevelApp && Application.softKeyboardBehavior == "none"); |
| var smStage:Stage = systemManager.stage; |
| |
| if (isOpen) |
| { |
| dispatchEvent(new PopUpEvent(PopUpEvent.OPEN, false, false)); |
| |
| if (softKeyboardEffectEnabled) |
| { |
| if (smStage) |
| { |
| // Install soft keyboard event handling on the stage |
| smStage.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE, |
| stage_softKeyboardActivateHandler, true, EventPriority.DEFAULT, true); |
| smStage.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, |
| stage_softKeyboardDeactivateHandler, true, EventPriority.DEFAULT, true); |
| |
| // Use lower priority listener to allow subclasses to act |
| // on the resize event before the soft keyboard effect does. |
| systemManager.addEventListener(Event.RESIZE, systemManager_resizeHandler, false, EventPriority.EFFECT); |
| |
| updateSoftKeyboardEffect(true); |
| } |
| } |
| } |
| else |
| { |
| // Dispatch the close event before removing from the PopUpManager. |
| dispatchEvent(closeEvent); |
| closeEvent = null; |
| |
| if (softKeyboardEffectEnabled && smStage) |
| { |
| // Uninstall soft keyboard event handling |
| smStage.removeEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE, |
| stage_softKeyboardActivateHandler, true); |
| smStage.removeEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, |
| stage_softKeyboardDeactivateHandler, true); |
| systemManager.removeEventListener(Event.RESIZE, systemManager_resizeHandler); |
| } |
| |
| // We just finished closing, remove from the PopUpManager. |
| PopUpManager.removePopUp(this); |
| addedToPopUpManager = false; |
| owner = null; |
| |
| // Position and size may be invalid if the close transition |
| // completes before (a) deactivate is fired or (b) a pending |
| // soft keyboard change is still queued. Update immediately. |
| updateSoftKeyboardEffect(true); |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| private function stage_softKeyboardActivateHandler(event:SoftKeyboardEvent=null):void |
| { |
| var isFirstActivate:Boolean = false; |
| |
| // Reset state |
| softKeyboardEffectPendingEventType = null; |
| |
| // Save the original y-position and height if this is the first |
| // ACTIVATE event and an existing effect is not already in progress. |
| if (!isSoftKeyboardEffectActive && !softKeyboardEffect) |
| isFirstActivate = true; |
| |
| if (isFirstActivate) |
| { |
| // Play the activate effect with animation |
| updateSoftKeyboardEffect(false); |
| } |
| else |
| { |
| // Keyboard height has changed. However, the effect can be delayed if |
| // a mouseUp within the pop-up is pending. An additional activate can |
| // occur while the keyboard is open to reflect size changes due to auto |
| // correction or orientation changes. |
| |
| // As in the deactivate case, the move and resize effects should be |
| // delayed until the user can complete any mouse interaction. This |
| // allows the size and position of the pop-up to stay stable allowing |
| // events like a button press to complete normally. |
| |
| if (isMouseDown) |
| setPendingSoftKeyboardEvent(event); |
| else |
| updateSoftKeyboardEffect(true); |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| private function stage_softKeyboardDeactivateHandler(event:SoftKeyboardEvent=null):void |
| { |
| // If the effect is not active (no move or resize was needed), do |
| // nothing. If we're in the middle of an orientation change, also do |
| // nothing. |
| if (!isSoftKeyboardEffectActive || softKeyboardEffectOrientationChanging) |
| return; |
| |
| // Reset state |
| softKeyboardEffectPendingEventType = null; |
| |
| if (event.triggerType == SoftKeyboardTrigger.USER_TRIGGERED) |
| { |
| // userTriggered indicates they keyboard was closed explicitly (soft |
| // button on soft keyboard) or on Android, pressing the back button. |
| // Play the deactivate effect immediately. |
| updateSoftKeyboardEffect(false); |
| } |
| else // if (event.triggerType == SoftKeyboardTrigger.CONTENT_TRIGGERED) |
| { |
| // contentTriggered indicates focus was lost by tapping away from |
| // StageText or a programmatic call. Unfortunately, this |
| // distinction isn't entirely intuitive. We only care about delaying |
| // the deactivate effect when due to a mouse event. Delaying the |
| // effect allows the pop-up position and size to stay static until |
| // any mouse interaction is complete (e.g. button click). |
| // However, the softKeyboardDeactivate event is fired before |
| // the mouseDown event: |
| // deactivate -> mouseDown -> mouseUp |
| |
| // The approach here is to assume that a mouseDown was the trigger |
| // for the softKeyboardDeactivate event. Continue to delay the |
| // deactivate effect until a mouseDown and mouseUp sequence is |
| // received. In the event that the deactivation was due to a |
| // programmatic call, we'll stop this process after a specified |
| // delay time. |
| |
| // If, in the future, the event order changes to either: |
| // (a) mouseDown -> deactivate -> mouseUp |
| // (b) mouseDown -> mouseUp -> deactivate |
| // this approach will still work for the button click use case. |
| // Sequence (b) would simply fire a normal button click and have |
| // the consequence of a delayed deactivate effect only. |
| setPendingSoftKeyboardEvent(event); |
| } |
| } |
| |
| /** |
| * @private |
| * Disable soft keyboard deactivate when orientation is changing. |
| */ |
| private function stage_orientationChangingHandler(event:Event):void |
| { |
| softKeyboardEffectOrientationChanging = true; |
| } |
| |
| /** |
| * @private |
| * Re-enable soft keyboard deactivate effect when orietation change |
| * completes. |
| */ |
| private function stage_orientationChangeHandler(event:Event):void |
| { |
| softKeyboardEffectOrientationChanging = false; |
| } |
| |
| /** |
| * @private |
| * Listens for mouse events while the soft keyboard effect is active. |
| */ |
| private function mouseHandler(event:MouseEvent):void |
| { |
| isMouseDown = (event.type == MouseEvent.MOUSE_DOWN); |
| } |
| |
| private function systemManager_mouseUpHandler(event:Event):void |
| { |
| isMouseDown = false; |
| } |
| |
| /** |
| * @private |
| * This function is only called when the pendingEffectTimer completed and no |
| * mouseDown and mouseUp sequence was fired. |
| */ |
| private function pendingEffectTimer_timerCompleteHandler(event:Event):void |
| { |
| playPendingEffect(false) |
| } |
| |
| /** |
| * @private |
| * Play the effect when (a) not triggered by a mouse event or |
| * (b) triggered by a mouseUp event |
| */ |
| private function playPendingEffect(isMouseEvent:Boolean):void |
| { |
| var isTimerRunning:Boolean = softKeyboardEffectPendingEventTimer && softKeyboardEffectPendingEventTimer.running; |
| |
| // Received a mouseDown event while the timer is still running. Stop |
| // the timer and wait for the next mouseUp. |
| var mouseDownDuringTimer:Boolean = isTimerRunning && |
| (isMouseEvent && isMouseDown); |
| |
| // Cleanup the timer if we're (a) waiting for the next mouseUp or |
| // (b) this function was called for timerComplete |
| if (softKeyboardEffectPendingEventTimer && (mouseDownDuringTimer || !isMouseEvent)) |
| { |
| softKeyboardEffectPendingEventTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, pendingEffectTimer_timerCompleteHandler); |
| softKeyboardEffectPendingEventTimer.stop(); |
| softKeyboardEffectPendingEventTimer = null; |
| |
| // If we caught a mouse down during the timer, we wait for the next |
| // mouseUp event. There is no effect to play. If this isn't a mouse |
| // event and the timer completed, then fall through and play the |
| // pending effect. |
| if (mouseDownDuringTimer) |
| return; |
| } |
| |
| // Sanity check that a pendingEvent still exists and that the pop-up |
| // is currently open. If we timed out or if we got a mouseUp, then |
| // allow the pending effect to play. |
| var canPlayEffect:Boolean = softKeyboardEffectPendingEventType && isOpen && |
| (!isMouseEvent || (isMouseEvent && !isMouseDown)); |
| |
| // Effect isn't played when still waiting for a mouseUp |
| if (canPlayEffect) |
| { |
| // Clear pending state |
| var isActivate:Boolean = softKeyboardEffectPendingEventType == SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE; |
| |
| // Snap the position only for pending activate events |
| updateSoftKeyboardEffect(isActivate); |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| private function softKeyboardEffectCleanup(event:EffectEvent):void |
| { |
| // Cleanup effect |
| softKeyboardEffect.removeEventListener(EffectEvent.EFFECT_END, softKeyboardEffectCleanup); |
| softKeyboardEffect.removeEventListener(EffectEvent.EFFECT_STOP, softKeyboardEffectCleanup); |
| softKeyboardEffect = null; |
| |
| if (isSoftKeyboardEffectActive) |
| { |
| installActiveResizeListener(); |
| } |
| else |
| { |
| // Remove resize listeners |
| uninstallActiveResizeListener(); |
| |
| // If the deactivate effect is complete, uninstall listeners for |
| // mouse delay and orientation change listeners |
| uninstallSoftKeyboardStateChangeListeners(); |
| } |
| } |
| |
| /** |
| * @private |
| * Set flags when explicit size changes are detected. |
| */ |
| private function resizeHandler(event:Event=null):void |
| { |
| setSoftKeyboardEffectExplicitWidthFlag(!isNaN(explicitWidth)); |
| setSoftKeyboardEffectExplicitHeightFlag(!isNaN(explicitHeight)); |
| } |
| |
| /** |
| * @private |
| * Update effect immediately after orientation change is |
| * followed by a system manager resize. |
| */ |
| private function systemManager_resizeHandler(event:Event):void |
| { |
| // Guard against extraneous resizing during orientation changing. |
| // See SDK-31860. |
| if (!softKeyboardEffectOrientationChanging) |
| updateSoftKeyboardEffect(true); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Private Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Update the position and height for this pop-up for various state changes |
| * including: soft keyboard activation and deactivation, delayed soft |
| * keyboard events due to mouse and keyboard event sequence, and |
| * orientation change events. |
| */ |
| private function updateSoftKeyboardEffect(snapPosition:Boolean):void |
| { |
| // Stop the current effect |
| if (softKeyboardEffect && softKeyboardEffect.isPlaying) |
| softKeyboardEffect.stop(); |
| |
| // Uninstall resize listeners during the effect. Listeners are |
| // installed again after the effect is complete or immediately affter |
| // the size and position are snapped. |
| uninstallActiveResizeListener(); |
| |
| var softKeyboardRect:Rectangle; |
| try { |
| softKeyboardRect = FlexGlobals.topLevelApplication.softKeyboardRect; |
| } |
| catch (error:Error) { |
| softKeyboardRect = null; |
| } |
| |
| var isKeyboardOpen:Boolean = isOpen && softKeyboardRect && (softKeyboardRect.height > 0); |
| |
| // Capture start values if not set |
| if (isNaN(softKeyboardEffectCachedYPosition)) |
| { |
| if (isKeyboardOpen) |
| { |
| // Save original y-position and height |
| softKeyboardEffectCachedYPosition = this.y; |
| setSoftKeyboardEffectCachedHeight(this.height); |
| |
| // Initialize explicit size flags |
| resizeHandler(); |
| } |
| else |
| { |
| // Keyboard is closed and we don't have any start values yet. |
| // Nothing to do. |
| return; |
| } |
| } |
| |
| var sandboxRoot:DisplayObject = systemManager.getSandboxRoot(); |
| |
| var yToLocal:Number = softKeyboardEffectCachedYPosition; |
| var heightToLocal:Number = softKeyboardEffectCachedHeight; |
| |
| // If the keyboard is active, check for overlap |
| if (isKeyboardOpen) |
| { |
| var scaleFactor:Number = 1; |
| |
| if (systemManager as SystemManager) |
| scaleFactor = SystemManager(systemManager).densityScale; |
| |
| // All calculations are done in stage coordinates and converted back to |
| // application coordinates when playing effects. Also note that |
| // softKeyboardRect is also in stage coordinates. |
| var popUpY:Number = yToLocal * scaleFactor; |
| var popUpHeight:Number = heightToLocal * scaleFactor; |
| var overlapGlobal:Number = (popUpY + popUpHeight) - softKeyboardRect.y; |
| |
| var yToGlobal:Number = popUpY; |
| var heightToGlobal:Number = popUpHeight; |
| |
| if (overlapGlobal > 0) |
| { |
| // shift y-position up to remove offset overlap |
| if (moveForSoftKeyboard) |
| yToGlobal = Math.max((softKeyboardEffectMarginTop * scaleFactor), (popUpY - overlapGlobal)); |
| |
| // adjust height based on new y-position |
| if (resizeForSoftKeyboard) |
| { |
| // compute new overlap |
| overlapGlobal = (yToGlobal + popUpHeight) - softKeyboardRect.y; |
| |
| // adjust height if there is overlap |
| if (overlapGlobal > 0) |
| heightToGlobal = popUpHeight - overlapGlobal - (softKeyboardEffectMarginBottom * scaleFactor); |
| } |
| } |
| |
| if ((yToGlobal != popUpY) || |
| (heightToGlobal != popUpHeight)) |
| { |
| // convert to application coordinates, move to pixel boundaries |
| yToLocal = Math.floor(yToGlobal / scaleFactor); |
| heightToLocal = Math.floor(heightToGlobal / scaleFactor); |
| |
| // preserve minimum height |
| heightToLocal = Math.max(heightToLocal, getMinBoundsHeight()); |
| } |
| } |
| |
| // Update state |
| _isSoftKeyboardEffectActive = isKeyboardOpen; |
| |
| var duration:Number = getStyle("softKeyboardEffectDuration"); |
| |
| // Only create an effect when not snapping and the duration is |
| // non-negative. An effect will not be created by default if there is |
| // no change. |
| if (!snapPosition && (duration > 0)) |
| softKeyboardEffect = createSoftKeyboardEffect(yToLocal, heightToLocal); |
| |
| if (softKeyboardEffect) |
| { |
| softKeyboardEffect.duration = duration; |
| |
| // Wait a frame so that any queued work can be completed by the framework |
| // and runtime before the effect starts. |
| addEventListener(Event.ENTER_FRAME, startEffect); |
| } |
| else |
| { |
| // No effect, snap. Set position and size explicitly. |
| this.y = yToLocal; |
| |
| if (isOpen) |
| { |
| this.height = heightToLocal; |
| |
| // Validate so that other listeners like Scroller get the |
| // updated dimensions. |
| validateNow(); |
| |
| if (isSoftKeyboardEffectActive) |
| { |
| installActiveResizeListener(); |
| installSoftKeyboardStateChangeListeners(); |
| } |
| } |
| else // if (!isOpen) |
| { |
| // Uninstall mouse delay and orientation change listeners |
| uninstallSoftKeyboardStateChangeListeners(); |
| |
| // Clear explicit size leftover from Resize |
| softKeyboardEffectResetExplicitSize(); |
| |
| // Clear start values |
| softKeyboardEffectCachedYPosition = NaN; |
| setSoftKeyboardEffectExplicitWidthFlag(false); |
| setSoftKeyboardEffectExplicitHeightFlag(false); |
| setSoftKeyboardEffectCachedHeight(NaN); |
| } |
| } |
| } |
| |
| /** |
| * @private |
| * Start waiting for a (mouseDown, mouseUp) event sequence before effect |
| * plays. |
| */ |
| private function setPendingSoftKeyboardEvent(event:SoftKeyboardEvent):void |
| { |
| softKeyboardEffectPendingEventType = event.type; |
| |
| // If the mouseDown event was already received, wait indefinitely |
| // for mouseUp. If the soft keyboard event fired before mouseDown, |
| // start a timer. |
| if (!isMouseDown) |
| { |
| softKeyboardEffectPendingEventTimer = new Timer(softKeyboardEffectPendingEffectDelay, 1); |
| softKeyboardEffectPendingEventTimer.addEventListener(TimerEvent.TIMER_COMPLETE, pendingEffectTimer_timerCompleteHandler); |
| softKeyboardEffectPendingEventTimer.start(); |
| } |
| } |
| |
| /** |
| * @private |
| * Listeners installed just before the soft keyboard effect makes changes |
| * to the original position and height. |
| */ |
| private function installSoftKeyboardStateChangeListeners():void |
| { |
| if (!softKeyboardStateChangeListenersInstalled) |
| { |
| // Listen for mouseDown event on the pop-up and delay the soft |
| // keyboard effect until mouseUp. This allows button click |
| // events to complete normally before the button is re-positioned. |
| // See SDK-31534. |
| var sandboxRoot:DisplayObject = systemManager.getSandboxRoot(); |
| addEventListener(MouseEvent.MOUSE_DOWN, mouseHandler); |
| |
| // Listen for mouseUp events anywhere to play the effect. See SDK-31534. |
| sandboxRoot.addEventListener(MouseEvent.MOUSE_UP, |
| mouseHandler, true /* useCapture */); |
| sandboxRoot.addEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, |
| systemManager_mouseUpHandler); |
| |
| // Listen for orientationChanging and orientationChange to suspend |
| // pop-up changes until the orientation change is complete. |
| // On iOS, the keyboard is deactivated after orientationChanging, |
| // then immediately activated after orientationChange is complete. |
| // On Android, the keyboard is deactivated after orientationChanging, |
| // but not reactivated after orienationChange. |
| // On QNX, the keyboard is not deactivated at all. The keyboard is |
| // simply activated again after orientationChange. |
| softKeyboardEffectOrientationChanging = false; |
| |
| var smStage:Stage = systemManager.stage; |
| smStage.addEventListener("orientationChanging", stage_orientationChangingHandler); |
| smStage.addEventListener("orientationChange", stage_orientationChangeHandler); |
| |
| softKeyboardStateChangeListenersInstalled = true; |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| private function uninstallSoftKeyboardStateChangeListeners():void |
| { |
| if (softKeyboardStateChangeListenersInstalled) |
| { |
| // Uninstall effect delay handling |
| removeEventListener(MouseEvent.MOUSE_DOWN, mouseHandler); |
| |
| var sandboxRoot:DisplayObject = systemManager.getSandboxRoot(); |
| sandboxRoot.removeEventListener(MouseEvent.MOUSE_UP, mouseHandler, true /* useCapture */); |
| sandboxRoot.removeEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, systemManager_mouseUpHandler); |
| |
| // Uninstall orientation change handling |
| var smStage:Stage = systemManager.stage; |
| smStage.removeEventListener("orientationChange", stage_orientationChangeHandler); |
| smStage.removeEventListener("orientationChanging", stage_orientationChangeHandler); |
| |
| softKeyboardStateChangeListenersInstalled = false; |
| } |
| } |
| |
| /** |
| * @private |
| * Listeners installed during the phase after the initial activate effect |
| * is complete and before the deactive effect starts. |
| */ |
| private function installActiveResizeListener():void |
| { |
| if (!resizeListenerInstalled) |
| { |
| // Flag size changes while the soft keyboard effect is active (but |
| // not playing) |
| addEventListener("explicitWidthChanged", resizeHandler); |
| addEventListener("explicitHeightChanged", resizeHandler); |
| resizeListenerInstalled = true; |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| private function uninstallActiveResizeListener():void |
| { |
| if (resizeListenerInstalled) |
| { |
| // Uninstall resize listener |
| removeEventListener("explicitWidthChanged", resizeHandler); |
| removeEventListener("explicitHeightChanged", resizeHandler); |
| resizeListenerInstalled = false; |
| } |
| } |
| |
| /** |
| * @private |
| * Clear explicit size that remains after a Resize effect. |
| */ |
| mx_internal function softKeyboardEffectResetExplicitSize():void |
| { |
| // Only remove and restore listeners if they are currently installed. |
| var installed:Boolean = resizeListenerInstalled; |
| |
| if (installed) |
| uninstallActiveResizeListener(); |
| |
| // Remove explicit settings if due to Resize effect |
| if (!softKeyboardEffectExplicitWidthFlag) |
| explicitWidth = NaN; |
| |
| if (!softKeyboardEffectExplicitHeightFlag) |
| explicitHeight = NaN; |
| |
| if (installed) |
| installActiveResizeListener(); |
| } |
| } |
| } |