package aerys.minko.render
{
	import aerys.minko.ns.minko_render;
	import aerys.minko.render.geometry.Geometry;
	import aerys.minko.render.geometry.stream.IVertexStream;
	import aerys.minko.render.geometry.stream.StreamUsage;
	import aerys.minko.render.geometry.stream.VertexStream;
	import aerys.minko.render.geometry.stream.format.VertexComponent;
	import aerys.minko.render.geometry.stream.format.VertexFormat;
	import aerys.minko.render.resource.Context3DResource;
	import aerys.minko.render.resource.IndexBuffer3DResource;
	import aerys.minko.render.resource.Program3DResource;
	import aerys.minko.render.resource.VertexBuffer3DResource;
	import aerys.minko.render.resource.texture.ITextureResource;
	import aerys.minko.render.shader.binding.IBinder;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.enum.Blending;
	import aerys.minko.type.enum.ColorMask;
	import aerys.minko.type.enum.TriangleCulling;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	import flash.display3D.Context3DProgramType;
	import flash.utils.Dictionary;

	/**
	 * DrawCall objects contain all the shader constants and buffer settings required
	 * to perform drawing operations using the Stage3D API.
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class DrawCall
	{
		use namespace minko_render;

		private static const PROGRAM_TYPE_VERTEX	: String			= Context3DProgramType.VERTEX;
		private static const PROGRAM_TYPE_FRAGMENT	: String			= Context3DProgramType.FRAGMENT;
		private static const NUM_TEXTURES			: uint				= 8;
		private static const NUM_VERTEX_BUFFERS		: uint				= 8;

		private static const TMP_VECTOR4			: Vector4			= new Vector4();
		private static const TMP_NUMBERS			: Vector.<Number>	= new Vector.<Number>(0xffff, true);
		private static const TMP_INTS				: Vector.<int>		= new Vector.<int>(0xffff, true);

		private var _bindings			: Object							= null;

		private var _vsInputComponents	: Vector.<VertexComponent>			= null;
		private var _vsInputIndices		: Vector.<uint>						= null;
		private var _cpuConstants		: Dictionary						= null;
		private var _vsConstants		: Vector.<Number>					= null;
		private var _fsConstants		: Vector.<Number>					= null;
		private var _fsTextures			: Vector.<ITextureResource>			= new Vector.<ITextureResource>(NUM_TEXTURES, true);

		// states
		private var _indexBuffer		: IndexBuffer3DResource				= null;
		private var _firstIndex			: int								= 0;
		private var _numTriangles		: int								= -1;

		private var _vertexBuffers		: Vector.<VertexBuffer3DResource>	= new Vector.<VertexBuffer3DResource>(NUM_VERTEX_BUFFERS, true);
		private var _numVertexComponents: uint								= 0;
		private var _offsets			: Vector.<int>						= new Vector.<int>(8, true);
		private var _formats			: Vector.<String>					= new Vector.<String>(8, true);

		private var _blending			: uint								= 0;
		private var _blendingSource		: String							= null;
		private var _blendingDest		: String							= null;

		private var _triangleCulling	: uint								= 0;
		private var _triangleCullingStr	: String							= null;

		private var _colorMask			: uint								= 0;
		private var _colorMaskR			: Boolean							= true;
		private var _colorMaskG			: Boolean							= true;
		private var _colorMaskB			: Boolean							= true;
		private var _colorMaskA			: Boolean							= true;

		private var _enabled			: Boolean							= true;

		private var _depth				: Number							= 0.;
		private var _invalidDepth		: Boolean							= false;
		private var _localToWorld		: Matrix4x4							= null;
		private var _worldToScreen		: Matrix4x4							= null;

        private var _changes            : Object                           = {};

		public function get vertexComponents() : Vector.<VertexComponent>
		{
			return _vsInputComponents;
		}

		public function get blending() : uint
		{
			return _blending;
		}
		public function set blending(value : uint) : void
		{
			_blending = value;
			_blendingSource = Blending.STRINGS[int(value & 0xffff)];
			_blendingDest	= Blending.STRINGS[int(value >>> 16)]
		}

		public function get triangleCulling() : uint
		{
			return _triangleCulling;
		}
		public function set triangleCulling(value : uint) : void
		{
			_triangleCulling = value;
			_triangleCullingStr = TriangleCulling.STRINGS[value];
		}

		public function get colorMask() : uint
		{
			return _colorMask;
		}
		public function set colorMask(value : uint) : void
		{
			_colorMask = value;
			_colorMaskR = (value & ColorMask.RED) != 0;
			_colorMaskG = (value & ColorMask.GREEN) != 0;
			_colorMaskB = (value & ColorMask.BLUE) != 0;
			_colorMaskA = (value & ColorMask.ALPHA) != 0;
		}

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

            if (value)
                for each (var bindingName : String in _changes)
                {
                    setParameter(bindingName, _changes[bindingName]);
                    delete _changes[bindingName];
                }
		}

		public function get depth() : Number
		{
			if (_invalidDepth && _enabled)
			{
				_invalidDepth = false;

				if (_localToWorld != null && _worldToScreen != null)
				{
					var worldSpacePosition 	: Vector4	= _localToWorld.transformVector(
						Vector4.ZERO, TMP_VECTOR4
					);
					var screenSpacePosition : Vector4 	= _worldToScreen.transformVector(
						worldSpacePosition, TMP_VECTOR4
					);

					_depth = screenSpacePosition.z / screenSpacePosition.w;
				}
			}

			return _depth;
		}

		public function configure(program		: Program3DResource,
								  geometry		: Geometry,
								  meshBindings	: DataBindings,
								  sceneBindings	: DataBindings,
								  computeDepth	: Boolean) : void
		{
//			if (_bindings != null)
//				unsetBindings(meshBindings, sceneBindings);

			_invalidDepth = computeDepth;

			setProgram(program);
			updateGeometry(geometry);
			setGeometry(geometry);
			setBindings(meshBindings, sceneBindings, computeDepth);
		}

		public function unsetBindings(meshBindings	: DataBindings,
									  sceneBindings	: DataBindings) : void
		{
            _changes = {};

			for (var parameter : String in _bindings)
			{
				meshBindings.removeCallback(parameter, parameterChangedHandler);
				sceneBindings.removeCallback(parameter, parameterChangedHandler);
			}

			if (sceneBindings.hasCallback('worldToScreen', transformChangedHandler))
				sceneBindings.removeCallback('worldToScreen', transformChangedHandler);

			if (meshBindings.hasCallback('localToWorld', transformChangedHandler))
				meshBindings.removeCallback('localToWorld', transformChangedHandler);
		}

		private function setProgram(program : Program3DResource) : void
		{
			_cpuConstants		= new Dictionary();
			_vsConstants		= program._vsConstants.concat();
			_fsConstants		= program._fsConstants.concat();
			_fsTextures			= program._fsTextures.concat();
			_vsInputComponents	= program._vertexInputComponents;
			_vsInputIndices		= program._vertexInputIndices;
			_bindings			= program._bindings;

			triangleCulling		= TriangleCulling.FRONT;
			blending			= Blending.NORMAL;
			colorMask			= ColorMask.RGBA;
		}

		 /**
		  * Ask geometry to compute additional vertex data if needed for this drawcall.
		  */
		public function updateGeometry(geometry : Geometry) : void
		{
			var vertexFormat : VertexFormat	= geometry.format;

			if (_vsInputComponents.indexOf(VertexComponent.TANGENT) >= 0
				&& !vertexFormat.hasComponent(VertexComponent.TANGENT))
			{
				geometry.computeTangentSpace();
			}
			else if (_vsInputComponents.indexOf(VertexComponent.NORMAL) >= 0
				&& !vertexFormat.hasComponent(VertexComponent.NORMAL))
			{
				geometry.computeNormals();
			}
		}

		/**
		 * Obtain a reference to each buffer and offset that apply() may possibly need.
		 *
		 */
		public function setGeometry(geometry : Geometry, frame : uint = 0) : void
		{
			_numVertexComponents = _vsInputComponents.length;
			_indexBuffer		 = geometry.indexStream.resource;
			_firstIndex			 = geometry.firstIndex;
			_numTriangles		 = geometry.numTriangles;

			for (var i : uint = 0; i < _numVertexComponents; ++i)
			{
				var component : VertexComponent	= _vsInputComponents[i];
				var index	  : uint			= _vsInputIndices[i];

				if (component)
				{
					var vertexStream	: IVertexStream = geometry.getVertexStream(index + frame);
					var stream	 		: VertexStream	= vertexStream.getStreamByComponent(component);

					if (stream == null)
					{
						throw new Error(
							'Missing vertex component: \'' + component.toString() + '\'.'
						);
					}

					_vertexBuffers[i]	= stream.resource;
					_formats[i]			= component.nativeFormatString;
					_offsets[i]			= stream.format.getOffsetForComponent(component);
				}
			}
		}

		/**
		 * @fixme There is a bug here
		 * @fixme We splitted properties between scene and mesh
		 * @fixme it should be done on the compiler also to avoid this ugly hack
		 */
		private function setBindings(meshBindings	: DataBindings,
									 sceneBindings	: DataBindings,
									 computeDepth	: Boolean) : void
		{
			for (var parameter : String in _bindings)
			{
				meshBindings.addCallback(parameter, parameterChangedHandler);
				sceneBindings.addCallback(parameter, parameterChangedHandler);

				if (meshBindings.propertyExists(parameter))
					setParameter(parameter, meshBindings.getProperty(parameter));
				else if (sceneBindings.propertyExists(parameter))
					setParameter(parameter, sceneBindings.getProperty(parameter));
			}

			if (computeDepth)
			{
				_worldToScreen = sceneBindings.getProperty('worldToScreen') as Matrix4x4;
				_localToWorld = meshBindings.getProperty('localToWorld') as Matrix4x4;

				sceneBindings.addCallback('worldToScreen', transformChangedHandler);
				meshBindings.addCallback('localToWorld', transformChangedHandler);

				_invalidDepth = true;
			}
		}

		public function apply(context : Context3DResource, previous : DrawCall) : uint
		{
			if (!_enabled)
				return 0;

			context.setColorMask(_colorMaskR, _colorMaskG, _colorMaskB, _colorMaskA)
				   .setProgramConstantsFromVector(PROGRAM_TYPE_VERTEX, 0, _vsConstants)
				   .setProgramConstantsFromVector(PROGRAM_TYPE_FRAGMENT, 0, _fsConstants);

			var numTextures	: uint	= _fsTextures.length;
			var maxTextures	: uint	= previous ? previous._fsTextures.length : NUM_TEXTURES;
			var maxBuffers	: uint	= previous ? previous._numVertexComponents : NUM_VERTEX_BUFFERS;
			var i 			: uint 	= 0;

			// setup textures
			for (i = 0; i < numTextures; ++i)
			{
				context.setTextureAt(
					i,
					(_fsTextures[i] as ITextureResource).getNativeTexture(context)
				);
			}

			while (i < maxTextures)
				context.setTextureAt(i++, null);

			// setup buffers
			for (i = 0; i < _numVertexComponents; ++i)
			{
				context.setVertexBufferAt(
					i,
					(_vertexBuffers[i] as VertexBuffer3DResource).getVertexBuffer3D(context),
					_offsets[i],
					_formats[i]
				);
			}

			while (i < maxBuffers)
				context.setVertexBufferAt(i++, null);

			// draw triangles
			context.drawTriangles(
				_indexBuffer.getIndexBuffer3D(context),
				_firstIndex,
				_numTriangles
			);

			return _numTriangles == -1 ? _indexBuffer.numIndices / 3 : _numTriangles;
		}

		public function setParameter(name : String, value : Object) : void
		{
			if (!_enabled)
            {
                _changes[name] = value;

				return ;
            }

			var binding : IBinder = _bindings[name] as IBinder;

			if (binding != null)
				binding.set(_cpuConstants, _vsConstants, _fsConstants, _fsTextures, value);
		}

		private function parameterChangedHandler(dataBindings	: DataBindings,
												 property		: String,
												 oldValue		: Object,
												 newValue		: Object) : void
		{
			newValue !== null && setParameter(property, newValue);
		}

		private function transformChangedHandler(bindings 	: DataBindings,
												 property 	: String,
												 oldValue	: Matrix4x4,
												 newValue 	: Matrix4x4) : void
		{
			if (property == 'worldToScreen')
				_worldToScreen = newValue;
			else if (property == 'localToWorld')
				_localToWorld = newValue;

			_invalidDepth = true;
		}
	}
}

package aerys.minko.render
{
	import aerys.minko.ns.minko_render;
	import aerys.minko.render.geometry.Geometry;
	import aerys.minko.render.geometry.stream.IVertexStream;
	import aerys.minko.render.geometry.stream.StreamUsage;
	import aerys.minko.render.geometry.stream.VertexStream;
	import aerys.minko.render.geometry.stream.format.VertexComponent;
	import aerys.minko.render.geometry.stream.format.VertexFormat;
	import aerys.minko.render.resource.Context3DResource;
	import aerys.minko.render.resource.IndexBuffer3DResource;
	import aerys.minko.render.resource.Program3DResource;
	import aerys.minko.render.resource.VertexBuffer3DResource;
	import aerys.minko.render.resource.texture.ITextureResource;
	import aerys.minko.render.shader.binding.IBinder;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.enum.Blending;
	import aerys.minko.type.enum.ColorMask;
	import aerys.minko.type.enum.TriangleCulling;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	import flash.display3D.Context3DProgramType;
	import flash.utils.Dictionary;

	/**
	 * DrawCall objects contain all the shader constants and buffer settings required
	 * to perform drawing operations using the Stage3D API.
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class DrawCall
	{
		use namespace minko_render;

		private static const PROGRAM_TYPE_VERTEX	: String			= Context3DProgramType.VERTEX;
		private static const PROGRAM_TYPE_FRAGMENT	: String			= Context3DProgramType.FRAGMENT;
		private static const NUM_TEXTURES			: uint				= 8;
		private static const NUM_VERTEX_BUFFERS		: uint				= 8;

		private static const TMP_VECTOR4			: Vector4			= new Vector4();
		private static const TMP_NUMBERS			: Vector.<Number>	= new Vector.<Number>(0xffff, true);
		private static const TMP_INTS				: Vector.<int>		= new Vector.<int>(0xffff, true);

		private var _bindings			: Object							= null;

		private var _vsInputComponents	: Vector.<VertexComponent>			= null;
		private var _vsInputIndices		: Vector.<uint>						= null;
		private var _cpuConstants		: Dictionary						= null;
		private var _vsConstants		: Vector.<Number>					= null;
		private var _fsConstants		: Vector.<Number>					= null;
		private var _fsTextures			: Vector.<ITextureResource>			= new Vector.<ITextureResource>(NUM_TEXTURES, true);

		// states
		private var _indexBuffer		: IndexBuffer3DResource				= null;
		private var _firstIndex			: int								= 0;
		private var _numTriangles		: int								= -1;

		private var _vertexBuffers		: Vector.<VertexBuffer3DResource>	= new Vector.<VertexBuffer3DResource>(NUM_VERTEX_BUFFERS, true);
		private var _numVertexComponents: uint								= 0;
		private var _offsets			: Vector.<int>						= new Vector.<int>(8, true);
		private var _formats			: Vector.<String>					= new Vector.<String>(8, true);

		private var _blending			: uint								= 0;
		private var _blendingSource		: String							= null;
		private var _blendingDest		: String							= null;

		private var _triangleCulling	: uint								= 0;
		private var _triangleCullingStr	: String							= null;

		private var _colorMask			: uint								= 0;
		private var _colorMaskR			: Boolean							= true;
		private var _colorMaskG			: Boolean							= true;
		private var _colorMaskB			: Boolean							= true;
		private var _colorMaskA			: Boolean							= true;

		private var _enabled			: Boolean							= true;

		private var _depth				: Number							= 0.;
		private var _invalidDepth		: Boolean							= false;
		private var _localToWorld		: Matrix4x4							= null;
		private var _worldToScreen		: Matrix4x4							= null;

        private var _changes            : Object                           = {};

		public function get vertexComponents() : Vector.<VertexComponent>
		{
			return _vsInputComponents;
		}

		public function get blending() : uint
		{
			return _blending;
		}
		public function set blending(value : uint) : void
		{
			_blending = value;
			_blendingSource = Blending.STRINGS[int(value & 0xffff)];
			_blendingDest	= Blending.STRINGS[int(value >>> 16)]
		}

		public function get triangleCulling() : uint
		{
			return _triangleCulling;
		}
		public function set triangleCulling(value : uint) : void
		{
			_triangleCulling = value;
			_triangleCullingStr = TriangleCulling.STRINGS[value];
		}

		public function get colorMask() : uint
		{
			return _colorMask;
		}
		public function set colorMask(value : uint) : void
		{
			_colorMask = value;
			_colorMaskR = (value & ColorMask.RED) != 0;
			_colorMaskG = (value & ColorMask.GREEN) != 0;
			_colorMaskB = (value & ColorMask.BLUE) != 0;
			_colorMaskA = (value & ColorMask.ALPHA) != 0;
		}

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

            if (value)
                for (var bindingName : String in _changes)
                {
                    setParameter(bindingName, _changes[bindingName]);
                    delete _changes[bindingName];
                }
		}

		public function get depth() : Number
		{
			if (_invalidDepth && _enabled)
			{
				_invalidDepth = false;

				if (_localToWorld != null && _worldToScreen != null)
				{
					var worldSpacePosition 	: Vector4	= _localToWorld.transformVector(
						Vector4.ZERO, TMP_VECTOR4
					);
					var screenSpacePosition : Vector4 	= _worldToScreen.transformVector(
						worldSpacePosition, TMP_VECTOR4
					);

					_depth = screenSpacePosition.z / screenSpacePosition.w;
				}
			}

			return _depth;
		}

		public function configure(program		: Program3DResource,
								  geometry		: Geometry,
								  meshBindings	: DataBindings,
								  sceneBindings	: DataBindings,
								  computeDepth	: Boolean) : void
		{
//			if (_bindings != null)
//				unsetBindings(meshBindings, sceneBindings);

			_invalidDepth = computeDepth;

			setProgram(program);
			updateGeometry(geometry);
			setGeometry(geometry);
			setBindings(meshBindings, sceneBindings, computeDepth);
		}

		public function unsetBindings(meshBindings	: DataBindings,
									  sceneBindings	: DataBindings) : void
		{
            _changes = {};

			for (var parameter : String in _bindings)
			{
				meshBindings.removeCallback(parameter, parameterChangedHandler);
				sceneBindings.removeCallback(parameter, parameterChangedHandler);
			}

			if (sceneBindings.hasCallback('worldToScreen', transformChangedHandler))
				sceneBindings.removeCallback('worldToScreen', transformChangedHandler);

			if (meshBindings.hasCallback('localToWorld', transformChangedHandler))
				meshBindings.removeCallback('localToWorld', transformChangedHandler);
		}

		private function setProgram(program : Program3DResource) : void
		{
			_cpuConstants		= new Dictionary();
			_vsConstants		= program._vsConstants.concat();
			_fsConstants		= program._fsConstants.concat();
			_fsTextures			= program._fsTextures.concat();
			_vsInputComponents	= program._vertexInputComponents;
			_vsInputIndices		= program._vertexInputIndices;
			_bindings			= program._bindings;

			triangleCulling		= TriangleCulling.FRONT;
			blending			= Blending.NORMAL;
			colorMask			= ColorMask.RGBA;
		}

		 /**
		  * Ask geometry to compute additional vertex data if needed for this drawcall.
		  */
		public function updateGeometry(geometry : Geometry) : void
		{
			var vertexFormat : VertexFormat	= geometry.format;

			if (_vsInputComponents.indexOf(VertexComponent.TANGENT) >= 0
				&& !vertexFormat.hasComponent(VertexComponent.TANGENT))
			{
				geometry.computeTangentSpace();
			}
			else if (_vsInputComponents.indexOf(VertexComponent.NORMAL) >= 0
				&& !vertexFormat.hasComponent(VertexComponent.NORMAL))
			{
				geometry.computeNormals();
			}
		}

		/**
		 * Obtain a reference to each buffer and offset that apply() may possibly need.
		 *
		 */
		public function setGeometry(geometry : Geometry, frame : uint = 0) : void
		{
			_numVertexComponents = _vsInputComponents.length;
			_indexBuffer		 = geometry.indexStream.resource;
			_firstIndex			 = geometry.firstIndex;
			_numTriangles		 = geometry.numTriangles;

			for (var i : uint = 0; i < _numVertexComponents; ++i)
			{
				var component : VertexComponent	= _vsInputComponents[i];
				var index	  : uint			= _vsInputIndices[i];

				if (component)
				{
					var vertexStream	: IVertexStream = geometry.getVertexStream(index + frame);
					var stream	 		: VertexStream	= vertexStream.getStreamByComponent(component);

					if (stream == null)
					{
						throw new Error(
							'Missing vertex component: \'' + component.toString() + '\'.'
						);
					}

					_vertexBuffers[i]	= stream.resource;
					_formats[i]			= component.nativeFormatString;
					_offsets[i]			= stream.format.getOffsetForComponent(component);
				}
			}
		}

		/**
		 * @fixme There is a bug here
		 * @fixme We splitted properties between scene and mesh
		 * @fixme it should be done on the compiler also to avoid this ugly hack
		 */
		private function setBindings(meshBindings	: DataBindings,
									 sceneBindings	: DataBindings,
									 computeDepth	: Boolean) : void
		{
			for (var parameter : String in _bindings)
			{
				meshBindings.addCallback(parameter, parameterChangedHandler);
				sceneBindings.addCallback(parameter, parameterChangedHandler);

				if (meshBindings.propertyExists(parameter))
					setParameter(parameter, meshBindings.getProperty(parameter));
				else if (sceneBindings.propertyExists(parameter))
					setParameter(parameter, sceneBindings.getProperty(parameter));
			}

			if (computeDepth)
			{
				_worldToScreen = sceneBindings.getProperty('worldToScreen') as Matrix4x4;
				_localToWorld = meshBindings.getProperty('localToWorld') as Matrix4x4;

				sceneBindings.addCallback('worldToScreen', transformChangedHandler);
				meshBindings.addCallback('localToWorld', transformChangedHandler);

				_invalidDepth = true;
			}
		}

		public function apply(context : Context3DResource, previous : DrawCall) : uint
		{
			if (!_enabled)
				return 0;

			context.setColorMask(_colorMaskR, _colorMaskG, _colorMaskB, _colorMaskA)
				   .setProgramConstantsFromVector(PROGRAM_TYPE_VERTEX, 0, _vsConstants)
				   .setProgramConstantsFromVector(PROGRAM_TYPE_FRAGMENT, 0, _fsConstants);

			var numTextures	: uint	= _fsTextures.length;
			var maxTextures	: uint	= previous ? previous._fsTextures.length : NUM_TEXTURES;
			var maxBuffers	: uint	= previous ? previous._numVertexComponents : NUM_VERTEX_BUFFERS;
			var i 			: uint 	= 0;

			// setup textures
			for (i = 0; i < numTextures; ++i)
			{
				context.setTextureAt(
					i,
					(_fsTextures[i] as ITextureResource).getNativeTexture(context)
				);
			}

			while (i < maxTextures)
				context.setTextureAt(i++, null);

			// setup buffers
			for (i = 0; i < _numVertexComponents; ++i)
			{
				context.setVertexBufferAt(
					i,
					(_vertexBuffers[i] as VertexBuffer3DResource).getVertexBuffer3D(context),
					_offsets[i],
					_formats[i]
				);
			}

			while (i < maxBuffers)
				context.setVertexBufferAt(i++, null);

			// draw triangles
			context.drawTriangles(
				_indexBuffer.getIndexBuffer3D(context),
				_firstIndex,
				_numTriangles
			);

			return _numTriangles == -1 ? _indexBuffer.numIndices / 3 : _numTriangles;
		}

		public function setParameter(name : String, value : Object) : void
		{
			if (!_enabled)
            {
                _changes[name] = value;

				return ;
            }

			var binding : IBinder = _bindings[name] as IBinder;

			if (binding != null)
				binding.set(_cpuConstants, _vsConstants, _fsConstants, _fsTextures, value);
		}

		private function parameterChangedHandler(dataBindings	: DataBindings,
												 property		: String,
												 oldValue		: Object,
												 newValue		: Object) : void
		{
			newValue !== null && setParameter(property, newValue);
		}

		private function transformChangedHandler(bindings 	: DataBindings,
												 property 	: String,
												 oldValue	: Matrix4x4,
												 newValue 	: Matrix4x4) : void
		{
			if (property == 'worldToScreen')
				_worldToScreen = newValue;
			else if (property == 'localToWorld')
				_localToWorld = newValue;

			_invalidDepth = true;
		}
	}
}

fix DrawCall.enabled setter using a 'for each' instead of a 'for' to loop on the changed bindings


package battlecode.world.signals {
    import battlecode.common.AttackType;
    import battlecode.common.MapLocation;

    public class RobotInfoSignal implements Signal {
        private var robotID:uint;
        private var health:Number;

        public function RobotInfoSignal(robotID:uint, health:Number) {
            this.robotID = robotID;
            this.health = health;
        }

        public function getRobotID():uint {
            return robotID;
        }

        public function getHealth():Number {
            return health;
        }

        public function accept(handler:SignalHandler):* {
            return handler.visitRobotInfoSignal(this);
        }

    }

}

package battlecode.world.signals {
    public class RobotInfoSignal implements Signal {
        private var robotID:uint;
        private var health:Number;

        public function RobotInfoSignal(robotID:uint, health:Number) {
            this.robotID = robotID;
            this.health = health;
        }

        public function getRobotID():uint {
            return robotID;
        }

        public function getHealth():Number {
            return health;
        }

        public function accept(handler:SignalHandler):* {
            return handler.visitRobotInfoSignal(this);
        }

    }
}

remove unused imports

remove unused imports


package org.openPyro.controls
{
	import org.openPyro.core.UIControl;

	import flash.display.Loader;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.net.URLRequest;
	import flash.system.LoaderContext;

	public class Image extends UIControl
	{
		private var _sourceURL:String = "";
		private var _loader:Loader;

		public function Image() {
			super();
		}

		override protected function createChildren():void
		{
			super.createChildren();
			_loader = new Loader();
			_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
			_loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
			addChild(_loader);

			if(!_loaderContext)
			{
				_loaderContext = new LoaderContext(true);
			}

			if(_autoLoad && (_sourceURL != "")){
				_loader.load(new URLRequest(_sourceURL), _loaderContext);
			}
		}

		protected var _autoLoad:Boolean = true;
		public function set autoLoad(b:Boolean):void
		{
			_autoLoad = b;
		}

		public function get autoLoad():Boolean
		{
			return _autoLoad;
		}

		public function set source(url:String):void
		{
			if(url == _sourceURL) return;
			_sourceURL = url;
			if(_loader && _autoLoad){
				_loader.load(new URLRequest(url), _loaderContext);
			}
		}

		private var _loaderContext:LoaderContext

		/**
		 * The LoaderContext that is used when loading an
		 * image.
		 */
		public function set loaderContext(context:LoaderContext):void
		{
			_loaderContext = context;
		}

		/**
		 * @private
		 */
		public function get loaderContext():LoaderContext
		{
			return _loaderContext;
		}

		/**
		 * Returns the raw loader being used to load the image
		 */
		public function get loader():Loader
		{
			return _loader;
		}

		protected var _originalContentWidth:Number = NaN;
		protected var _originalContentHeight:Number = NaN;

		protected function onLoadComplete(event:Event):void
		{
			_originalContentWidth = _loader.content.width;
			_originalContentHeight = _loader.content.height;
			dispatchEvent(new Event(Event.COMPLETE));
			forceInvalidateDisplayList = true;
			invalidateSize();
			invalidateDisplayList()
		}
		override protected function doChildBasedValidation():void{
			if(!_loader ||  !_loader.content) return;
			if(isNaN(this._explicitWidth) && isNaN(this._percentWidth) && isNaN(_percentUnusedWidth)){
				measuredWidth = _originalContentWidth + _padding.left + _padding.right;
			}
			if(isNaN(this._explicitHeight) && isNaN(this._percentHeight) && isNaN(_percentUnusedHeight))
			{
				measuredHeight = _originalContentWidth + _padding.top + _padding.bottom;
			}
		}

		public function get contentWidth():Number{
			return _loader.content.width;
		}

		public function get contentHeight():Number{
			return _loader.content.height;
		}

		public function get originalContentWidth():Number{
			return _originalContentWidth
		}

		public function get originalContentHeight():Number{
			return _originalContentHeight;
		}

		protected function onIOError(event:IOErrorEvent):void
		{
			//todo: Put broken thumb skin here//
		}

		protected var _scaleToFit:Boolean = true;
		public function get scaleToFit():Boolean
		{
			return _scaleToFit;
		}

		public function set scaleToFit(value:Boolean):void
		{
			_scaleToFit = value;
			if(_scaleToFit && _loader && _loader.content)
			{
				scaleImageContent()
			}
		}

		protected var _maintainAspectRatio:Boolean = true;
		public function set maintainAspectRatio(value:Boolean):void
		{
			_maintainAspectRatio = value;
		}
		public function get maintainAspectRatio():Boolean
		{
			return _maintainAspectRatio;
		}

		protected function scaleImageContent():void
		{

			var scaleX:Number;
			var scaleY:Number;
			scaleX = width / _originalContentWidth;
			scaleY = height / _originalContentHeight;

			if(_maintainAspectRatio)
			{
				var scale:Number = Math.min(scaleX, scaleY);
				_loader.content.width = _originalContentWidth*scale;
				_loader.content.height = _originalContentHeight*scale;
			}
			else
			{
				_loader.content.width = _originalContentWidth*scaleX;
				_loader.content.height = _originalContentHeight*scaleY;
			}
		}

		override public function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
		{
			super.updateDisplayList(unscaledWidth, unscaledHeight);
			if(_loader && _loader.content && _scaleToFit){
				scaleImageContent()
			}
			_loader.x = _padding.left
			_loader.y = _padding.top;
		}

	}
}

package org.openPyro.controls
{
	import org.openPyro.core.UIControl;

	import flash.display.Loader;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.net.URLRequest;
	import flash.system.LoaderContext;

	public class Image extends UIControl
	{
		private var _sourceURL:String = "";
		private var _loader:Loader;

		public function Image() {
			super();
		}

		override protected function createChildren():void
		{
			super.createChildren();
			_loader = new Loader();
			_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
			_loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
			addChild(_loader);
			_loader.visible = false;
			if(!_loaderContext)
			{
				_loaderContext = new LoaderContext(true);
			}

			if(_autoLoad && (_sourceURL != "")){
				_loader.load(new URLRequest(_sourceURL), _loaderContext);
			}
		}

		protected var _autoLoad:Boolean = true;
		public function set autoLoad(b:Boolean):void
		{
			_autoLoad = b;
		}

		public function get autoLoad():Boolean
		{
			return _autoLoad;
		}

		public function set source(url:String):void
		{
			if(url == _sourceURL) return;
			_sourceURL = url;
			if(_loader && _autoLoad){
				_loader.load(new URLRequest(url), _loaderContext);
			}
		}

		private var _loaderContext:LoaderContext

		/**
		 * The LoaderContext that is used when loading an
		 * image.
		 */
		public function set loaderContext(context:LoaderContext):void
		{
			_loaderContext = context;
		}

		/**
		 * @private
		 */
		public function get loaderContext():LoaderContext
		{
			return _loaderContext;
		}

		/**
		 * Returns the raw loader being used to load the image
		 */
		public function get loader():Loader
		{
			return _loader;
		}

		protected var _originalContentWidth:Number = NaN;
		protected var _originalContentHeight:Number = NaN;

		protected function onLoadComplete(event:Event):void
		{
			_originalContentWidth = _loader.content.width;
			_originalContentHeight = _loader.content.height;
			dispatchEvent(new Event(Event.COMPLETE));
			forceInvalidateDisplayList = true;
			invalidateSize();
			invalidateDisplayList()
		}
		override protected function doChildBasedValidation():void{
			if(!_loader ||  !_loader.content) return;
			if(isNaN(this._explicitWidth) && isNaN(this._percentWidth) && isNaN(_percentUnusedWidth)){
				measuredWidth = _originalContentWidth + _padding.left + _padding.right;
			}
			if(isNaN(this._explicitHeight) && isNaN(this._percentHeight) && isNaN(_percentUnusedHeight))
			{
				measuredHeight = _originalContentWidth + _padding.top + _padding.bottom;
			}
		}

		public function get contentWidth():Number{
			return _loader.content.width;
		}

		public function get contentHeight():Number{
			return _loader.content.height;
		}

		public function get originalContentWidth():Number{
			return _originalContentWidth
		}

		public function get originalContentHeight():Number{
			return _originalContentHeight;
		}

		protected function onIOError(event:IOErrorEvent):void
		{
			//todo: Put broken thumb skin here//
		}

		protected var _scaleToFit:Boolean = true;
		public function get scaleToFit():Boolean
		{
			return _scaleToFit;
		}

		public function set scaleToFit(value:Boolean):void
		{
			_scaleToFit = value;
			if(_scaleToFit && _loader && _loader.content)
			{
				scaleImageContent()
			}
		}

		protected var _maintainAspectRatio:Boolean = true;
		public function set maintainAspectRatio(value:Boolean):void
		{
			_maintainAspectRatio = value;
		}
		public function get maintainAspectRatio():Boolean
		{
			return _maintainAspectRatio;
		}

		protected function scaleImageContent():void
		{

			var scaleX:Number;
			var scaleY:Number;
			scaleX = width / _originalContentWidth;
			scaleY = height / _originalContentHeight;

			if(_maintainAspectRatio)
			{
				var scale:Number = Math.min(scaleX, scaleY);
				_loader.content.width = _originalContentWidth*scale;
				_loader.content.height = _originalContentHeight*scale;
			}
			else
			{
				_loader.content.width = _originalContentWidth*scaleX;
				_loader.content.height = _originalContentHeight*scaleY;
			}
		}

		override public function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
		{
			super.updateDisplayList(unscaledWidth, unscaledHeight);
			if(_loader && _loader.content){
				_loader.visible = true;
				if(_scaleToFit){
					scaleImageContent();
				}
			}
			_loader.x = _padding.left
			_loader.y = _padding.top;
		}

	}
}

Fix to Image class so loader remains invisible till the content has been resized.


////////////////////////////////////////////////////////////////////////////////
// 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.controllers.supportClasses
{

import com.esri.builder.model.Model;
import com.esri.builder.model.WidgetType;
import com.esri.builder.model.WidgetTypeRegistryModel;
import com.esri.builder.supportClasses.LogUtil;
import com.esri.builder.views.BuilderAlert;

import flash.events.Event;
import flash.events.EventDispatcher;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.system.ApplicationDomain;
import flash.utils.ByteArray;

import modules.IBuilderModule;
import modules.supportClasses.CustomXMLModule;

import mx.core.FlexGlobals;
import mx.events.ModuleEvent;
import mx.logging.ILogger;
import mx.logging.Log;
import mx.modules.IModuleInfo;
import mx.modules.ModuleManager;
import mx.resources.ResourceManager;

public class WidgetTypeLoader extends EventDispatcher
{
    private static const LOG:ILogger = LogUtil.createLogger(WidgetTypeLoader);

    private var _moduleInfoArr:Array = [];
    private var _widgetTypeArr:Array = [];

    public function loadWidgetTypes():void
    {
        if (Log.isInfo())
        {
            LOG.info('Loading modules...');
        }

        const modulesDirectory:File = WellKnownDirectories.getInstance().bundledModules;
        loadCustomWidgetTypes(WellKnownDirectories.getInstance().customModules);

        // Find all swf files under the modules folder.
        const swfList:Array = [];
        if (modulesDirectory.isDirectory)
        {
            getSWFList(modulesDirectory, swfList);
        }
        // Load the found modules.
        swfList.forEach(function(file:File, index:int, source:Array):void
        {
            if (Log.isDebug())
            {
                LOG.debug('loading module {0}', file.url);
            }

            var fileBytes:ByteArray = new ByteArray();
            var fileStream:FileStream = new FileStream();
            fileStream.open(file, FileMode.READ);
            fileStream.readBytes(fileBytes);
            fileStream.close();

            const moduleInfo:IModuleInfo = ModuleManager.getModule(file.url);
            _moduleInfoArr.push(moduleInfo);
            moduleInfo.addEventListener(ModuleEvent.READY, moduleInfo_readyHandler);
            moduleInfo.addEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);
            moduleInfo.load(ApplicationDomain.currentDomain, null, fileBytes, FlexGlobals.topLevelApplication.moduleFactory);
        });

        checkIfNoMoreModuleInfosLeft();
    }

    private function loadCustomWidgetTypes(modulesDirectory:File):void
    {
        var moduleDirectoryContents:Array = modulesDirectory.getDirectoryListing();
        for each (var fileOrFolder:File in moduleDirectoryContents)
        {
            loadCustomWidgetTypeConfig(fileOrFolder);
        }
    }

    public function loadCustomWidgetTypeConfig(configFile:File):void
    {
        const customModuleFileName:RegExp = /^.*Module\.xml$/;
        if (!configFile.isDirectory && customModuleFileName.test(configFile.name))
        {
            var customModule:IBuilderModule = createCustomModuleFromConfig(configFile);
            if (customModule)
            {
                WidgetTypeRegistryModel.getInstance().widgetTypeRegistry.addWidgetType(new WidgetType(customModule));
            }
        }
    }

    private function createCustomModuleFromConfig(configFile:File):IBuilderModule
    {
        var fileStream:FileStream = new FileStream();
        var customModule:CustomXMLModule;
        try
        {
            fileStream.open(configFile, FileMode.READ);
            const configXML:XML = XML(fileStream.readUTFBytes(fileStream.bytesAvailable));
            customModule = parseCustomModule(configXML);
        }
        catch (e:Error)
        {
            if (Log.isWarn())
            {
                LOG.warn('Error creating custom module {0}', configFile.nativePath);
            }
        }
        finally
        {
            fileStream.close();
        }

        return customModule;
    }

    private function parseCustomModule(configXML:XML):CustomXMLModule
    {
        var customModule:CustomXMLModule = new CustomXMLModule();
        customModule.widgetName = configXML.name;
        customModule.isOpenByDefault = (configXML.openbydefault == 'true');

        var widgetLabel:String = configXML.label[0];
        var widgetDescription:String = configXML.description[0];
        var widgetHelpURL:String = configXML.helpurl[0];
        var widgetConfiguration:String = configXML.configuration[0];
        var widgetVersion:String = configXML.widgetversion[0];

        customModule.widgetIconLocation = createWidgetIconPath(configXML.icon[0], customModule.widgetName);
        customModule.widgetLabel = widgetLabel ? widgetLabel : customModule.widgetName;
        customModule.widgetVersion = widgetVersion;
        customModule.widgetDescription = widgetDescription ? widgetDescription : "";
        customModule.widgetHelpURL = widgetHelpURL ? widgetHelpURL : "";
        customModule.configXML = widgetConfiguration ? widgetConfiguration : "<configuration></configuration>";

        return customModule;
    }

    private function createWidgetIconPath(iconPath:String, widgetName:String):String
    {
        var widgetIconPath:String;

        if (iconPath)
        {
            widgetIconPath = "widgets/" + widgetName + "/" + iconPath;
        }
        else
        {
            widgetIconPath = "assets/images/i_widget.png";
        }

        return widgetIconPath;
    }

    /**
     * This will go through all sub folders in 'modules' looking for .swf files.
     */
    private function getSWFList(parentFile:File, arr:Array):void
    {
        const moduleFileName:RegExp = /^.*Module\.swf$/;
        const list:Array = parentFile.getDirectoryListing();
        list.forEach(function(file:File, index:int, source:Array):void
        {
            if (file.isDirectory)
            {
                getSWFList(file, arr);
            }
            else if (moduleFileName.test(file.name))
            {
                arr.push(file);
            }
        }
        );
    }

    private function moduleInfo_readyHandler(event:ModuleEvent):void
    {
        var moduleInfo:IModuleInfo = event.currentTarget as IModuleInfo;
        moduleInfo.removeEventListener(ModuleEvent.READY, moduleInfo_readyHandler);
        moduleInfo.removeEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);

        const builderModule:IBuilderModule = event.module.factory.create() as IBuilderModule;
        if (builderModule)
        {
            const widgetType:WidgetType = new WidgetType(builderModule);
            _widgetTypeArr.push(widgetType);

            // Resolve the object
            //            FlexGlobals.topLevelApplication.registry.resolve(builderModule, ApplicationDomain.currentDomain);
            if (Log.isDebug())
            {
                LOG.debug('Module {0} is resolved', widgetType.name);
            }

            removeModuleInfo(event.module);
        }
    }

    private function removeModuleInfo(moduleInfo:IModuleInfo):void
    {
        const index:int = _moduleInfoArr.indexOf(moduleInfo);
        if (index > -1)
        {
            _moduleInfoArr.splice(index, 1);
            checkIfNoMoreModuleInfosLeft();
        }
    }

    private function checkIfNoMoreModuleInfosLeft():void
    {
        if (_moduleInfoArr.length === 0)
        {
            sortAndAssignWidgetTypes();
        }
    }

    private function sortAndAssignWidgetTypes():void
    {
        if (Log.isInfo())
        {
            LOG.info('All modules resolved');
        }

        _widgetTypeArr.sort(compareWidgetTypes);

        var widgetTypes:Array = _widgetTypeArr.filter(widgetTypeFilter);
        for each (var widgetType:WidgetType in widgetTypes)
        {
            WidgetTypeRegistryModel.getInstance().widgetTypeRegistry.addWidgetType(widgetType);
        }

        var layoutWidgetTypes:Array = _widgetTypeArr.filter(layoutWidgetTypeFilter);
        for each (var layoutWidgetType:WidgetType in layoutWidgetTypes)
        {
            WidgetTypeRegistryModel.getInstance().layoutWidgetTypeRegistry.addWidgetType(layoutWidgetType);
        }

        function widgetTypeFilter(item:WidgetType, index:int, source:Array):Boolean
        {
            return item.isManaged;
        }
        function layoutWidgetTypeFilter(item:WidgetType, index:int, source:Array):Boolean
        {
            return !item.isManaged;
        }

        dispatchEvent(new WidgetTypeLoaderEvent(WidgetTypeLoaderEvent.LOAD_TYPES_COMPLETE));
    }

    private function compareWidgetTypes(lhs:WidgetType, rhs:WidgetType):int
    {
        const lhsLabel:String = lhs.label.toLowerCase();
        const rhsLabel:String = rhs.label.toLowerCase();
        if (lhsLabel < rhsLabel)
        {
            return -1;
        }
        if (lhsLabel > rhsLabel)
        {
            return 1;
        }
        return 0;
    }

    private function moduleInfo_errorHandler(event:ModuleEvent):void
    {
        if (Log.isWarn())
        {
            LOG.warn('Module error: {0}', event.errorText);
        }

        var moduleInfo:IModuleInfo = event.currentTarget as IModuleInfo;
        moduleInfo.removeEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);
        moduleInfo.removeEventListener(ModuleEvent.READY, moduleInfo_readyHandler);

        removeModuleInfo(event.module);
        Model.instance.status = event.errorText;
        BuilderAlert.show(event.errorText,
                          ResourceManager.getInstance().getString('BuilderStrings', 'error'));
    }
}
}

////////////////////////////////////////////////////////////////////////////////
// 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.controllers.supportClasses
{

import com.esri.builder.model.Model;
import com.esri.builder.model.WidgetType;
import com.esri.builder.model.WidgetTypeRegistryModel;
import com.esri.builder.supportClasses.LogUtil;
import com.esri.builder.views.BuilderAlert;

import flash.events.EventDispatcher;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.system.ApplicationDomain;
import flash.utils.ByteArray;

import modules.IBuilderModule;
import modules.supportClasses.CustomXMLModule;

import mx.core.FlexGlobals;
import mx.events.ModuleEvent;
import mx.logging.ILogger;
import mx.logging.Log;
import mx.modules.IModuleInfo;
import mx.modules.ModuleManager;
import mx.resources.ResourceManager;

public class WidgetTypeLoader extends EventDispatcher
{
    private static const LOG:ILogger = LogUtil.createLogger(WidgetTypeLoader);

    private var _moduleInfoArr:Array = [];
    private var _widgetTypeArr:Array = [];

    public function loadWidgetTypes():void
    {
        if (Log.isInfo())
        {
            LOG.info('Loading modules...');
        }

        const modulesDirectory:File = WellKnownDirectories.getInstance().bundledModules;
        loadCustomWidgetTypes(WellKnownDirectories.getInstance().customModules);

        const swfList:Array = getModuleSWFs(modulesDirectory);

        // Load the found modules.
        swfList.forEach(function(file:File, index:int, source:Array):void
        {
            if (Log.isDebug())
            {
                LOG.debug('loading module {0}', file.url);
            }

            var fileBytes:ByteArray = new ByteArray();
            var fileStream:FileStream = new FileStream();
            fileStream.open(file, FileMode.READ);
            fileStream.readBytes(fileBytes);
            fileStream.close();

            const moduleInfo:IModuleInfo = ModuleManager.getModule(file.url);
            _moduleInfoArr.push(moduleInfo);
            moduleInfo.addEventListener(ModuleEvent.READY, moduleInfo_readyHandler);
            moduleInfo.addEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);
            moduleInfo.load(ApplicationDomain.currentDomain, null, fileBytes, FlexGlobals.topLevelApplication.moduleFactory);
        });

        checkIfNoMoreModuleInfosLeft();
    }

    private function loadCustomWidgetTypes(modulesDirectory:File):void
    {
        var moduleDirectoryContents:Array = modulesDirectory.getDirectoryListing();
        for each (var fileOrFolder:File in moduleDirectoryContents)
        {
            loadCustomWidgetTypeConfig(fileOrFolder);
        }
    }

    public function loadCustomWidgetTypeConfig(configFile:File):void
    {
        const customModuleFileName:RegExp = /^.*Module\.xml$/;
        if (!configFile.isDirectory && customModuleFileName.test(configFile.name))
        {
            var customModule:IBuilderModule = createCustomModuleFromConfig(configFile);
            if (customModule)
            {
                WidgetTypeRegistryModel.getInstance().widgetTypeRegistry.addWidgetType(new WidgetType(customModule));
            }
        }
    }

    private function createCustomModuleFromConfig(configFile:File):IBuilderModule
    {
        var fileStream:FileStream = new FileStream();
        var customModule:CustomXMLModule;
        try
        {
            fileStream.open(configFile, FileMode.READ);
            const configXML:XML = XML(fileStream.readUTFBytes(fileStream.bytesAvailable));
            customModule = parseCustomModule(configXML);
        }
        catch (e:Error)
        {
            if (Log.isWarn())
            {
                LOG.warn('Error creating custom module {0}', configFile.nativePath);
            }
        }
        finally
        {
            fileStream.close();
        }

        return customModule;
    }

    private function parseCustomModule(configXML:XML):CustomXMLModule
    {
        var customModule:CustomXMLModule = new CustomXMLModule();
        customModule.widgetName = configXML.name;
        customModule.isOpenByDefault = (configXML.openbydefault == 'true');

        var widgetLabel:String = configXML.label[0];
        var widgetDescription:String = configXML.description[0];
        var widgetHelpURL:String = configXML.helpurl[0];
        var widgetConfiguration:String = configXML.configuration[0];
        var widgetVersion:String = configXML.widgetversion[0];

        customModule.widgetIconLocation = createWidgetIconPath(configXML.icon[0], customModule.widgetName);
        customModule.widgetLabel = widgetLabel ? widgetLabel : customModule.widgetName;
        customModule.widgetVersion = widgetVersion;
        customModule.widgetDescription = widgetDescription ? widgetDescription : "";
        customModule.widgetHelpURL = widgetHelpURL ? widgetHelpURL : "";
        customModule.configXML = widgetConfiguration ? widgetConfiguration : "<configuration></configuration>";

        return customModule;
    }

    private function createWidgetIconPath(iconPath:String, widgetName:String):String
    {
        var widgetIconPath:String;

        if (iconPath)
        {
            widgetIconPath = "widgets/" + widgetName + "/" + iconPath;
        }
        else
        {
            widgetIconPath = "assets/images/i_widget.png";
        }

        return widgetIconPath;
    }

    /* Finds module SWF files at the parent-directory level */
    private function getModuleSWFs(directory:File):Array
    {
        var swfFiles:Array = [];

        if (directory.isDirectory)
        {
            const moduleFileName:RegExp = /^.*Module\.swf$/;
            const files:Array = directory.getDirectoryListing();

            for each (var file:File in files)
            {
                if (!file.isDirectory && moduleFileName.test(file.name))
                {
                    swfFiles.push(file);
                }
            }
        }

        return swfFiles;
    }

    private function moduleInfo_readyHandler(event:ModuleEvent):void
    {
        var moduleInfo:IModuleInfo = event.currentTarget as IModuleInfo;
        moduleInfo.removeEventListener(ModuleEvent.READY, moduleInfo_readyHandler);
        moduleInfo.removeEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);

        const builderModule:IBuilderModule = event.module.factory.create() as IBuilderModule;
        if (builderModule)
        {
            const widgetType:WidgetType = new WidgetType(builderModule);
            _widgetTypeArr.push(widgetType);

            // Resolve the object
            //            FlexGlobals.topLevelApplication.registry.resolve(builderModule, ApplicationDomain.currentDomain);
            if (Log.isDebug())
            {
                LOG.debug('Module {0} is resolved', widgetType.name);
            }

            removeModuleInfo(event.module);
        }
    }

    private function removeModuleInfo(moduleInfo:IModuleInfo):void
    {
        const index:int = _moduleInfoArr.indexOf(moduleInfo);
        if (index > -1)
        {
            _moduleInfoArr.splice(index, 1);
            checkIfNoMoreModuleInfosLeft();
        }
    }

    private function checkIfNoMoreModuleInfosLeft():void
    {
        if (_moduleInfoArr.length === 0)
        {
            sortAndAssignWidgetTypes();
        }
    }

    private function sortAndAssignWidgetTypes():void
    {
        if (Log.isInfo())
        {
            LOG.info('All modules resolved');
        }

        _widgetTypeArr.sort(compareWidgetTypes);

        var widgetTypes:Array = _widgetTypeArr.filter(widgetTypeFilter);
        for each (var widgetType:WidgetType in widgetTypes)
        {
            WidgetTypeRegistryModel.getInstance().widgetTypeRegistry.addWidgetType(widgetType);
        }

        var layoutWidgetTypes:Array = _widgetTypeArr.filter(layoutWidgetTypeFilter);
        for each (var layoutWidgetType:WidgetType in layoutWidgetTypes)
        {
            WidgetTypeRegistryModel.getInstance().layoutWidgetTypeRegistry.addWidgetType(layoutWidgetType);
        }

        function widgetTypeFilter(item:WidgetType, index:int, source:Array):Boolean
        {
            return item.isManaged;
        }
        function layoutWidgetTypeFilter(item:WidgetType, index:int, source:Array):Boolean
        {
            return !item.isManaged;
        }

        dispatchEvent(new WidgetTypeLoaderEvent(WidgetTypeLoaderEvent.LOAD_TYPES_COMPLETE));
    }

    private function compareWidgetTypes(lhs:WidgetType, rhs:WidgetType):int
    {
        const lhsLabel:String = lhs.label.toLowerCase();
        const rhsLabel:String = rhs.label.toLowerCase();
        if (lhsLabel < rhsLabel)
        {
            return -1;
        }
        if (lhsLabel > rhsLabel)
        {
            return 1;
        }
        return 0;
    }

    private function moduleInfo_errorHandler(event:ModuleEvent):void
    {
        if (Log.isWarn())
        {
            LOG.warn('Module error: {0}', event.errorText);
        }

        var moduleInfo:IModuleInfo = event.currentTarget as IModuleInfo;
        moduleInfo.removeEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);
        moduleInfo.removeEventListener(ModuleEvent.READY, moduleInfo_readyHandler);

        removeModuleInfo(event.module);
        Model.instance.status = event.errorText;
        BuilderAlert.show(event.errorText,
                          ResourceManager.getInstance().getString('BuilderStrings', 'error'));
    }
}
}

Simplify logic for finding module SWFs.

Simplify logic for finding module SWFs.

* Expects widget SWFs to be at the parent-directory level.


package
{
	import com.adobe.audio.format.WAVWriter;
	import flash.events.TimerEvent;
	import flash.events.Event;
	import flash.events.ErrorEvent;
	import flash.events.SampleDataEvent;
	import flash.external.ExternalInterface;
	import flash.media.Microphone;
	import flash.media.Sound;
	import flash.media.SoundChannel;
	import flash.system.Capabilities;
	import flash.utils.ByteArray;
	import flash.utils.getTimer;
	import flash.utils.Timer;
	import flash.system.Security;
	import flash.system.SecurityPanel;
	import flash.events.StatusEvent;
    import flash.utils.getQualifiedClassName;

	import mx.collections.ArrayCollection;

	import ru.inspirit.net.MultipartURLLoader;

	public class Recorder
	{
		public function Recorder(logger)
		{
			this.logger = logger;
		}

		private var logger;
		public function addExternalInterfaceCallbacks():void {
			ExternalInterface.addCallback("record", 		this.record);
			ExternalInterface.addCallback("_stop",  		this.stop);
			ExternalInterface.addCallback("_play",          this.play);
			ExternalInterface.addCallback("upload",         this.upload);
			ExternalInterface.addCallback("audioData",      this.audioData);
			ExternalInterface.addCallback("showFlash",      this.showFlash);
			ExternalInterface.addCallback("recordingDuration",     this.recordingDuration);
			triggerEvent("initialized", {});
			logger.log("Recorder initialized");
		}

		protected var isRecording:Boolean = false;
		protected var isPlaying:Boolean = false;
		protected var microphoneWasMuted:Boolean;
		protected var playingProgressTimer:Timer;
		protected var microphone:Microphone;
		protected var buffer:ByteArray;
		protected var sound:Sound;
		protected var channel:SoundChannel;
		protected var recordingStartTime = 0;
		protected static var sampleRate = 44.1;

		protected function record():void
		{
			if(!microphone){
				setupMicrophone();
			}

			microphoneWasMuted = microphone.muted;
			if(microphoneWasMuted){
				logger.log('showFlashRequired');
				triggerEvent('showFlash','');
			}else{
				notifyRecordingStarted();
			}

			buffer = new ByteArray();
			microphone.addEventListener(SampleDataEvent.SAMPLE_DATA, recordSampleDataHandler);
		}

		protected function recordStop():int
		{
			logger.log('stopRecording');
			isRecording = false;
			triggerEvent('recordingStop', {duration: recordingDuration()});
			microphone.removeEventListener(SampleDataEvent.SAMPLE_DATA, recordSampleDataHandler);
			return recordingDuration();
		}

		protected function play():void
		{
			logger.log('startPlaying');
			isPlaying = true;
			triggerEvent('playingStart', {});
			buffer.position = 0;
			sound = new Sound();
			sound.addEventListener(SampleDataEvent.SAMPLE_DATA, playSampleDataHandler);

			channel = sound.play();
			channel.addEventListener(Event.SOUND_COMPLETE, function(){
				playStop();
			});

			if(playingProgressTimer){
				playingProgressTimer.reset();
			}
			playingProgressTimer = new Timer(250);
			playingProgressTimer.addEventListener(TimerEvent.TIMER, function playingProgressTimerHandler(event:TimerEvent){
				triggerEvent('playingProgress', int(channel.position));
			});
			playingProgressTimer.start();
		}

		protected function stop():int
		{
			playStop();
			return recordStop();
		}

		protected function playStop():void
		{
			logger.log('stopPlaying');
			if(channel){
				channel.stop();
				playingProgressTimer.reset();

				triggerEvent('playingStop', {});
				isPlaying = false;
			}
		}

		/* Networking functions */

		protected function upload(uri:String, audioParam:String, parameters): void
		{
			logger.log("upload");
			buffer.position = 0;
			var wav:ByteArray = prepareWav();
			var ml:MultipartURLLoader = new MultipartURLLoader();
			ml.addEventListener(Event.COMPLETE, onReady);
			function onReady(e:Event):void
			{
				triggerEvent('uploadSuccess', externalInterfaceEncode(e.target.loader.data));
				logger.log('uploading done');
			}

			if(getQualifiedClassName(parameters.constructor) == "Array"){
				for(var i=0; i<parameters.length; i++){
					ml.addVariable(parameters[i][0], parameters[i][1]);
				}
			}else{
				for(var k in parameters){
					ml.addVariable(k, parameters[k]);
				}
			}

			ml.addFile(wav, 'audio.wav', audioParam);
			ml.load(uri, false);

		}

		private function externalInterfaceEncode(data:String){
			return data.split("%").join("%25").split("\\").join("%5c").split("\"").join("%22").split("&").join("%26");
		}

		protected function audioData():String
		{
			var ret:String="";
			buffer.position = 0;
			while (buffer.bytesAvailable > 0)
			{
				ret += buffer.readFloat().toString() + ";";
			}
			return ret;
		}

		protected function showFlash():void
		{
			Security.showSettings(SecurityPanel.PRIVACY);
			triggerEvent('showFlash','');
		}

		/* Recording Helper */
		protected function setupMicrophone():void
		{
			logger.log('setupMicrophone');
			microphone = Microphone.getMicrophone();
			microphone.setSilenceLevel(0);
			microphone.rate = sampleRate;
			microphone.gain = 50;
			microphone.addEventListener(StatusEvent.STATUS, function statusHandler(e:Event) {
				logger.log('Microphone Status Change');
				if(microphone.muted){
					triggerEvent('recordingCancel','');
				}else{
					if(!isRecording){
						notifyRecordingStarted();
					}
				}
			});

			logger.log('setupMicrophone done: ' + microphone.name + ' ' + microphone.muted);
		}

		protected function notifyRecordingStarted():void
		{
			if(microphoneWasMuted){
				microphoneWasMuted = false;
				triggerEvent('hideFlash','');
			}
			recordingStartTime = getTimer();
			triggerEvent('recordingStart', {});
			logger.log('startRecording');
			isRecording = true;
		}

		/* Sample related */

		protected function prepareWav():ByteArray
		{
			var wavData:ByteArray = new ByteArray();
			var wavWriter:WAVWriter = new WAVWriter();
			buffer.position = 0;
			wavWriter.numOfChannels = 1; // set the inital properties of the Wave Writer
			wavWriter.sampleBitRate = 16;
			wavWriter.samplingRate = sampleRate * 1000;
			wavWriter.processSamples(wavData, buffer, sampleRate * 1000, 1);
			return wavData;
		}

		protected function recordingDuration():int
		{
			var latency = 650;
			var duration = int(getTimer() - recordingStartTime - latency);
			return Math.max(duration, 0);
		}

		protected function recordSampleDataHandler(event:SampleDataEvent):void
		{
			while(event.data.bytesAvailable)
			{
				var sample:Number = event.data.readFloat();

				buffer.writeFloat(sample);
				if(buffer.length % 40000 == 0){
					triggerEvent('recordingProgress', recordingDuration(), 	microphone.activityLevel);
				}
			}
		}

		protected function playSampleDataHandler(event:SampleDataEvent):void
		{
			for (var i:int = 0; i < 8192 && buffer.bytesAvailable; i++)
			{
				var sample:Number = buffer.readFloat();
				event.data.writeFloat(sample);
				event.data.writeFloat(sample);
			}
		}

		/* ExternalInterface Communication */

		protected function triggerEvent(eventName:String, arg0, arg1 = null):void
		{
			ExternalInterface.call("Recorder.triggerEvent", eventName, arg0, arg1);
		}
	}
}

package
{
	import com.adobe.audio.format.WAVWriter;
	import flash.events.TimerEvent;
	import flash.events.Event;
	import flash.events.ErrorEvent;
	import flash.events.SampleDataEvent;
	import flash.external.ExternalInterface;
	import flash.media.Microphone;
	import flash.media.Sound;
	import flash.media.SoundChannel;
	import flash.system.Capabilities;
	import flash.utils.ByteArray;
	import flash.utils.getTimer;
	import flash.utils.Timer;
	import flash.system.Security;
	import flash.system.SecurityPanel;
	import flash.events.StatusEvent;
    import flash.utils.getQualifiedClassName;

	import mx.collections.ArrayCollection;

	import ru.inspirit.net.MultipartURLLoader;

	public class Recorder
	{
		public function Recorder(logger)
		{
			this.logger = logger;
		}

		private var logger;
		public function addExternalInterfaceCallbacks():void {
			ExternalInterface.addCallback("record", 		this.record);
			ExternalInterface.addCallback("_stop",  		this.stop);
			ExternalInterface.addCallback("_play",          this.play);
			ExternalInterface.addCallback("upload",         this.upload);
			ExternalInterface.addCallback("audioData",      this.audioData);
			ExternalInterface.addCallback("showFlash",      this.showFlash);
			ExternalInterface.addCallback("recordingDuration",     this.recordingDuration);
			triggerEvent("initialized", {});
			logger.log("Recorder initialized");
		}

		protected var isRecording:Boolean = false;
		protected var isPlaying:Boolean = false;
		protected var microphoneWasMuted:Boolean;
		protected var playingProgressTimer:Timer;
		protected var microphone:Microphone;
		protected var buffer:ByteArray;
		protected var sound:Sound;
		protected var channel:SoundChannel;
		protected var recordingStartTime = 0;
		protected static var sampleRate = 44.1;

		protected function record():void
		{
			if(!microphone){
				setupMicrophone();
			}

			microphoneWasMuted = microphone.muted;
			if(microphoneWasMuted){
				logger.log('showFlashRequired');
				triggerEvent('showFlash','');
			}else{
				notifyRecordingStarted();
			}

			buffer = new ByteArray();
			microphone.addEventListener(SampleDataEvent.SAMPLE_DATA, recordSampleDataHandler);
		}

		protected function recordStop():int
		{
			logger.log('stopRecording');
			isRecording = false;
			triggerEvent('recordingStop', {duration: recordingDuration()});
			microphone.removeEventListener(SampleDataEvent.SAMPLE_DATA, recordSampleDataHandler);
			return recordingDuration();
		}

		protected function play():void
		{
			logger.log('startPlaying');
			isPlaying = true;
			triggerEvent('playingStart', {});
			buffer.position = 0;
			sound = new Sound();
			sound.addEventListener(SampleDataEvent.SAMPLE_DATA, playSampleDataHandler);

			channel = sound.play();
			channel.addEventListener(Event.SOUND_COMPLETE, function(){
				playStop();
			});

			if(playingProgressTimer){
				playingProgressTimer.reset();
			}
			playingProgressTimer = new Timer(250);
			playingProgressTimer.addEventListener(TimerEvent.TIMER, function playingProgressTimerHandler(event:TimerEvent){
				triggerEvent('playingProgress', int(channel.position));
			});
			playingProgressTimer.start();
		}

		protected function stop():int
		{
			playStop();
			return recordStop();
		}

		protected function playStop():void
		{
			logger.log('stopPlaying');
			if(channel){
				channel.stop();
				playingProgressTimer.reset();

				triggerEvent('playingStop', {});
				isPlaying = false;
			}
		}

		/* Networking functions */

		protected function upload(uri:String, audioParam:String, parameters): void
		{
			logger.log("upload");
			buffer.position = 0;
			var wav:ByteArray = prepareWav();
			var ml:MultipartURLLoader = new MultipartURLLoader();
			ml.addEventListener(Event.COMPLETE, onReady);
			function onReady(e:Event):void
			{
				triggerEvent('uploadSuccess', externalInterfaceEncode(e.target.loader.data));
				logger.log('uploading done');
			}

			if(getQualifiedClassName(parameters.constructor) == "Array"){
				for(var i=0; i<parameters.length; i++){
					ml.addVariable(parameters[i][0], parameters[i][1]);
				}
			}else{
				for(var k in parameters){
					ml.addVariable(k, parameters[k]);
				}
			}

			ml.addFile(wav, 'audio.wav', audioParam);
			ml.load(uri, false);

		}

		private function externalInterfaceEncode(data:String){
			return data.split("%").join("%25").split("\\").join("%5c").split("\"").join("%22").split("&").join("%26");
		}

		protected function audioData():String
		{
			var ret:String="";
			buffer.position = 0;
			while (buffer.bytesAvailable > 0)
			{
				ret += buffer.readFloat().toString() + ";";
			}
			return ret;
		}

		protected function showFlash():void
		{
			Security.showSettings(SecurityPanel.PRIVACY);
			triggerEvent('showFlash','');
		}

		/* Recording Helper */
		protected function setupMicrophone():void
		{
			logger.log('setupMicrophone');
			microphone = Microphone.getMicrophone();
			microphone.codec = "Nellymoser";
			microphone.setSilenceLevel(0);
			microphone.rate = sampleRate;
			microphone.gain = 50;
			microphone.addEventListener(StatusEvent.STATUS, function statusHandler(e:Event) {
				logger.log('Microphone Status Change');
				if(microphone.muted){
					triggerEvent('recordingCancel','');
				}else{
					if(!isRecording){
						notifyRecordingStarted();
					}
				}
			});

			logger.log('setupMicrophone done: ' + microphone.name + ' ' + microphone.muted);
		}

		protected function notifyRecordingStarted():void
		{
			if(microphoneWasMuted){
				microphoneWasMuted = false;
				triggerEvent('hideFlash','');
			}
			recordingStartTime = getTimer();
			triggerEvent('recordingStart', {});
			logger.log('startRecording');
			isRecording = true;
		}

		/* Sample related */

		protected function prepareWav():ByteArray
		{
			var wavData:ByteArray = new ByteArray();
			var wavWriter:WAVWriter = new WAVWriter();
			buffer.position = 0;
			wavWriter.numOfChannels = 1; // set the inital properties of the Wave Writer
			wavWriter.sampleBitRate = 16;
			wavWriter.samplingRate = sampleRate * 1000;
			wavWriter.processSamples(wavData, buffer, sampleRate * 1000, 1);
			return wavData;
		}

		protected function recordingDuration():int
		{
			var latency = 650;
			var duration = int(getTimer() - recordingStartTime - latency);
			return Math.max(duration, 0);
		}

		protected function recordSampleDataHandler(event:SampleDataEvent):void
		{
			while(event.data.bytesAvailable)
			{
				var sample:Number = event.data.readFloat();

				buffer.writeFloat(sample);
				if(buffer.length % 40000 == 0){
					triggerEvent('recordingProgress', recordingDuration(), 	microphone.activityLevel);
				}
			}
		}

		protected function playSampleDataHandler(event:SampleDataEvent):void
		{
			for (var i:int = 0; i < 8192 && buffer.bytesAvailable; i++)
			{
				var sample:Number = buffer.readFloat();
				event.data.writeFloat(sample);
				event.data.writeFloat(sample);
			}
		}

		/* ExternalInterface Communication */

		protected function triggerEvent(eventName:String, arg0, arg1 = null):void
		{
			ExternalInterface.call("Recorder.triggerEvent", eventName, arg0, arg1);
		}
	}
}

Update flash/Recorder.as

Update flash/Recorder.as

Explicitly setting the codec used. If this library is used in conjunction with another flash library that makes use of the microphone, and that library changes the codec to Speex, then the sampling rate will be forever stuck at "16". This causes recordings to sound like "chipmunks".


/**
	A wrapper around Flash 8's ExternalInterface; DojoExternalInterface is needed so that we
	can do a Flash 6 implementation of ExternalInterface, and be able
	to support having a single codebase that uses DojoExternalInterface
	across Flash versions rather than having two seperate source bases,
	where one uses ExternalInterface and the other uses DojoExternalInterface.

	DojoExternalInterface class does a variety of optimizations to bypass ExternalInterface's
	unbelievably bad performance so that we can have good performance
	on Safari; see the blog post
	http://codinginparadise.org/weblog/2006/02/how-to-speed-up-flash-8s.html
	for details.

	@author Brad Neuberg, bkn3@columbia.edu
*/

#include "kernel/swf/dojo/flash8/ExpressInstall.as"

class DojoExternalInterfaceClass {
	var available = null;
	var flashMethods = [];
	var numArgs = null;
	var argData = null;
	var resultData = null;
	var _id;

	function DojoExternalInterfaceClass(id){
		// extract the dojo base path
		//Debug.write('initialize', flash.external.ExternalInterface.addCallback);

		// see if we need to do an express install
		var install = new ExpressInstall();
		if(install.needsUpdate){
			install.init();
		}
		this._id = id;

		// register our callback functions
		var s = flash.external.ExternalInterface.addCallback("startExec", this, this.startExec);
		//Debug.write('addCallback', s, this, this.startExec);
		flash.external.ExternalInterface.addCallback("setNumberArguments", this, this.setNumberArguments);
		flash.external.ExternalInterface.addCallback("chunkArgumentData", this, this.chunkArgumentData);
		flash.external.ExternalInterface.addCallback("exec", this, this.exec);
		flash.external.ExternalInterface.addCallback("getReturnLength", this, this.getReturnLength);
		flash.external.ExternalInterface.addCallback("chunkReturnData", this, this.chunkReturnData);
		flash.external.ExternalInterface.addCallback("endExec", this, this.endExec);

		// set whether communication is available
		DojoExternalInterface.available = flash.external.ExternalInterface.available;
		DojoExternalInterface.call("loaded");
	}

	function addCallback(methodName, instance, method) {
		//Debug.write('addCallback', methodName, instance, method);
		// register DojoExternalInterface methodName with it's instance
		this.flashMethods[methodName] = instance;

		// tell JavaScript about DojoExternalInterface new method so we can create a proxy
		//Debug.write('calling dojo.flash.comm.' + this._id + "._addExternalInterfaceCallback", methodName, this._id);
		flash.external.ExternalInterface.call("dojo.flash.comm." + this._id + "._addExternalInterfaceCallback", methodName, this._id);

		return true;
	}

	function call(methodName, resultsCallback) {
		// we might have any number of optional arguments, so we have to
		// pass them in dynamically; strip out the results callback
		var parameters = [];
		for(var i = 0; i < arguments.length; i++){
			if(i != 1){ // skip the callback
				parameters.push(arguments[i]);
			}
		}

		var results = flash.external.ExternalInterface.call.apply(flash.external.ExternalInterface, parameters);

		// immediately give the results back, since ExternalInterface is
		// synchronous
		if(resultsCallback != null && typeof resultsCallback != "undefined"){
			resultsCallback.call(null, results);
		}
	}

	/**
			Called by Flash to indicate to JavaScript that we are ready to have
			our Flash functions called. Calling loaded()
			will fire the dojo.flash.loaded() event, so that JavaScript can know that
			Flash has finished loading and adding its callbacks, and can begin to
			interact with the Flash file.
	*/
	function loaded(){
		DojoExternalInterface.call("dojo.flash.loaded", null, this._id);
		LzBrowserKernel.__jsready();
	}

	function startExec(){
		//Debug.write('startExec');
		DojoExternalInterface.numArgs = null;
		DojoExternalInterface.argData = null;
		DojoExternalInterface.resultData = null;
	}

	function setNumberArguments(numArgs){
		//Debug.write('setNumberArguments', numArgs);
		DojoExternalInterface.numArgs = numArgs;
		DojoExternalInterface.argData = [];
	}

	function chunkArgumentData(value, argIndex){
		//Debug.write('chunkArgumentData', value, argIndex);
		//getURL("javascript:dojo.debug('FLASH: chunkArgumentData, value="+value+", argIndex="+argIndex+"')");
		var currentValue = DojoExternalInterface.argData[argIndex];
		if(currentValue == null || typeof currentValue == "undefined"){
			DojoExternalInterface.argData[argIndex] = value;
		}else{
			DojoExternalInterface.argData[argIndex] += value;
		}
	}

	function exec(methodName){
		//Debug.write('exec', methodName);
		// decode all of the arguments that were passed in
		for(var i = 0; i < DojoExternalInterface.argData.length; i++){
			DojoExternalInterface.argData[i] =
				DojoExternalInterface.decodeData(DojoExternalInterface.argData[i]);
		}

		var instance = DojoExternalInterface.flashMethods[methodName];
		//Debug.write('instance', instance, instance[methodName]);
		DojoExternalInterface.resultData = instance[methodName].apply(
																			instance, DojoExternalInterface.argData) + '';
		//Debug.write('result', DojoExternalInterface.resultData);
		// encode the result data
		DojoExternalInterface.resultData =
			DojoExternalInterface.encodeData(DojoExternalInterface.resultData);

		//Debug.write('resultData', DojoExternalInterface.resultData);

		//getURL("javascript:dojo.debug('FLASH: encoded result data="+DojoExternalInterface.resultData+"')");
	}

	function getReturnLength(){
	 if(DojoExternalInterface.resultData == null ||
	 					typeof DojoExternalInterface.resultData == "undefined"){
	 	return 0;
	 }
	 var segments = Math.ceil(DojoExternalInterface.resultData.length / 1024);
	//Debug.write('getReturnLength', typeof DojoExternalInterface.resultData, DojoExternalInterface.resultData.length, segments);
	 return segments;
	}

	function chunkReturnData(segment){
		var numSegments = DojoExternalInterface.getReturnLength();
		var startCut = segment * 1024;
		var endCut = segment * 1024 + 1024;
		if(segment == (numSegments - 1)){
			endCut = segment * 1024 + DojoExternalInterface.resultData.length;
		}

		var piece = DojoExternalInterface.resultData.substring(startCut, endCut);

		//getURL("javascript:dojo.debug('FLASH: chunking return piece="+piece+"')");

		return piece;
	}

	function endExec(){
	}

	function decodeData(data){
		// we have to use custom encodings for certain characters when passing
		// them over; for example, passing a backslash over as //// from JavaScript
		// to Flash doesn't work
		data = DojoExternalInterface.replaceStr(data, "&custom_backslash;", "\\");

		data = DojoExternalInterface.replaceStr(data, "\\\'", "\'");
		data = DojoExternalInterface.replaceStr(data, "\\\"", "\"");
		//Debug.write('decodeData', data);

		return data;
	}

	function encodeData(data){
		//getURL("javascript:dojo.debug('inside flash, data before="+data+"')");

		// double encode all entity values, or they will be mis-decoded
		// by Flash when returned
		data = DojoExternalInterface.replaceStr(data, "&", "&");

		// certain XMLish characters break Flash's wire serialization for
		// ExternalInterface; encode these into a custom encoding, rather than
		// the standard entity encoding, because otherwise we won't be able to
		// differentiate between our own encoding and any entity characters
		// that are being used in the string itself
		data = DojoExternalInterface.replaceStr(data, '<', '&custom_lt;');
		data = DojoExternalInterface.replaceStr(data, '>', '&custom_gt;');

		// encode control characters and JavaScript delimiters
		data = DojoExternalInterface.replaceStr(data, "\n", "\\n");
		data = DojoExternalInterface.replaceStr(data, "\r", "\\r");
		data = DojoExternalInterface.replaceStr(data, "\f", "\\f");
		data = DojoExternalInterface.replaceStr(data, "'", "\\'");
		data = DojoExternalInterface.replaceStr(data, '"', '\"');

		//Debug.write('encodeData', data);
		//getURL("javascript:dojo.debug('inside flash, data after="+data+"')");
		return data;
	}

	/**
			Flash ActionScript has no String.replace method or support for
			Regular Expressions! We roll our own very simple one.
	*/
	function replaceStr(inputStr, replaceThis, withThis){
		var splitStr = inputStr.split(replaceThis)
		inputStr = splitStr.join(withThis)
		return inputStr;
	}

	function getDojoPath(){
		var url = _root._url;
		var start = url.indexOf("baseRelativePath=") + "baseRelativePath=".length;
		var path = url.substring(start);
		var end = path.indexOf("&");
		if(end != -1){
			path = path.substring(0, end);
		}
		return path;
	}
}

/* X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2001-2008 Laszlo Systems, Inc.  All Rights Reserved.          *
* Use is subject to license terms.                                        *
* X_LZ_COPYRIGHT_END ******************************************************/

// vim:ts=4:noet:tw=0:

/**
	A wrapper around Flash 8's ExternalInterface; DojoExternalInterface is needed so that we
	can do a Flash 6 implementation of ExternalInterface, and be able
	to support having a single codebase that uses DojoExternalInterface
	across Flash versions rather than having two seperate source bases,
	where one uses ExternalInterface and the other uses DojoExternalInterface.

	DojoExternalInterface class does a variety of optimizations to bypass ExternalInterface's
	unbelievably bad performance so that we can have good performance
	on Safari; see the blog post
	http://codinginparadise.org/weblog/2006/02/how-to-speed-up-flash-8s.html
	for details.

	@author Brad Neuberg, bkn3@columbia.edu
*/

#include "kernel/swf/dojo/flash8/ExpressInstall.as"

class DojoExternalInterfaceClass {
	var available = null;
	var flashMethods = [];
	var numArgs = null;
	var argData = null;
	var resultData = null;
	var _id;

	function DojoExternalInterfaceClass(id){
		// extract the dojo base path
		//Debug.write('initialize', flash.external.ExternalInterface.addCallback);

		// see if we need to do an express install
		var install = new ExpressInstall();
		if(install.needsUpdate){
			install.init();
		}

		// not loaded from the wrapper JS...
		if (id == null) return;

		this._id = id;

		// register our callback functions
		var s = flash.external.ExternalInterface.addCallback("startExec", this, this.startExec);
		//Debug.write('addCallback', s, this, this.startExec);
		flash.external.ExternalInterface.addCallback("setNumberArguments", this, this.setNumberArguments);
		flash.external.ExternalInterface.addCallback("chunkArgumentData", this, this.chunkArgumentData);
		flash.external.ExternalInterface.addCallback("exec", this, this.exec);
		flash.external.ExternalInterface.addCallback("getReturnLength", this, this.getReturnLength);
		flash.external.ExternalInterface.addCallback("chunkReturnData", this, this.chunkReturnData);
		flash.external.ExternalInterface.addCallback("endExec", this, this.endExec);

		// set whether communication is available
		DojoExternalInterface.available = flash.external.ExternalInterface.available;
		DojoExternalInterface.call("loaded");
	}

	function addCallback(methodName, instance, method) {
		//Debug.write('addCallback', methodName, instance, method);
		// register DojoExternalInterface methodName with it's instance
		this.flashMethods[methodName] = instance;

		// tell JavaScript about DojoExternalInterface new method so we can create a proxy
		//Debug.write('calling dojo.flash.comm.' + this._id + "._addExternalInterfaceCallback", methodName, this._id);
		flash.external.ExternalInterface.call("dojo.flash.comm." + this._id + "._addExternalInterfaceCallback", methodName, this._id);

		return true;
	}

	function call(methodName, resultsCallback) {
		// we might have any number of optional arguments, so we have to
		// pass them in dynamically; strip out the results callback
		var parameters = [];
		for(var i = 0; i < arguments.length; i++){
			if(i != 1){ // skip the callback
				parameters.push(arguments[i]);
			}
		}

		var results = flash.external.ExternalInterface.call.apply(flash.external.ExternalInterface, parameters);

		// immediately give the results back, since ExternalInterface is
		// synchronous
		if(resultsCallback != null && typeof resultsCallback != "undefined"){
			resultsCallback.call(null, results);
		}
	}

	/**
			Called by Flash to indicate to JavaScript that we are ready to have
			our Flash functions called. Calling loaded()
			will fire the dojo.flash.loaded() event, so that JavaScript can know that
			Flash has finished loading and adding its callbacks, and can begin to
			interact with the Flash file.
	*/
	function loaded(){
		DojoExternalInterface.call("dojo.flash.loaded", null, this._id);
		LzBrowserKernel.__jsready();
	}

	function startExec(){
		//Debug.write('startExec');
		DojoExternalInterface.numArgs = null;
		DojoExternalInterface.argData = null;
		DojoExternalInterface.resultData = null;
	}

	function setNumberArguments(numArgs){
		//Debug.write('setNumberArguments', numArgs);
		DojoExternalInterface.numArgs = numArgs;
		DojoExternalInterface.argData = [];
	}

	function chunkArgumentData(value, argIndex){
		//Debug.write('chunkArgumentData', value, argIndex);
		//getURL("javascript:dojo.debug('FLASH: chunkArgumentData, value="+value+", argIndex="+argIndex+"')");
		var currentValue = DojoExternalInterface.argData[argIndex];
		if(currentValue == null || typeof currentValue == "undefined"){
			DojoExternalInterface.argData[argIndex] = value;
		}else{
			DojoExternalInterface.argData[argIndex] += value;
		}
	}

	function exec(methodName){
		//Debug.write('exec', methodName);
		// decode all of the arguments that were passed in
		for(var i = 0; i < DojoExternalInterface.argData.length; i++){
			DojoExternalInterface.argData[i] =
				DojoExternalInterface.decodeData(DojoExternalInterface.argData[i]);
		}

		var instance = DojoExternalInterface.flashMethods[methodName];
		//Debug.write('instance', instance, instance[methodName]);
		DojoExternalInterface.resultData = instance[methodName].apply(
																			instance, DojoExternalInterface.argData) + '';
		//Debug.write('result', DojoExternalInterface.resultData);
		// encode the result data
		DojoExternalInterface.resultData =
			DojoExternalInterface.encodeData(DojoExternalInterface.resultData);

		//Debug.write('resultData', DojoExternalInterface.resultData);

		//getURL("javascript:dojo.debug('FLASH: encoded result data="+DojoExternalInterface.resultData+"')");
	}

	function getReturnLength(){
	 if(DojoExternalInterface.resultData == null ||
	 					typeof DojoExternalInterface.resultData == "undefined"){
	 	return 0;
	 }
	 var segments = Math.ceil(DojoExternalInterface.resultData.length / 1024);
	//Debug.write('getReturnLength', typeof DojoExternalInterface.resultData, DojoExternalInterface.resultData.length, segments);
	 return segments;
	}

	function chunkReturnData(segment){
		var numSegments = DojoExternalInterface.getReturnLength();
		var startCut = segment * 1024;
		var endCut = segment * 1024 + 1024;
		if(segment == (numSegments - 1)){
			endCut = segment * 1024 + DojoExternalInterface.resultData.length;
		}

		var piece = DojoExternalInterface.resultData.substring(startCut, endCut);

		//getURL("javascript:dojo.debug('FLASH: chunking return piece="+piece+"')");

		return piece;
	}

	function endExec(){
	}

	function decodeData(data){
		// we have to use custom encodings for certain characters when passing
		// them over; for example, passing a backslash over as //// from JavaScript
		// to Flash doesn't work
		data = DojoExternalInterface.replaceStr(data, "&custom_backslash;", "\\");

		data = DojoExternalInterface.replaceStr(data, "\\\'", "\'");
		data = DojoExternalInterface.replaceStr(data, "\\\"", "\"");
		//Debug.write('decodeData', data);

		return data;
	}

	function encodeData(data){
		//getURL("javascript:dojo.debug('inside flash, data before="+data+"')");

		// double encode all entity values, or they will be mis-decoded
		// by Flash when returned
		data = DojoExternalInterface.replaceStr(data, "&", "&");

		// certain XMLish characters break Flash's wire serialization for
		// ExternalInterface; encode these into a custom encoding, rather than
		// the standard entity encoding, because otherwise we won't be able to
		// differentiate between our own encoding and any entity characters
		// that are being used in the string itself
		data = DojoExternalInterface.replaceStr(data, '<', '&custom_lt;');
		data = DojoExternalInterface.replaceStr(data, '>', '&custom_gt;');

		// encode control characters and JavaScript delimiters
		data = DojoExternalInterface.replaceStr(data, "\n", "\\n");
		data = DojoExternalInterface.replaceStr(data, "\r", "\\r");
		data = DojoExternalInterface.replaceStr(data, "\f", "\\f");
		data = DojoExternalInterface.replaceStr(data, "'", "\\'");
		data = DojoExternalInterface.replaceStr(data, '"', '\"');

		//Debug.write('encodeData', data);
		//getURL("javascript:dojo.debug('inside flash, data after="+data+"')");
		return data;
	}

	/**
			Flash ActionScript has no String.replace method or support for
			Regular Expressions! We roll our own very simple one.
	*/
	function replaceStr(inputStr, replaceThis, withThis){
		var splitStr = inputStr.split(replaceThis)
		inputStr = splitStr.join(withThis)
		return inputStr;
	}

	function getDojoPath(){
		var url = _root._url;
		var start = url.indexOf("baseRelativePath=") + "baseRelativePath=".length;
		var path = url.substring(start);
		var end = path.indexOf("&");
		if(end != -1){
			path = path.substring(0, end);
		}
		return path;
	}
}

/* X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2001-2008 Laszlo Systems, Inc.  All Rights Reserved.          *
* Use is subject to license terms.                                        *
* X_LZ_COPYRIGHT_END ******************************************************/

// vim:ts=4:noet:tw=0:

Change 20080521-maxcarlson-m by maxcarlson@Roboto on 2008-05-21 14:39:56 PDT
    in /Users/maxcarlson/openlaszlo/trunk-clean
    for http://svn.openlaszlo.org/openlaszlo/trunk

Summary: Don't init flash/js communication when apps are running outside the html wrappers.

New Features:

Bugs Fixed: LPP-5383 - Refresh Or Close Of SWF 8 Solo App In IE7 Causes Javascript 'No Object' Errors

Technical Reviewer: promanik
QA Reviewer: hminsky
Doc Reviewer: (pending)

Documentation:

Release Notes:

Details: Return after running the installer if a null id is passed into the constructor, which happens when there is no id init arg.  It's a shame the flash.external.ExternalInterface.available API does nothing to help in this case!

Tests: Compile a non-proxied version of an app, then load it in IE 7 directly  Reloading the app should no longer produce Javascript warnings in IE...  I used http://192.168.1.119:8080/trunk/my-apps/copy-of-hello.lzx?lzr=swf8&lzproxied=false followed by http://192.168.1.119:8080/trunk/my-apps/copy-of-hello.lzx.lzr%3Dswf8.swf

git-svn-id: d62bde4b5aa582fdf07c8d403e53e0399a7ed285@9285 fa20e4f9-1d0a-0410-b5f3-dc9c16b8b17c


package com.kaltura.kdpfl.plugin
{
	import org.osmf.traits.LoaderBase;

	public class WVLoader extends LoaderBase
	{
		public function WVLoader()
		{
			super();
		}
	}
}

package com.kaltura.kdpfl.plugin
{
	import flash.external.ExternalInterface;

	import org.osmf.media.MediaResourceBase;
	import org.osmf.traits.LoaderBase;

	public class WVLoader extends LoaderBase
	{
		public function WVLoader()
		{
			super();
		}

		override public function canHandleResource(resource:MediaResourceBase):Boolean
		{
			if (resource.hasOwnProperty("url") && resource["url"].toString().indexOf(".wvm") > -1 )
			{
				try
				{
					ExternalInterface.call("mediaURL" , resource["url"].toString());
				}
				catch(error:Error)
				{
					trace("Failed to call external interface");
				}
				return true;
			}
			return false;
		}
	}
}

Fix a bug that caused "ChangeMedia" to fail


package goplayer
{
  public class ConfigurationParser
  {
    private const result : Configuration = new Configuration

    private var parameters : Object

    public function ConfigurationParser(parameters : Object)
    { this.parameters = parameters }

    public function execute() : void
    {
      // XXX: Clean up the host stuff.
      result.host = parameters.host || "staging.streamio.se"
      result.skinURL = parameters.skinUrl
        || "http://" + result.host + "/swfs/goplayer-black-skin.swf"
      result.movieID = parameters.movieId
      result.trackerID = parameters.trackerId || "global"
      result.bitratePolicy = getBitratePolicy("bitrate", BitratePolicy.BEST)
      result.enableRTMP = getBoolean("rtmp", true)
      result.enableAutoplay = getBoolean("autoplay", false)
      result.enableLooping = getBoolean("loop", false)
      result.enableChrome = getBoolean("chrome", true)
      result.enableTitleBar = getBoolean("titleBar", true)
    }

    public static function parse(parameters : Object) : Configuration
    {
      const parser : ConfigurationParser
        = new ConfigurationParser(parameters)

      parser.execute()

      return parser.result
    }

    // -----------------------------------------------------

    private function getBitratePolicy
      (name : String, fallback : BitratePolicy) : BitratePolicy
    {
      if (name in parameters)
        return $getBitratePolicy(name, parameters[name], fallback)
      else
        return fallback
    }

    private function $getBitratePolicy
      (name : String, value : String,
       fallback : BitratePolicy) : BitratePolicy
    {
      try
        { return $$getBitratePolicy(value) }
      catch (error : Error)
        {
          reportInvalidParameter(name, value, BITRATE_POLICY_VALUES)
          return fallback
        }

      throw new Error
    }

    private const BITRATE_POLICY_VALUES : Array =
      ["<number>kbps", "min", "max", "best"]

    private function $$getBitratePolicy(value : String) : BitratePolicy
    {
      if (value == "max")
        return BitratePolicy.MAX
      else if (value == "min")
        return BitratePolicy.MIN
      else if (value == "best")
        return BitratePolicy.BEST
      else if (Bitrate.parse(value))
        return BitratePolicy.specific(Bitrate.parse(value))
      else
        throw new Error
    }

    // -----------------------------------------------------

    private function getBoolean
      (name : String, fallback : Boolean) : Boolean
    {
      if (name in parameters)
        return $getBoolean(name, parameters[name], fallback)
      else
        return fallback
    }

    private function $getBoolean
      (name : String, value : String, fallback : Boolean) : Boolean
    {
      try
        { return $$getBoolean(value) }
      catch (error : Error)
        {
          reportInvalidParameter(name, value, ["on", "off"])
          return fallback
        }

      throw new Error
    }

    private function $$getBoolean(value : String) : Boolean
    {
      if (value == "on")
        return true
      else if (value == "off")
        return false
      else
        throw new Error
    }

    // -----------------------------------------------------

    private function reportInvalidParameter
      (name : String, value : String, validValues : Array) : void
    {
      debug("Error: Invalid parameter: “" + name + "=" + value + "”; " +
            getInvalidParameterHint(validValues) + ".")
    }

    private function getInvalidParameterHint(values : Array) : String
    { return $getInvalidParameterHint(getQuotedValues(values)) }

    private function $getInvalidParameterHint(values : Array) : String
    {
      return "please use " +
        "either " + values.slice(0, -1).join(", ") + " " +
        "or " + values[values.length - 1]
    }

    private function getQuotedValues(values : Array) : Array
    {
      var result : Array = []

      for each (var value : String in values)
        result.push("“" + value + "”")

      return result
    }
  }
}

package goplayer
{
  public class ConfigurationParser
  {
    private const result : Configuration = new Configuration

    private var parameters : Object

    public function ConfigurationParser(parameters : Object)
    { this.parameters = parameters }

    public function execute() : void
    {
      // XXX: Clean up the host stuff.
      result.host = parameters.host || "staging.streamio.se"
      result.skinURL = parameters.skinUrl
        || "http://" + result.host + "/swfs/goplayer-black-skin.swf"
      result.movieID = parameters.movieId
      result.trackerID = parameters.trackerId || "global"
      result.bitratePolicy = getBitratePolicy("bitrate", BitratePolicy.BEST)
      result.enableRTMP = getBoolean("rtmp", true)
      result.enableAutoplay = getBoolean("autoplay", false)
      result.enableLooping = getBoolean("loop", false)
      result.enableChrome = getBoolean("chrome", true)
      result.enableTitleBar = getBoolean("titleBar", true)
    }

    public static function parse(parameters : Object) : Configuration
    {
      const parser : ConfigurationParser
        = new ConfigurationParser(parameters)

      parser.execute()

      return parser.result
    }

    // -----------------------------------------------------

    private function getBitratePolicy
      (name : String, fallback : BitratePolicy) : BitratePolicy
    {
      if (name in parameters)
        return $getBitratePolicy(name, parameters[name], fallback)
      else
        return fallback
    }

    private function $getBitratePolicy
      (name : String, value : String,
       fallback : BitratePolicy) : BitratePolicy
    {
      try
        { return $$getBitratePolicy(value) }
      catch (error : Error)
        {
          reportInvalidParameter(name, value, BITRATE_POLICY_VALUES)
          return fallback
        }

      throw new Error
    }

    private const BITRATE_POLICY_VALUES : Array =
      ["<number>kbps", "min", "max", "best"]

    private function $$getBitratePolicy(value : String) : BitratePolicy
    {
      if (value == "max")
        return BitratePolicy.MAX
      else if (value == "min")
        return BitratePolicy.MIN
      else if (value == "best")
        return BitratePolicy.BEST
      else if (Bitrate.parse(value))
        return BitratePolicy.specific(Bitrate.parse(value))
      else
        throw new Error
    }

    // -----------------------------------------------------

    private function getBoolean
      (name : String, fallback : Boolean) : Boolean
    {
      if (name in parameters)
        return $getBoolean(name, parameters[name], fallback)
      else
        return fallback
    }

    private function $getBoolean
      (name : String, value : String, fallback : Boolean) : Boolean
    {
      try
        { return $$getBoolean(value) }
      catch (error : Error)
        {
          reportInvalidParameter(name, value, ["true", "false"])
          return fallback
        }

      throw new Error
    }

    private function $$getBoolean(value : String) : Boolean
    {
      if (value == "true")
        return true
      else if (value == "false")
        return false
      else
        throw new Error
    }

    // -----------------------------------------------------

    private function reportInvalidParameter
      (name : String, value : String, validValues : Array) : void
    {
      debug("Error: Invalid parameter: “" + name + "=" + value + "”; " +
            getInvalidParameterHint(validValues) + ".")
    }

    private function getInvalidParameterHint(values : Array) : String
    { return $getInvalidParameterHint(getQuotedValues(values)) }

    private function $getInvalidParameterHint(values : Array) : String
    {
      return "please use " +
        "either " + values.slice(0, -1).join(", ") + " " +
        "or " + values[values.length - 1]
    }

    private function getQuotedValues(values : Array) : Array
    {
      var result : Array = []

      for each (var value : String in values)
        result.push("“" + value + "”")

      return result
    }
  }
}

Use ‘true’ and ‘false’ instead of ‘on’ and ‘off’.