/*
 * Copyright (c) 2013 Maxime Cowez a.k.a. RIAstar.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
 * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * @author RIAstar
 */
package net.riastar.components {

import flash.events.TimerEvent;
import flash.utils.Dictionary;
import flash.utils.Timer;

import mx.collections.ArrayList;
import mx.collections.IList;
import mx.core.IDataRenderer;
import mx.core.IFactory;
import mx.core.IVisualElement;
import mx.effects.Effect;
import mx.events.EffectEvent;
import mx.events.ResizeEvent;

import net.riastar.components.supportClasses.PopUpAbsolutePosition;

import spark.components.DataGroup;
import spark.components.supportClasses.ListBase;
import spark.events.IndexChangeEvent;
import spark.events.RendererExistenceEvent;
import spark.events.SkinPartEvent;

public class Toaster extends SkinnablePopUp {

    /* ----------------- */
    /* --- skinparts --- */
    /* ----------------- */

    [SkinPart(required="true")]
    public var messageList:ListBase;

    [SkinPart(required="false")]
    public var rendererCreationEffect:Effect;

    [SkinPart(required="false")]
    public var rendererDestructionEffect:Effect;

    /* ------------------ */
    /* --- properties --- */
    /* ------------------ */

    public var showTime:int = 5000;

    private var _labelField:String;
    public function get labelField():String { return _labelField; }
    public function set labelField(value:String):void {
        _labelField = value;
        if (messageList) messageList.labelField = value;
    }

    private var _labelFunction:Function;
    public function get labelFunction():Function { return _labelFunction; }
    public function set labelFunction(value:Function):void {
        _labelFunction = value;
        if (messageList) messageList.labelFunction = value;
    }

    private var _itemRenderer:IFactory;
    public function get itemRenderer():IFactory { return _itemRenderer; }
    public function set itemRenderer(value:IFactory):void {
        _itemRenderer = value;
        if (messageList) messageList.itemRenderer = value;
    }

    private var messages:IList = new ArrayList();
    private var timerMap:Dictionary = new Dictionary();

    /* -------------------- */
    /* --- construction --- */
    /* -------------------- */

    override public function initialize():void {
        position = new PopUpAbsolutePosition();
        super.initialize();
    }

    override protected function partAdded(partName:String, instance:Object):void {
        super.partAdded(partName, instance);

        switch (instance) {
            case messageList:
                initMessageList();
                break;
            case rendererDestructionEffect:
                rendererDestructionEffect.addEventListener(EffectEvent.EFFECT_END, handleDestructionEffectEnd);
                break;
        }
    }

    private function initMessageList():void {
        messageList.labelField = _labelField;
        messageList.labelFunction = _labelFunction;
        messageList.dataProvider = messages;

        if (_itemRenderer) messageList.itemRenderer = _itemRenderer;
        else _itemRenderer = messageList.itemRenderer;

        messageList.addEventListener(IndexChangeEvent.CHANGE, handleMessageSelection);
        messageList.addEventListener(ResizeEvent.RESIZE, handleListResize);
        messageList.addEventListener(SkinPartEvent.PART_ADDED, initRendererListeners);
    }

    private function initRendererListeners(event:SkinPartEvent):void {
        if (!(event.instance is DataGroup)) return;

        messageList.removeEventListener(SkinPartEvent.PART_ADDED, initRendererListeners);
        messageList.dataGroup.addEventListener(RendererExistenceEvent.RENDERER_ADD, handleItemRendererAdded);
    }

    /* ---------------------- */
    /* --- event handlers --- */
    /* ---------------------- */

    private function handleMessageSelection(event:IndexChangeEvent):void {
        //TODO handle toast selection
    }

    private function handleTimerComplete(event:TimerEvent):void {
        var timer:Timer = event.currentTarget as Timer;
        removeToast(timerMap[timer], true);
        destroyTimer(timer);
    }

    private function handleListResize(event:ResizeEvent):void {
        position.update(this);
    }

    private function handleItemRendererAdded(event:RendererExistenceEvent):void {
        playCreationEffect(event.renderer);
    }

    private function handleDestructionEffectEnd(event:EffectEvent):void {
        var itemRenderer:IDataRenderer = event.effectInstance.target as IDataRenderer;
        removeToast(itemRenderer.data, false);
    }

    /* ----------------- */
    /* --- behaviour --- */
    /* ----------------- */

    public function toast(item:Object):void {
        messages.addItem(item);
        createTimer(item);
        invalidateState();
    }

    protected function removeToast(item:Object, playEffect:Boolean):void {
        var index:int = messages.getItemIndex(item);
        if (index == -1) return;

        var itemRenderer:IVisualElement = messageList.dataGroup.getElementAt(index);
        if (!playEffect || !playDestructionEffect(itemRenderer)) {
            messages.removeItemAt(index);
            invalidateState();
        }
    }

    protected function playCreationEffect(itemRenderer:IVisualElement):Boolean {
        if (rendererCreationEffect) rendererCreationEffect.play([itemRenderer]);
        return rendererCreationEffect != null;
    }

    protected function playDestructionEffect(itemRenderer:IVisualElement):Boolean {
        if (rendererDestructionEffect) rendererDestructionEffect.play([itemRenderer]);
        return rendererDestructionEffect != null;
    }

    protected function createTimer(item:Object):void {
        var timer:Timer = new Timer(showTime, 1);
        timer.addEventListener(TimerEvent.TIMER_COMPLETE, handleTimerComplete);
        timerMap[timer] = item;
        timer.start();
    }

    protected function destroyTimer(timer:Timer):void {
        timer.removeEventListener(TimerEvent.TIMER_COMPLETE, handleTimerComplete);
        delete timerMap[timer];
        timer = null;
    }

    protected function invalidateState():void {
        messages.length ? open() : close();
    }

}
}

/*
 * Copyright (c) 2013 Maxime Cowez a.k.a. RIAstar.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
 * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * @author RIAstar
 */
package net.riastar.components {

import flash.events.TimerEvent;
import flash.utils.Dictionary;
import flash.utils.Timer;

import mx.collections.ArrayList;
import mx.collections.IList;
import mx.core.IDataRenderer;
import mx.core.IFactory;
import mx.core.IVisualElement;
import mx.effects.Effect;
import mx.events.EffectEvent;
import mx.events.ResizeEvent;

import net.riastar.components.supportClasses.PopUpAbsolutePosition;

import spark.components.DataGroup;
import spark.components.supportClasses.ListBase;
import spark.events.IndexChangeEvent;
import spark.events.RendererExistenceEvent;
import spark.events.SkinPartEvent;

public class Toaster extends SkinnablePopUp {

    /* ----------------- */
    /* --- skinparts --- */
    /* ----------------- */

    [SkinPart(required="true")]
    public var messageList:ListBase;

    [SkinPart(required="false")]
    public var rendererCreationEffect:Effect;

    [SkinPart(required="false")]
    public var rendererDestructionEffect:Effect;

    /* ------------------ */
    /* --- properties --- */
    /* ------------------ */

    public var showTime:int = 5000;

    private var _labelField:String;
    public function get labelField():String { return _labelField; }
    public function set labelField(value:String):void {
        _labelField = value;
        if (messageList) messageList.labelField = value;
    }

    private var _labelFunction:Function;
    public function get labelFunction():Function { return _labelFunction; }
    public function set labelFunction(value:Function):void {
        _labelFunction = value;
        if (messageList) messageList.labelFunction = value;
    }

    private var _itemRenderer:IFactory;
    public function get itemRenderer():IFactory { return _itemRenderer; }
    public function set itemRenderer(value:IFactory):void {
        _itemRenderer = value;
        if (messageList) messageList.itemRenderer = value;
    }

    private var messages:IList = new ArrayList();
    private var timerMap:Dictionary = new Dictionary();

    /* -------------------- */
    /* --- construction --- */
    /* -------------------- */

    override public function initialize():void {
        position = new PopUpAbsolutePosition();
        super.initialize();
    }

    override protected function partAdded(partName:String, instance:Object):void {
        super.partAdded(partName, instance);

        switch (instance) {
            case messageList:
                initMessageList();
                break;
            case rendererDestructionEffect:
                rendererDestructionEffect.addEventListener(EffectEvent.EFFECT_END, handleDestructionEffectEnd);
                break;
        }
    }

    private function initMessageList():void {
        messageList.labelField = _labelField;
        messageList.labelFunction = _labelFunction;
        messageList.dataProvider = messages;

        if (_itemRenderer) messageList.itemRenderer = _itemRenderer;
        else _itemRenderer = messageList.itemRenderer;

        messageList.addEventListener(IndexChangeEvent.CHANGE, handleMessageSelection);
        messageList.addEventListener(ResizeEvent.RESIZE, handleListResize);
        messageList.addEventListener(SkinPartEvent.PART_ADDED, initRendererListeners);
    }

    private function initRendererListeners(event:SkinPartEvent):void {
        if (!(event.instance is DataGroup)) return;

        messageList.removeEventListener(SkinPartEvent.PART_ADDED, initRendererListeners);
        messageList.dataGroup.addEventListener(RendererExistenceEvent.RENDERER_ADD, handleItemRendererAdded);
    }

    /* ---------------------- */
    /* --- event handlers --- */
    /* ---------------------- */

    private function handleMessageSelection(event:IndexChangeEvent):void {
        //TODO handle toast selection
    }

    private function handleTimerComplete(event:TimerEvent):void {
        var timer:Timer = event.currentTarget as Timer;
        removeToast(timerMap[timer], true);
        destroyTimer(timer);
    }

    private function handleListResize(event:ResizeEvent):void {
        position.update(this);
    }

    private function handleItemRendererAdded(event:RendererExistenceEvent):void {
        playCreationEffect(event.renderer);
    }

    private function handleDestructionEffectEnd(event:EffectEvent):void {
        var itemRenderer:IDataRenderer = event.effectInstance.target as IDataRenderer;
        removeToast(itemRenderer.data, false);
    }

    /* ----------------- */
    /* --- behaviour --- */
    /* ----------------- */

    public function toast(item:Object):void {
        messages.addItem(item);
        createTimer(item);
        invalidateState();
    }

    protected function removeToast(item:Object, playEffect:Boolean):void {
        var index:int = messages.getItemIndex(item);
        if (index == -1) return;

        var itemRenderer:IVisualElement = messageList.dataGroup.getElementAt(index);
        if (!playEffect || !playDestructionEffect(itemRenderer)) {
            messages.removeItemAt(index);
            invalidateState();
        }
    }

    protected function playCreationEffect(itemRenderer:IVisualElement):Boolean {
        if (rendererCreationEffect) rendererCreationEffect.play([itemRenderer]);
        return rendererCreationEffect != null;
    }

    protected function playDestructionEffect(itemRenderer:IVisualElement):Boolean {
        if (rendererDestructionEffect) rendererDestructionEffect.play([itemRenderer]);
        return rendererDestructionEffect != null;
    }

    protected function createTimer(item:Object):void {
        var timer:Timer = new Timer(showTime, 1);
        timer.addEventListener(TimerEvent.TIMER_COMPLETE, handleTimerComplete);
        timerMap[timer] = item;
        timer.start();
    }

    protected function destroyTimer(timer:Timer):void {
        timer.removeEventListener(TimerEvent.TIMER_COMPLETE, handleTimerComplete);
        delete timerMap[timer];
        timer = null;
    }

    protected function invalidateState():void {
        messages.length ? open() : close();
    }

    /* ------------------- */
    /* --- destruction --- */
    /* ------------------- */

    override protected function partRemoved(partName:String, instance:Object):void {
        switch (instance) {
            case messageList:
                messageList.dataGroup.removeEventListener(RendererExistenceEvent.RENDERER_ADD, handleItemRendererAdded);
                messageList.removeEventListener(IndexChangeEvent.CHANGE, handleMessageSelection);
                messageList.removeEventListener(ResizeEvent.RESIZE, handleListResize);
                break;
            case rendererDestructionEffect:
                rendererDestructionEffect.removeEventListener(EffectEvent.EFFECT_END, handleDestructionEffectEnd);
                break;
        }

        super.partRemoved(partName, instance);
    }

}
}

clean up event listeners

clean up event listeners


package com.hipchat {

	public class Linkify {
		public static const RE_EMAIL_PATTERN:String = '(?<=\\s|\\A)[\\w.-]+\\+*[\\w.-]+@(?:(?:[\\w-]+\\.)+[A-Za-z]{2,6}|(?:\\d{1,3}\\.){3}\\d{1,3})';
		public static const RE_TLD:String = '(?:aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|fi|fj|fk|fm|fo|fr|ga|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)'; // Top level domains
		public static const RE_URL_MIDCHAR:String = '[^\\s<>]'; // characters allowed in URL center
		public static const RE_URL_ENDCHAR:String = '[\\w@?^=%&/~\+#-]';     // URL must end with one of these chars
		public static const RE_URL_ENDING:String = '(?:'+RE_URL_MIDCHAR+'*'+RE_URL_ENDCHAR+')?';
		public static const RE_URL_SCHEME:String = '(?:http|ftp|https|news|mms)://';

		// If we have the scheme/protocol, always linkify (don't require a valid TLD)
		public static const RE_FULL_URL:String = RE_URL_SCHEME+'\\w+(?:.\\w+)'+RE_URL_ENDING;
		// URLs not starting with a protocol (e.g. www.google.com instead of http://www.google.com)
		public static const RE_OTHER_URL:String = '\\w[\\w_-]*(?:\\.\\w[\\w_-]*)*\\.'+RE_TLD+'(?:\\/'+RE_URL_ENDING+')?\\b';

		// Text to turn in to emoticon images
		// emoticons.regex is a pattern to be used in the RegExp constructor
		// emoticons.shortcut is the text actually typed to get the emoticon
		// Width and height provided to avoid jittering as images load
		private static const emoticons:Array = [
			{regex: ':-?D',        		  shortcut: ':D',        		   width: 18,	height: 18,		image: 'bigsmile.png'},
			{regex: ':-?o',          	  shortcut: ':o',         		   width: 18,	height: 18,		image: 'gasp.png'},
			{regex: ':-?p',         	  shortcut: ':p',         		   width: 18,	height: 18,		image: 'tongue.png'},
			{regex: '8-?\\)',        	  shortcut: '8)',        		   width: 18,	height: 18,		image: 'cool.png'},
			{regex: ':-?\\(',        	  shortcut: ':(',        		   width: 18,	height: 18,		image: 'frown.png'},
			{regex: ':-\\*',      		  shortcut: ':-*',      		   width: 18,	height: 18,		image: 'kiss.png'},
			{regex: ':\\\\',      		  shortcut: ':\\',      		   width: 18,	height: 18,		image: 'slant.png'},
			{regex: ':-?\\)',        	  shortcut: ':)',        		   width: 18,	height: 18,		image: 'smile.png'},
			{regex: ':-?\\|',             shortcut: ':|', 	               width: 18,	height: 18,		image: 'straightface.png'},
			{regex: ';-?\\)',        	  shortcut: ';)',        		   width: 18,	height: 18,		image: 'wink.png'}];

		/**
		 * Linkify a string, replacing text urls with url
		 *
		 * @param text - String to be linkified
		 * @param emoticonify - Whether to replace text emoticons with images
		 * @param dispatchEvent - Whether to dispatch a link: event (to be handled in actionscript)
		 *  see: http://blog.flexexamples.com/2008/01/26/listening-for-the-link-event-in-a-flex-label-control/
		 *  see: http://livedocs.adobe.com/flex/3/html/help.html?content=textcontrols_04.html
		 * @param matchedLinks - Return param (pass by ref) - Array of links matched during linkification
		 **/
		public static function linkify(text:String,
									   emoticonify:Boolean = true,
									   dispatchEvent:Boolean = false,
									   truncateLength:uint = 0,
									   matchedLinks:Array = null):String {
			text = Linkify.matchAndReplace(RE_EMAIL_PATTERN, text, false, false, dispatchEvent, truncateLength, matchedLinks);
			text = Linkify.matchAndReplace(RE_FULL_URL, text, true, false, dispatchEvent, truncateLength, matchedLinks);
			text = Linkify.matchAndReplace(RE_OTHER_URL, text, true, true, dispatchEvent, truncateLength, matchedLinks);

			if (emoticonify)
				text = Linkify.emoticonText(text);

			return text;
		}

		/**
		 * Internal helper function for linkification
		 **/
		private static function matchAndReplace(pattern:String,
												input:String,
												isURL:Boolean = false,
												addHTTP:Boolean = true,
												dispatchEvent:Boolean = false,
												truncateLength:uint = 0,
												matchedLinks:Array = null):String {
			var start:int = 0;
			var offset:int = 0;
			var matchLength:int = 0;
			var endTagPos:int = 0;
			var closeAnchorRe:RegExp = /<\/[aA]>/;
			var re:RegExp = new RegExp(pattern, "g");

			var match:Object = new Object();

			// Limit the number of links we match in one pass
			var maxIter:int = 20;
			var curIter:int = 0;
			while (match = re.exec(input)) {
				curIter++;
				if (curIter > maxIter)
					break;

				start = match.index; // start of match
				matchLength = match[0].length;

				// If we find an opening anchor tag, advance to its end and continue looking
				var substr:String = input.substring(offset, start);
				if (substr.search(/<[aA]/) >= 0) {
					// Find the closing anchor tag
					closeAnchorRe.lastIndex = offset;
					var closeAnchorPos:int = input.substring(offset, input.length).search(closeAnchorRe);

					// If we find an opening tag without a closing match, just return the current input
					if (closeAnchorPos < 0)
						return input;

					endTagPos = closeAnchorPos + offset;

					// RegExp.lastIndex is used to tell the regexp where to start matching
					re.lastIndex = endTagPos + 4;
					offset = endTagPos + 4;

					continue;
				}

				// Do the replacement of url text with anchor tag
				var address:String = input.substr(start, matchLength);
				var actual:String = input.substr(start, matchLength);

				if (addHTTP) {
					actual = 'http://'+actual;
				}
				var replacement:String = ' truncateLength) {
					address = address.substr(0, truncateLength) + '...';
				}

				// Add word break tags to allow wrapping where appropriate
				// Manually put font styles around the link so they actually look like links
				// see: http://livedocs.adobe.com/flex/3/html/help.html?content=textcontrols_04.html
				replacement += '>'+address.replace(new RegExp("([/=&])", 'g'), "<wbr>$1")+'';
				if (matchedLinks) {
					matchedLinks.push(actual);
				}

				input = input.slice(0, start) + replacement + input.slice(start+matchLength, input.length);
				re.lastIndex = start + replacement.length;
				offset = start + replacement.length;
			}

			return input;
		}

		/**
		 * Replace text emoticons with images
		 **/
		private static function emoticonText(text:String):String {
			for each (var eData:Object in Linkify.emoticons) {
				var regex:String = eData.regex;
				var img:String = eData.image;

				// (not a word character)(smiley regex)(not a word character)
				var pattern:RegExp = new RegExp('(?<!\\w)'+regex+'(?!\\w)', 'gim');
				text = text.replace(pattern, '');
			}
			return text;
		}
	}
}

package com.hipchat {

	public class Linkify {
		public static const RE_EMAIL_PATTERN:String = '(?<=\\s|\\A)[\\w.-]+\\+*[\\w.-]+@(?:(?:[\\w-]+\\.)+[A-Za-z]{2,6}|(?:\\d{1,3}\\.){3}\\d{1,3})';
		public static const RE_TLD:String = '(?:aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|fi|fj|fk|fm|fo|fr|ga|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)'; // Top level domains
		public static const RE_URL_MIDCHAR:String = '[^\\s<>]'; // characters allowed in URL center
		public static const RE_URL_ENDCHAR:String = '[\\w@?^=%&/~\+#-]';     // URL must end with one of these chars
		public static const RE_URL_ENDING:String = '(?:'+RE_URL_MIDCHAR+'*'+RE_URL_ENDCHAR+')?';
		public static const RE_URL_SCHEME:String = '(?:http|ftp|https|news|mms)://';

		// If we have the scheme/protocol, always linkify (don't require a valid TLD)
		public static const RE_FULL_URL:String = RE_URL_SCHEME+'\\w+(?:.\\w+)'+RE_URL_ENDING;
		// URLs not starting with a protocol (e.g. www.google.com instead of http://www.google.com)
		public static const RE_OTHER_URL:String = '\\w[\\w_-]*(?:\\.\\w[\\w_-]*)*\\.'+RE_TLD+'(?:\\/'+RE_URL_ENDING+')?';

		// Text to turn in to emoticon images
		// emoticons.regex is a pattern to be used in the RegExp constructor
		// emoticons.shortcut is the text actually typed to get the emoticon
		// Width and height provided to avoid jittering as images load
		private static const emoticons:Array = [
			{regex: ':-?D',        		  shortcut: ':D',        		   width: 18,	height: 18,		image: 'bigsmile.png'},
			{regex: ':-?o',          	  shortcut: ':o',         		   width: 18,	height: 18,		image: 'gasp.png'},
			{regex: ':-?p',         	  shortcut: ':p',         		   width: 18,	height: 18,		image: 'tongue.png'},
			{regex: '8-?\\)',        	  shortcut: '8)',        		   width: 18,	height: 18,		image: 'cool.png'},
			{regex: ':-?\\(',        	  shortcut: ':(',        		   width: 18,	height: 18,		image: 'frown.png'},
			{regex: ':-\\*',      		  shortcut: ':-*',      		   width: 18,	height: 18,		image: 'kiss.png'},
			{regex: ':\\\\',      		  shortcut: ':\\',      		   width: 18,	height: 18,		image: 'slant.png'},
			{regex: ':-?\\)',        	  shortcut: ':)',        		   width: 18,	height: 18,		image: 'smile.png'},
			{regex: ':-?\\|',             shortcut: ':|', 	               width: 18,	height: 18,		image: 'straightface.png'},
			{regex: ';-?\\)',        	  shortcut: ';)',        		   width: 18,	height: 18,		image: 'wink.png'}];

		/**
		 * Linkify a string, replacing text urls with url
		 *
		 * @param text - String to be linkified
		 * @param emoticonify - Whether to replace text emoticons with images
		 * @param dispatchEvent - Whether to dispatch a link: event (to be handled in actionscript)
		 *  see: http://blog.flexexamples.com/2008/01/26/listening-for-the-link-event-in-a-flex-label-control/
		 *  see: http://livedocs.adobe.com/flex/3/html/help.html?content=textcontrols_04.html
		 * @param matchedLinks - Return param (pass by ref) - Array of links matched during linkification
		 **/
		public static function linkify(text:String,
									   emoticonify:Boolean = true,
									   dispatchEvent:Boolean = false,
									   truncateLength:uint = 0,
									   matchedLinks:Array = null):String {
			text = Linkify.matchAndReplace(RE_EMAIL_PATTERN, text, false, false, dispatchEvent, truncateLength, matchedLinks);
			text = Linkify.matchAndReplace(RE_FULL_URL, text, true, false, dispatchEvent, truncateLength, matchedLinks);
			text = Linkify.matchAndReplace(RE_OTHER_URL, text, true, true, dispatchEvent, truncateLength, matchedLinks);

			if (emoticonify)
				text = Linkify.emoticonText(text);

			return text;
		}

		/**
		 * Internal helper function for linkification
		 **/
		private static function matchAndReplace(pattern:String,
												input:String,
												isURL:Boolean = false,
												addHTTP:Boolean = true,
												dispatchEvent:Boolean = false,
												truncateLength:uint = 0,
												matchedLinks:Array = null):String {
			var start:int = 0;
			var offset:int = 0;
			var matchLength:int = 0;
			var endTagPos:int = 0;
			var closeAnchorRe:RegExp = /<\/[aA]>/;
			var re:RegExp = new RegExp(pattern, "g");

			var match:Object = new Object();

			// Limit the number of links we match in one pass
			var maxIter:int = 20;
			var curIter:int = 0;
			while (match = re.exec(input)) {
				curIter++;
				if (curIter > maxIter)
					break;

				start = match.index; // start of match
				matchLength = match[0].length;

				// If we find an opening anchor tag, advance to its end and continue looking
				var substr:String = input.substring(offset, start);
				if (substr.search(/<[aA]/) >= 0) {
					// Find the closing anchor tag
					closeAnchorRe.lastIndex = offset;
					var closeAnchorPos:int = input.substring(offset, input.length).search(closeAnchorRe);

					// If we find an opening tag without a closing match, just return the current input
					if (closeAnchorPos < 0)
						return input;

					endTagPos = closeAnchorPos + offset;

					// RegExp.lastIndex is used to tell the regexp where to start matching
					re.lastIndex = endTagPos + 4;
					offset = endTagPos + 4;

					continue;
				}

				// Do the replacement of url text with anchor tag
				var address:String = input.substr(start, matchLength);
				var actual:String = input.substr(start, matchLength);

				if (addHTTP) {
					actual = 'http://'+actual;
				}
				var replacement:String = ' truncateLength) {
					address = address.substr(0, truncateLength) + '...';
				}

				// Add word break tags to allow wrapping where appropriate
				// Manually put font styles around the link so they actually look like links
				// see: http://livedocs.adobe.com/flex/3/html/help.html?content=textcontrols_04.html
				replacement += '>'+address.replace(new RegExp("([/=&])", 'g'), "<wbr>$1")+'';
				if (matchedLinks) {
					matchedLinks.push(actual);
				}

				input = input.slice(0, start) + replacement + input.slice(start+matchLength, input.length);
				re.lastIndex = start + replacement.length;
				offset = start + replacement.length;
			}

			return input;
		}

		/**
		 * Replace text emoticons with images
		 **/
		private static function emoticonText(text:String):String {
			for each (var eData:Object in Linkify.emoticons) {
				var regex:String = eData.regex;
				var img:String = eData.image;

				// (not a word character)(smiley regex)(not a word character)
				var pattern:RegExp = new RegExp('(?<!\\w)'+regex+'(?!\\w)', 'gim');
				text = text.replace(pattern, '');
			}
			return text;
		}
	}
}

Fix trailing slash linkification

Fix trailing slash linkification


package aerys.minko.type.clone
{
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.controller.AnimationController;
	import aerys.minko.scene.controller.TransformController;
	import aerys.minko.scene.controller.camera.CameraController;
	import aerys.minko.scene.controller.mesh.MeshController;
	import aerys.minko.scene.controller.mesh.MeshVisibilityController;
	import aerys.minko.scene.controller.mesh.skinning.SkinningController;

	public final class CloneOptions
	{
		private var _clonedControllerTypes		: Vector.<Class>;
		private var _ignoredControllerTypes		: Vector.<Class>;
		private var _reassignedControllerTypes	: Vector.<Class>;
		private var _defaultControllerAction	: uint;

		public function get defaultControllerAction() : uint
		{
			return _defaultControllerAction;
		}
		public function set defaultControllerAction(v : uint) : void
		{
			if (v != ControllerCloneAction.CLONE
				&& v != ControllerCloneAction.IGNORE
				&& v != ControllerCloneAction.REASSIGN)
				throw new Error('Unknown action type.');

			_defaultControllerAction = v;
		}

		public function CloneOptions()
		{
			initialize();
		}

		private function initialize() : void
		{
			_clonedControllerTypes = new <Class>[];
			_ignoredControllerTypes = new <Class>[];
			_reassignedControllerTypes = new <Class>[];
			_defaultControllerAction = ControllerCloneAction.REASSIGN;
		}

		public static function get defaultOptions() : CloneOptions
		{
			var cloneOptions : CloneOptions = new CloneOptions();

			cloneOptions._clonedControllerTypes.push(
				AnimationController,
				SkinningController
			);
			cloneOptions._ignoredControllerTypes.push(
				MeshController,
				MeshVisibilityController,
				CameraController,
				TransformController
			);
			cloneOptions._defaultControllerAction = ControllerCloneAction.REASSIGN;

			return cloneOptions;
		}

		public static function get cloneAllOptions() : CloneOptions
		{
			var cloneOptions : CloneOptions = new CloneOptions();

			cloneOptions._ignoredControllerTypes.push(
				MeshController,
				MeshVisibilityController,
				CameraController,
				TransformController
			);
			cloneOptions._defaultControllerAction = ControllerCloneAction.CLONE;

			return cloneOptions;
		}

		public function addControllerAction(controllerClass : Class, action : uint) : CloneOptions
		{
			switch (action)
			{
				case ControllerCloneAction.CLONE:
					_clonedControllerTypes.push(controllerClass);
					break;

				case ControllerCloneAction.IGNORE:
					_ignoredControllerTypes.push(controllerClass);
					break;

				case ControllerCloneAction.REASSIGN:
					_reassignedControllerTypes.push(controllerClass);
					break;

				default:
					throw new Error('Unknown action type.');
			}

            return this;
		}

		public function removeControllerAction(controllerClass : Class) : void
		{
			throw new Error('Implement me.');
		}

		public function getActionForController(controller : AbstractController) : uint
		{
			var numControllersToClone		: uint = _clonedControllerTypes.length;
			var numControllersToIgnore		: uint = _ignoredControllerTypes.length;
			var numControllersToReassign	: uint = _reassignedControllerTypes.length;
			var controllerId				: uint;

			for (controllerId = 0; controllerId < numControllersToClone; ++controllerId)
				if (controller is _clonedControllerTypes[controllerId])
					return ControllerCloneAction.CLONE;

			for (controllerId = 0; controllerId < numControllersToIgnore; ++controllerId)
				if (controller is _ignoredControllerTypes[controllerId])
					return ControllerCloneAction.IGNORE;

			for (controllerId = 0; controllerId < numControllersToReassign; ++controllerId)
				if (controller is _reassignedControllerTypes[controllerId])
					return ControllerCloneAction.REASSIGN;

			return _defaultControllerAction;
		}

	}
}

package aerys.minko.type.clone
{
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.controller.AnimationController;
	import aerys.minko.scene.controller.TransformController;
	import aerys.minko.scene.controller.camera.CameraController;
	import aerys.minko.scene.controller.light.DirectionalLightController;
	import aerys.minko.scene.controller.light.LightController;
	import aerys.minko.scene.controller.light.PointLightController;
	import aerys.minko.scene.controller.light.SpotLightController;
	import aerys.minko.scene.controller.mesh.MeshController;
	import aerys.minko.scene.controller.mesh.MeshVisibilityController;
	import aerys.minko.scene.controller.mesh.skinning.SkinningController;

	public final class CloneOptions
	{
		private var _clonedControllerTypes		: Vector.<Class>;
		private var _ignoredControllerTypes		: Vector.<Class>;
		private var _reassignedControllerTypes	: Vector.<Class>;
		private var _defaultControllerAction	: uint;

		public function get defaultControllerAction() : uint
		{
			return _defaultControllerAction;
		}
		public function set defaultControllerAction(v : uint) : void
		{
			if (v != ControllerCloneAction.CLONE
				&& v != ControllerCloneAction.IGNORE
				&& v != ControllerCloneAction.REASSIGN)
				throw new Error('Unknown action type.');

			_defaultControllerAction = v;
		}

		public function CloneOptions()
		{
			initialize();
		}

		private function initialize() : void
		{
			_clonedControllerTypes = new <Class>[];
			_ignoredControllerTypes = new <Class>[];
			_reassignedControllerTypes = new <Class>[];
			_defaultControllerAction = ControllerCloneAction.REASSIGN;
		}

		public static function get defaultOptions() : CloneOptions
		{
			var cloneOptions : CloneOptions = new CloneOptions();

			cloneOptions._clonedControllerTypes.push(
				AnimationController,
				SkinningController
			);
			cloneOptions._ignoredControllerTypes.push(
				MeshController,
				MeshVisibilityController,
				CameraController,
				TransformController,
                LightController,
                DirectionalLightController,
                SpotLightController,
                PointLightController,
			);
			cloneOptions._defaultControllerAction = ControllerCloneAction.REASSIGN;

			return cloneOptions;
		}

		public static function get cloneAllOptions() : CloneOptions
		{
			var cloneOptions : CloneOptions = new CloneOptions();

			cloneOptions._ignoredControllerTypes.push(
				MeshController,
				MeshVisibilityController,
				CameraController,
				TransformController,
                LightController,
                DirectionalLightController,
                SpotLightController,
                PointLightController
			);
			cloneOptions._defaultControllerAction = ControllerCloneAction.CLONE;

			return cloneOptions;
		}

		public function addControllerAction(controllerClass : Class, action : uint) : CloneOptions
		{
			switch (action)
			{
				case ControllerCloneAction.CLONE:
					_clonedControllerTypes.push(controllerClass);
					break;

				case ControllerCloneAction.IGNORE:
					_ignoredControllerTypes.push(controllerClass);
					break;

				case ControllerCloneAction.REASSIGN:
					_reassignedControllerTypes.push(controllerClass);
					break;

				default:
					throw new Error('Unknown action type.');
			}

            return this;
		}

		public function removeControllerAction(controllerClass : Class) : void
		{
			throw new Error('Implement me.');
		}

		public function getActionForController(controller : AbstractController) : uint
		{
			var numControllersToClone		: uint = _clonedControllerTypes.length;
			var numControllersToIgnore		: uint = _ignoredControllerTypes.length;
			var numControllersToReassign	: uint = _reassignedControllerTypes.length;
			var controllerId				: uint;

			for (controllerId = 0; controllerId < numControllersToClone; ++controllerId)
				if (controller is _clonedControllerTypes[controllerId])
					return ControllerCloneAction.CLONE;

			for (controllerId = 0; controllerId < numControllersToIgnore; ++controllerId)
				if (controller is _ignoredControllerTypes[controllerId])
					return ControllerCloneAction.IGNORE;

			for (controllerId = 0; controllerId < numControllersToReassign; ++controllerId)
				if (controller is _reassignedControllerTypes[controllerId])
					return ControllerCloneAction.REASSIGN;

			return _defaultControllerAction;
		}

	}
}

update CloneOptions.cloneAllOptions and CloneOptions.defaultOptions to ignore the light related controller


/*	SWFObject v2.2 <http://code.google.com/p/swfobject/> is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
	Express Install Copyright (c) 2007-2008 Adobe Systems Incorporated and its licensors. All Rights Reserved.
*/

System.security.allowDomain("fpdownload2.macromedia.com");

var time = 0;
var timeOut = 5; // in seconds
var delay = 10; // in milliseconds
var int_id = setInterval(checkLoaded, delay);
var old_si = null;
var loaderClip = this.createEmptyMovieClip("loaderClip", 0);
var updateSWF = "http://fpdownload2.macromedia.com/pub/flashplayer/update/current/swf/autoUpdater.swf?" + Math.random();
loaderClip.loadMovie(updateSWF);

function checkLoaded(){
	time += delay / 1000;
	if (time > timeOut) {
		// updater did not load in time, abort load and force alternative content
		clearInterval(int_id);
		loaderClip.unloadMovie();
		loadTimeOut();
	}
	else if (loaderClip.startInstall.toString() == "[type Function]") {
		// updater has loaded successfully AND has determined that it can do the express install
		if (old_si == null) {
			old_si = loaderClip.startInstall;
			loaderClip.startInstall = function() {
				clearInterval(int_id);
				old_si();
			}
			loadComplete();
		}
	}
}

function loadTimeOut() {
	callbackSWFObject();
}

function callbackSWFObject() {
	getURL("javascript:swfobject.expressInstallCallback();");
}

function loadComplete() {
	loaderClip.redirectURL = _level0.MMredirectURL;
	loaderClip.MMplayerType = _level0.MMplayerType;
	loaderClip.MMdoctitle = _level0.MMdoctitle;
	loaderClip.startUpdate();
}

function installStatus(statusValue) {
	switch (statusValue) {
		case "Download.Complete":
			// Installation is complete.
			// In most cases the browser window that this SWF is hosted in will be closed by the installer or otherwise it has to be closed manually by the end user.
			// The Adobe Flash installer will attempt to reopen the browser window and reload the page containing the SWF.
		break;
		case "Download.Cancelled":
			// The end user chose "NO" when prompted to install the new player.
			// By default the SWFObject callback function is called to force alternative content.
			callbackSWFObject();
		break;
		case "Download.Failed":
			// The end user failed to download the installer due to a network failure.
			// By default the SWFObject callback function is called to force alternative content.
			callbackSWFObject();
		break;
	}
}

/*	SWFObject v2.2 <http://code.google.com/p/swfobject/> is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
	Express Install Copyright (c) 2007-2008 Adobe Systems Incorporated and its licensors. All Rights Reserved.
*/

System.security.allowDomain("fpdownload.macromedia.com");

var time = 0;
var timeOut = 5; // in seconds
var delay = 10; // in milliseconds
var int_id = setInterval(checkLoaded, delay);
var old_si = null;
var loaderClip = this.createEmptyMovieClip("loaderClip", 0);
var updateSWF = "http://fpdownload.macromedia.com/pub/flashplayer/update/current/swf/autoUpdater.swf?" + Math.random();
loaderClip.loadMovie(updateSWF);

function checkLoaded(){
	time += delay / 1000;
	if (time > timeOut) {
		// updater did not load in time, abort load and force alternative content
		clearInterval(int_id);
		loaderClip.unloadMovie();
		loadTimeOut();
	}
	else if (loaderClip.startInstall.toString() == "[type Function]") {
		// updater has loaded successfully AND has determined that it can do the express install
		if (old_si == null) {
			old_si = loaderClip.startInstall;
			loaderClip.startInstall = function() {
				clearInterval(int_id);
				old_si();
			}
			loadComplete();
		}
	}
}

function loadTimeOut() {
	callbackSWFObject();
}

function callbackSWFObject() {
	getURL("javascript:swfobject.expressInstallCallback();");
}

function loadComplete() {
	loaderClip.redirectURL = _level0.MMredirectURL;
	loaderClip.MMplayerType = _level0.MMplayerType;
	loaderClip.MMdoctitle = _level0.MMdoctitle;
	loaderClip.startUpdate();
}

function installStatus(statusValue) {
	switch (statusValue) {
		case "Download.Complete":
			// Installation is complete.
			// In most cases the browser window that this SWF is hosted in will be closed by the installer or otherwise it has to be closed manually by the end user.
			// The Adobe Flash installer will attempt to reopen the browser window and reload the page containing the SWF.
		break;
		case "Download.Cancelled":
			// The end user chose "NO" when prompted to install the new player.
			// By default the SWFObject callback function is called to force alternative content.
			callbackSWFObject();
		break;
		case "Download.Failed":
			// The end user failed to download the installer due to a network failure.
			// By default the SWFObject callback function is called to force alternative content.
			callbackSWFObject();
		break;
	}
}

Rollback to previous version, Adobe change request was cancelled

git-svn-id: c79b92e96100ef39c59e158da2f25328426f47b5@372 5cf17b04-402e-0410-8052-5f67ef7ba858


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

    import flash.events.Event;
    import flash.events.IEventDispatcher;
    import flash.events.MouseEvent;
    import flash.display.InteractiveObject;

    import mx.core.UIComponent;
    import mx.events.FlexMouseEvent;
    import mx.events.SandboxMouseEvent;

    [ExcludeClass]

    /**
     *  @private
     *
     *  Utilities to help with mouse event dispatching or event handling.
     */
    public class MouseEventUtil
    {
        include "../core/Version.as";

        //--------------------------------------------------------------------------
        //
        //  Static Methods
        //
        //--------------------------------------------------------------------------

        /**
         *  Set up event listeners for a complete down-drag-up mouse gesture.
         *
         *  Add MOUSE_DOWN, MOUSE_MOVE, and MOUSE_UP listeners to the target component
         *  which call the handleDown(), handleDrag(), and handleUp() functions respectively.
         *
         *  The handleDrag() method will be called for as long as the mouse button is down,
         *  whenever the mouse is dragged within the stage.   The handleUp() method will be
         *  called no matter where the mouse button is released, however a MouseEvent type argument
         *  will only be provided if the button is released over the stage.
         *
         *  The handleDown() and handleDrag() functions are passed a single MouseEvent
         *  argument, and the handleUp() is passed an Event which may be a MouseEvent
         *  or a SandboxMouseEvent.  Typically handleUp() functions ignore their event
         *  argument.
         *
         *  Any of the functions arguments can be null.
         *
         *  The implementation only adds MOUSE_MOVE and MOUSE_UP listeners in response
         *  to a MOUSE_DOWN event.   Similarly, the implementation removes its MOUSE_MOVE
         *  and MOUSE_UP listeners when it receives a MOUSE_UP event.
         *
         *  @param target A MOUSE_DOWN event on this component begins the down-drag-up gesture
         *  @param handleDown A function with a MouseEvent parameter; called when the MOUSE_DOWN event occurs.
         *  @param handleDrag A function with a MouseEvent parameter; called when the mouse moves with the button down.
         *  @param handleUp A function with an Event parameter; called when the button is relesed.
         *
         *  @see #removeDownDragUpListeners
         */
        public static function addDownDragUpListeners(
            target:UIComponent,
            handleDown:Function,
            handleDrag:Function,
            handleUp:Function):void
        {
            var f:Function = function(e:Event):void
            {
                var sbr:IEventDispatcher;
                switch(e.type)
                {
                    case MouseEvent.MOUSE_DOWN:
                        if (e.isDefaultPrevented())
                            break;
                        handleDown(e);
                        sbr = target.systemManager.getSandboxRoot();
                        sbr.addEventListener(MouseEvent.MOUSE_MOVE, f, true);
                        sbr.addEventListener(MouseEvent.MOUSE_UP, f, true );
                        sbr.addEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, f, true);
                        // Add the mouse shield so we can drag over untrusted applications.
                        target.systemManager.deployMouseShields(true);
                        break;
                    case MouseEvent.MOUSE_MOVE:
                        handleDrag(e);
                        break;
                    case MouseEvent.MOUSE_UP:
                        handleUp(e);
                        sbr = target.systemManager.getSandboxRoot();
                        sbr.removeEventListener(MouseEvent.MOUSE_MOVE, f, true);
                        sbr.removeEventListener(MouseEvent.MOUSE_UP, f, true);
                        sbr.removeEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, f, true);
                        target.systemManager.deployMouseShields(false);
                        break;
                    case "removeHandler":
                        target.removeEventListener("removeHandler", f);
                        target.removeEventListener(MouseEvent.MOUSE_DOWN, f);
                        sbr = target.systemManager.getSandboxRoot();
                        sbr.removeEventListener(MouseEvent.MOUSE_MOVE, f, true);
                        sbr.removeEventListener(MouseEvent.MOUSE_UP, f, true);
                        sbr.removeEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, f, true);
                        target.systemManager.deployMouseShields(false);
                        break;
                }
            }
            target.addEventListener(MouseEvent.MOUSE_DOWN, f);
            target.addEventListener("removeHandler", f);
        }

        /**
         *  Removes the listeners added by a matching addDownDragUpListeners call.
         *
         *  @param target A MOUSE_DOWN event on this component begins the down-drag-up gesture
         *  @param handleDown The listener for MOUSE_DOWN events
         *  @param handleDrag The listener for MOUSE_MOVE events when the button is down.
         *  @param handleUp The listener for MOUSE_UP events
         *
         *  @see #addDownDragUpListeners
         */
        public static function removeDownDragUpListeners(
            target:UIComponent,
            handleDown:Function,
            handleDrag:Function,
            handleUp:Function):void
        {
            target.dispatchEvent(
                new RemoveHandlerEvent(handleDown, handleDrag, handleUp));
        }

        /**
         *  Create a 'mouseWheelChanging' FlexMouseEvent from a 'mouseWheel'
         *  MouseEvent.
         */
        public static function createMouseWheelChangingEvent(event:MouseEvent):FlexMouseEvent
        {
            const flexEvent:FlexMouseEvent = new FlexMouseEvent(
                FlexMouseEvent.MOUSE_WHEEL_CHANGING,
                true, true,     // bubbles and cancelable
                event.localX, event.localY,
                InteractiveObject(event.target),
                event.ctrlKey, event.altKey, event.shiftKey,
                event.buttonDown, event.delta);

            return flexEvent;
        }
    }
}

/**
 * Event used to remove the handlers associated with the press-drag-release
 * gesture handlers.
 */
class RemoveHandlerEvent extends flash.events.Event
{
    public var handleDown:Function;
    public var handleDrag:Function;
    public var handleUp:Function;
    public function RemoveHandlerEvent(handleDown:Function,
                                       handleDrag:Function = null,
                                       handleUp:Function = null)
    {
        this.handleDown = handleDown;
        this.handleDown = handleDrag;
        this.handleUp = handleUp;
        super("removeHandler");
    }
}

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

    import flash.events.Event;
    import flash.events.IEventDispatcher;
    import flash.events.MouseEvent;
    import flash.display.InteractiveObject;

    import mx.core.UIComponent;
    import mx.events.FlexMouseEvent;
    import mx.events.SandboxMouseEvent;

    [ExcludeClass]

    /**
     *  @private
     *
     *  Utilities to help with mouse event dispatching or event handling.
     */
    public class MouseEventUtil
    {
        include "../core/Version.as";

        //--------------------------------------------------------------------------
        //
        //  Static Methods
        //
        //--------------------------------------------------------------------------

        /**
         *  Set up event listeners for a complete down-drag-up mouse gesture.
         *
         *  Add MOUSE_DOWN, MOUSE_MOVE, and MOUSE_UP listeners to the target component
         *  which call the handleDown(), handleDrag(), and handleUp() functions respectively.
         *
         *  The handleDrag() method will be called for as long as the mouse button is down,
         *  whenever the mouse is dragged within the stage.   The handleUp() method will be
         *  called no matter where the mouse button is released, however a MouseEvent type argument
         *  will only be provided if the button is released over the stage.
         *
         *  The handleDown() and handleDrag() functions are passed a single MouseEvent
         *  argument, and the handleUp() is passed an Event which may be a MouseEvent
         *  or a SandboxMouseEvent.  Typically handleUp() functions ignore their event
         *  argument.
         *
         *  Any of the functions arguments can be null.
         *
         *  The implementation only adds MOUSE_MOVE and MOUSE_UP listeners in response
         *  to a MOUSE_DOWN event.   Similarly, the implementation removes its MOUSE_MOVE
         *  and MOUSE_UP listeners when it receives a MOUSE_UP event.
         *
         *  @param target A MOUSE_DOWN event on this component begins the down-drag-up gesture
         *  @param handleDown A function with a MouseEvent parameter; called when the MOUSE_DOWN event occurs.
         *  @param handleDrag A function with a MouseEvent parameter; called when the mouse moves with the button down.
         *  @param handleUp A function with an Event parameter; called when the button is relesed.
         *
         *  @see #removeDownDragUpListeners
         */
        public static function addDownDragUpListeners(
            target:UIComponent,
            handleDown:Function,
            handleDrag:Function,
            handleUp:Function):void
        {
            var f:Function = function(e:Event):void
            {
                var sbr:IEventDispatcher;
                switch(e.type)
                {
                    case MouseEvent.MOUSE_DOWN:
                        if (e.isDefaultPrevented())
                            break;
                        handleDown(e);
                        sbr = target.systemManager.getSandboxRoot();
                        sbr.addEventListener(MouseEvent.MOUSE_MOVE, f, true);
                        sbr.addEventListener(MouseEvent.MOUSE_UP, f, true );
                        sbr.addEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, f, true);
                        // Add the mouse shield so we can drag over untrusted applications.
                        target.systemManager.deployMouseShields(true);
                        break;
                    case MouseEvent.MOUSE_MOVE:
                        handleDrag(e);
                        break;
                    case MouseEvent.MOUSE_UP:
                        handleUp(e);
                        sbr = target.systemManager.getSandboxRoot();
                        sbr.removeEventListener(MouseEvent.MOUSE_MOVE, f, true);
                        sbr.removeEventListener(MouseEvent.MOUSE_UP, f, true);
                        sbr.removeEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, f, true);
                        target.systemManager.deployMouseShields(false);
                        break;
                    case "removeHandler":
                        target.removeEventListener("removeHandler", f);
                        target.removeEventListener(MouseEvent.MOUSE_DOWN, f);
                        sbr = target.systemManager.getSandboxRoot();
                        sbr.removeEventListener(MouseEvent.MOUSE_MOVE, f, true);
                        sbr.removeEventListener(MouseEvent.MOUSE_UP, f, true);
                        sbr.removeEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, f, true);
                        target.systemManager.deployMouseShields(false);
                        break;
                }
            }
            target.addEventListener(MouseEvent.MOUSE_DOWN, f);
            target.addEventListener("removeHandler", f);
        }

        /**
         *  Removes the listeners added by a matching addDownDragUpListeners call.
         *
         *  @param target A MOUSE_DOWN event on this component begins the down-drag-up gesture
         *  @param handleDown The listener for MOUSE_DOWN events
         *  @param handleDrag The listener for MOUSE_MOVE events when the button is down.
         *  @param handleUp The listener for MOUSE_UP events
         *
         *  @see #addDownDragUpListeners
         */
        public static function removeDownDragUpListeners(
            target:UIComponent,
            handleDown:Function,
            handleDrag:Function,
            handleUp:Function):void
        {
            target.dispatchEvent(
                new RemoveHandlerEvent(handleDown, handleDrag, handleUp));
        }

        /**
         *  Create a 'mouseWheelChanging' FlexMouseEvent from a 'mouseWheel'
         *  MouseEvent.
         */
        public static function createMouseWheelChangingEvent(event:MouseEvent):FlexMouseEvent
        {
            const flexEvent:FlexMouseEvent = new FlexMouseEvent(
                FlexMouseEvent.MOUSE_WHEEL_CHANGING,
                true, true,     // bubbles and cancelable
                event.localX, event.localY,
                InteractiveObject(event.target),
                event.ctrlKey, event.altKey, event.shiftKey,
                event.buttonDown, event.delta);

            return flexEvent;
        }
    }
}

import flash.events.Event;

/**
 * Event used to remove the handlers associated with the press-drag-release
 * gesture handlers.
 */
class RemoveHandlerEvent extends flash.events.Event
{
    public var handleDown:Function;
    public var handleDrag:Function;
    public var handleUp:Function;
    public function RemoveHandlerEvent(handleDown:Function,
                                       handleDrag:Function = null,
                                       handleUp:Function = null)
    {
        this.handleDown = handleDown;
        this.handleDown = handleDrag;
        this.handleUp = handleUp;
        super("removeHandler");
    }
}

add missing import. Falcon cares about this stuff


////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2008-2013 Esri. All Rights Reserved.
//
// Licensed 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 com.esri.builder.views.supportClasses
{

import com.esri.ags.layers.supportClasses.Field;
import com.esri.ags.portal.supportClasses.PopUpFieldFormat;
import com.esri.ags.portal.supportClasses.PopUpFieldInfo;
import com.esri.builder.components.ItemModifierDataGrid;
import com.esri.builder.components.ModifyItemEvent;
import com.esri.builder.views.popups.FieldFormatPopUp;

import flash.display.DisplayObjectContainer;
import flash.utils.Dictionary;

import mx.collections.IList;
import mx.core.FlexGlobals;

import spark.components.supportClasses.SkinnableComponent;
import spark.events.PopUpEvent;

public class KeyValueEditor extends SkinnableComponent
{
    [SkinPart(required="true")]
    public var fieldEditor:ItemModifierDataGrid;

    private var editedFieldInfo:PopUpFieldInfo;

    private var _popUpFieldInfos:IList;

    [Bindable]
    public function get popUpFieldInfos():IList
    {
        return _popUpFieldInfos;
    }

    public function set popUpFieldInfos(value:IList):void
    {
        _popUpFieldInfos = value;
        invalidateProperties();
    }

    [Bindable]
    private var _fields:IList;

    public function get fields():IList
    {
        return _fields;
    }

    public function set fields(value:IList):void
    {
        _fields = value;
        invalidateProperties();
    }

    override protected function commitProperties():void
    {
        if (!fieldNameToFieldLookUp
            && _fields && _popUpFieldInfos)
        {
            initFieldNameToFieldLookUp();
        }

        super.commitProperties();
    }

    public var fieldNameToFieldLookUp:Dictionary;

    private function initFieldNameToFieldLookUp():void
    {
        fieldNameToFieldLookUp = new Dictionary();

        for each (var field:Field in _fields.toArray())
        {
            fieldNameToFieldLookUp[field.name] = field;
        }
    }

    override protected function partAdded(partName:String, instance:Object):void
    {
        super.partAdded(partName, instance);

        if (instance == fieldEditor)
        {
            fieldEditor.addEventListener(ModifyItemEvent.MOVE_ITEM_DOWN, popUpFieldsDataGrid_moveItemDownHandler);
            fieldEditor.addEventListener(ModifyItemEvent.MOVE_ITEM_UP, popUpFieldsDataGrid_moveItemUpHandler);
            fieldEditor.addEventListener(ModifyItemEvent.EDIT_ITEM, popUpFieldsDataGrid_editItemHandler);
        }
    }

    protected function popUpFieldsDataGrid_moveItemDownHandler(event:ModifyItemEvent):void
    {
        var itemIndex:int = _popUpFieldInfos.getItemIndex(event.item);

        var isItemAtBottom:Boolean = (itemIndex == (_popUpFieldInfos.length - 1));
        if (isItemAtBottom)
        {
            return;
        }

        var removedItem:Object = _popUpFieldInfos.removeItemAt(itemIndex);
        _popUpFieldInfos.addItemAt(removedItem, ++itemIndex);
    }

    protected function popUpFieldsDataGrid_moveItemUpHandler(event:ModifyItemEvent):void
    {
        var itemIndex:int = _popUpFieldInfos.getItemIndex(event.item);

        var isItemAtTop:Boolean = (itemIndex == 0);
        if (isItemAtTop)
        {
            return;
        }

        var removedItem:Object = _popUpFieldInfos.removeItemAt(itemIndex);
        _popUpFieldInfos.addItemAt(removedItem, --itemIndex);
    }

    private function popUpFieldsDataGrid_editItemHandler(event:ModifyItemEvent):void
    {
        var fieldFormatPopUp:FieldFormatPopUp = new FieldFormatPopUp();
        editedFieldInfo = event.item as PopUpFieldInfo;

        fieldFormatPopUp.field = findMatchingField(editedFieldInfo.fieldName);
        if (editedFieldInfo.format)
        {
            fieldFormatPopUp.overrideFieldFormat(editedFieldInfo.format);
        }
        fieldFormatPopUp.addEventListener(PopUpEvent.CLOSE, fieldFormatPopUp_closeHandler);
        fieldFormatPopUp.open(FlexGlobals.topLevelApplication as DisplayObjectContainer, true);
    }

    public function findMatchingField(fieldName:String):Field
    {
        return fieldNameToFieldLookUp[fieldName];
    }

    private function fieldFormatPopUp_closeHandler(event:PopUpEvent):void
    {
        var fieldFormatPopUp:FieldFormatPopUp = event.currentTarget as FieldFormatPopUp;
        fieldFormatPopUp.removeEventListener(PopUpEvent.CLOSE, fieldFormatPopUp_closeHandler);
        if (event.commit)
        {
            editedFieldInfo.format = event.data.fieldFormat as PopUpFieldFormat;
        }
    }

    override protected function partRemoved(partName:String, instance:Object):void
    {
        super.partRemoved(partName, instance);

        if (instance == fieldEditor)
        {
            fieldEditor.removeEventListener(ModifyItemEvent.MOVE_ITEM_DOWN, popUpFieldsDataGrid_moveItemDownHandler);
            fieldEditor.removeEventListener(ModifyItemEvent.MOVE_ITEM_UP, popUpFieldsDataGrid_moveItemUpHandler);
            fieldEditor.removeEventListener(ModifyItemEvent.EDIT_ITEM, popUpFieldsDataGrid_editItemHandler);
        }
    }
}
}

////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2008-2013 Esri. All Rights Reserved.
//
// Licensed 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 com.esri.builder.views.supportClasses
{

import com.esri.ags.layers.supportClasses.Field;
import com.esri.ags.portal.supportClasses.PopUpFieldFormat;
import com.esri.ags.portal.supportClasses.PopUpFieldInfo;
import com.esri.builder.components.ItemModifierDataGrid;
import com.esri.builder.components.ModifyItemEvent;
import com.esri.builder.views.popups.FieldFormatPopUp;

import flash.display.DisplayObjectContainer;
import flash.utils.Dictionary;

import mx.collections.IList;
import mx.core.FlexGlobals;

import spark.components.supportClasses.SkinnableComponent;
import spark.events.PopUpEvent;

public class KeyValueEditor extends SkinnableComponent
{
    [SkinPart(required="true")]
    public var fieldEditor:ItemModifierDataGrid;

    private var editedFieldInfo:PopUpFieldInfo;

    private var _popUpFieldInfos:IList;

    [Bindable]
    public function get popUpFieldInfos():IList
    {
        return _popUpFieldInfos;
    }

    public function set popUpFieldInfos(value:IList):void
    {
        _popUpFieldInfos = value;
        invalidateProperties();
    }

    [Bindable]
    private var _fields:IList;

    public function get fields():IList
    {
        return _fields;
    }

    public function set fields(value:IList):void
    {
        _fields = value;
        invalidateProperties();
    }

    override protected function commitProperties():void
    {
        if (!fieldNameToFieldLookUp
            && _fields && _popUpFieldInfos)
        {
            initFieldNameToFieldLookUp();
        }

        super.commitProperties();
    }

    public var fieldNameToFieldLookUp:Dictionary;

    private function initFieldNameToFieldLookUp():void
    {
        fieldNameToFieldLookUp = new Dictionary();

        for each (var field:Field in _fields.toArray())
        {
            fieldNameToFieldLookUp[field.name] = field;
        }
    }

    override protected function partAdded(partName:String, instance:Object):void
    {
        super.partAdded(partName, instance);

        if (instance == fieldEditor)
        {
            fieldEditor.addEventListener(ModifyItemEvent.MOVE_ITEM_DOWN, popUpFieldsDataGrid_moveItemDownHandler);
            fieldEditor.addEventListener(ModifyItemEvent.MOVE_ITEM_UP, popUpFieldsDataGrid_moveItemUpHandler);
            fieldEditor.addEventListener(ModifyItemEvent.EDIT_ITEM, popUpFieldsDataGrid_editItemHandler);
        }
    }

    protected function popUpFieldsDataGrid_moveItemDownHandler(event:ModifyItemEvent):void
    {
        var itemIndex:int = _popUpFieldInfos.getItemIndex(event.item);

        var isItemAtBottom:Boolean = (itemIndex == (_popUpFieldInfos.length - 1));
        if (isItemAtBottom)
        {
            return;
        }

        var removedItem:Object = _popUpFieldInfos.removeItemAt(itemIndex);
        _popUpFieldInfos.addItemAt(removedItem, ++itemIndex);
    }

    protected function popUpFieldsDataGrid_moveItemUpHandler(event:ModifyItemEvent):void
    {
        var itemIndex:int = _popUpFieldInfos.getItemIndex(event.item);

        var isItemAtTop:Boolean = (itemIndex == 0);
        if (isItemAtTop)
        {
            return;
        }

        var removedItem:Object = _popUpFieldInfos.removeItemAt(itemIndex);
        _popUpFieldInfos.addItemAt(removedItem, --itemIndex);
    }

    private function popUpFieldsDataGrid_editItemHandler(event:ModifyItemEvent):void
    {
        var fieldFormatPopUp:FieldFormatPopUp = new FieldFormatPopUp();
        editedFieldInfo = event.item as PopUpFieldInfo;

        fieldFormatPopUp.field = findMatchingField(editedFieldInfo.fieldName);
        if (editedFieldInfo.format)
        {
            fieldFormatPopUp.overrideFieldFormat(editedFieldInfo.format);
        }
        fieldFormatPopUp.addEventListener(PopUpEvent.CLOSE, fieldFormatPopUp_closeHandler);
        fieldFormatPopUp.open(FlexGlobals.topLevelApplication as DisplayObjectContainer, true);
    }

    public function findMatchingField(fieldName:String):Field
    {
        return fieldNameToFieldLookUp ? fieldNameToFieldLookUp[fieldName] : null;
    }

    private function fieldFormatPopUp_closeHandler(event:PopUpEvent):void
    {
        var fieldFormatPopUp:FieldFormatPopUp = event.currentTarget as FieldFormatPopUp;
        fieldFormatPopUp.removeEventListener(PopUpEvent.CLOSE, fieldFormatPopUp_closeHandler);
        if (event.commit)
        {
            editedFieldInfo.format = event.data.fieldFormat as PopUpFieldFormat;
        }
    }

    override protected function partRemoved(partName:String, instance:Object):void
    {
        super.partRemoved(partName, instance);

        if (instance == fieldEditor)
        {
            fieldEditor.removeEventListener(ModifyItemEvent.MOVE_ITEM_DOWN, popUpFieldsDataGrid_moveItemDownHandler);
            fieldEditor.removeEventListener(ModifyItemEvent.MOVE_ITEM_UP, popUpFieldsDataGrid_moveItemUpHandler);
            fieldEditor.removeEventListener(ModifyItemEvent.EDIT_ITEM, popUpFieldsDataGrid_editItemHandler);
        }
    }
}
}

Handle uninitialized KeyValueEditor field map.


/**
 * Hex
 *
 * Utility class to convert Hex strings to ByteArray or String types.
 * Copyright (c) 2007 Henri Torgemane
 *
 * See LICENSE.txt for full license information.
 */
package org.mangui.hls.utils {
    import flash.utils.ByteArray;

    public class Hex {
        /**
         * Generates byte-array from given hexadecimal string
         *
         * Supports straight and colon-laced hex (that means 23:03:0e:f0, but *NOT* 23:3:e:f0)
         * The first nibble (hex digit) may be omitted.
         * Any whitespace characters are ignored.
         */
        public static function toArray(hex : String) : ByteArray {
            hex = hex.replace(/^0x|\s|:/gm, '');
            var a : ByteArray = new ByteArray;
            if ((hex.length & 1) == 1) hex = "0" + hex;
            for (var i : uint = 0; i < hex.length; i += 2) {
                a[i / 2] = parseInt(hex.substr(i, 2), 16);
            }
            return a;
        }

        /**
         * Generates lowercase hexadecimal string from given byte-array
         */
        public static function fromArray(array : ByteArray, colons : Boolean = false) : String {
            var s : String = "";
            for (var i : uint = 0; i < array.length; i++) {
                s += ("0" + array[i].toString(16)).substr(-2, 2);
                if (colons) {
                    if (i < array.length - 1) s += ":";
                }
            }
            return s;
        }
    }
}

/**
 * Hex
 *
 * Utility class to convert Hex strings to ByteArray or String types.
 * Copyright (c) 2007 Henri Torgemane
 *
 * See LICENSE.txt for full license information.
 */
package org.mangui.hls.utils {
    import flash.utils.ByteArray;

    public class Hex {
        /**
         * Generates byte-array from given hexadecimal string
         *
         * Supports straight and colon-laced hex (that means 23:03:0e:f0, but *NOT* 23:3:e:f0)
         * The first nibble (hex digit) may be omitted.
         * Any whitespace characters are ignored.
         */
        public static function toArray(hex : String) : ByteArray {
            hex = hex.replace(/^0x|\s|:/gm, '');
            var a : ByteArray = new ByteArray;
            var len : uint = hex.length;
            if ((len & 1) == 1) hex = "0" + hex;
            for (var i : uint = 0; i < len; i += 2) {
                a[i / 2] = parseInt(hex.substr(i, 2), 16);
            }
            return a;
        }

        /**
         * Generates lowercase hexadecimal string from given byte-array
         */
        public static function fromArray(array : ByteArray, colons : Boolean = false) : String {
            var s : String = "";
            var len : uint = array.length;
            for (var i : uint = 0; i < len; i++) {
                s += ("0" + array[i].toString(16)).substr(-2, 2);
                if (colons) {
                    if (i < len - 1) s += ":";
                }
            }
            return s;
        }
    }
}

optimize loop

optimize loop


/**
 * User: MerlinDS
 * Date: 18.07.2014
 * Time: 15:14
 */
package com.merlinds.miracle_tool.controllers {
	import com.merlinds.debug.log;
	import com.merlinds.miracle.meshes.MeshMatrix;
	import com.merlinds.miracle.utils.UMath;
	import com.merlinds.miracle_tool.events.ActionEvent;
	import com.merlinds.miracle_tool.events.DialogEvent;
	import com.merlinds.miracle_tool.models.ProjectModel;
	import com.merlinds.miracle_tool.models.vo.AnimationVO;
	import com.merlinds.miracle_tool.models.vo.ElementVO;
	import com.merlinds.miracle_tool.models.vo.FrameVO;
	import com.merlinds.miracle_tool.models.vo.SourceVO;
	import com.merlinds.miracle_tool.models.vo.TimelineVO;
	import com.merlinds.miracle_tool.services.ActionService;
	import com.merlinds.miracle_tool.services.FileSystemService;
	import com.merlinds.miracle_tool.utils.MeshUtils;

	import flash.display.BitmapData;
	import flash.geom.Point;

	import flash.utils.ByteArray;

	import org.robotlegs.mvcs.Command;

	public class PublishCommand extends Command {

		[Inject]
		public var projectModel:ProjectModel;
		[Inject]
		public var fileSystemService:FileSystemService;
		[Inject]
		public var actionService:ActionService;
		[Inject]
		public var event:ActionEvent;

		private var _mesh:ByteArray;
		private var _png:ByteArray;
		private var _animations:ByteArray;
		//==============================================================================
		//{region							PUBLIC METHODS
		public function PublishCommand() {
			super();
		}

		override public function execute():void {
			log(this, "execute");
			if(event.body == null){
				var data:Object = {
					projectName:this.projectModel.name
				};
				this.actionService.addAcceptActions(new <String>[ActionEvent.PUBLISHING]);
				this.dispatch(new DialogEvent(DialogEvent.PUBLISH_SETTINGS, data));
			}else{
				var  projectName:String = event.body.projectName;
				this.createOutput();
				this.fileSystemService.writeTexture(projectName, _png, _mesh);
				this.fileSystemService.writeAnimation(_animations);
			}
		}

		//} endregion PUBLIC METHODS ===================================================

		//==============================================================================
		//{region						PRIVATE\PROTECTED METHODS
		private function createOutput():void{
			var buffer:BitmapData = new BitmapData(this.projectModel.outputSize, this.projectModel.outputSize, true, 0x0);
			var mesh:Array = [];
			var n:int = this.projectModel.sources.length;
			for(var i:int = 0; i < n; i++){
				var source:SourceVO = this.projectModel.sources[i];
				var m:int = source.elements.length;
				for(var j:int = 0; j < m; j++){
					//push element view to buffer
					var element:ElementVO = source.elements[j];
					var depth:Point = new Point(element.x + this.projectModel.boundsOffset,
									element.y + this.projectModel.boundsOffset);
					buffer.copyPixels(element.bitmapData, element.bitmapData.rect, depth);
					//get mesh
					mesh.push({
						name:element.name,
						vertexes:MeshUtils.flipToY(element.vertexes),
						uv:MeshUtils.covertToRelative(element.uv, this.projectModel.outputSize),
						indexes:element.indexes
					});
				}
				//get animation
				m = source.animations.length;
				var animations:Array = [];
				for(j = 0; j < m; j++){
					var animation:Object = this.createAnimationOutput(source.animations[j]);
					animations.push(animation);
				}
			}
			_mesh = new ByteArray();
			_mesh.writeObject(mesh);
			_png = PNGEncoder.encode(buffer);
			_animations = new ByteArray();
			_animations.writeObject(animations);
		}

		private function createAnimationOutput(animationVO:AnimationVO):Object {
			var data:Object = { name:animationVO.name.substr(0, -4),// name of the matrix
				totalFrames:animationVO.totalFrames,// Total animation frames
				layers:[]};
			var n:int = animationVO.timelines.length;
			//find matrix sequence for current animation
			for(var i:int = 0; i < n; i++){
				var timelineVO:TimelineVO = animationVO.timelines[i];
				var m:int = timelineVO.frames.length;
				var layer:Layer = new Layer();
				for(var j:int = 0; j < m; j++){
					var frameVO:FrameVO = timelineVO.frames[j];
					//create matrix
					var prevMatrix:MeshMatrix = layer.matrixList.length > 0
							? layer.matrixList[ layer.matrixList.length - 1] : null;
					var matrix:MeshMatrix = MeshMatrix.fromMatrix(frameVO.matrix, 0, 0);
					if(prevMatrix != null && matrix != null){
						//calculate shortest angle between previous matrix skew and current matrix skew
						matrix.skewX = this.getShortest(matrix.skewX, prevMatrix.skewX);
						matrix.skewY = this.getShortest(matrix.skewY, prevMatrix.skewY);
						//
					}
					var index:int = matrix == null ? -1 : layer.matrixList.push(matrix) - 1;
					var framesArray:Array = this.createFramesInfo(index, frameVO.name,
							frameVO.duration, frameVO.type == "motion");
					layer.framesList = layer.framesList.concat(framesArray);
					prevMatrix = matrix;
				}
				data.layers.unshift(layer);
			}
			trace(JSON.stringify(data));
			return data;
		}

		[Inline]
		private function createFramesInfo(index:int, polygonName:String,
		                                  duration:int, motion:Boolean):Array {
			var result:Array = new Array(duration);
			if(duration < 2){
				motion = false;
			}
			if(index > -1){
				var t:Number = 1 / duration;
				for(var i:int = 0; i < duration; i++){
					var frameInfoData:FrameInfoData = new FrameInfoData();
					frameInfoData.index = index;
					frameInfoData.polygonName = polygonName;
					frameInfoData.motion = motion;
					frameInfoData.t = motion ? t * i : 0;
					result[i] = frameInfoData;
				}
			}
			return result;
		}

		private function getShortest(a:Number, b:Number):Number {
			if(Math.abs(a - b) > Math.PI){
				if(a > 0){
					return a - Math.PI * 2;
				}else if(a < 0){
					return Math.PI * 2 + a;
				}
			}
			return a;
		}
		//} endregion PRIVATE\PROTECTED METHODS ========================================

		//==============================================================================
		//{region							EVENTS HANDLERS
		//} endregion EVENTS HANDLERS ==================================================

		//==============================================================================
		//{region							GETTERS/SETTERS
		//} endregion GETTERS/SETTERS ==================================================
	}
}
class Layer{
	public var matrixList:Array;
	public var framesList:Array;

	public function Layer() {
		this.framesList = [];
		this.matrixList = [];
	}
}

class FrameInfoData{
	public var polygonName:String;
	public var motion:Boolean;
	public var index:int;
	public var t:Number;
}

/**
 * User: MerlinDS
 * Date: 18.07.2014
 * Time: 15:14
 */
package com.merlinds.miracle_tool.controllers {
	import com.merlinds.debug.log;
	import com.merlinds.miracle.meshes.MeshMatrix;
	import com.merlinds.miracle_tool.events.ActionEvent;
	import com.merlinds.miracle_tool.events.DialogEvent;
	import com.merlinds.miracle_tool.models.ProjectModel;
	import com.merlinds.miracle_tool.models.vo.AnimationVO;
	import com.merlinds.miracle_tool.models.vo.ElementVO;
	import com.merlinds.miracle_tool.models.vo.FrameVO;
	import com.merlinds.miracle_tool.models.vo.SourceVO;
	import com.merlinds.miracle_tool.models.vo.TimelineVO;
	import com.merlinds.miracle_tool.services.ActionService;
	import com.merlinds.miracle_tool.services.FileSystemService;
	import com.merlinds.miracle_tool.utils.MeshUtils;

	import flash.display.BitmapData;
	import flash.geom.Point;

	import flash.utils.ByteArray;

	import org.robotlegs.mvcs.Command;

	public class PublishCommand extends Command {

		[Inject]
		public var projectModel:ProjectModel;
		[Inject]
		public var fileSystemService:FileSystemService;
		[Inject]
		public var actionService:ActionService;
		[Inject]
		public var event:ActionEvent;

		private var _mesh:ByteArray;
		private var _png:ByteArray;
		private var _animations:ByteArray;
		//==============================================================================
		//{region							PUBLIC METHODS
		public function PublishCommand() {
			super();
		}

		override public function execute():void {
			log(this, "execute");
			if(event.body == null){
				var data:Object = {
					projectName:this.projectModel.name
				};
				this.actionService.addAcceptActions(new <String>[ActionEvent.PUBLISHING]);
				this.dispatch(new DialogEvent(DialogEvent.PUBLISH_SETTINGS, data));
			}else{
				var  projectName:String = event.body.projectName;
				this.createOutput();
				this.fileSystemService.writeTexture(projectName, _png, _mesh);
				this.fileSystemService.writeAnimation(_animations);
			}
		}

		//} endregion PUBLIC METHODS ===================================================

		//==============================================================================
		//{region						PRIVATE\PROTECTED METHODS
		private function createOutput():void{
			var buffer:BitmapData = new BitmapData(this.projectModel.outputSize, this.projectModel.outputSize, true, 0x0);
			var mesh:Array = [];
			var n:int = this.projectModel.sources.length;
			for(var i:int = 0; i < n; i++){
				var source:SourceVO = this.projectModel.sources[i];
				var m:int = source.elements.length;
				for(var j:int = 0; j < m; j++){
					//push element view to buffer
					var element:ElementVO = source.elements[j];
					var depth:Point = new Point(element.x + this.projectModel.boundsOffset,
									element.y + this.projectModel.boundsOffset);
					buffer.copyPixels(element.bitmapData, element.bitmapData.rect, depth);
					//get mesh
					mesh.push({
						name:element.name,
						vertexes:MeshUtils.flipToY(element.vertexes),
						uv:MeshUtils.covertToRelative(element.uv, this.projectModel.outputSize),
						indexes:element.indexes
					});
				}
				//get animation
				m = source.animations.length;
				var animations:Array = [];
				for(j = 0; j < m; j++){
					var animation:Object = this.createAnimationOutput(source.animations[j]);
					animations.push(animation);
				}
			}
			_mesh = new ByteArray();
			_mesh.writeObject(mesh);
			_png = PNGEncoder.encode(buffer);
			_animations = new ByteArray();
			_animations.writeObject(animations);
		}

		private function createAnimationOutput(animationVO:AnimationVO):Object {
			var data:Object = { name:animationVO.name.substr(0, -4),// name of the matrix
				totalFrames:animationVO.totalFrames,// Total animation frames
				layers:[]};
			var n:int = animationVO.timelines.length;
			//find matrix sequence for current animation
			for(var i:int = 0; i < n; i++){
				var timelineVO:TimelineVO = animationVO.timelines[i];
				var m:int = timelineVO.frames.length;
				var layer:Layer = new Layer();
				for(var j:int = 0; j < m; j++){
					var frameVO:FrameVO = timelineVO.frames[j];
					//create matrix
					var prevMatrix:MeshMatrix = layer.matrixList.length > 0
							? layer.matrixList[ layer.matrixList.length - 1] : null;
					var matrix:MeshMatrix = MeshMatrix.fromMatrix(frameVO.matrix, frameVO.transformationPoint.x, frameVO.transformationPoint.y);
					if(prevMatrix != null && matrix != null){
						//calculate shortest angle between previous matrix skew and current matrix skew
						matrix.skewX = this.getShortest(matrix.skewX, prevMatrix.skewX);
						matrix.skewY = this.getShortest(matrix.skewY, prevMatrix.skewY);
						//
					}
					var index:int = matrix == null ? -1 : layer.matrixList.push(matrix) - 1;
					var framesArray:Array = this.createFramesInfo(index, frameVO.name,
							frameVO.duration, frameVO.type == "motion");
					layer.framesList = layer.framesList.concat(framesArray);
					prevMatrix = matrix;
				}
				data.layers.unshift(layer);
			}
			trace(JSON.stringify(data));
			return data;
		}

		[Inline]
		private function createFramesInfo(index:int, polygonName:String,
		                                  duration:int, motion:Boolean):Array {
			var result:Array = new Array(duration);
			if(duration < 2){
				motion = false;
			}
			if(index > -1){
				var t:Number = 1 / duration;
				for(var i:int = 0; i < duration; i++){
					var frameInfoData:FrameInfoData = new FrameInfoData();
					frameInfoData.index = index;
					frameInfoData.polygonName = polygonName;
					frameInfoData.motion = motion;
					frameInfoData.t = motion ? t * i : 0;
					result[i] = frameInfoData;
				}
			}
			return result;
		}

		private function getShortest(a:Number, b:Number):Number {
			if(Math.abs(a - b) > Math.PI){
				if(a > 0){
					return a - Math.PI * 2;
				}else if(a < 0){
					return Math.PI * 2 + a;
				}
			}
			return a;
		}
		//} endregion PRIVATE\PROTECTED METHODS ========================================

		//==============================================================================
		//{region							EVENTS HANDLERS
		//} endregion EVENTS HANDLERS ==================================================

		//==============================================================================
		//{region							GETTERS/SETTERS
		//} endregion GETTERS/SETTERS ==================================================
	}
}
class Layer{
	public var matrixList:Array;
	public var framesList:Array;

	public function Layer() {
		this.framesList = [];
		this.matrixList = [];
	}
}

class FrameInfoData{
	public var polygonName:String;
	public var motion:Boolean;
	public var index:int;
	public var t:Number;
}

refactor scene

refactor scene