/*
#
# Copyright (c) 2008-2010 rPath, Inc.
#
# This program is distributed under the terms of the MIT License as found
# in a file called LICENSE. If it is not present, the license
# is always available at http://www.opensource.org/licenses/mit-license.php.
#
# This program is distributed in the hope that it will be useful, but
# without any warranty; without even the implied warranty of merchantability
# or fitness for a particular purpose. See the MIT License for full details.
*/

package com.rpath.raf.util
{
import flash.events.Event;
import flash.events.IEventDispatcher;
import flash.utils.Dictionary;

import mx.events.DynamicEvent;
import mx.events.FlexEvent;
import mx.events.ValidationResultEvent;
import mx.validators.Validator;

/** ValidationHelper provides a way to aggregate the checking of multiple
 * Validators to arrive at an "isValid" decision. It offers the ability
 * to maintain the isValid property on a target object, typically a View that
 * contains a form, so that Save buttons, etc can be bound to the isValid
 * property rather than wiring up various event listeners to the underlying
 * validation events.
 *
 * ValidationHelper accepts an array of Validators as well as the target to
 * inform of isValid changes.
 *
 * ValidationHelper can also manage the presentation of errorTips.
 *
 */

[Bindable]
public class ValidationHelper extends LifecycleObject implements IValidationAware
{
    public static const FIND_ERROR_TIP_MANAGER:String = "findErrorTipManager";

    public function ValidationHelper(vals:Array=null,
                                     target:IEventDispatcher=null,
                                     property:String="isValid",
                                     newErrorTipManager:ErrorTipManager=null)
    {
        super();

        this.property = property;
        this.target = target;

        if (!this.errorTipManager)
        {
            if (newErrorTipManager)
                this.errorTipManager = newErrorTipManager;
            else
                errorTipManager = new ErrorTipManager();
        }

        errorTipManager.increaseSuppressionCount();

        this.errorTipManager = errorTipManager;

        // do this last, since it wires up errorTipManager as a side effect
        itemsToValidate = vals;
    }

    private var _isValid:Boolean = true;

    /** isValid indicates whether this ValidationHelper as a whole is
     * valid. This is also bound to the target.property (if provided)
     */
    public function get isValid():Boolean
    {
        return _isValid;
    }

    /**
     * @private
     */
    public function set isValid(value:Boolean):void
    {
        if (_isValid == value)
            return;

        _isValid = value;

        if (target)
        {
            target[property] = _isValid;
            // propagate a validation changed event through our target view
            // rethrow the validation event so that our whole form can go invalid
            dispatchEvent(new FlexEvent(_isValid ? FlexEvent.VALID : FlexEvent.INVALID));
            target.dispatchEvent(new FlexEvent(_isValid ? FlexEvent.VALID : FlexEvent.INVALID));
        }
    }

    /** required for conformance with the IValidationAware protocol
     */

    public function get validationHelper():ValidationHelper
    {
        return this;
    }

    public function set validationHelper(v:ValidationHelper):void
    {
        // ignore;
    }

    private var property:String;

    // we use a further helper instance to pop errorTips

    public function get errorTipManager():ErrorTipManager
    {
        return _errorTipManager;
    }

    private var _errorTipManager:ErrorTipManager;

    public function set errorTipManager(value:ErrorTipManager):void
    {
        _errorTipManager = value;
        // now inform it of all our validators
        for (var v:* in _validators)
        {
            _errorTipManager.registerValidator(v as Validator);
        }
    }

    public function get target():IEventDispatcher
    {
        return _target;
    }

    private var _target:IEventDispatcher;

    public function set target(v:IEventDispatcher):void
    {
        if (_target)
        {
            _target.removeEventListener(FIND_ERROR_TIP_MANAGER, handleRequestErrorTipManager);
            _target.removeEventListener(Event.CLOSE, handleTargetRemovedFromStage);
            _target.removeEventListener(FlexEvent.HIDE, handleTargetRemovedFromStage);
            _target.removeEventListener(Event.REMOVED_FROM_STAGE, handleTargetRemovedFromStage);
            _target.removeEventListener(Event.ADDED_TO_STAGE, handleTargetAddedToStage);
            _target.removeEventListener(FlexEvent.UPDATE_COMPLETE, handleTargetUpdateComplete);
        }

        _target = v;

        if (_target)
        {
            _target[property] = isValid;

            // go get our errorTip manager up the visual heirarchy
            findErrorTipManager();

            _target.addEventListener(FIND_ERROR_TIP_MANAGER, handleRequestErrorTipManager,false,0,true);
            _target.addEventListener(FlexEvent.UPDATE_COMPLETE, handleTargetUpdateComplete,false,0,true);
            _target.addEventListener(Event.CLOSE, handleTargetRemovedFromStage,false,0,true);
            _target.addEventListener(FlexEvent.HIDE, handleTargetRemovedFromStage,false,0,true);
            _target.addEventListener(Event.REMOVED_FROM_STAGE, handleTargetRemovedFromStage,false,0,true);
            _target.addEventListener(Event.ADDED_TO_STAGE, handleTargetAddedToStage,false,0,true);
        }
    }

    private function findErrorTipManager():void
    {
        var findEvent:DynamicEvent = new DynamicEvent(FIND_ERROR_TIP_MANAGER, true, false);
        findEvent.validationHelper = this;
        findEvent.errorTipManager = null;
        target.dispatchEvent(findEvent);
        if (findEvent.errorTipManager)
        {
            errorTipManager = findEvent.errorTipManager;
        }
    }

    private function handleRequestErrorTipManager(event:DynamicEvent):void
    {
        if (errorTipManager && !event.errorTipManager)
            event.errorTipManager = errorTipManager;
    }

    private function handleTargetUpdateComplete(event:Event):void
    {
        // remove all the error tips
        if (event.target == target)
        {
            errorTipManager.decreaseSuppressionCount();
        }
    }

    private function handleTargetAddedToStage(event:Event):void
    {
        // add all the error tip intercepts back
        if (event.target == target)
        {
            var vals:Array = itemsToValidate;

            // forcibly re-add all validators
            itemsToValidate = vals;
        }
    }

    private function handleTargetRemovedFromStage(event:Event):void
    {
        // remove all the error tips
        if (event.target == target)
        {
            reset();
        }
    }

    public function removeAllValidators():void
    {
        var v:*;

        errorTipManager.reset();

        for (v in _others)
        {
            var valAware:IValidationAware = v as IValidationAware;
            if (valAware)
            {
                if (valAware.validationHelper)
                    valAware.validationHelper.removeAllValidators();
                if (valAware.errorTipManager)
                    valAware.errorTipManager.reset();
            }
        }

        // and forget all items
        for each (v in itemsToValidate)
        {
            removeItemToValidate(v);
        }

    }

    public function reset():void
    {
        var v:*;

        //errorTipManager.reset();

        // cascade the reset
        /*            for (var v:* in _others)
        {
        var valAware:IValidationAware = v as IValidationAware;
        if (valAware)
        {
        if (valAware.validationHelper)
        valAware.validationHelper.reset();
        if (valAware.errorTipManager)
        valAware.errorTipManager.reset();
        }
        }*/

        // and forget all items
        for each (v in itemsToValidate)
        {
            suspendItemToValidate(v);
        }

    }

    private var _itemsToValidate:Dictionary = new Dictionary(true);

    // _vals is a partition of actual Validator instances
    private var _validators:Dictionary = new Dictionary(true);

    // _others is a partition of non-Validator instances we're also
    // monitoring as part of the total validation set
    private var _others:Dictionary = new Dictionary(true);

    public function addItemToValidate(v:IEventDispatcher):void
    {
        if (!v)
            return;

        _itemsToValidate[v] = true;

        var validator:Validator = v as Validator;
        if (validator)
        {
            _validators[validator] = true;
            setupListeners(validator);
            if (errorTipManager)
                errorTipManager.registerValidator(validator);
        }
        else
        {
            // we want to share a single errorTipManager
            var vAware:IValidationAware = v as IValidationAware;

            if (vAware)
            {
                vAware.errorTipManager = errorTipManager;
            }
            else // must just be a plain old item...
            {
                errorTipManager.addComponentListeners(v);
            }

            _others[v] = true;
            setupListeners(v);
        }
    }

    /** suspendItemToValidate prevents an item from being actively tracked
     * but does NOT remove it from the overal list of items. This is necessary
     * in the case where scroll bars cause a REMOVED_FROM_STAGE followed by an
     * ADD_TO_STAGE which can fool us into prematurely removing validators
     */

    public function suspendItemToValidate(v:*):void
    {
        if (v is IEventDispatcher)
        {
            removeListeners(v);
        }

        var validator:Validator = v as Validator;
        var valAware:IValidationAware = v as IValidationAware;

        if (validator)
        {
            errorTipManager.unregisterValidator(validator);
        }
        else if (valAware)
        {
            if (valAware.validationHelper)
                valAware.validationHelper.reset();
        }
        else
        {
            errorTipManager.removeComponentListeners(v);
            errorTipManager.hideErrorTip(v);
        }

        // clean up our various partitioned validators
        delete _others[v];
        delete _validators[v];
    }

    public function removeItemToValidate(v:*):void
    {
        suspendItemToValidate(v);
        // and now also remove it frmo the list entirely
        delete _itemsToValidate[v];
    }

    protected function setupListeners(v:IEventDispatcher):void
    {
        v.addEventListener(ValidationResultEvent.VALID, handleItemValidationEvent,false,0,true);
        v.addEventListener(ValidationResultEvent.INVALID, handleItemValidationEvent,false,0,true);
        v.addEventListener(FlexEvent.VALID, handleItemValidationEvent,false,0,true);
        v.addEventListener(FlexEvent.INVALID, handleItemValidationEvent,false,0,true);
        //v.addEventListener("validChanged", handleItemValidationEvent,false,0,true);
    }

    protected function removeListeners(v:IEventDispatcher):void
    {
        v.removeEventListener(ValidationResultEvent.VALID, handleItemValidationEvent);
        v.removeEventListener(ValidationResultEvent.INVALID, handleItemValidationEvent);
        v.removeEventListener(FlexEvent.VALID, handleItemValidationEvent);
        v.removeEventListener(FlexEvent.INVALID, handleItemValidationEvent);
        //v.removeEventListener("validChanged", handleItemValidationEvent);
    }

    public function get itemsToValidate():Array
    {
        return DictionaryUtils.getKeys(_itemsToValidate);
    }

    public function set itemsToValidate(vals:Array):void
    {
        var v:*;

        removeAllValidators();
        reset();
        for each (v in vals)
        {
            // allow for nested sets
            if (v is Array)
            {
                for each (var v2:* in v)
                {
                    addItemToValidate(v2);
                }
            }
            else
            {
                addItemToValidate(v);
            }

        }

        // now ensure our own isValid state reflects validity of all
        // new Validators
        _needsSuppressedValidation = true;
        invalidateProperties();
    }

    private var _validating:Boolean;

    /** checkValidity runs through all validators and checks their state
     *  Note that we handle the events carefully to control whether the
     *  actual UI elements hear the events or not
     */

    public function checkValidity(validators:Array, suppressEvents:Boolean=true):Array
    {
        var result:Array = [];

        var n:int = validators.length;
        for (var i:int = 0; i < n; i++)
        {
            var v:Validator = validators[i] as Validator;

            if (v && v.enabled && v.source)
            {
                // Validate with event dispatch so that we and our errorTip
                // manager get to hear the events.
                var resultEvent:ValidationResultEvent = v.validate(null, suppressEvents);

                _validationStates[v] = (resultEvent.type == ValidationResultEvent.VALID);

                if (resultEvent.type != ValidationResultEvent.VALID)
                {
                    result.push(resultEvent);
                }

            }
        }

        return result;
    }

    // unfortunately, FlexEvent.VALID and ValidationResultEvent.VALID are the
    // same string constant

    private var _validationStates:Dictionary = new Dictionary(true);

    private function computeValidityFromCache():void
    {
        var newValid:Boolean = true;
        var item:*;
        var items:Array;

        items = DictionaryUtils.getKeys(_validationStates);

        // recompute validity from the cache
        for each (item in items)
        {
            newValid = newValid && _validationStates[item];
        }

        isValid = newValid;
    }

    public function handleItemValidationEvent(event:Event):void
    {

        // plain FlexEvent is the way a UIComponent reports it's validity
        // typically after setting errorString on itself
        if (event is FlexEvent)
        {
            // make a note of the source of the event since we should
            // check this object directly in computing valid status
            addItemToValidate(event.target as IEventDispatcher);

            // and recompute our own validity

            _validationStates[event.target] = (event.type == FlexEvent.VALID);

            if (!_validating)
            {
                computeValidityFromCache();
            }
        }
            // ValidationResultEvents are sent by Validators to report their
            // assessment of a source items validity. We're inserted in the
            // middle of this chain
        else if (event is ValidationResultEvent)
        {

            _validationStates[event.target] = (event.type == ValidationResultEvent.VALID);

            if (!_validating)
            {
                computeValidityFromCache();
            }
        }
    }

    public function validate(suppressEvents:Boolean=false):void
    {
        if (!_validating)
        {
            _validating = true;

            //errorTipManager.suppressErrors = suppressEvents;

            var valid:Boolean = false;

            var results:Array = checkValidity(DictionaryUtils.getKeys(_validators), suppressEvents);

            valid = (results.length == 0);

            // now all the "others"
            for (var v:* in _others)
            {
                var valAware:IValidationAware = v as IValidationAware;

                if (valAware)
                {
                    valAware.validate(suppressEvents);
                    _validationStates[valAware] = valAware.isValid;
                    valid = valid && valAware.isValid;
                }
                else
                {
                    trace("found non valaware item");
                    // what to do with this? Assume NOT valid
                    _validationStates[v] = false;
                }
            }

            _validating = false;

            isValid = valid;
        }
    }

    // ----------------- Convenience FACADE for ErrorTipManager

    /** showErrors() performs a validation pass and pops errorTips at the
     * same time. ErrorTips will remain visible until the errors are cleared
     * or until the hideErrors() method is called.
     */

    public function showErrors():void
    {
        // revalidate and throw events so we get errorTips
        //errorTipManager.showAllErrors = true;
        validate(false);
        errorTipManager.showAllErrorTips();
    }

    public function hideErrors():void
    {
        errorTipManager.hideAllErrorTips();
        // but still allow new errors to pop
        errorTipManager.decreaseSuppressionCount();
    }

    public function checkAlso(...args):Boolean
    {
        var t:Boolean = isValid;
        for each (var b:Boolean in args)
        {
            t = t && b;
            if (!t)
                break;
        }
        return t;
    }

    private var _needsValidation:Boolean;
    private var _needsSuppressedValidation:Boolean;

    override protected function commitProperties():void
    {

        if (_needsValidation)
        {
            _needsValidation = false;
            // temporarily disable errors flags
            errorTipManager.increaseSuppressionCount();
            // but validate with error marker dispatch
            validate(false);
            // start showing the problems hereafter
            errorTipManager.decreaseSuppressionCount();
        }

        if (_needsSuppressedValidation)
        {
            _needsSuppressedValidation = false;

            // temporarily disable errors flags
            errorTipManager.increaseSuppressionCount();
            // validate without error markers
            validate(true);
            // start showing the problems hereafter
            errorTipManager.decreaseSuppressionCount();
        }
        super.commitProperties();
    }
}
}

/*
#
# Copyright (c) 2008-2010 rPath, Inc.
#
# This program is distributed under the terms of the MIT License as found
# in a file called LICENSE. If it is not present, the license
# is always available at http://www.opensource.org/licenses/mit-license.php.
#
# This program is distributed in the hope that it will be useful, but
# without any warranty; without even the implied warranty of merchantability
# or fitness for a particular purpose. See the MIT License for full details.
*/

package com.rpath.raf.util
{
import flash.events.Event;
import flash.events.IEventDispatcher;
import flash.utils.Dictionary;

import mx.events.DynamicEvent;
import mx.events.FlexEvent;
import mx.events.ValidationResultEvent;
import mx.validators.Validator;

/** ValidationHelper provides a way to aggregate the checking of multiple
 * Validators to arrive at an "isValid" decision. It offers the ability
 * to maintain the isValid property on a target object, typically a View that
 * contains a form, so that Save buttons, etc can be bound to the isValid
 * property rather than wiring up various event listeners to the underlying
 * validation events.
 *
 * ValidationHelper accepts an array of Validators as well as the target to
 * inform of isValid changes.
 *
 * ValidationHelper can also manage the presentation of errorTips.
 *
 */

[Bindable]
public class ValidationHelper extends LifecycleObject implements IValidationAware
{
    public static const FIND_ERROR_TIP_MANAGER:String = "findErrorTipManager";

    public function ValidationHelper(vals:Array=null,
                                     target:IEventDispatcher=null,
                                     property:String="isValid",
                                     newErrorTipManager:ErrorTipManager=null)
    {
        super();

        this.property = property;
        this.target = target;

        if (!this.errorTipManager)
        {
            if (newErrorTipManager)
                this.errorTipManager = newErrorTipManager;
            else
                errorTipManager = new ErrorTipManager();
        }

        errorTipManager.increaseSuppressionCount();

        this.errorTipManager = errorTipManager;

        // do this last, since it wires up errorTipManager as a side effect
        itemsToValidate = vals;
    }

    private var _isValid:Boolean = true;

    /** isValid indicates whether this ValidationHelper as a whole is
     * valid. This is also bound to the target.property (if provided)
     */
    public function get isValid():Boolean
    {
        return _isValid;
    }

    /**
     * @private
     */
    public function set isValid(value:Boolean):void
    {
        if (_isValid == value)
            return;

        _isValid = value;

        if (target)
        {
            target[property] = _isValid;
            // propagate a validation changed event through our target view
            // rethrow the validation event so that our whole form can go invalid
            dispatchEvent(new FlexEvent(_isValid ? FlexEvent.VALID : FlexEvent.INVALID));
            target.dispatchEvent(new FlexEvent(_isValid ? FlexEvent.VALID : FlexEvent.INVALID));
        }
    }

    /** required for conformance with the IValidationAware protocol
     */

    public function get validationHelper():ValidationHelper
    {
        return this;
    }

    public function set validationHelper(v:ValidationHelper):void
    {
        // ignore;
    }

    private var property:String;

    // we use a further helper instance to pop errorTips

    public function get errorTipManager():ErrorTipManager
    {
        return _errorTipManager;
    }

    private var _errorTipManager:ErrorTipManager;

    public function set errorTipManager(value:ErrorTipManager):void
    {
        _errorTipManager = value;
        // now inform it of all our validators
        for (var v:* in _validators)
        {
            _errorTipManager.registerValidator(v as Validator);
        }
    }

    public function get target():IEventDispatcher
    {
        return _target;
    }

    private var _target:IEventDispatcher;

    public function set target(v:IEventDispatcher):void
    {
        if (_target)
        {
            _target.removeEventListener(Event.ADDED_TO_STAGE, handleTargetAddedToStage);
            _target.removeEventListener(FlexEvent.SHOW, handleTargetAddedToStage);

            _target.removeEventListener(Event.CLOSE, handleTargetRemovedFromStage);
            _target.removeEventListener(FlexEvent.HIDE, handleTargetRemovedFromStage);
            _target.removeEventListener(Event.REMOVED_FROM_STAGE, handleTargetRemovedFromStage);

            _target.removeEventListener(FlexEvent.UPDATE_COMPLETE, handleTargetUpdateComplete);
            _target.removeEventListener(FIND_ERROR_TIP_MANAGER, handleRequestErrorTipManager);

        }

        _target = v;

        if (_target)
        {
            _target[property] = isValid;

            // go get our errorTip manager up the visual heirarchy
            findErrorTipManager();

            _target.addEventListener(FIND_ERROR_TIP_MANAGER, handleRequestErrorTipManager,false, 0, true);
            _target.addEventListener(FlexEvent.UPDATE_COMPLETE, handleTargetUpdateComplete,false, 0, true);

            _target.addEventListener(Event.CLOSE, handleTargetRemovedFromStage,false, 0, true);
            _target.addEventListener(FlexEvent.HIDE, handleTargetRemovedFromStage,false, 0, true);
            _target.addEventListener(Event.REMOVED_FROM_STAGE, handleTargetRemovedFromStage,false, 0, true);

            _target.addEventListener(FlexEvent.SHOW, handleTargetAddedToStage, false, 0, true);
            _target.addEventListener(Event.ADDED_TO_STAGE, handleTargetAddedToStage,false, 0, true);
        }
    }

    private function findErrorTipManager():void
    {
        var findEvent:DynamicEvent = new DynamicEvent(FIND_ERROR_TIP_MANAGER, true, false);
        findEvent.validationHelper = this;
        findEvent.errorTipManager = null;
        target.dispatchEvent(findEvent);
        if (findEvent.errorTipManager)
        {
            errorTipManager = findEvent.errorTipManager;
        }
    }

    private function handleRequestErrorTipManager(event:DynamicEvent):void
    {
        if (errorTipManager && !event.errorTipManager)
            event.errorTipManager = errorTipManager;
    }

    private function handleTargetUpdateComplete(event:Event):void
    {
        // remove all the error tips
        if (event.target == target)
        {
            errorTipManager.decreaseSuppressionCount();
        }
    }

    private function handleTargetAddedToStage(event:Event):void
    {
        // add all the error tip intercepts back
        if (event.target == target)
        {
            var vals:Array = itemsToValidate;

            // forcibly re-add all validators
            itemsToValidate = vals;
        }
    }

    private function handleTargetRemovedFromStage(event:Event):void
    {
        // remove all the error tips
        if (event.target == target)
        {
            reset();
        }
    }

    public function removeAllValidators():void
    {
        var v:*;

        errorTipManager.reset();

        for (v in _others)
        {
            var valAware:IValidationAware = v as IValidationAware;
            if (valAware)
            {
                if (valAware.validationHelper)
                    valAware.validationHelper.removeAllValidators();
                if (valAware.errorTipManager)
                    valAware.errorTipManager.reset();
            }
        }

        // and forget all items
        for each (v in itemsToValidate)
        {
            removeItemToValidate(v);
        }

    }

    public function reset():void
    {
        var v:*;

        //errorTipManager.reset();

        // cascade the reset
        /*            for (var v:* in _others)
        {
        var valAware:IValidationAware = v as IValidationAware;
        if (valAware)
        {
        if (valAware.validationHelper)
        valAware.validationHelper.reset();
        if (valAware.errorTipManager)
        valAware.errorTipManager.reset();
        }
        }*/

        // and forget all items
        for each (v in itemsToValidate)
        {
            suspendItemToValidate(v);
        }

    }

    private var _itemsToValidate:Dictionary = new Dictionary(true);

    // _vals is a partition of actual Validator instances
    private var _validators:Dictionary = new Dictionary(true);

    // _others is a partition of non-Validator instances we're also
    // monitoring as part of the total validation set
    private var _others:Dictionary = new Dictionary(true);

    public function addItemToValidate(v:IEventDispatcher):void
    {
        if (!v)
            return;

        _itemsToValidate[v] = true;

        var validator:Validator = v as Validator;
        if (validator)
        {
            _validators[validator] = true;
            setupListeners(validator);
            if (errorTipManager)
                errorTipManager.registerValidator(validator);
        }
        else
        {
            // we want to share a single errorTipManager
            var vAware:IValidationAware = v as IValidationAware;

            if (vAware)
            {
                vAware.errorTipManager = errorTipManager;
            }
            else // must just be a plain old item...
            {
                errorTipManager.addComponentListeners(v);
            }

            _others[v] = true;
            setupListeners(v);
        }
    }

    /** suspendItemToValidate prevents an item from being actively tracked
     * but does NOT remove it from the overal list of items. This is necessary
     * in the case where scroll bars cause a REMOVED_FROM_STAGE followed by an
     * ADD_TO_STAGE which can fool us into prematurely removing validators
     */

    public function suspendItemToValidate(v:*):void
    {
        if (v is IEventDispatcher)
        {
            removeListeners(v);
        }

        var validator:Validator = v as Validator;
        var valAware:IValidationAware = v as IValidationAware;

        if (validator)
        {
            errorTipManager.unregisterValidator(validator);
        }
        else if (valAware)
        {
            if (valAware.validationHelper)
                valAware.validationHelper.reset();
        }
        else
        {
            errorTipManager.removeComponentListeners(v);
            errorTipManager.hideErrorTip(v);
        }

        // clean up our various partitioned validators
        delete _others[v];
        delete _validators[v];
    }

    public function removeItemToValidate(v:*):void
    {
        suspendItemToValidate(v);
        // and now also remove it frmo the list entirely
        delete _itemsToValidate[v];
    }

    protected function setupListeners(v:IEventDispatcher):void
    {
        v.addEventListener(ValidationResultEvent.VALID, handleItemValidationEvent,false,0,true);
        v.addEventListener(ValidationResultEvent.INVALID, handleItemValidationEvent,false,0,true);
        v.addEventListener(FlexEvent.VALID, handleItemValidationEvent,false,0,true);
        v.addEventListener(FlexEvent.INVALID, handleItemValidationEvent,false,0,true);
        //v.addEventListener("validChanged", handleItemValidationEvent,false,0,true);
    }

    protected function removeListeners(v:IEventDispatcher):void
    {
        v.removeEventListener(ValidationResultEvent.VALID, handleItemValidationEvent);
        v.removeEventListener(ValidationResultEvent.INVALID, handleItemValidationEvent);
        v.removeEventListener(FlexEvent.VALID, handleItemValidationEvent);
        v.removeEventListener(FlexEvent.INVALID, handleItemValidationEvent);
        //v.removeEventListener("validChanged", handleItemValidationEvent);
    }

    public function get itemsToValidate():Array
    {
        return DictionaryUtils.getKeys(_itemsToValidate);
    }

    public function set itemsToValidate(vals:Array):void
    {
        var v:*;

        removeAllValidators();
        reset();
        for each (v in vals)
        {
            // allow for nested sets
            if (v is Array)
            {
                for each (var v2:* in v)
                {
                    addItemToValidate(v2);
                }
            }
            else
            {
                addItemToValidate(v);
            }

        }

        // now ensure our own isValid state reflects validity of all
        // new Validators
        _needsSuppressedValidation = true;
        invalidateProperties();
    }

    private var _validating:Boolean;

    /** checkValidity runs through all validators and checks their state
     *  Note that we handle the events carefully to control whether the
     *  actual UI elements hear the events or not
     */

    public function checkValidity(validators:Array, suppressEvents:Boolean=true):Array
    {
        var result:Array = [];

        var n:int = validators.length;
        for (var i:int = 0; i < n; i++)
        {
            var v:Validator = validators[i] as Validator;

            if (v && v.enabled && v.source)
            {
                // Validate with event dispatch so that we and our errorTip
                // manager get to hear the events.
                var resultEvent:ValidationResultEvent = v.validate(null, suppressEvents);

                _validationStates[v] = (resultEvent.type == ValidationResultEvent.VALID);

                if (resultEvent.type != ValidationResultEvent.VALID)
                {
                    result.push(resultEvent);
                }

            }
        }

        return result;
    }

    // unfortunately, FlexEvent.VALID and ValidationResultEvent.VALID are the
    // same string constant

    private var _validationStates:Dictionary = new Dictionary(true);

    private function computeValidityFromCache():void
    {
        var newValid:Boolean = true;
        var item:*;
        var items:Array;

        items = DictionaryUtils.getKeys(_validationStates);

        // recompute validity from the cache
        for each (item in items)
        {
            newValid = newValid && _validationStates[item];
            if (!newValid)
                break; // bail quickly
        }

        isValid = newValid;
    }

    public function handleItemValidationEvent(event:Event):void
    {

        // plain FlexEvent is the way a UIComponent reports it's validity
        // typically after setting errorString on itself
        if (event is FlexEvent)
        {
            // make a note of the source of the event since we should
            // check this object directly in computing valid status
            addItemToValidate(event.target as IEventDispatcher);

            // and recompute our own validity

            _validationStates[event.target] = (event.type == FlexEvent.VALID);

            if (!_validating)
            {
                computeValidityFromCache();
            }
        }
            // ValidationResultEvents are sent by Validators to report their
            // assessment of a source items validity. We're inserted in the
            // middle of this chain
        else if (event is ValidationResultEvent)
        {

            _validationStates[event.target] = (event.type == ValidationResultEvent.VALID);

            if (!_validating)
            {
                computeValidityFromCache();
            }
        }
    }

    public function validate(suppressEvents:Boolean=false):void
    {
        if (!_validating)
        {
            _validating = true;

            //errorTipManager.suppressErrors = suppressEvents;

            var valid:Boolean = false;

            var results:Array = checkValidity(DictionaryUtils.getKeys(_validators), suppressEvents);

            valid = (results.length == 0);

            // now all the "others"
            for (var v:* in _others)
            {
                var valAware:IValidationAware = v as IValidationAware;

                if (valAware)
                {
                    valAware.validate(suppressEvents);
                    _validationStates[valAware] = valAware.isValid;
                    valid = valid && valAware.isValid;
                }
                else
                {
                    trace("found non valaware item");
                    // what to do with this? Assume NOT valid
                    _validationStates[v] = false;
                }
            }

            _validating = false;

            isValid = valid;
        }
    }

    // ----------------- Convenience FACADE for ErrorTipManager

    /** showErrors() performs a validation pass and pops errorTips at the
     * same time. ErrorTips will remain visible until the errors are cleared
     * or until the hideErrors() method is called.
     */

    public function showErrors():void
    {
        // revalidate and throw events so we get errorTips
        //errorTipManager.showAllErrors = true;
        validate(false);
        errorTipManager.showAllErrorTips();
    }

    public function hideErrors():void
    {
        errorTipManager.hideAllErrorTips();
        // but still allow new errors to pop
        errorTipManager.decreaseSuppressionCount();
    }

    public function checkAlso(...args):Boolean
    {
        var t:Boolean = isValid;
        for each (var b:Boolean in args)
        {
            t = t && b;
            if (!t)
                break;
        }
        return t;
    }

    private var _needsValidation:Boolean;
    private var _needsSuppressedValidation:Boolean;

    override protected function commitProperties():void
    {

        if (_needsValidation)
        {
            _needsValidation = false;
            // temporarily disable errors flags
            errorTipManager.increaseSuppressionCount();
            // but validate with error marker dispatch
            validate(false);
            // start showing the problems hereafter
            errorTipManager.decreaseSuppressionCount();
        }

        if (_needsSuppressedValidation)
        {
            _needsSuppressedValidation = false;

            // temporarily disable errors flags
            errorTipManager.increaseSuppressionCount();
            // validate without error markers
            validate(true);
            // start showing the problems hereafter
            errorTipManager.decreaseSuppressionCount();
        }
        super.commitProperties();
    }
}
}

Fix for validation not happening after switching archs in ImageDefs panel. General problem with forms that were show/hide Mingle 650


/* ***** BEGIN LICENSE BLOCK *****
 *
 * This file is part of Weave.
 *
 * The Initial Developer of Weave is the Institute for Visualization
 * and Perception Research at the University of Massachusetts Lowell.
 * Portions created by the Initial Developer are Copyright (C) 2008-2015
 * the Initial Developer. All Rights Reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * ***** END LICENSE BLOCK ***** */

package weavejs.net
{
	import weavejs.WeaveAPI;
	import weavejs.api.core.ILinkableObject;
	import weavejs.util.WeavePromise;

	public class JsonCache implements ILinkableObject
	{
		public static function buildURL(base:String, params:Object):String
		{
			var paramsStr:String = '';
			for (var key:String in params)
				paramsStr += (paramsStr ? '&' : '?') + encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
			return base + paramsStr;
		}

		/**
		 * @param requestHeaders Optionally set this to an Object mapping header names to values.
		 */
		public function JsonCache(requestHeaders:Object = null)
		{
			this.requestHeaders = requestHeaders;
		}

		private var requestHeaders:Object = null;

		private var cache:Object = {};

		public function clearCache():void
		{
			for each (var promise:WeavePromise in cache)
				Weave.dispose(promise);
			cache = {};
			Weave.getCallbacks(this).triggerCallbacks();
		}

		/**
		 * @param url The URL to get JSON data
		 * @return The cached Object.
		 */
		public function getJsonObject(url:String):Object
		{
			return getJsonPromise(url).getResult();
		}

		public function getJsonPromise(url:String):WeavePromise
		{
			var promise:WeavePromise = cache[url];
			if (!promise)
			{
				var request:URLRequest = new URLRequest(url);
				request.requestHeaders = requestHeaders;
				request.responseType = ResponseType.JSON;
				promise = new WeavePromise(this)
					.setResult(WeaveAPI.URLRequestUtils.request(this, request))
					.then(function(result:Object):Object { return result || {}; });
			}
			return promise;
		}
	}
}

/* ***** BEGIN LICENSE BLOCK *****
 *
 * This file is part of Weave.
 *
 * The Initial Developer of Weave is the Institute for Visualization
 * and Perception Research at the University of Massachusetts Lowell.
 * Portions created by the Initial Developer are Copyright (C) 2008-2015
 * the Initial Developer. All Rights Reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * ***** END LICENSE BLOCK ***** */

package weavejs.net
{
	import weavejs.WeaveAPI;
	import weavejs.api.core.ILinkableObject;
	import weavejs.util.WeavePromise;

	public class JsonCache implements ILinkableObject
	{
		public static function buildURL(base:String, params:Object):String
		{
			var paramsStr:String = '';
			for (var key:String in params)
				paramsStr += (paramsStr ? '&' : '?') + encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
			return base + paramsStr;
		}

		/**
		 * @param requestHeaders Optionally set this to an Object mapping header names to values.
		 */
		public function JsonCache(requestHeaders:Object = null)
		{
			this.requestHeaders = requestHeaders;
		}

		private var requestHeaders:Object = null;

		private var cache:Object = {};

		public function clearCache():void
		{
			for each (var promise:WeavePromise in cache)
				Weave.dispose(promise);
			cache = {};
			Weave.getCallbacks(this).triggerCallbacks();
		}

		/**
		 * @param url The URL to get JSON data
		 * @return The cached Object.
		 */
		public function getJsonObject(url:String):Object
		{
			return getJsonPromise(url).getResult();
		}

		public function getJsonPromise(url:String):WeavePromise
		{
			var promise:WeavePromise = cache[url];
			if (!promise)
			{
				var request:URLRequest = new URLRequest(url);
				request.requestHeaders = requestHeaders;
				request.responseType = ResponseType.JSON;
				promise = new WeavePromise(this)
					.setResult(WeaveAPI.URLRequestUtils.request(this, request))
					.then(function(result:Object):Object { return result || {}; });
				cache[url] = promise;
			}
			return promise;
		}
	}
}

Make JsonCache actually cache.

Make JsonCache actually cache.


package aerys.minko.scene.controller.mesh
{
	import aerys.minko.ns.minko_math;
	import aerys.minko.render.geometry.Geometry;
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Mesh;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.scene.node.camera.AbstractCamera;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.enum.FrustumCulling;
	import aerys.minko.type.math.BoundingBox;
	import aerys.minko.type.math.BoundingSphere;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	/**
	 * The MeshVisibilityController watches the Mesh and the active Camera of a Scene
	 * to determine whether the object is actually inside the view frustum or not.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class MeshVisibilityController extends AbstractController
	{
		use namespace minko_math;

		private static const TMP_VECTOR4		: Vector4	= new Vector4();

		private static const STATE_UNDEFINED	: uint		= 0;
		private static const STATE_INSIDE		: uint		= 1;
		private static const STATE_OUTSIDE		: uint		= 2;

		private var _mesh			    : Mesh;
		private var _state			    : uint;
		private var _lastTest		    : int;
		private var _boundingBox	    : BoundingBox;
		private var _boundingSphere	    : BoundingSphere;

        private var _frustumCulling     : uint;
        private var _insideFrustum      : Boolean;
        private var _computedVisibility : Boolean;

		public function get frustumCulling() : uint
		{
			return _frustumCulling;
		}
		public function set frustumCulling(value : uint) : void
		{
			_frustumCulling = value;
            testCulling();
		}

		public function get insideFrustum() : Boolean
		{
			return _insideFrustum;
		}

        public function get computedVisibility() : Boolean
        {
            return frustumCulling == FrustumCulling.DISABLED || _computedVisibility;
        }

		public function MeshVisibilityController()
		{
			super(Mesh);

			initialize();
		}

		private function initialize() : void
		{
			_boundingBox = new BoundingBox(Vector4.ZERO, Vector4.ONE);
			_boundingSphere = new BoundingSphere(Vector4.ZERO, 0.);

            _state = STATE_UNDEFINED;

			targetAdded.add(targetAddedHandler);
			targetRemoved.add(targetRemovedHandler);
		}

		private function targetAddedHandler(ctrl	: MeshVisibilityController,
											target	: Mesh) : void
		{
			if (_mesh != null)
				throw new Error();

			_mesh = target;

			target.addedToScene.add(meshAddedToSceneHandler);
			target.removedFromScene.add(meshRemovedFromSceneHandler);

			if (target.root is Scene)
				meshAddedToSceneHandler(target, target.root as Scene);
		}

		private function targetRemovedHandler(ctrl		: MeshVisibilityController,
											  target	: Mesh) : void
		{
			_mesh = null;

			target.addedToScene.remove(meshAddedToSceneHandler);
			target.removedFromScene.remove(meshRemovedFromSceneHandler);

			if (target.root is Scene)
				meshRemovedFromSceneHandler(target, target.root as Scene);
		}

		private function meshAddedToSceneHandler(mesh	: Mesh, scene	: Scene) : void
		{
			scene.bindings.addCallback('worldToScreen', worldToScreenChangedHandler);

            meshLocalToWorldChangedHandler(mesh.localToWorld);
			mesh.localToWorld.changed.add(meshLocalToWorldChangedHandler);
            mesh.visibilityChanged.add(visiblityChangedHandler);
            mesh.parent.computedVisibilityChanged.add(visiblityChangedHandler);
            mesh.removed.add(meshRemovedHandler);
		}

        private function meshRemovedHandler(mesh : Mesh, ancestor : Group) : void
        {
            if (!mesh.parent)
                ancestor.computedVisibilityChanged.remove(visiblityChangedHandler);
        }

		private function meshRemovedFromSceneHandler(mesh : Mesh, scene	: Scene) : void
		{
			scene.bindings.removeCallback('worldToScreen', worldToScreenChangedHandler);

			mesh.localToWorld.changed.remove(meshLocalToWorldChangedHandler);
            mesh.visibilityChanged.remove(visiblityChangedHandler);
            mesh.removed.remove(meshRemovedHandler);
		}

        private function visiblityChangedHandler(node : ISceneNode, visibility : Boolean) : void
        {
            _computedVisibility = _mesh.visible && _mesh.parent.computedVisibility
                && _insideFrustum;
            _mesh.computedVisibilityChanged.execute(_mesh, _computedVisibility);
        }

		private function worldToScreenChangedHandler(bindings	    : DataBindings,
                                                     propertyName	: String,
                                                     oldValue		: Matrix4x4,
                                                     newValue		: Matrix4x4) : void
		{
			testCulling();
		}

		private function meshLocalToWorldChangedHandler(transform : Matrix4x4) : void
		{
			var geom 	: Geometry 	= _mesh.geometry;
			var culling	: uint		= _frustumCulling;

			if (!geom.boundingBox || !geom.boundingSphere)
				return ;

			if (culling & FrustumCulling.BOX)
				transform.transformRawVectors(geom.boundingBox._vertices, _boundingBox._vertices);

			if (culling & FrustumCulling.SPHERE)
			{
				var center 	: Vector4 	= transform.transformVector(geom.boundingSphere.center);
				var scale 	: Vector4 	= transform.deltaTransformVector(Vector4.ONE);
				var radius	: Number	= geom.boundingSphere.radius * Math.max(
					Math.abs(scale.x), Math.abs(scale.y), Math.abs(scale.z)
				);

				_boundingSphere.update(center, radius);
			}

			testCulling();
		}

		private function testCulling() : void
		{
			var culling	: uint	= _frustumCulling;

			if (culling != FrustumCulling.DISABLED && _mesh && _mesh.geometry.boundingBox
                && _mesh.visible)
			{
				var camera : AbstractCamera = (_mesh.root as Scene).activeCamera;

				if (!camera)
					return ;

				_lastTest = camera.cameraData.frustum.testBoundingVolume(
					_boundingSphere,
					_boundingBox,
					null,
					culling,
					_lastTest
				);

				var inside : Boolean = _lastTest == -1;

				if (inside && _state != STATE_INSIDE)
				{
					_insideFrustum = true;
                    _computedVisibility = _mesh.parent.computedVisibility && _mesh.visible;
                    _mesh.computedVisibilityChanged.execute(_mesh, computedVisibility);
					_state = STATE_INSIDE;
				}
				else if (!inside && _state != STATE_OUTSIDE)
				{
					_insideFrustum = false;
                    _computedVisibility = false;
                    _mesh.computedVisibilityChanged.execute(_mesh, false);
					_state = STATE_OUTSIDE;
				}
			}
		}

		override public function clone() : AbstractController
		{
			return new MeshVisibilityController();;
		}
	}
}

package aerys.minko.scene.controller.mesh
{
	import aerys.minko.ns.minko_math;
	import aerys.minko.render.geometry.Geometry;
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Mesh;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.scene.node.camera.AbstractCamera;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.enum.FrustumCulling;
	import aerys.minko.type.math.BoundingBox;
	import aerys.minko.type.math.BoundingSphere;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	/**
	 * The MeshVisibilityController watches the Mesh and the active Camera of a Scene
	 * to determine whether the object is actually inside the view frustum or not.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class MeshVisibilityController extends AbstractController
	{
		use namespace minko_math;

		private static const TMP_VECTOR4		: Vector4	= new Vector4();

		private static const STATE_UNDEFINED	: uint		= 0;
		private static const STATE_INSIDE		: uint		= 1;
		private static const STATE_OUTSIDE		: uint		= 2;

		private var _mesh			    : Mesh;
		private var _state			    : uint;
		private var _lastTest		    : int;
		private var _boundingBox	    : BoundingBox;
		private var _boundingSphere	    : BoundingSphere;

        private var _frustumCulling     : uint;
        private var _insideFrustum      : Boolean;
        private var _computedVisibility : Boolean;

		public function get frustumCulling() : uint
		{
			return _frustumCulling;
		}
		public function set frustumCulling(value : uint) : void
		{
			_frustumCulling = value;
            testCulling();
		}

		public function get insideFrustum() : Boolean
		{
			return _insideFrustum;
		}

        public function get computedVisibility() : Boolean
        {
            return frustumCulling == FrustumCulling.DISABLED || _computedVisibility;
        }

		public function MeshVisibilityController()
		{
			super(Mesh);

			initialize();
		}

		private function initialize() : void
		{
			_boundingBox = new BoundingBox(Vector4.ZERO, Vector4.ONE);
			_boundingSphere = new BoundingSphere(Vector4.ZERO, 0.);

            _state = STATE_UNDEFINED;

			targetAdded.add(targetAddedHandler);
			targetRemoved.add(targetRemovedHandler);
		}

		private function targetAddedHandler(ctrl	: MeshVisibilityController,
											target	: Mesh) : void
		{
			if (_mesh != null)
				throw new Error();

			_mesh = target;

			target.addedToScene.add(meshAddedToSceneHandler);
			target.removedFromScene.add(meshRemovedFromSceneHandler);

			if (target.root is Scene)
				meshAddedToSceneHandler(target, target.root as Scene);
		}

		private function targetRemovedHandler(ctrl		: MeshVisibilityController,
											  target	: Mesh) : void
		{
			_mesh = null;

			target.addedToScene.remove(meshAddedToSceneHandler);
			target.removedFromScene.remove(meshRemovedFromSceneHandler);

			if (target.root is Scene)
				meshRemovedFromSceneHandler(target, target.root as Scene);
		}

		private function meshAddedToSceneHandler(mesh	: Mesh, scene	: Scene) : void
		{
			scene.bindings.addCallback('worldToScreen', worldToScreenChangedHandler);

            meshLocalToWorldChangedHandler(mesh.localToWorld);
			mesh.localToWorld.changed.add(meshLocalToWorldChangedHandler);
            mesh.visibilityChanged.add(visiblityChangedHandler);
            mesh.parent.computedVisibilityChanged.add(visiblityChangedHandler);
            mesh.removed.add(meshRemovedHandler);
		}

        private function meshRemovedHandler(mesh : Mesh, ancestor : Group) : void
        {
            if (!mesh.parent)
                ancestor.computedVisibilityChanged.remove(visiblityChangedHandler);
        }

		private function meshRemovedFromSceneHandler(mesh : Mesh, scene	: Scene) : void
		{
			scene.bindings.removeCallback('worldToScreen', worldToScreenChangedHandler);

			mesh.localToWorld.changed.remove(meshLocalToWorldChangedHandler);
            mesh.visibilityChanged.remove(visiblityChangedHandler);
            mesh.removed.remove(meshRemovedHandler);
		}

        private function visiblityChangedHandler(node : ISceneNode, visibility : Boolean) : void
        {
            _computedVisibility = _mesh.visible && _mesh.parent.computedVisibility
                && _insideFrustum;
            _mesh.computedVisibilityChanged.execute(_mesh, _computedVisibility);
        }

		private function worldToScreenChangedHandler(bindings	    : DataBindings,
                                                     propertyName	: String,
                                                     oldValue		: Matrix4x4,
                                                     newValue		: Matrix4x4) : void
		{
			testCulling();
		}

		private function meshLocalToWorldChangedHandler(transform : Matrix4x4) : void
		{
			var geom 	: Geometry 	= _mesh.geometry;
			var culling	: uint		= _frustumCulling;

			if (!geom.boundingBox || !geom.boundingSphere || culling == FrustumCulling.DISABLED)
				return ;

			if (culling & FrustumCulling.BOX)
				transform.transformRawVectors(geom.boundingBox._vertices, _boundingBox._vertices);

			if (culling & FrustumCulling.SPHERE)
			{
				var center 	: Vector4 	= transform.transformVector(geom.boundingSphere.center);
				var scale 	: Vector4 	= transform.deltaTransformVector(Vector4.ONE);
				var radius	: Number	= geom.boundingSphere.radius * Math.max(
					Math.abs(scale.x), Math.abs(scale.y), Math.abs(scale.z)
				);

				_boundingSphere.update(center, radius);
			}

			testCulling();
		}

		private function testCulling() : void
		{
			var culling	: uint	= _frustumCulling;

			if (culling != FrustumCulling.DISABLED && _mesh && _mesh.geometry.boundingBox
                && _mesh.visible)
			{
				var camera : AbstractCamera = (_mesh.root as Scene).activeCamera;

				if (!camera)
					return ;

				_lastTest = camera.cameraData.frustum.testBoundingVolume(
					_boundingSphere,
					_boundingBox,
					null,
					culling,
					_lastTest
				);

				var inside : Boolean = _lastTest == -1;

				if (inside && _state != STATE_INSIDE)
				{
					_insideFrustum = true;
                    _computedVisibility = _mesh.parent.computedVisibility && _mesh.visible;
                    _mesh.computedVisibilityChanged.execute(_mesh, computedVisibility);
					_state = STATE_INSIDE;
				}
				else if (!inside && _state != STATE_OUTSIDE)
				{
					_insideFrustum = false;
                    _computedVisibility = false;
                    _mesh.computedVisibilityChanged.execute(_mesh, false);
					_state = STATE_OUTSIDE;
				}
			}
		}

		override public function clone() : AbstractController
		{
			return new MeshVisibilityController();;
		}
	}
}

fix MeshVisibilityController.meshLocalToWorldChangedHandler() to return early when culling == FrustumCulling.DISABLED


////////////////////////////////////////////////////////////////////////////////
//
//  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
{

/**
 *  @private
 *  This class is used to link additional classes into rpc.swc
 *  beyond those that are found by dependecy analysis starting
 *  from the classes specified in manifest.xml.
 */
internal class FlexJSUIClasses
{
	import org.apache.flex.html.staticControls.beads.IButtonBead; IButtonBead;
	import org.apache.flex.html.staticControls.beads.TextButtonBead; TextButtonBead;
	import org.apache.flex.html.staticControls.beads.TextFieldBead; TextFieldBead;
	import org.apache.flex.html.staticControls.beads.TextInputBead; TextInputBead;
	import org.apache.flex.html.staticControls.beads.TextInputWithBorderBead; TextInputWithBorderBead;
	import org.apache.flex.html.staticControls.beads.TextAreaBead; TextAreaBead;
	import org.apache.flex.html.staticControls.beads.CheckBoxBead; CheckBoxBead;
	import org.apache.flex.html.staticControls.beads.RadioButtonBead; RadioButtonBead;
	import org.apache.flex.html.staticControls.beads.ComboBoxBead; ComboBoxBead;
	import org.apache.flex.html.staticControls.beads.models.TextModel; TextModel;
	import org.apache.flex.html.staticControls.beads.models.ComboBoxModel; ComboBoxModel;
	import org.apache.flex.html.staticControls.beads.models.ToggleButtonModel; ToggleButtonModel;
	import org.apache.flex.html.staticControls.beads.models.ValueToggleButtonModel; ValueToggleButtonModel;
    import org.apache.flex.html.staticControls.beads.models.ArraySelectionModel; ArraySelectionModel;
}

}

////////////////////////////////////////////////////////////////////////////////
//
//  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
{

/**
 *  @private
 *  This class is used to link additional classes into rpc.swc
 *  beyond those that are found by dependecy analysis starting
 *  from the classes specified in manifest.xml.
 */
internal class FlexJSUIClasses
{
	import org.apache.flex.html.staticControls.beads.IButtonBead; IButtonBead;
	import org.apache.flex.html.staticControls.beads.TextButtonBead; TextButtonBead;
	import org.apache.flex.html.staticControls.beads.TextFieldBead; TextFieldBead;
	import org.apache.flex.html.staticControls.beads.TextInputBead; TextInputBead;
	import org.apache.flex.html.staticControls.beads.TextInputWithBorderBead; TextInputWithBorderBead;
	import org.apache.flex.html.staticControls.beads.TextAreaBead; TextAreaBead;
	import org.apache.flex.html.staticControls.beads.CheckBoxBead; CheckBoxBead;
	import org.apache.flex.html.staticControls.beads.RadioButtonBead; RadioButtonBead;
	import org.apache.flex.html.staticControls.beads.ComboBoxBead; ComboBoxBead;
	import org.apache.flex.html.staticControls.beads.models.TextModel; TextModel;
	import org.apache.flex.html.staticControls.beads.models.ComboBoxModel; ComboBoxModel;
	import org.apache.flex.html.staticControls.beads.models.ToggleButtonModel; ToggleButtonModel;
	import org.apache.flex.html.staticControls.beads.models.ValueToggleButtonModel; ValueToggleButtonModel;
    import org.apache.flex.html.staticControls.beads.models.ArraySelectionModel; ArraySelectionModel;
	import org.apache.flex.events.CustomEvent; CustomEvent;
	import org.apache.flex.utils.Timer; Timer;
}

}

Add new classes to SWC

Add new classes to SWC


/**
 * User: MerlinDS
 * Date: 18.07.2014
 * Time: 17:42
 */
package com.merlinds.miracle_tool.viewer {
	import com.bit101.components.List;
	import com.bit101.components.Window;
	import com.merlinds.debug.log;
	import com.merlinds.miracle.Miracle;
	import com.merlinds.miracle.animations.AnimationHelper;
	import com.merlinds.miracle.display.MiracleDisplayObject;
	import com.merlinds.miracle.utils.Asset;
	import com.merlinds.miracle.utils.MafReader;
	import com.merlinds.miracle_tool.models.AppModel;

	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.utils.ByteArray;

	[SWF(backgroundColor="0x333333", frameRate=60)]
	public class ViewerView extends Sprite {

		private var _model:AppModel;
		private var _assets:Vector.<Asset>;
		private var _window:Window;
		private var _name:String;

		public function ViewerView(model:AppModel = null) {
			super();
			_model = model;
			if(_model == null){
				_model = new AppModel();
			}

			this.addEventListener(Event.ADDED_TO_STAGE, this.initialize);
		}
		//==============================================================================
		//{region							PUBLIC METHODS
		//} endregion PUBLIC METHODS ===================================================

		//==============================================================================
		//{region						PRIVATE\PROTECTED METHODS
		private function initialize(event:Event):void {
			this.removeEventListener(event.type, this.initialize);
			this.stage.scaleMode = StageScaleMode.NO_SCALE;
			this.stage.align = StageAlign.TOP_LEFT;
			Miracle.start(this.stage, this.createHandler, true);
		}

		private function choseAnimation():void {
			//find animation asset and add all of animations to chose list
			var n:int = _assets.length;
			for(var i:int = 0; i < n; i++){
				var asset:Asset = _assets[i];
				if(asset.type == Asset.TIMELINE_TYPE){
					//parse output
					_window = new Window(this, 0, 0, "Chose mesh");
					var list:List = new List(_window, 0, 0, this.getAnimations(asset.output));
					_window.x = this.stage.stageWidth - _window.width;
					list.addEventListener(Event.SELECT, this.selectAnimationHandler);
				}
			}
		}

		[Inline]
		private function getAnimations(bytes:ByteArray):Array {
			var result:Array = [];
			var reader:MafReader = new MafReader();
			reader.execute(bytes);
			var animations:Vector.<AnimationHelper> = reader.animations;
			for each(var animation:AnimationHelper in animations){
				result.push(animation.name);
			}
			return result;
		}
		//} endregion PRIVATE\PROTECTED METHODS ========================================

		//==============================================================================
		//{region							EVENTS HANDLERS
		private function createHandler(animation:Boolean = false):void {
			//TODO: Show view if it exit
			if(animation || _model.viewerInput == null){
				_model.viewerInput = _model.lastFileDirection;
				_model.viewerInput.addEventListener(Event.SELECT, this.selectFileHandler);
				_model.viewerInput.browseForOpen("Open " + animation ? "Animation" : "Sprite"
						+ "file that you want to view");
			}
		}

		private function selectFileHandler(event:Event):void {
			_model.viewerInput.removeEventListener(event.type, this.selectFileHandler);
			_model.lastFileDirection = _model.viewerInput.parent;
			var byteArray:ByteArray = new ByteArray();
			var stream:FileStream = new FileStream();
			stream.open(_model.viewerInput, FileMode.READ);
			stream.readBytes(byteArray);
			stream.close();
			//parse
			if(_assets == null)_assets = new <Asset>[];
			var asset:Asset = new Asset(_model.viewerInput.name, byteArray);
			if(asset.type == Asset.TIMELINE_TYPE){
				_name = asset.name;
			}
			_assets.push(asset);
			if(_assets.length > 1){
				this.choseAnimation();
				Miracle.createScene(_assets, 1);
				Miracle.resume();
			}else{
				this.createHandler(true);
			}
		}

		private function selectAnimationHandler(event:Event):void {
			var list:List = event.target as List;
			//add animation to miracle
			var animation:String = list.selectedItem.toString();
			log(this, "selectAnimationHandler", animation);
			Miracle.currentScene.createAnimation(_name, _name + "." +animation)
					.addEventListener(Event.ADDED_TO_STAGE, this.imageAddedToStage);
		}

		private function imageAddedToStage(event:Event):void {
			var target:MiracleDisplayObject = event.target as MiracleDisplayObject;
			target.moveTO(
					this.stage.stageWidth - target.width >> 1,
					this.stage.stageHeight - target.height >> 1
			);
		}
		//} endregion EVENTS HANDLERS ==================================================

		//==============================================================================
		//{region							GETTERS/SETTERS
		//} endregion GETTERS/SETTERS ==================================================
	}
}

/**
 * User: MerlinDS
 * Date: 18.07.2014
 * Time: 17:42
 */
package com.merlinds.miracle_tool.viewer {
	import com.bit101.components.List;
	import com.bit101.components.Window;
	import com.merlinds.debug.log;
	import com.merlinds.miracle.Miracle;
	import com.merlinds.miracle.animations.AnimationHelper;
	import com.merlinds.miracle.display.MiracleDisplayObject;
	import com.merlinds.miracle.utils.Asset;
	import com.merlinds.miracle.utils.MafReader;
	import com.merlinds.miracle_tool.models.AppModel;

	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.html.script.Package;
	import flash.utils.ByteArray;

	[SWF(backgroundColor="0x333333", frameRate=60)]
	public class ViewerView extends Sprite {

		private var _model:AppModel;
		private var _assets:Vector.<Asset>;
		private var _window:Window;
		private var _name:String;
		private var _current:MiracleDisplayObject;

		public function ViewerView(model:AppModel = null) {
			super();
			_model = model;
			if(_model == null){
				_model = new AppModel();
			}

			this.addEventListener(Event.ADDED_TO_STAGE, this.initialize);
		}
		//==============================================================================
		//{region							PUBLIC METHODS
		//} endregion PUBLIC METHODS ===================================================

		//==============================================================================
		//{region						PRIVATE\PROTECTED METHODS
		private function initialize(event:Event):void {
			this.removeEventListener(event.type, this.initialize);
			this.stage.scaleMode = StageScaleMode.NO_SCALE;
			this.stage.align = StageAlign.TOP_LEFT;
			Miracle.start(this.stage, this.createHandler, true);
		}

		private function choseAnimation():void {
			//find animation asset and add all of animations to chose list
			var n:int = _assets.length;
			for(var i:int = 0; i < n; i++){
				var asset:Asset = _assets[i];
				if(asset.type == Asset.TIMELINE_TYPE){
					//parse output
					_window = new Window(this, 0, 0, "Chose animation");
					var list:List = new List(_window, 0, 0, this.getAnimations(asset.output));
					_window.x = this.stage.stageWidth - _window.width;
					list.addEventListener(Event.SELECT, this.selectAnimationHandler);
				}
			}
			var w2:Window = new Window(this, _window.x, _window.y + _window.height + 10, "FPS");
			list = new List(w2, 0, 0, [1, 5, 16, 24, 30, 35, 40, 60]);
			w2.height = 120;
			list.addEventListener(Event.SELECT, this.selectFpsHandler);
		}

		[Inline]
		private function getAnimations(bytes:ByteArray):Array {
			var result:Array = [];
			var reader:MafReader = new MafReader();
			reader.execute(bytes);
			var animations:Vector.<AnimationHelper> = reader.animations;
			for each(var animation:AnimationHelper in animations){
				result.push(animation.name);
			}
			return result;
		}
		//} endregion PRIVATE\PROTECTED METHODS ========================================

		//==============================================================================
		//{region							EVENTS HANDLERS
		private function createHandler(animation:Boolean = false):void {
			//TODO: Show view if it exit
			if(animation || _model.viewerInput == null){
				_model.viewerInput = _model.lastFileDirection;
				_model.viewerInput.addEventListener(Event.SELECT, this.selectFileHandler);
				_model.viewerInput.browseForOpen("Open " + animation ? "Animation" : "Sprite"
						+ "file that you want to view");
			}
		}

		private function selectFileHandler(event:Event):void {
			_model.viewerInput.removeEventListener(event.type, this.selectFileHandler);
			_model.lastFileDirection = _model.viewerInput.parent;
			var byteArray:ByteArray = new ByteArray();
			var stream:FileStream = new FileStream();
			stream.open(_model.viewerInput, FileMode.READ);
			stream.readBytes(byteArray);
			stream.close();
			//parse
			if(_assets == null)_assets = new <Asset>[];
			var asset:Asset = new Asset(_model.viewerInput.name, byteArray);
			if(asset.type == Asset.TIMELINE_TYPE){
				_name = asset.name;
			}
			_assets.push(asset);
			if(_assets.length > 1){
				this.choseAnimation();
				Miracle.createScene(_assets, 1);
				Miracle.resume();
			}else{
				this.createHandler(true);
			}
		}

		private function selectAnimationHandler(event:Event):void {
			var list:List = event.target as List;
			//add animation to miracle
			var animation:String = list.selectedItem.toString();
			log(this, "selectAnimationHandler", animation);
			Miracle.currentScene.createAnimation(_name, _name + "." + animation, 60)
					.addEventListener(Event.ADDED_TO_STAGE, this.imageAddedToStage);
		}

		private function imageAddedToStage(event:Event):void {
			var target:MiracleDisplayObject = event.target as MiracleDisplayObject;
			target.moveTO(
					300,300 /*this.stage.stageWidth - target.width >> 1,
					this.stage.stageHeight - target.height >> 1*/
			);
			_current = target;
		}

		private function selectFpsHandler(event:Event):void {
			var list:List = event.target as List;
			//add animation to miracle
			if(_current != null){
				_current.fps = int(list.selectedItem);
			}
		}
		//} endregion EVENTS HANDLERS ==================================================

		//==============================================================================
		//{region							GETTERS/SETTERS
		//} endregion GETTERS/SETTERS ==================================================
	}
}

add fps changer

add fps changer


package aerys.minko.scene.controller.debug
{
    import aerys.minko.render.geometry.primitive.CubeGeometry;
    import aerys.minko.render.material.Material;
    import aerys.minko.render.material.basic.BasicMaterial;
    import aerys.minko.scene.controller.AbstractController;
    import aerys.minko.scene.controller.mesh.skinning.SkinningController;
    import aerys.minko.scene.node.Group;
    import aerys.minko.scene.node.Mesh;
    import aerys.minko.type.enum.DepthTest;
    import aerys.minko.type.math.Vector4;

    public final class BonesDebugController extends AbstractController
    {
        private var _material   : Material;

        public function BonesDebugController(bonesMaterial : Material = null)
        {
            super(Mesh);

            initialize(bonesMaterial);
        }

        private function initialize(bonesMaterial : Material) : void
        {
            _material = bonesMaterial;
            if (!_material)
            {
                var bonesBasicMaterial : BasicMaterial = new BasicMaterial();

                bonesBasicMaterial.diffuseColor = 0x00ff00ff;
                bonesBasicMaterial.depthWriteEnabled = false;
                bonesBasicMaterial.depthTest = DepthTest.ALWAYS;
                _material = bonesBasicMaterial;
            }

            targetAdded.add(targetAddedHandler);
            targetRemoved.add(targetRemovedHandler);
        }

        private function targetAddedHandler(ctrl	: AbstractController,
                                            target	: Mesh) : void
        {
            var skinningCtrl : SkinningController = target.getControllersByType(SkinningController)[0]
                as SkinningController;

            for each (var joint : Group in skinningCtrl.joints)
                addBonesMeshes(joint);
        }

        private function targetRemovedHandler(ctrl 		: AbstractController,
                                              target	: Mesh) : void
        {
            var skinningCtrl : SkinningController = target.getControllersByType(SkinningController)[0]
                as SkinningController;

            for each (var joint : Group in skinningCtrl.joints)
                joint.getChildByName('__bone__').parent = null;
        }

        private function addBonesMeshes(joint : Group) : void
        {
            if (joint.getChildByName('__bone__'))
                return ;

            var numChildren : uint = joint.numChildren;

            for (var i : uint = 0; i < numChildren; ++i)
            {
                var child : Group = joint.getChildAt(i) as Group;

                if (child != null)
                {
                    var nextJointPosition 	: Vector4 	= child.transform.transformVector(
                        Vector4.ZERO
                    );
                    var boneLength 			: Number 	= nextJointPosition.length;

                    if (boneLength != 0.)
                    {
                        var boneMesh : Mesh = new Mesh(
                            CubeGeometry.cubeGeometry, _material, '__bone__'
                        );

                        boneMesh.transform
                            .lookAt(nextJointPosition)
                            .prependTranslation(0, 0, boneLength * .5)
                            .prependScale(.02, .02, boneLength);

                        joint.addChild(boneMesh);
                    }
                }
            }
        }
    }
}

package aerys.minko.scene.controller.debug
{
    import aerys.minko.render.geometry.primitive.CubeGeometry;
    import aerys.minko.render.material.Material;
    import aerys.minko.render.material.basic.BasicMaterial;
    import aerys.minko.scene.controller.AbstractController;
    import aerys.minko.scene.controller.mesh.skinning.SkinningController;
    import aerys.minko.scene.node.Group;
    import aerys.minko.scene.node.Mesh;
    import aerys.minko.type.enum.DepthTest;
    import aerys.minko.type.math.Vector4;

    public final class BonesDebugController extends AbstractController
    {
		private var _bonesThickness	: Number;
        private var _material   	: Material;

        public function BonesDebugController(bonesThickness	: Number	= 0.02,
											 bonesMaterial 	: Material 	= null)
        {
            super(Mesh);

            initialize(bonesThickness, bonesMaterial);
        }

        private function initialize(bonesThickness : Number, bonesMaterial : Material) : void
        {
			_bonesThickness = bonesThickness;
            _material = bonesMaterial;

            if (!_material)
            {
                var bonesBasicMaterial : BasicMaterial = new BasicMaterial();

                bonesBasicMaterial.diffuseColor = 0x00ff00ff;
                bonesBasicMaterial.depthWriteEnabled = false;
                bonesBasicMaterial.depthTest = DepthTest.ALWAYS;
                _material = bonesBasicMaterial;
            }

            targetAdded.add(targetAddedHandler);
            targetRemoved.add(targetRemovedHandler);
        }

        private function targetAddedHandler(ctrl	: AbstractController,
                                            target	: Mesh) : void
        {
            var skinningCtrl : SkinningController = target.getControllersByType(SkinningController)[0]
                as SkinningController;

            for each (var joint : Group in skinningCtrl.joints)
                addBonesMeshes(joint);
        }

        private function targetRemovedHandler(ctrl 		: AbstractController,
                                              target	: Mesh) : void
        {
            var skinningCtrl : SkinningController = target.getControllersByType(SkinningController)[0]
                as SkinningController;

            for each (var joint : Group in skinningCtrl.joints)
                joint.getChildByName('__bone__').parent = null;
        }

        private function addBonesMeshes(joint : Group) : void
        {
            if (joint.getChildByName('__bone__'))
                return ;

            var numChildren : uint = joint.numChildren;

            for (var i : uint = 0; i < numChildren; ++i)
            {
                var child : Group = joint.getChildAt(i) as Group;

                if (child != null)
                {
                    var nextJointPosition 	: Vector4 	= child.transform.transformVector(
                        Vector4.ZERO
                    );
                    var boneLength 			: Number 	= nextJointPosition.length;

                    if (boneLength != 0.)
                    {
                        var boneMesh : Mesh = new Mesh(
                            CubeGeometry.cubeGeometry, _material, '__bone__'
                        );

                        boneMesh.transform
                            .lookAt(nextJointPosition)
                            .prependTranslation(0, 0, boneLength * .5)
                            .prependScale(_bonesThickness, _bonesThickness, boneLength);

                        joint.addChild(boneMesh);
                    }
                }
            }
        }
    }
}

add customizable thickness for the bones debug meshes


package aerys.minko.type
{
	public final class Signal
	{
		private var _name			: String	= null;

		private var _callbacks		: Array		= [];
		private var _numCallbacks	: uint		= 0;

		private var _executed		: Boolean	= false;
		private var _numAdded		: uint		= 0;
		private var _toAdd			: Array		= null;
		private var _numRemoved		: uint		= 0;
		private var _toRemove		: Array		= null;

		public function get numCallbacks() : uint
		{
			return _numCallbacks;
		}

		public function Signal(name : String)
		{
			_name = name;
		}

		public function add(callback : Function) : void
		{
            if (_callbacks.indexOf(callback) >= 0)
                throw new Error('The same callback cannot be adde twice.');

			if (_executed)
			{
				if (_toAdd)
					_toAdd.push(callback);
				else
					_toAdd = [callback];
				++_numAdded;

				return ;
			}

			_callbacks[_numCallbacks] = callback;
			++_numCallbacks;
		}

		public function remove(callback : Function) : void
		{
			var index : int = _callbacks.indexOf(callback);

			if (index < 0)
				throw new Error('This callback does not exist.');

			if (_executed)
			{
				if (_toRemove)
					_toRemove.push(callback);
				else
					_toRemove = [callback];
				++_numRemoved;

				return ;
			}

			--_numCallbacks;
			_callbacks[index] = _callbacks[_numCallbacks];
			_callbacks.length = _numCallbacks;
		}

		public function hasCallback(callback : Function) : Boolean
		{
			return _callbacks.indexOf(callback) >= 0;
		}

		public function execute(...params) : void
		{
			if (_numCallbacks)
			{
				_executed = true;
				for (var i : uint = 0; i < _numCallbacks; ++i)
					(_callbacks[i] as Function).apply(null, params);
				_executed = false;

				for (i = 0; i < _numAdded; ++i)
					add(_toAdd[i]);
				_numAdded = 0;
				_toAdd = null;

				for (i = 0; i < _numRemoved; ++i)
					remove(_toRemove[i]);
				_numRemoved = 0;
				_toRemove = null;
			}
		}
	}
}

package aerys.minko.type
{
	public final class Signal
	{
		private var _name			: String	= null;

		private var _callbacks		: Array		= [];
		private var _numCallbacks	: uint		= 0;

		private var _executed		: Boolean	= false;
		private var _numAdded		: uint		= 0;
		private var _toAdd			: Array		= null;
		private var _numRemoved		: uint		= 0;
		private var _toRemove		: Array		= null;

		public function get numCallbacks() : uint
		{
			return _numCallbacks;
		}

		public function Signal(name : String)
		{
			_name = name;
		}

		public function add(callback : Function) : void
		{
            if (_callbacks.indexOf(callback) >= 0)
                throw new Error('The same callback cannot be added twice.');

			if (_executed)
			{
				if (_toAdd)
					_toAdd.push(callback);
				else
					_toAdd = [callback];
				++_numAdded;

				return ;
			}

			_callbacks[_numCallbacks] = callback;
			++_numCallbacks;
		}

		public function remove(callback : Function) : void
		{
			var index : int = _callbacks.indexOf(callback);

			if (index < 0)
				throw new Error('This callback does not exist.');

			if (_executed)
			{
				if (_toRemove)
					_toRemove.push(callback);
				else
					_toRemove = [callback];
				++_numRemoved;

				return ;
			}

			--_numCallbacks;
			_callbacks[index] = _callbacks[_numCallbacks];
			_callbacks.length = _numCallbacks;
		}

		public function hasCallback(callback : Function) : Boolean
		{
			return _callbacks.indexOf(callback) >= 0;
		}

		public function execute(...params) : void
		{
			if (_numCallbacks)
			{
				_executed = true;
				for (var i : uint = 0; i < _numCallbacks; ++i)
					(_callbacks[i] as Function).apply(null, params);
				_executed = false;

				for (i = 0; i < _numAdded; ++i)
					add(_toAdd[i]);
				_numAdded = 0;
				_toAdd = null;

				for (i = 0; i < _numRemoved; ++i)
					remove(_toRemove[i]);
				_numRemoved = 0;
				_toRemove = null;
			}
		}
	}
}

fix typo

fix typo


package aerys.minko.type
{
	public final class Signal
	{
		private var _name					: String;
        private var _enabled        		: Boolean;
		private var _disableWhenNoCallbacks	: Boolean;

		private var _callbacks				: Vector.<Function>;
		private var _numCallbacks			: uint;

		private var _executed				: Boolean;
		private var _numAdded				: uint;
		private var _toAdd					: Vector.<Function>;
		private var _numRemoved				: uint;
		private var _toRemove				: Vector.<Function>;

        public function get enabled() : Boolean
        {
            return _enabled;
        }
        public function set enabled(value : Boolean) : void
        {
            _enabled = value;
        }

		public function get numCallbacks() : uint
		{
			return _numCallbacks;
		}

		public function Signal(name 					: String,
							   enabled 					: Boolean 	= true,
							   disableWhenNoCallbacks	: Boolean	= false)
		{
			_name = name;
            _enabled = enabled;
			_disableWhenNoCallbacks = disableWhenNoCallbacks;

            _callbacks = new <Function>[];
		}

		public function add(callback : Function) : void
		{
            if (_callbacks.indexOf(callback) >= 0)
            {
                var removeIndex : int = _toRemove ? _toRemove.indexOf(callback) : -1;

                // if that callback is in the temp. remove list, we simply remove it from this list
                // instead of removing/adding it all over again
                if (removeIndex >= 0)
                {
                    --_numRemoved;
                    _toRemove[removeIndex] = _toRemove[_numRemoved];
                    _toRemove.length = _numRemoved;

                    return;
                }
                else
                    throw new Error('The same callback cannot be added twice.');
            }

			if (_executed)
			{
				if (_toAdd)
					_toAdd.push(callback);
				else
					_toAdd = new <Function>[callback];
				++_numAdded;

				return ;
			}

			_callbacks[_numCallbacks] = callback;
			++_numCallbacks;

			if (_numCallbacks == 1 && _disableWhenNoCallbacks)
				_enabled = true;
		}

		public function remove(callback : Function) : void
		{
			var index : int = _callbacks.indexOf(callback);

			if (index < 0)
            {
                var addIndex : int = _toAdd ? _toAdd.indexOf(callback) : -1;

                // if that callback is in the temp. add list, we simply remove it from this list
                // instead of adding/removing it all over again
                if (addIndex >= 0)
                {
                    --_numAdded;
                    _toAdd[addIndex] = _toAdd[_numAdded];
                    _toAdd.length = _numAdded;
                }
                else
    				throw new Error('This callback does not exist.');
            }

			if (_executed)
			{
				if (_toRemove)
					_toRemove.push(callback);
				else
					_toRemove = new <Function>[callback];
				++_numRemoved;

				return ;
			}

			--_numCallbacks;
			_callbacks[index] = _callbacks[_numCallbacks];
			_callbacks.length = _numCallbacks;

			if (!_numCallbacks && _disableWhenNoCallbacks)
				_enabled = false;
		}

		public function hasCallback(callback : Function) : Boolean
		{
			return _callbacks.indexOf(callback) >= 0;
		}

		public function execute(...params) : void
		{
			if (_numCallbacks && _enabled)
			{
				_executed = true;
				for (var i : uint = 0; i < _numCallbacks; ++i)
					_callbacks[i].apply(null, params);
				_executed = false;

				for (i = 0; i < _numAdded; ++i)
					add(_toAdd[i]);
				_numAdded = 0;
				_toAdd = null;

				for (i = 0; i < _numRemoved; ++i)
					remove(_toRemove[i]);
				_numRemoved = 0;
				_toRemove = null;
			}
		}
	}
}

package aerys.minko.type
{
	public final class Signal
	{
		private var _name					: String;
        private var _enabled        		: Boolean;
		private var _disableWhenNoCallbacks	: Boolean;

		private var _callbacks				: Vector.<Function>;
		private var _numCallbacks			: uint;

		private var _executed				: Boolean;
		private var _numAdded				: uint;
		private var _toAdd					: Vector.<Function>;
		private var _numRemoved				: uint;
		private var _toRemove				: Vector.<Function>;

        public function get enabled() : Boolean
        {
            return _enabled;
        }
        public function set enabled(value : Boolean) : void
        {
            _enabled = value;
        }

		public function get numCallbacks() : uint
		{
			return _numCallbacks;
		}

		public function Signal(name 					: String,
							   enabled 					: Boolean 	= true,
							   disableWhenNoCallbacks	: Boolean	= false)
		{
			_name = name;
            _enabled = enabled;
			_disableWhenNoCallbacks = disableWhenNoCallbacks;
		}

		public function add(callback : Function) : void
		{
            if (_callbacks && _callbacks.indexOf(callback) >= 0)
            {
                var removeIndex : int = _toRemove ? _toRemove.indexOf(callback) : -1;

                // if that callback is in the temp. remove list, we simply remove it from this list
                // instead of removing/adding it all over again
                if (removeIndex >= 0)
                {
                    --_numRemoved;
                    _toRemove[removeIndex] = _toRemove[_numRemoved];
                    _toRemove.length = _numRemoved;

                    return;
                }
                else
                    throw new Error('The same callback cannot be added twice.');
            }

			if (_executed)
			{
				if (_toAdd)
					_toAdd.push(callback);
				else
					_toAdd = new <Function>[callback];
				++_numAdded;

				return ;
			}

			if (_callbacks) _callbacks[_numCallbacks] = callback;
			else _callbacks = new <Function>[callback];
			++_numCallbacks;

			if (_numCallbacks == 1 && _disableWhenNoCallbacks)
				_enabled = true;
		}

		public function remove(callback : Function) : void
		{
			var index : int = (_callbacks ? _callbacks.indexOf(callback) : -1);

			if (index < 0)
            {
                var addIndex : int = _toAdd ? _toAdd.indexOf(callback) : -1;

                // if that callback is in the temp. add list, we simply remove it from this list
                // instead of adding/removing it all over again
                if (addIndex >= 0)
                {
                    --_numAdded;
                    _toAdd[addIndex] = _toAdd[_numAdded];
                    _toAdd.length = _numAdded;
                }
                else
    				throw new Error('This callback does not exist.');
            }

			if (_executed)
			{
				if (_toRemove)
					_toRemove.push(callback);
				else
					_toRemove = new <Function>[callback];
				++_numRemoved;

				return ;
			}

			--_numCallbacks;
			_callbacks[index] = _callbacks[_numCallbacks];
			_callbacks.length = _numCallbacks;

			if (!_numCallbacks && _disableWhenNoCallbacks)
				_enabled = false;
		}

		public function hasCallback(callback : Function) : Boolean
		{
			return _callbacks && _callbacks.indexOf(callback) >= 0;
		}

		public function execute(...params) : void
		{
			if (_numCallbacks && _enabled)
			{
				_executed = true;
				for (var i : uint = 0; i < _numCallbacks; ++i)
					_callbacks[i].apply(null, params);
				_executed = false;

				for (i = 0; i < _numAdded; ++i)
					add(_toAdd[i]);
				_numAdded = 0;
				_toAdd = null;

				for (i = 0; i < _numRemoved; ++i)
					remove(_toRemove[i]);
				_numRemoved = 0;
				_toRemove = null;
			}
		}
	}
}

Update Signal.as

Update Signal.as