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;

		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;
		}

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

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

					var screenSpacePosition : Vector4 = _worldToScreen.transformVector(TMP_VECTOR4, 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
		{
			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(StreamUsage.DYNAMIC);
			}
			else if (_vsInputComponents.indexOf(VertexComponent.NORMAL) >= 0
				&& !vertexFormat.hasComponent(VertexComponent.NORMAL))
			{
				geometry.computeNormals(StreamUsage.DYNAMIC);
			}
		}

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

		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;
		}

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

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

					var screenSpacePosition : Vector4 = _worldToScreen.transformVector(TMP_VECTOR4, 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
		{
			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
		{
			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;
		}
	}
}

update DrawCall.updateGeometry() to use default arguments for Geometry.computeTangentsSpace() and Geometry.computeNormals()


/**
 * Copyright 2011, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

package com.mxi.image
{
	import com.mxi.BinaryReader;
	import flash.external.ExternalInterface;
	import flash.utils.ByteArray;

	public class JPEG
	{
		protected var _markers:Object = {
			0xFFE1: {
				app: 'EXIF',
				name: 'APP1',
				signature: "Exif"
			},
			0xFFE2: {
				app: 'ICC',
				name: 'APP2',
				signature: "ICC_PROFILE"
			},
			0xFFED: {
				app: 'IPTC',
				name: 'APP13',
				signature: "Photoshop 3.0"
			}
		};

		protected var _headers:Array = [];
		protected var _br:BinaryReader;

		public function JPEG(binData:ByteArray)
		{
			_br = new BinaryReader;
			_br.init(binData);
		}

		static public function test(binData:ByteArray) : Boolean
		{
			var sign:Array = [ 255, 216 ];

			for (var i:int = sign.length - 1; i >= 0 ; i--) {
				if (binData[i] != sign[i]) {
					return false;
				}
			}
			return true;
		}

		public function info() : Object
		{
			var idx:uint = 0, marker:uint, length:uint, limit:uint;

			limit = Math.min(1048576, _br.length);

			while (idx <= limit) { // make sure we examine just enough data
				marker = _br.SHORT(idx += 2);
				if (marker >= 0xFFC0 && marker <= 0xFFC3) { // SOFn
					idx += 5; // marker (2 bytes) + length (2 bytes) + Sample precision (1 byte)
					return {
						height: _br.SHORT(idx),
						width: _br.SHORT(idx += 2)
					};
				}
				length = _br.SHORT(idx += 2);
				idx += length - 2;
			}

			return null;
		}

		public function extractHeaders() : Array
		{
			var idx:uint, limit:uint, marker:uint, length:uint;

			idx = 2;
			limit = Math.min(1048576, _br.length);

			while (idx <= limit) {
				marker = _br.SHORT(idx);

				// omit RST (restart) markers
				if (marker >= 0xFFD0 && marker <= 0xFFD7) {
					idx += 2;
					continue;
				}

				// no headers allowed after SOS marker
				if (marker === 0xFFDA || marker === 0xFFD9) {
					break;
				}

				length = _br.SHORT(idx + 2) + 2;

				if (_markers[marker] &&
					_br.STRING(idx + 4, _markers[marker].signature.length) === _markers[marker].signature) {
					_headers.push({
						hex: marker,
						app: _markers[marker].app.toUpperCase(),
						name: _markers[marker].name.toUpperCase(),
						start: idx,
						length: length,
						segment: _br.SEGMENT(idx, length)
					});
				}
				idx += length;
			}

			return _headers;
		}

		public function getHeaders(app:String = null) : Array
		{
			var headers:Array, array:Array = [];

			headers = _headers.length ? _headers : extractHeaders();

			if (!app) {
				return headers;
			}

			for (var i:uint = 0, max:uint = headers.length; i < max; i++) {
				if (headers[i].app === app.toUpperCase()) {
					array.push(headers[i].segment);
				}
			}
			return array;
		}

		public function setHeaders(app:String, segment:*) : void
		{
			var array:Array = [];

			if (segment is ByteArray) {
				array.push(segment);
			} else {
				array = segment;
			}

			for (var i:uint = 0, ii:uint = 0, max:uint = _headers.length; i < max; i++) {
				if (_headers[i].app === app.toUpperCase()) {
					_headers[i].segment = array[ii];
					_headers[i].length = array[ii].length;
					ii++;
				}
				if (ii >= array.length) break;
			}
		}

		public function insertHeaders(binData:ByteArray, headers:Array = null) : ByteArray
		{
			var idx:uint, br:BinaryReader = new BinaryReader;

			if (!headers || !headers.length) {
				headers = _headers;
			}

			br.init(binData);

			// Check if data is jpeg
			if (br.SHORT(0) !== 0xFFD8) {
				throw new Error("Invalid JPEG");
			}

			if (headers.length) {
				idx = br.SHORT(2) == 0xFFE0 ? 4 + br.SHORT(4) : 2;

				for (var i:uint = 0, max:uint = headers.length; i < max; i++) {
					br.SEGMENT(idx, 0, headers[i].segment);
					idx += headers[i].length;
				}
			}
			return br.SEGMENT();
		}

		public function purge() : void
		{
			_br.clear();
		}
	}
}

/**
 * Copyright 2011, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

package com.mxi.image
{
	import com.mxi.BinaryReader;
	import flash.external.ExternalInterface;
	import flash.utils.ByteArray;

	public class JPEG
	{
		protected var _markers:Object = {
			0xFFE1: {
				app: 'EXIF',
				name: 'APP1',
				signature: "Exif"
			},
			0xFFE2: {
				app: 'ICC',
				name: 'APP2',
				signature: "ICC_PROFILE"
			},
			0xFFED: {
				app: 'IPTC',
				name: 'APP13',
				signature: "Photoshop 3.0"
			}
		};

		protected var _headers:Array = [];
		protected var _br:BinaryReader;

		public function JPEG(binData:ByteArray)
		{
			_br = new BinaryReader;
			_br.init(binData);
		}

		static public function test(binData:ByteArray) : Boolean
		{
			var sign:Array = [ 255, 216 ];

			for (var i:int = sign.length - 1; i >= 0 ; i--) {
				if (binData[i] != sign[i]) {
					return false;
				}
			}
			return true;
		}

		public function info() : Object
		{
			var idx:uint = 0, marker:uint, length:uint;

			// examine all through the end, since some images might have very large APP segments
			while (idx <= _br.length) {
				marker = _br.SHORT(idx += 2);

				if (marker >= 0xFFC0 && marker <= 0xFFC3) { // SOFn
					idx += 5; // marker (2 bytes) + length (2 bytes) + Sample precision (1 byte)
					return {
						height: _br.SHORT(idx),
						width: _br.SHORT(idx += 2)
					};
				}
				length = _br.SHORT(idx += 2);
				idx += length - 2;
			}

			return null;
		}

		public function extractHeaders() : Array
		{
			var idx:uint, marker:uint, length:uint;

			idx = 2;

			while (idx <= _br.length) {
				marker = _br.SHORT(idx);

				// omit RST (restart) markers
				if (marker >= 0xFFD0 && marker <= 0xFFD7) {
					idx += 2;
					continue;
				}

				// no headers allowed after SOS marker
				if (marker === 0xFFDA || marker === 0xFFD9) {
					break;
				}

				length = _br.SHORT(idx + 2) + 2;

				if (_markers[marker] &&
					_br.STRING(idx + 4, _markers[marker].signature.length) === _markers[marker].signature) {
					_headers.push({
						hex: marker,
						app: _markers[marker].app.toUpperCase(),
						name: _markers[marker].name.toUpperCase(),
						start: idx,
						length: length,
						segment: _br.SEGMENT(idx, length)
					});
				}
				idx += length;
			}

			return _headers;
		}

		public function getHeaders(app:String = null) : Array
		{
			var headers:Array, array:Array = [];

			headers = _headers.length ? _headers : extractHeaders();

			if (!app) {
				return headers;
			}

			for (var i:uint = 0, max:uint = headers.length; i < max; i++) {
				if (headers[i].app === app.toUpperCase()) {
					array.push(headers[i].segment);
				}
			}
			return array;
		}

		public function setHeaders(app:String, segment:*) : void
		{
			var array:Array = [];

			if (segment is ByteArray) {
				array.push(segment);
			} else {
				array = segment;
			}

			for (var i:uint = 0, ii:uint = 0, max:uint = _headers.length; i < max; i++) {
				if (_headers[i].app === app.toUpperCase()) {
					_headers[i].segment = array[ii];
					_headers[i].length = array[ii].length;
					ii++;
				}
				if (ii >= array.length) break;
			}
		}

		public function insertHeaders(binData:ByteArray, headers:Array = null) : ByteArray
		{
			var idx:uint, br:BinaryReader = new BinaryReader;

			if (!headers || !headers.length) {
				headers = _headers;
			}

			br.init(binData);

			// Check if data is jpeg
			if (br.SHORT(0) !== 0xFFD8) {
				throw new Error("Invalid JPEG");
			}

			if (headers.length) {
				idx = br.SHORT(2) == 0xFFE0 ? 4 + br.SHORT(4) : 2;

				for (var i:uint = 0, max:uint = headers.length; i < max; i++) {
					br.SEGMENT(idx, 0, headers[i].segment);
					idx += headers[i].length;
				}
			}
			return br.SEGMENT();
		}

		public function purge() : void
		{
			_br.clear();
		}
	}
}

Flash: Remove limit on JPEG format check - performance gain negligible, but in some cases causes wrong IMAGE_FORMAT_ERRORs.


package com.amanitadesign.steam
{
	public class SteamConstants
	{
		public static const RESPONSE_OK:int = 1;
		public static const RESPONSE_FAILED:int = 2;
		public static const RESPONSE_OnUserStatsReceived:int = 0;
		public static const RESPONSE_OnUserStatsStored:int = 1;
		public static const RESPONSE_OnAchievementStored:int = 2;
		public static const RESPONSE_OnGameOverlayActivated:int = 3;
	}
}

package com.amanitadesign.steam
{
	public class SteamConstants
	{
		public static const RESPONSE_OK:int = 0;
		public static const RESPONSE_FAILED:int = 1;
		public static const RESPONSE_OnUserStatsReceived:int = 0;
		public static const RESPONSE_OnUserStatsStored:int = 1;
		public static const RESPONSE_OnAchievementStored:int = 2;
		public static const RESPONSE_OnGameOverlayActivated:int = 3;
	}
}

Fix response values

Fix response values


////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package org.apache.flex.html.beads.layouts
{
	import org.apache.flex.core.IBeadLayout;
	import org.apache.flex.core.ILayoutParent;
	import org.apache.flex.core.IStrand;
    import org.apache.flex.core.IParent;
    import org.apache.flex.core.IUIBase;
	import org.apache.flex.core.UIBase;
	import org.apache.flex.core.ValuesManager;
	import org.apache.flex.events.Event;
	import org.apache.flex.events.IEventDispatcher;

    /**
     *  The FlexibleFirstChildHorizontalLayout class is a simple layout
     *  bead.  It takes the set of children and lays them out
     *  horizontally in one row, separating them according to
     *  CSS layout rules for margin and padding styles. But it
     *  will size the first child to take up as much or little
     *  room as possible.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10.2
     *  @playerversion AIR 2.6
     *  @productversion FlexJS 0.0
     */
	public class FlexibleFirstChildHorizontalLayout implements IBeadLayout
	{
        /**
         *  Constructor.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function FlexibleFirstChildHorizontalLayout()
		{
		}

		private var _strand:IStrand;

        /**
         *  @copy org.apache.flex.core.IBead#strand
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function set strand(value:IStrand):void
		{
			_strand = value;
            IEventDispatcher(value).addEventListener("layoutNeeded", changeHandler);
			IEventDispatcher(value).addEventListener("widthChanged", changeHandler);
			IEventDispatcher(value).addEventListener("childrenAdded", changeHandler);
			IEventDispatcher(value).addEventListener("itemsCreated", changeHandler);
		}

		private function changeHandler(event:Event):void
		{
			var layoutParent:ILayoutParent = _strand.getBeadByType(ILayoutParent) as ILayoutParent;
			var contentView:IParent = layoutParent.contentView;

			var n:int = contentView.numElements;
			var marginLeft:Object;
			var marginRight:Object;
			var marginTop:Object;
			var marginBottom:Object;
			var margin:Object;
			var maxHeight:Number = 0;
			var verticalMargins:Array = [];

            var xx:Number = layoutParent.resizableView.width;
            var padding:Object = determinePadding();
            xx -= padding.paddingLeft + padding.paddingRight;

            for (var i:int = n - 1; i >= 0; i--)
			{
				var child:IUIBase = contentView.getElementAt(i) as IUIBase;
				margin = ValuesManager.valuesImpl.getValue(child, "margin");
				if (margin is Array)
				{
					if (margin.length == 1)
						marginLeft = marginTop = marginRight = marginBottom = margin[0];
					else if (margin.length <= 3)
					{
						marginLeft = marginRight = margin[1];
						marginTop = marginBottom = margin[0];
					}
					else if (margin.length == 4)
					{
						marginLeft = margin[3];
						marginBottom = margin[2];
						marginRight = margin[1];
						marginTop = margin[0];
					}
				}
				else if (margin == null)
				{
					marginLeft = ValuesManager.valuesImpl.getValue(child, "margin-left");
					marginTop = ValuesManager.valuesImpl.getValue(child, "margin-top");
					marginRight = ValuesManager.valuesImpl.getValue(child, "margin-right");
					marginBottom = ValuesManager.valuesImpl.getValue(child, "margin-bottom");
				}
				else
				{
					marginLeft = marginTop = marginBottom = marginRight = margin;
				}
				var ml:Number;
				var mr:Number;
				var mt:Number;
				var mb:Number;
				var lastmr:Number;
				mt = Number(marginTop);
				if (isNaN(mt))
					mt = 0;
				mb = Number(marginBottom);
				if (isNaN(mb))
					mb = 0;
				if (marginLeft == "auto")
					ml = 0;
				else
				{
					ml = Number(marginLeft);
					if (isNaN(ml))
						ml = 0;
				}
				if (marginRight == "auto")
					mr = 0;
				else
				{
					mr = Number(marginRight);
					if (isNaN(mr))
						mr = 0;
				}
				child.y = mt;
				maxHeight = Math.max(maxHeight, ml + child.height + mr);
				if (i == 0)
                {
                    child.x = ml;
                    child.width = xx - mr;
                }
				else
                    child.x = xx - child.width - mr;
				xx -= child.width + mr + ml;
				lastmr = mr;
				var valign:Object = ValuesManager.valuesImpl.getValue(child, "vertical-align");
				verticalMargins.push({ marginTop: mt, marginBottom: mb, valign: valign });
			}
			for (i = 0; i < n; i++)
			{
				var obj:Object = verticalMargins[0]
				child = contentView.getElementAt(i) as IUIBase;
				if (obj.valign == "middle")
					child.y = maxHeight - child.height / 2;
				else if (valign == "bottom")
					child.y = maxHeight - child.height - obj.marginBottom;
				else
					child.y = obj.marginTop;
			}
            layoutParent.resizableView.height = maxHeight;
		}

        // TODO (aharui): utility class or base class
        /**
         *  Determines the top and left padding values, if any, as set by
         *  padding style values. This includes "padding" for all padding values
         *  as well as "padding-left" and "padding-top".
         *
         *  Returns an object with paddingLeft and paddingTop properties.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        protected function determinePadding():Object
        {
            var paddingLeft:Object;
            var paddingTop:Object;
            var paddingRight:Object;
            var padding:Object = ValuesManager.valuesImpl.getValue(_strand, "padding");
            if (typeof(padding) == "Array")
            {
                if (padding.length == 1)
                    paddingLeft = paddingTop = paddingRight = padding[0];
                else if (padding.length <= 3)
                {
                    paddingLeft = padding[1];
                    paddingTop = padding[0];
                    paddingRight = padding[1];
                }
                else if (padding.length == 4)
                {
                    paddingLeft = padding[3];
                    paddingTop = padding[0];
                    paddingRight = padding[1];
                }
            }
            else if (padding == null)
            {
                paddingLeft = ValuesManager.valuesImpl.getValue(_strand, "padding-left");
                paddingTop = ValuesManager.valuesImpl.getValue(_strand, "padding-top");
                paddingRight = ValuesManager.valuesImpl.getValue(_strand, "padding-right");
            }
            else
            {
                paddingLeft = paddingTop = paddingRight = padding;
            }
            var pl:Number = Number(paddingLeft);
            var pt:Number = Number(paddingTop);
            var pr:Number = Number(paddingRight);

            return {paddingLeft:pl, paddingTop:pt, paddingRight:pr};
        }

    }

}

////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package org.apache.flex.html.beads.layouts
{
	import org.apache.flex.core.IBeadLayout;
	import org.apache.flex.core.ILayoutParent;
	import org.apache.flex.core.IStrand;
    import org.apache.flex.core.IParent;
    import org.apache.flex.core.IUIBase;
	import org.apache.flex.core.UIBase;
	import org.apache.flex.core.ValuesManager;
	import org.apache.flex.events.Event;
	import org.apache.flex.events.IEventDispatcher;

    /**
     *  The FlexibleFirstChildHorizontalLayout class is a simple layout
     *  bead.  It takes the set of children and lays them out
     *  horizontally in one row, separating them according to
     *  CSS layout rules for margin and padding styles. But it
     *  will size the first child to take up as much or little
     *  room as possible.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10.2
     *  @playerversion AIR 2.6
     *  @productversion FlexJS 0.0
     */
	public class FlexibleFirstChildHorizontalLayout implements IBeadLayout
	{
        /**
         *  Constructor.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function FlexibleFirstChildHorizontalLayout()
		{
		}

		private var _strand:IStrand;

        /**
         *  @copy org.apache.flex.core.IBead#strand
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function set strand(value:IStrand):void
		{
			_strand = value;
            IEventDispatcher(value).addEventListener("layoutNeeded", changeHandler);
			IEventDispatcher(value).addEventListener("widthChanged", changeHandler);
			IEventDispatcher(value).addEventListener("childrenAdded", changeHandler);
			IEventDispatcher(value).addEventListener("itemsCreated", changeHandler);
		}

		private function changeHandler(event:Event):void
		{
			var layoutParent:ILayoutParent = _strand.getBeadByType(ILayoutParent) as ILayoutParent;
			var contentView:IParent = layoutParent.contentView;

			var n:int = contentView.numElements;
			var marginLeft:Object;
			var marginRight:Object;
			var marginTop:Object;
			var marginBottom:Object;
			var margin:Object;
			var maxHeight:Number = 0;
			var verticalMargins:Array = [];

            var xx:Number = layoutParent.resizableView.width;
            var padding:Object = determinePadding();
            xx -= padding.paddingLeft + padding.paddingRight;

            for (var i:int = n - 1; i >= 0; i--)
			{
				var child:IUIBase = contentView.getElementAt(i) as IUIBase;
				margin = ValuesManager.valuesImpl.getValue(child, "margin");
				if (margin is Array)
				{
					if (margin.length == 1)
						marginLeft = marginTop = marginRight = marginBottom = margin[0];
					else if (margin.length <= 3)
					{
						marginLeft = marginRight = margin[1];
						marginTop = marginBottom = margin[0];
					}
					else if (margin.length == 4)
					{
						marginLeft = margin[3];
						marginBottom = margin[2];
						marginRight = margin[1];
						marginTop = margin[0];
					}
				}
				else if (margin == null)
				{
					marginLeft = ValuesManager.valuesImpl.getValue(child, "margin-left");
					marginTop = ValuesManager.valuesImpl.getValue(child, "margin-top");
					marginRight = ValuesManager.valuesImpl.getValue(child, "margin-right");
					marginBottom = ValuesManager.valuesImpl.getValue(child, "margin-bottom");
				}
				else
				{
					marginLeft = marginTop = marginBottom = marginRight = margin;
				}
				var ml:Number;
				var mr:Number;
				var mt:Number;
				var mb:Number;
				var lastmr:Number;
				mt = Number(marginTop);
				if (isNaN(mt))
					mt = 0;
				mb = Number(marginBottom);
				if (isNaN(mb))
					mb = 0;
				if (marginLeft == "auto")
					ml = 0;
				else
				{
					ml = Number(marginLeft);
					if (isNaN(ml))
						ml = 0;
				}
				if (marginRight == "auto")
					mr = 0;
				else
				{
					mr = Number(marginRight);
					if (isNaN(mr))
						mr = 0;
				}
				child.y = mt;
				maxHeight = Math.max(maxHeight, mt + child.height + mb);
				if (i == 0)
                {
                    child.x = ml;
                    child.width = xx - mr;
                }
				else
                    child.x = xx - child.width - mr;
				xx -= child.width + mr + ml;
				lastmr = mr;
				var valign:Object = ValuesManager.valuesImpl.getValue(child, "vertical-align");
				verticalMargins.push({ marginTop: mt, marginBottom: mb, valign: valign });
			}
			for (i = 0; i < n; i++)
			{
				var obj:Object = verticalMargins[0]
				child = contentView.getElementAt(i) as IUIBase;
				if (obj.valign == "middle")
					child.y = maxHeight - child.height / 2;
				else if (valign == "bottom")
					child.y = maxHeight - child.height - obj.marginBottom;
				else
					child.y = obj.marginTop;
			}
            layoutParent.resizableView.height = maxHeight;
		}

        // TODO (aharui): utility class or base class
        /**
         *  Determines the top and left padding values, if any, as set by
         *  padding style values. This includes "padding" for all padding values
         *  as well as "padding-left" and "padding-top".
         *
         *  Returns an object with paddingLeft and paddingTop properties.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        protected function determinePadding():Object
        {
            var paddingLeft:Object;
            var paddingTop:Object;
            var paddingRight:Object;
            var padding:Object = ValuesManager.valuesImpl.getValue(_strand, "padding");
            if (typeof(padding) == "Array")
            {
                if (padding.length == 1)
                    paddingLeft = paddingTop = paddingRight = padding[0];
                else if (padding.length <= 3)
                {
                    paddingLeft = padding[1];
                    paddingTop = padding[0];
                    paddingRight = padding[1];
                }
                else if (padding.length == 4)
                {
                    paddingLeft = padding[3];
                    paddingTop = padding[0];
                    paddingRight = padding[1];
                }
            }
            else if (padding == null)
            {
                paddingLeft = ValuesManager.valuesImpl.getValue(_strand, "padding-left");
                paddingTop = ValuesManager.valuesImpl.getValue(_strand, "padding-top");
                paddingRight = ValuesManager.valuesImpl.getValue(_strand, "padding-right");
            }
            else
            {
                paddingLeft = paddingTop = paddingRight = padding;
            }
            var pl:Number = Number(paddingLeft);
            var pt:Number = Number(paddingTop);
            var pr:Number = Number(paddingRight);

            return {paddingLeft:pl, paddingTop:pt, paddingRight:pr};
        }

    }

}

fix height calculation

fix height calculation


////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package {

import flash.display.DisplayObject;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.events.Event;
import flash.system.Capabilities;

[Mixin]
/**
 *  A hash table of tests not to run, read from IncludeList.txt
 *  The file is one test per line of the form ScriptName$TestID
 *  The location of the file is assumed to be c:/tmp on windows,
 *  or /tmp on Unix
 */
public class IncludeFileLocation
{

	private static var loader:URLLoader;

	/**
	 * tell UnitTester it should wait for this load to complete
	 */
	UnitTester.waitForIncludes = true;

	/**
	 *  Mixin callback that gets everything ready to go.
	 *  Table is of form: ScriptName$TestID: 1,
	 */
	public static function init(root:DisplayObject):void
	{

		var currentOS:String = Capabilities.os;

		var useFile:String;

		if (currentOS.indexOf ("Windows") != -1)
		{
			useFile = "c:/tmp/IncludeList.txt";
		} else {

			useFile = "/tmp/IncludeList.txt";

		}

		var req:URLRequest = new URLRequest(useFile);
		loader = new URLLoader();
		loader.addEventListener("complete", completeHandler);
		loader.load(req);

	}

	private static function completeHandler(event:Event):void
	{
		var data:String = loader.data;
		// DOS end of line
		var delimiter:RegExp = new RegExp("\r\n", "g");
		data = data.replace(delimiter, ",");
		// Unix end of line
		delimiter = new RegExp("\n", "g");
		data = data.replace(delimiter, ",");

		UnitTester.includeList = new Object();
		var items:Array = data.split(",");
		var n:int = items.length;
		for (var i:int = 0; i < n; i++)
		{
			var s:String = items[i];
			if (s.length)
				UnitTester.includeList[s] = 1;
		}
		UnitTester.waitForIncludes = false;
		UnitTester.pre_startEventHandler(event);
	}
}

}

////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package {

import flash.display.DisplayObject;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.events.Event;
import flash.system.Capabilities;
import flash.system.Security;

[Mixin]
/**
 *  A hash table of tests not to run, read from IncludeList.txt
 *  The file is one test per line of the form ScriptName$TestID
 *  The location of the file is assumed to be c:/tmp on windows,
 *  or /tmp on Unix
 */
public class IncludeFileLocation
{

	private static var loader:URLLoader;

	/**
	 * tell UnitTester it should wait for this load to complete
	 */
	UnitTester.waitForIncludes = true;

	/**
	 *  Mixin callback that gets everything ready to go.
	 *  Table is of form: ScriptName$TestID: 1,
	 */
	public static function init(root:DisplayObject):void
	{

		var currentOS:String = Capabilities.os;

		var useFile:String;

		if (currentOS.indexOf ("Windows") != -1)
		{
			useFile = "c:/tmp/IncludeList.txt";
		} else {

			useFile = "/tmp/IncludeList.txt";
			if (Security.sandboxType == Security.APPLICATION)
				useFile = "file:///tmp/IncludeList.txt";

		}

		var req:URLRequest = new URLRequest(useFile);
		loader = new URLLoader();
		loader.addEventListener("complete", completeHandler);
		loader.load(req);

	}

	private static function completeHandler(event:Event):void
	{
		var data:String = loader.data;
		// DOS end of line
		var delimiter:RegExp = new RegExp("\r\n", "g");
		data = data.replace(delimiter, ",");
		// Unix end of line
		delimiter = new RegExp("\n", "g");
		data = data.replace(delimiter, ",");

		UnitTester.includeList = new Object();
		var items:Array = data.split(",");
		var n:int = items.length;
		for (var i:int = 0; i < n; i++)
		{
			var s:String = items[i];
			if (s.length)
				UnitTester.includeList[s] = 1;
		}
		UnitTester.waitForIncludes = false;
		UnitTester.pre_startEventHandler(event);
	}
}

}

Allow -failures to work on AIR on Mac.  Might need fixing on Windows, but haven't tried it yet.

git-svn-id: 7d913817b0a978d84de757191e4bce372605a781@1402724 13f79535-47bb-0310-9956-ffa450edef68


format code Cookie.as

format code Cookie.as


//
// Flump - Copyright 2012 Three Rings Design

package flump.export {

import flash.desktop.NativeApplication;
import flash.display.NativeMenu;
import flash.display.NativeMenuItem;
import flash.display.NativeWindow;
import flash.display.Stage;
import flash.display.StageQuality;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.filesystem.File;
import flash.net.SharedObject;
import flash.utils.IDataOutput;

import flump.executor.Executor;
import flump.executor.Future;
import flump.export.Ternary;
import flump.xfl.ParseError;
import flump.xfl.XflLibrary;

import mx.collections.ArrayList;
import mx.events.CollectionEvent;
import mx.events.PropertyChangeEvent;

import spark.components.DataGrid;
import spark.components.DropDownList;
import spark.components.List;
import spark.components.Window;
import spark.events.GridSelectionEvent;

import com.threerings.util.F;
import com.threerings.util.Log;
import com.threerings.util.StringUtil;

import starling.display.Sprite;

public class Exporter
{
    public static const NA :NativeApplication = NativeApplication.nativeApplication;

    public function Exporter (win :ExporterWindow) {
        Log.setLevel("", Log.INFO);
        _win = win;
        _errors = _win.errors;
        _libraries = _win.libraries;

        function updatePreviewAndExport (..._) :void {
            _win.export.enabled = _exportChooser.dir != null && _libraries.selectionLength > 0 &&
                _libraries.selectedItems.some(function (status :DocStatus, ..._) :Boolean {
                    return status.isValid;
            });

            var status :DocStatus = _libraries.selectedItem as DocStatus;
            _win.preview.enabled = status != null && status.isValid;

            if (_exportChooser.dir == null) return;
            _conf.exportDir = _confFile.parent.getRelativePath(_exportChooser.dir, /*useDotDot=*/true);
        }

        var fileMenuItem :NativeMenuItem;
        if (NativeApplication.supportsMenu) {
            // Grab the existing menu on macs. Use an index to get it as it's not going to be
            // 'File' in all languages
            fileMenuItem = NA.menu.getItemAt(1);
            // Add a separator before the existing close command
            fileMenuItem.submenu.addItemAt(new NativeMenuItem("Sep", /*separator=*/true), 0);
        } else {
            _win.nativeWindow.menu = new NativeMenu();
            fileMenuItem = _win.nativeWindow.menu.addSubmenu(new NativeMenu(), "File");
        }

        // Add save and save as by index to work with the existing items on Mac
        // Mac menus have an existing "Close" item, so everything we add should go ahead of that
        var newMenuItem :NativeMenuItem = fileMenuItem.submenu.addItemAt(new NativeMenuItem("New"), 0);
        newMenuItem.keyEquivalent = "n";
        newMenuItem.addEventListener(Event.SELECT, function (..._) :void {
            _confFile = null;
            _conf = new FlumpConf();
            updatePublisher();
        });
        var openMenuItem :NativeMenuItem =
            fileMenuItem.submenu.addItemAt(new NativeMenuItem("Open..."), 1);
        openMenuItem.keyEquivalent = "o";
        openMenuItem.addEventListener(Event.SELECT, function (..._) :void {
            var file :File = new File();
            file.addEventListener(Event.SELECT, function (..._) :void {
                _confFile = file;
                openConf();
                updatePublisher();
            });
            file.browseForOpen("Open Flump Configuration");
        });
        fileMenuItem.submenu.addItemAt(new NativeMenuItem("Sep", /*separator=*/true), 2);

        const saveMenuItem :NativeMenuItem =
            fileMenuItem.submenu.addItemAt(new NativeMenuItem("Save"), 3);
        saveMenuItem.keyEquivalent = "s";
        function saveConf () :void {
            Files.write(_confFile, function (out :IDataOutput) :void {
                // Set directories relative to where this file is being saved. Fall back to absolute
                // paths if relative paths aren't possible.
                if (_importChooser.dir != null) {
                    _conf.importDir = _confFile.parent.getRelativePath(_importChooser.dir, /*useDotDot=*/true);
                    if (_conf.importDir == null) _conf.importDir = _importChooser.dir.nativePath;
                }

                if (_exportChooser.dir != null) {
                    _conf.exportDir = _confFile.parent.getRelativePath(_exportChooser.dir, /*useDotDot=*/true);
                    if (_conf.exportDir == null) _conf.exportDir = _exportChooser.dir.nativePath;
                }

                out.writeUTFBytes(JSON.stringify(_conf, null, /*space=*/2));
            });
        };
        function saveAs (..._) :void {
            var file :File = new File();
            file.addEventListener(Event.SELECT, function (..._) :void {
                _confFile = file;
                trace("Conf file is now " + _confFile.nativePath);
                _settings.data["CONF_FILE"] = _confFile.nativePath;
                _settings.flush();
                saveConf();
            });
            file.browseForSave("Save Flump Configuration");
        };
        saveMenuItem.addEventListener(Event.SELECT, function (..._) :void {
            if (_confFile == null) saveAs();
            else saveConf();
        });

        function openConf () :void {
            try {
                _conf = FlumpConf.fromJSON(JSONFormat.readJSON(_confFile));
                _win.title = _confFile.name;
                var dir :String = _confFile.parent.nativePath + File.separator + _conf.importDir;
                setImport(new File(dir));
            } catch (e :Error) {
                log.warning("Unable to parse conf", e);
                _errors.dataProvider.addItem(new ParseError(_confFile.nativePath,
                    ParseError.CRIT, "Unable to read configuration"));
                _confFile = null;
            }
        };

        const saveAsMenuItem :NativeMenuItem =
            fileMenuItem.submenu.addItemAt(new NativeMenuItem("Save As..."), 4);
        saveAsMenuItem.keyEquivalent = "S";
        saveAsMenuItem.addEventListener(Event.SELECT, saveAs);

        if (_settings.data.hasOwnProperty("CONF_FILE")) {
            _confFile = new File(_settings.data["CONF_FILE"]);
            openConf();
        }

        var curSelection :DocStatus = null;
        _libraries.addEventListener(GridSelectionEvent.SELECTION_CHANGE, function (..._) :void {
            log.info("Changed", "selected", _libraries.selectedIndices);
            updatePreviewAndExport();

            if (curSelection != null) {
                curSelection.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, updatePreviewAndExport);
            }
            var newSelection :DocStatus = _libraries.selectedItem as DocStatus;
            if (newSelection != null) {
                newSelection.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, updatePreviewAndExport);
            }
            curSelection = newSelection;
        });
        _win.reload.addEventListener(MouseEvent.CLICK, function (..._) :void {
            setImport(_importChooser.dir);
            updatePreviewAndExport();
        });
        _win.export.addEventListener(MouseEvent.CLICK, function (..._) :void {
            for each (var status :DocStatus in _libraries.selectedItems) {
                exportFlashDocument(status);
            }
        });
        _win.preview.addEventListener(MouseEvent.CLICK, function (..._) :void {
            showPreviewWindow(_libraries.selectedItem.lib);
        });
        _importChooser = new DirChooser(null, _win.importRoot, _win.browseImport);
        _importChooser.changed.add(setImport);
        setImport(_importChooser.dir);
        _exportChooser = new DirChooser(null, _win.exportRoot, _win.browseExport);
        _exportChooser.changed.add(updatePreviewAndExport);
        function updatePublisher (..._) :void {
            if (_confFile != null) {
                _importChooser.dir = (_conf.importDir != null) ? _confFile.parent.resolvePath(_conf.importDir) : null;
                _exportChooser.dir = (_conf.exportDir != null) ? _confFile.parent.resolvePath(_conf.exportDir) : null;
            } else {
                _importChooser.dir = null;
                _exportChooser.dir = null;
            }
            if (_exportChooser.dir == null || _conf.exports.length == 0) _publisher = null;
            else _publisher = new Publisher(_exportChooser.dir, Vector.<ExportConf>(_conf.exports));

            var formatNames :Array = [];
            for each (var export :ExportConf in _conf.exports) formatNames.push(export.name);
            _win.formatOverview.text = formatNames.join(", ");
        };
        _exportChooser.changed.add(updatePublisher);

        var editFormats :EditFormatsWindow;
        _win.editFormats.addEventListener(MouseEvent.CLICK, function (..._) :void {
            if (editFormats == null || editFormats.closed) {
                editFormats = new EditFormatsWindow();
                editFormats.open();
            } else editFormats.orderToFront();

            var dataProvider :ArrayList = new ArrayList(_conf.exports);
            dataProvider.addEventListener(CollectionEvent.COLLECTION_CHANGE, updatePublisher);

            editFormats.exports.dataProvider = dataProvider;
            editFormats.buttonAdd.addEventListener(MouseEvent.CLICK, function (..._) :void {
                var export :ExportConf = new ExportConf();
                export.name = "format" + (_conf.exports.length+1);
                if (_conf.exports.length > 0) {
                    export.format = _conf.exports[0].format;
                }
                dataProvider.addItem(export);
            });
            editFormats.exports.addEventListener(GridSelectionEvent.SELECTION_CHANGE, function (..._) :void {
                editFormats.buttonRemove.enabled = (editFormats.exports.selectedItem != null);
            });
            editFormats.buttonRemove.addEventListener(MouseEvent.CLICK, function (..._) :void {
                for each (var export :ExportConf in editFormats.exports.selectedItems) {
                    dataProvider.removeItem(export);
                }
            });
        });

        updatePublisher();
        _win.addEventListener(Event.CLOSE, function (..._) :void { NA.exit(0); });
    }

    protected function setImport (root :File) :void {
        _libraries.dataProvider.removeAll();
        _errors.dataProvider.removeAll();
        if (root == null) return;
        _rootLen = root.nativePath.length + 1;
        if (_docFinder != null) _docFinder.shutdownNow();
        _docFinder = new Executor();
        findFlashDocuments(root, _docFinder, true);
        _win.reload.enabled = true;
    }

    protected function showPreviewWindow (lib :XflLibrary) :void {
        if (_previewController == null || _previewWindow.closed || _previewControls.closed) {
            _previewWindow = new PreviewWindow();
            _previewControls = new PreviewControlsWindow();
            _previewWindow.started = function (container :Sprite) :void {
                _previewController = new PreviewController(lib, container, _previewWindow,
                    _previewControls);
            }

            _previewWindow.open();
            _previewControls.open();

            preventWindowClose(_previewWindow.nativeWindow);
            preventWindowClose(_previewControls.nativeWindow);

        } else {
            _previewController.lib = lib;
            _previewWindow.nativeWindow.visible = true;
            _previewControls.nativeWindow.visible = true;
        }

        _previewWindow.orderToFront();
        _previewControls.orderToFront();
    }

    // Causes a window to be hidden, rather than closed, when its close box is clicked
    protected static function preventWindowClose (window :NativeWindow) :void {
        window.addEventListener(Event.CLOSING, function (e :Event) :void {
            e.preventDefault();
            window.visible = false;
        });
    }

    protected var _previewController :PreviewController;
    protected var _previewWindow :PreviewWindow;
    protected var _previewControls :PreviewControlsWindow;

    protected function findFlashDocuments (base :File, exec :Executor,
        ignoreXflAtBase :Boolean = false) :void {
        Files.list(base, exec).succeeded.add(function (files :Array) :void {
            if (exec.isShutdown) return;
            for each (var file :File in files) {
                if (Files.hasExtension(file, "xfl")) {
                    if (ignoreXflAtBase) {
                        _errors.dataProvider.addItem(new ParseError(base.nativePath,
                            ParseError.CRIT, "The import directory can't be an XFL directory, did you mean " +
                            base.parent.nativePath + "?"));
                    } else addFlashDocument(file);
                    return;
                }
            }
            for each (file in files) {
                if (StringUtil.startsWith(file.name, ".", "RECOVER_")) {
                    continue; // Ignore hidden VCS directories, and recovered backups created by Flash
                }
                if (file.isDirectory) findFlashDocuments(file, exec);
                else addFlashDocument(file);
            }
        });
    }

    protected function exportFlashDocument (status :DocStatus) :void {
        const stage :Stage = NA.activeWindow.stage;
        const prevQuality :String = stage.quality;

        stage.quality = StageQuality.BEST;
        _publisher.publish(status.lib);

        stage.quality = prevQuality;
        status.updateModified(Ternary.FALSE);
    }

    protected function addFlashDocument (file :File) :void {
        var name :String = file.nativePath.substring(_rootLen).replace(
            new RegExp("\\" + File.separator, "g"), "/");
        var load :Future;
        switch (Files.getExtension(file)) {
        case "xfl":
            name = name.substr(0, name.lastIndexOf("/"));
            load = new XflLoader().load(name, file.parent);
            break;
        case "fla":
            name = name.substr(0, name.lastIndexOf("."));
            load = new FlaLoader().load(name, file);
            break;
        default:
            // Unsupported file type, ignore
            return;
        }

        const status :DocStatus = new DocStatus(name, _rootLen, Ternary.UNKNOWN, Ternary.UNKNOWN, null);
        _libraries.dataProvider.addItem(status);

        load.succeeded.add(function (lib :XflLibrary) :void {
            status.lib = lib;
            status.updateModified(Ternary.of(_publisher == null || _publisher.modified(lib)));
            for each (var err :ParseError in lib.getErrors()) _errors.dataProvider.addItem(err);
            status.updateValid(Ternary.of(lib.valid));
        });
        load.failed.add(function (error :Error) :void {
            trace("Failed to load " + file.nativePath + ": " + error);
            status.updateValid(Ternary.FALSE);
            throw error;
        });
    }

    protected var _rootLen :int;

    protected var _publisher :Publisher;
    protected var _docFinder :Executor;
    protected var _win :ExporterWindow;
    protected var _libraries :DataGrid;
    protected var _errors :DataGrid;
    protected var _exportChooser :DirChooser;
    protected var _importChooser :DirChooser;
    protected var _authoredResolution :DropDownList;
    protected var _conf :FlumpConf = new FlumpConf();
    protected var _confFile :File;
    protected const _settings :SharedObject = SharedObject.getLocal("flump/Exporter");

    private static const log :Log = Log.getLog(Exporter);
}
}

import flash.events.EventDispatcher;

import flump.export.Ternary;
import flump.xfl.XflLibrary;

import mx.core.IPropertyChangeNotifier;
import mx.events.PropertyChangeEvent;

class DocStatus extends EventDispatcher implements IPropertyChangeNotifier {
    public var path :String;
    public var modified :String;
    public var valid :String = QUESTION;
    public var lib :XflLibrary;

    public function DocStatus (path :String, rootLen :int, modified :Ternary, valid :Ternary, lib :XflLibrary) {
        this.lib = lib;
        this.path = path;
        _uid = path;

        updateModified(modified);
        updateValid(valid);
    }

    public function updateValid (newValid :Ternary) :void {
        changeField("valid", function (..._) :void {
            if (newValid == Ternary.TRUE) valid = CHECK;
            else if (newValid == Ternary.FALSE) valid = FROWN;
            else valid = QUESTION;
        });
    }

    public function get isValid () :Boolean { return valid == CHECK; }

    public function updateModified (newModified :Ternary) :void {
        changeField("modified", function (..._) :void {
            if (newModified == Ternary.TRUE) modified = CHECK;
            else if (newModified == Ternary.FALSE) modified = " ";
            else modified = QUESTION;
        });
    }

    protected function changeField(fieldName :String, modifier :Function) :void {
        const oldValue :Object = this[fieldName];
        modifier();
        const newValue :Object = this[fieldName];
        dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, fieldName, oldValue, newValue));
    }

    public function get uid () :String { return _uid; }
    public function set uid (uid :String) :void { _uid = uid; }

    protected var _uid :String;

    protected static const QUESTION :String = "?";
    protected static const FROWN :String = "☹";
    protected static const CHECK :String = "✓";
}

//
// Flump - Copyright 2012 Three Rings Design

package flump.export {

import flash.desktop.NativeApplication;
import flash.display.NativeMenu;
import flash.display.NativeMenuItem;
import flash.display.NativeWindow;
import flash.display.Stage;
import flash.display.StageQuality;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.filesystem.File;
import flash.net.SharedObject;
import flash.utils.IDataOutput;

import flump.executor.Executor;
import flump.executor.Future;
import flump.export.Ternary;
import flump.xfl.ParseError;
import flump.xfl.XflLibrary;

import mx.collections.ArrayList;
import mx.events.CollectionEvent;
import mx.events.PropertyChangeEvent;

import spark.components.DataGrid;
import spark.components.DropDownList;
import spark.components.List;
import spark.components.Window;
import spark.events.GridSelectionEvent;

import com.threerings.util.F;
import com.threerings.util.Log;
import com.threerings.util.StringUtil;

import starling.display.Sprite;

public class Exporter
{
    public static const NA :NativeApplication = NativeApplication.nativeApplication;

    public function Exporter (win :ExporterWindow) {
        Log.setLevel("", Log.INFO);
        _win = win;
        _errors = _win.errors;
        _libraries = _win.libraries;

        function updatePreviewAndExport (..._) :void {
            _win.export.enabled = _exportChooser.dir != null && _libraries.selectionLength > 0 &&
                _libraries.selectedItems.some(function (status :DocStatus, ..._) :Boolean {
                    return status.isValid;
            });

            var status :DocStatus = _libraries.selectedItem as DocStatus;
            _win.preview.enabled = status != null && status.isValid;

            if (_exportChooser.dir == null) return;
            _conf.exportDir = _confFile.parent.getRelativePath(_exportChooser.dir, /*useDotDot=*/true);
        }

        var fileMenuItem :NativeMenuItem;
        if (NativeApplication.supportsMenu) {
            // Grab the existing menu on macs. Use an index to get it as it's not going to be
            // 'File' in all languages
            fileMenuItem = NA.menu.getItemAt(1);
            // Add a separator before the existing close command
            fileMenuItem.submenu.addItemAt(new NativeMenuItem("Sep", /*separator=*/true), 0);
        } else {
            _win.nativeWindow.menu = new NativeMenu();
            fileMenuItem = _win.nativeWindow.menu.addSubmenu(new NativeMenu(), "File");
        }

        // Add save and save as by index to work with the existing items on Mac
        // Mac menus have an existing "Close" item, so everything we add should go ahead of that
        var newMenuItem :NativeMenuItem = fileMenuItem.submenu.addItemAt(new NativeMenuItem("New"), 0);
        newMenuItem.keyEquivalent = "n";
        newMenuItem.addEventListener(Event.SELECT, function (..._) :void {
            _confFile = null;
            _conf = new FlumpConf();
            updatePublisher();
        });
        var openMenuItem :NativeMenuItem =
            fileMenuItem.submenu.addItemAt(new NativeMenuItem("Open..."), 1);
        openMenuItem.keyEquivalent = "o";
        openMenuItem.addEventListener(Event.SELECT, function (..._) :void {
            var file :File = new File();
            file.addEventListener(Event.SELECT, function (..._) :void {
                _confFile = file;
                openConf();
                updatePublisher();
            });
            file.browseForOpen("Open Flump Configuration");
        });
        fileMenuItem.submenu.addItemAt(new NativeMenuItem("Sep", /*separator=*/true), 2);

        const saveMenuItem :NativeMenuItem =
            fileMenuItem.submenu.addItemAt(new NativeMenuItem("Save"), 3);
        saveMenuItem.keyEquivalent = "s";
        function saveConf () :void {
            Files.write(_confFile, function (out :IDataOutput) :void {
                // Set directories relative to where this file is being saved. Fall back to absolute
                // paths if relative paths aren't possible.
                if (_importChooser.dir != null) {
                    _conf.importDir = _confFile.parent.getRelativePath(_importChooser.dir, /*useDotDot=*/true);
                    if (_conf.importDir == null) _conf.importDir = _importChooser.dir.nativePath;
                }

                if (_exportChooser.dir != null) {
                    _conf.exportDir = _confFile.parent.getRelativePath(_exportChooser.dir, /*useDotDot=*/true);
                    if (_conf.exportDir == null) _conf.exportDir = _exportChooser.dir.nativePath;
                }

                out.writeUTFBytes(JSON.stringify(_conf, null, /*space=*/2));
            });
        };
        function saveAs (..._) :void {
            var file :File = new File();
            file.addEventListener(Event.SELECT, function (..._) :void {
                _confFile = file;
                trace("Conf file is now " + _confFile.nativePath);
                _settings.data["CONF_FILE"] = _confFile.nativePath;
                _settings.flush();
                saveConf();
            });
            file.browseForSave("Save Flump Configuration");
        };
        saveMenuItem.addEventListener(Event.SELECT, function (..._) :void {
            if (_confFile == null) saveAs();
            else saveConf();
        });

        function openConf () :void {
            try {
                _conf = FlumpConf.fromJSON(JSONFormat.readJSON(_confFile));
                _win.title = _confFile.name;
                var dir :String = _confFile.parent.resolvePath(_conf.importDir).nativePath;
                setImport(new File(dir));
            } catch (e :Error) {
                log.warning("Unable to parse conf", e);
                _errors.dataProvider.addItem(new ParseError(_confFile.nativePath,
                    ParseError.CRIT, "Unable to read configuration"));
                _confFile = null;
            }
        };

        const saveAsMenuItem :NativeMenuItem =
            fileMenuItem.submenu.addItemAt(new NativeMenuItem("Save As..."), 4);
        saveAsMenuItem.keyEquivalent = "S";
        saveAsMenuItem.addEventListener(Event.SELECT, saveAs);

        if (_settings.data.hasOwnProperty("CONF_FILE")) {
            _confFile = new File(_settings.data["CONF_FILE"]);
            openConf();
        }

        var curSelection :DocStatus = null;
        _libraries.addEventListener(GridSelectionEvent.SELECTION_CHANGE, function (..._) :void {
            log.info("Changed", "selected", _libraries.selectedIndices);
            updatePreviewAndExport();

            if (curSelection != null) {
                curSelection.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, updatePreviewAndExport);
            }
            var newSelection :DocStatus = _libraries.selectedItem as DocStatus;
            if (newSelection != null) {
                newSelection.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, updatePreviewAndExport);
            }
            curSelection = newSelection;
        });
        _win.reload.addEventListener(MouseEvent.CLICK, function (..._) :void {
            setImport(_importChooser.dir);
            updatePreviewAndExport();
        });
        _win.export.addEventListener(MouseEvent.CLICK, function (..._) :void {
            for each (var status :DocStatus in _libraries.selectedItems) {
                exportFlashDocument(status);
            }
        });
        _win.preview.addEventListener(MouseEvent.CLICK, function (..._) :void {
            showPreviewWindow(_libraries.selectedItem.lib);
        });
        _importChooser = new DirChooser(null, _win.importRoot, _win.browseImport);
        _importChooser.changed.add(setImport);
        setImport(_importChooser.dir);
        _exportChooser = new DirChooser(null, _win.exportRoot, _win.browseExport);
        _exportChooser.changed.add(updatePreviewAndExport);
        function updatePublisher (..._) :void {
            if (_confFile != null) {
                _importChooser.dir = (_conf.importDir != null) ? _confFile.parent.resolvePath(_conf.importDir) : null;
                _exportChooser.dir = (_conf.exportDir != null) ? _confFile.parent.resolvePath(_conf.exportDir) : null;
            } else {
                _importChooser.dir = null;
                _exportChooser.dir = null;
            }
            if (_exportChooser.dir == null || _conf.exports.length == 0) _publisher = null;
            else _publisher = new Publisher(_exportChooser.dir, Vector.<ExportConf>(_conf.exports));

            var formatNames :Array = [];
            for each (var export :ExportConf in _conf.exports) formatNames.push(export.name);
            _win.formatOverview.text = formatNames.join(", ");
        };
        _exportChooser.changed.add(updatePublisher);

        var editFormats :EditFormatsWindow;
        _win.editFormats.addEventListener(MouseEvent.CLICK, function (..._) :void {
            if (editFormats == null || editFormats.closed) {
                editFormats = new EditFormatsWindow();
                editFormats.open();
            } else editFormats.orderToFront();

            var dataProvider :ArrayList = new ArrayList(_conf.exports);
            dataProvider.addEventListener(CollectionEvent.COLLECTION_CHANGE, updatePublisher);

            editFormats.exports.dataProvider = dataProvider;
            editFormats.buttonAdd.addEventListener(MouseEvent.CLICK, function (..._) :void {
                var export :ExportConf = new ExportConf();
                export.name = "format" + (_conf.exports.length+1);
                if (_conf.exports.length > 0) {
                    export.format = _conf.exports[0].format;
                }
                dataProvider.addItem(export);
            });
            editFormats.exports.addEventListener(GridSelectionEvent.SELECTION_CHANGE, function (..._) :void {
                editFormats.buttonRemove.enabled = (editFormats.exports.selectedItem != null);
            });
            editFormats.buttonRemove.addEventListener(MouseEvent.CLICK, function (..._) :void {
                for each (var export :ExportConf in editFormats.exports.selectedItems) {
                    dataProvider.removeItem(export);
                }
            });
        });

        updatePublisher();
        _win.addEventListener(Event.CLOSE, function (..._) :void { NA.exit(0); });
    }

    protected function setImport (root :File) :void {
        _libraries.dataProvider.removeAll();
        _errors.dataProvider.removeAll();
        if (root == null) return;
        _rootLen = root.nativePath.length + 1;
        if (_docFinder != null) _docFinder.shutdownNow();
        _docFinder = new Executor();
        findFlashDocuments(root, _docFinder, true);
        _win.reload.enabled = true;
    }

    protected function showPreviewWindow (lib :XflLibrary) :void {
        if (_previewController == null || _previewWindow.closed || _previewControls.closed) {
            _previewWindow = new PreviewWindow();
            _previewControls = new PreviewControlsWindow();
            _previewWindow.started = function (container :Sprite) :void {
                _previewController = new PreviewController(lib, container, _previewWindow,
                    _previewControls);
            }

            _previewWindow.open();
            _previewControls.open();

            preventWindowClose(_previewWindow.nativeWindow);
            preventWindowClose(_previewControls.nativeWindow);

        } else {
            _previewController.lib = lib;
            _previewWindow.nativeWindow.visible = true;
            _previewControls.nativeWindow.visible = true;
        }

        _previewWindow.orderToFront();
        _previewControls.orderToFront();
    }

    // Causes a window to be hidden, rather than closed, when its close box is clicked
    protected static function preventWindowClose (window :NativeWindow) :void {
        window.addEventListener(Event.CLOSING, function (e :Event) :void {
            e.preventDefault();
            window.visible = false;
        });
    }

    protected var _previewController :PreviewController;
    protected var _previewWindow :PreviewWindow;
    protected var _previewControls :PreviewControlsWindow;

    protected function findFlashDocuments (base :File, exec :Executor,
        ignoreXflAtBase :Boolean = false) :void {
        Files.list(base, exec).succeeded.add(function (files :Array) :void {
            if (exec.isShutdown) return;
            for each (var file :File in files) {
                if (Files.hasExtension(file, "xfl")) {
                    if (ignoreXflAtBase) {
                        _errors.dataProvider.addItem(new ParseError(base.nativePath,
                            ParseError.CRIT, "The import directory can't be an XFL directory, did you mean " +
                            base.parent.nativePath + "?"));
                    } else addFlashDocument(file);
                    return;
                }
            }
            for each (file in files) {
                if (StringUtil.startsWith(file.name, ".", "RECOVER_")) {
                    continue; // Ignore hidden VCS directories, and recovered backups created by Flash
                }
                if (file.isDirectory) findFlashDocuments(file, exec);
                else addFlashDocument(file);
            }
        });
    }

    protected function exportFlashDocument (status :DocStatus) :void {
        const stage :Stage = NA.activeWindow.stage;
        const prevQuality :String = stage.quality;

        stage.quality = StageQuality.BEST;
        _publisher.publish(status.lib);

        stage.quality = prevQuality;
        status.updateModified(Ternary.FALSE);
    }

    protected function addFlashDocument (file :File) :void {
        var name :String = file.nativePath.substring(_rootLen).replace(
            new RegExp("\\" + File.separator, "g"), "/");
        var load :Future;
        switch (Files.getExtension(file)) {
        case "xfl":
            name = name.substr(0, name.lastIndexOf("/"));
            load = new XflLoader().load(name, file.parent);
            break;
        case "fla":
            name = name.substr(0, name.lastIndexOf("."));
            load = new FlaLoader().load(name, file);
            break;
        default:
            // Unsupported file type, ignore
            return;
        }

        const status :DocStatus = new DocStatus(name, _rootLen, Ternary.UNKNOWN, Ternary.UNKNOWN, null);
        _libraries.dataProvider.addItem(status);

        load.succeeded.add(function (lib :XflLibrary) :void {
            status.lib = lib;
            status.updateModified(Ternary.of(_publisher == null || _publisher.modified(lib)));
            for each (var err :ParseError in lib.getErrors()) _errors.dataProvider.addItem(err);
            status.updateValid(Ternary.of(lib.valid));
        });
        load.failed.add(function (error :Error) :void {
            trace("Failed to load " + file.nativePath + ": " + error);
            status.updateValid(Ternary.FALSE);
            throw error;
        });
    }

    protected var _rootLen :int;

    protected var _publisher :Publisher;
    protected var _docFinder :Executor;
    protected var _win :ExporterWindow;
    protected var _libraries :DataGrid;
    protected var _errors :DataGrid;
    protected var _exportChooser :DirChooser;
    protected var _importChooser :DirChooser;
    protected var _authoredResolution :DropDownList;
    protected var _conf :FlumpConf = new FlumpConf();
    protected var _confFile :File;
    protected const _settings :SharedObject = SharedObject.getLocal("flump/Exporter");

    private static const log :Log = Log.getLog(Exporter);
}
}

import flash.events.EventDispatcher;

import flump.export.Ternary;
import flump.xfl.XflLibrary;

import mx.core.IPropertyChangeNotifier;
import mx.events.PropertyChangeEvent;

class DocStatus extends EventDispatcher implements IPropertyChangeNotifier {
    public var path :String;
    public var modified :String;
    public var valid :String = QUESTION;
    public var lib :XflLibrary;

    public function DocStatus (path :String, rootLen :int, modified :Ternary, valid :Ternary, lib :XflLibrary) {
        this.lib = lib;
        this.path = path;
        _uid = path;

        updateModified(modified);
        updateValid(valid);
    }

    public function updateValid (newValid :Ternary) :void {
        changeField("valid", function (..._) :void {
            if (newValid == Ternary.TRUE) valid = CHECK;
            else if (newValid == Ternary.FALSE) valid = FROWN;
            else valid = QUESTION;
        });
    }

    public function get isValid () :Boolean { return valid == CHECK; }

    public function updateModified (newModified :Ternary) :void {
        changeField("modified", function (..._) :void {
            if (newModified == Ternary.TRUE) modified = CHECK;
            else if (newModified == Ternary.FALSE) modified = " ";
            else modified = QUESTION;
        });
    }

    protected function changeField(fieldName :String, modifier :Function) :void {
        const oldValue :Object = this[fieldName];
        modifier();
        const newValue :Object = this[fieldName];
        dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, fieldName, oldValue, newValue));
    }

    public function get uid () :String { return _uid; }
    public function set uid (uid :String) :void { _uid = uid; }

    protected var _uid :String;

    protected static const QUESTION :String = "?";
    protected static const FROWN :String = "☹";
    protected static const CHECK :String = "✓";
}

Fix for when importDir is an absolute path.

We still need to handle absolute paths, since on Windows you can't have a
relative path between different drive letters. Plus, absolute paths may be
useful in some cases anyways.


package com.kaltura.kdpfl.plugin
{
	import com.kaltura.hls.SubtitleTrait;
	import com.kaltura.kdpfl.model.MediaProxy;
	import com.kaltura.kdpfl.model.type.NotificationType;
	import com.kaltura.kdpfl.view.controls.KTrace;

	import org.osmf.events.MediaElementEvent;
	import org.osmf.traits.DVRTrait;
	import org.osmf.traits.MediaTraitType;
	import org.puremvc.as3.interfaces.INotification;
	import org.puremvc.as3.patterns.mediator.Mediator;

	public class KalturaHLSMediator extends Mediator
	{
		public static const NAME:String = "KalturaHLSMediator";
		public static const HLS_END_LIST:String = "hlsEndList";
		public static const HLS_TRACK_SWITCH:String = "doTextTrackSwitch";

		private var _mediaProxy:MediaProxy;
		private var _subtitleTrait:SubtitleTrait;

		public function KalturaHLSMediator( viewComponent:Object=null)
		{
			super(NAME, viewComponent);
		}

		override public function onRegister():void
		{
			_mediaProxy = facade.retrieveProxy(MediaProxy.NAME) as MediaProxy;
			super.onRegister();
		}

		override public function listNotificationInterests():Array
		{
			return [
				NotificationType.DURATION_CHANGE,
				NotificationType.MEDIA_ELEMENT_READY,
				HLS_TRACK_SWITCH
			];
		}

		override public function handleNotification(notification:INotification):void
		{
			if ( notification.getName() == NotificationType.DURATION_CHANGE ) {
				if ( _mediaProxy.vo.isLive && _mediaProxy.vo.media.hasTrait(MediaTraitType.DVR) ) {
					var dvrTrait:DVRTrait = _mediaProxy.vo.media.getTrait(MediaTraitType.DVR) as DVRTrait;
					//recording stopped - endlist
					if ( !dvrTrait.isRecording ) {
						sendNotification( HLS_END_LIST );
					}
				}
			}

			if ( notification.getName() == NotificationType.MEDIA_ELEMENT_READY ) {
				_mediaProxy.vo.media.addEventListener(MediaElementEvent.TRAIT_ADD, getSubtitleTrait); // catch and save SubtitleTrait the moment video object is ready
			}

			if ( notification.getName() == HLS_TRACK_SWITCH ) { //trigered by JS changeEmbeddedTextTrack helper in order to change language
				if ( _subtitleTrait && notification.getBody() && notification.getBody().hasOwnProperty("textIndex"))
					_subtitleTrait.language = _subtitleTrait.languages[ notification.getBody().textIndex ]; // change the language index inside subtitleTrait reference of video object
				else
					KTrace.getInstance().log("KalturaHLSMediator :: doTextTrackSwitch >> subtitleTrait or textIndex error.");
			}

		}

		protected function getSubtitleTrait(event:MediaElementEvent):void
		{
			if(event.traitType == SubtitleTrait.TYPE){
				_mediaProxy.vo.media.removeEventListener(MediaElementEvent.TRAIT_ADD, getSubtitleTrait);
				_subtitleTrait = _mediaProxy.vo.media.getTrait( SubtitleTrait.TYPE ) as SubtitleTrait; // save SubtitleTrait in order to read languages
				if ( _subtitleTrait && _subtitleTrait.languages.length > 0 )
				{
					var langArray:Array = new Array();
					var i:int = 0;
					while (i < _subtitleTrait.languages.length){
						langArray.push({"label":_subtitleTrait.languages[0], "index": i++}); // build languages array in a format that JS expects to receive
					}

					sendNotification("textTracksReceived", {languages:langArray}); //will triger ClosedCaptions textTracksReceived function, through kplayer onTextTracksReceived listener
				}
			}
		}

	}
}

package com.kaltura.kdpfl.plugin
{
	import com.kaltura.hls.SubtitleTrait;
	import com.kaltura.kdpfl.model.MediaProxy;
	import com.kaltura.kdpfl.model.type.NotificationType;
	import com.kaltura.kdpfl.view.controls.KTrace;

	import org.osmf.events.MediaElementEvent;
	import org.osmf.traits.DVRTrait;
	import org.osmf.traits.MediaTraitType;
	import org.puremvc.as3.interfaces.INotification;
	import org.puremvc.as3.patterns.mediator.Mediator;

	public class KalturaHLSMediator extends Mediator
	{
		public static const NAME:String = "KalturaHLSMediator";
		public static const HLS_END_LIST:String = "hlsEndList";
		public static const HLS_TRACK_SWITCH:String = "doTextTrackSwitch";

		private var _mediaProxy:MediaProxy;
		private var _subtitleTrait:SubtitleTrait;

		public function KalturaHLSMediator( viewComponent:Object=null)
		{
			super(NAME, viewComponent);
		}

		override public function onRegister():void
		{
			_mediaProxy = facade.retrieveProxy(MediaProxy.NAME) as MediaProxy;
			super.onRegister();
		}

		override public function listNotificationInterests():Array
		{
			return [
				NotificationType.DURATION_CHANGE,
				NotificationType.MEDIA_ELEMENT_READY,
				HLS_TRACK_SWITCH
			];
		}

		override public function handleNotification(notification:INotification):void
		{
			if ( notification.getName() == NotificationType.DURATION_CHANGE ) {
				if ( _mediaProxy.vo.isLive && _mediaProxy.vo.media.hasTrait(MediaTraitType.DVR) ) {
					var dvrTrait:DVRTrait = _mediaProxy.vo.media.getTrait(MediaTraitType.DVR) as DVRTrait;
					//recording stopped - endlist
					if ( !dvrTrait.isRecording ) {
						sendNotification( HLS_END_LIST );
					}
				}
			}

			if ( notification.getName() == NotificationType.MEDIA_ELEMENT_READY ) {
				_mediaProxy.vo.media.addEventListener(MediaElementEvent.TRAIT_ADD, getSubtitleTrait); // catch and save SubtitleTrait the moment video object is ready
			}

			if ( notification.getName() == HLS_TRACK_SWITCH ) { //trigered by JS changeEmbeddedTextTrack helper in order to change language
				if ( _subtitleTrait && notification.getBody() && notification.getBody().hasOwnProperty("textIndex"))
					_subtitleTrait.language = _subtitleTrait.languages[ notification.getBody().textIndex ]; // change the language index inside subtitleTrait reference of video object
				else
					KTrace.getInstance().log("KalturaHLSMediator :: doTextTrackSwitch >> subtitleTrait or textIndex error.");
			}

		}

		protected function getSubtitleTrait(event:MediaElementEvent):void
		{
			if(event.traitType == SubtitleTrait.TYPE){
				_mediaProxy.vo.media.removeEventListener(MediaElementEvent.TRAIT_ADD, getSubtitleTrait);
				_subtitleTrait = _mediaProxy.vo.media.getTrait( SubtitleTrait.TYPE ) as SubtitleTrait; // save SubtitleTrait in order to read languages
				if ( _subtitleTrait && _subtitleTrait.languages.length > 0 )
				{
					var langArray:Array = new Array();
					var i:int = 0;
					while (i < _subtitleTrait.languages.length){
						langArray.push({"label":_subtitleTrait.languages[i], "index": i++}); // build languages array in a format that JS expects to receive
					}

					sendNotification("textTracksReceived", {languages:langArray}); //will triger ClosedCaptions textTracksReceived function, through kplayer onTextTracksReceived listener
				}
			}
		}

	}
}

fix for languages labels

fix for languages labels

VebVTT captions multi language stream - CC plugin menu was pulled with
all the languages traits as eng. FIXED