//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.util {

import flash.system.Capabilities;

/**
 * Simple implementation of assertion checks for debug environments.
 * When running in a debug player, each function will test the assert expression,
 * and if it fails, log an error message with a stack trace. When running in a
 * release player, these functions do not run any tests, and exit immediately.
 *
 * Note: stack dumping is controlled via the Assert.dumpStack parameter.
 *
 * Usage example:
 * <pre>
 *   Assert.isNotNull(mystack.top());
 *   Assert.isTrue(mystack.length == 1, "Unexpected number of items on stack!");
 * </pre>
 */
public class Assert
{
    /** Controls whether stack dumps should be included in the error log (default value is true).*/
    public static var dumpStack :Boolean = true;

    /** Asserts that the value is equal to null. */
    public static function isNull (value :*, message :String = null) :void
    {
        if (_debug && ! (value == null)) {
            fail(message);
        }
    }

    /** Asserts that the value is not equal to null. */
    public static function isNotNull (value :*, message :String = null) :void
    {
        if (_debug && ! (value != null)) {
            fail(message);
        }
    }

    /** Asserts that the value is false. */
    public static function isFalse (value :Boolean, message :String = null) :void
    {
        if (_debug && value) {
            fail(message);
        }
    }

    /** Asserts that the value is true. */
    public static function isTrue (value :Boolean, message :String = null) :void
    {
        if (_debug && ! value) {
            fail(message);
        }
    }

    /** Displays an error message, with an optional stack trace. */
    public static function fail (message :String) :void
    {
        _log.warning("Failure" + ((message != null) ? (": " + message) : ""));
        if (dumpStack) {
            _log.warning(new Error("dumpStack").getStackTrace());
        }
    }

    protected static var _log :Log = Log.getLog(Assert);
    protected static var _debug :Boolean = Capabilities.isDebugger;
}
}

//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.util {

import flash.system.Capabilities;

/**
 * Simple implementation of assertion checks for debug environments.
 * When running in a debug player, each function will test the assert expression,
 * and if it fails, log an error message with a stack trace. When running in a
 * release player, these functions do not run any tests, and exit immediately.
 *
 * Note: stack dumping is controlled via the Assert.dumpStack parameter.
 *
 * Usage example:
 * <pre>
 *   Assert.isNotNull(mystack.top());
 *   Assert.isTrue(mystack.length == 1, "Unexpected number of items on stack!");
 * </pre>
 */
public class Assert
{
    /** Controls whether stack dumps should be included in the error log (default value is true).*/
    public static var dumpStack :Boolean = true;

    /** Asserts that the value is equal to null. */
    public static function isNull (value :Object, message :String = null) :void
    {
        if (_debug && ! (value == null)) {
            fail(message);
        }
    }

    /** Asserts that the value is not equal to null. */
    public static function isNotNull (value :Object, message :String = null) :void
    {
        if (_debug && ! (value != null)) {
            fail(message);
        }
    }

    /** Asserts that the value is false. */
    public static function isFalse (value :Boolean, message :String = null) :void
    {
        if (_debug && value) {
            fail(message);
        }
    }

    /** Asserts that the value is true. */
    public static function isTrue (value :Boolean, message :String = null) :void
    {
        if (_debug && ! value) {
            fail(message);
        }
    }

    /** Displays an error message, with an optional stack trace. */
    public static function fail (message :String) :void
    {
        _log.warning("Failure" + ((message != null) ? (": " + message) : ""));
        if (dumpStack) {
            _log.warning(new Error("dumpStack").getStackTrace());
        }
    }

    protected static var _log :Log = Log.getLog(Assert);
    protected static var _debug :Boolean = Capabilities.isDebugger;
}
}

Make it explicit in the function signature that we're just doing an
"Object == null" comparison. Otherwise the user might pass in an
undefined, and expect a strict equality test.

git-svn-id: a1a4b28b82a3276cc491891159dd9963a0a72fae@4886 542714f4-19e9-0310-aa3c-eee0fc999fb1


// vim: tabstop=4 shiftwidth=4

// Copyright (c) 2010 , NetEase.com,Inc. All rights reserved.
//
// Author: Yang Bo (pop.atry@gmail.com)
//
// Use, modification and distribution are subject to the "New BSD License"
// as listed at <url: http://www.opensource.org/licenses/bsd-license.php >.

package com.netease.protobuf {
	import flash.errors.*;
	import flash.utils.*;
	public final class WritingBuffer extends ByteArray {
		private const _slices:ByteArray = new ByteArray
		public function beginBlock():uint {
			_slices.writeUnsignedInt(position)
			const beginSliceIndex:uint = _slices.length
			if (beginSliceIndex % 8 != 4) {
				throw new IllegalOperationError
			}
			_slices.writeDouble(0)
			_slices.writeUnsignedInt(position)
			return beginSliceIndex
		}
		public function endBlock(beginSliceIndex:uint):void {
			if (_slices.length % 8 != 0) {
				throw new IllegalOperationError
			}
			_slices.writeUnsignedInt(position)
			_slices.position = beginSliceIndex + 8
			const beginPosition:uint = _slices.readUnsignedInt()
			_slices.position = beginSliceIndex
			_slices.writeUnsignedInt(position)
			WriteUtils.write_TYPE_UINT32(this, position - beginPosition)
			_slices.writeUnsignedInt(position)
			_slices.position = _slices.length
			_slices.writeUnsignedInt(position)
		}
		public function toNormal(output:IDataOutput):void {
			if (_slices.length % 8 != 0) {
				throw new IllegalOperationError
			}
			_slices.position = 0
			var begin:uint = 0
			while (_slices.bytesAvailable > 0) {
				var end:uint = _slices.readUnsignedInt()
				if (end > begin) {
					output.writeBytes(this, begin, end - begin)
				} else if (end < begin) {
					throw new IllegalOperationError
				}
				begin = _slices.readUnsignedInt()
			}
			if (begin < length) {
				output.writeBytes(this, begin)
			}
		}
	}
}

// vim: tabstop=4 shiftwidth=4

// Copyright (c) 2010 , NetEase.com,Inc. All rights reserved.
//
// Author: Yang Bo (pop.atry@gmail.com)
//
// Use, modification and distribution are subject to the "New BSD License"
// as listed at <url: http://www.opensource.org/licenses/bsd-license.php >.

package com.netease.protobuf {
	import flash.errors.*;
	import flash.utils.*;
	/**
	 * @private
	 */
	public final class WritingBuffer extends ByteArray {
		private const _slices:ByteArray = new ByteArray
		public function beginBlock():uint {
			_slices.writeUnsignedInt(position)
			const beginSliceIndex:uint = _slices.length
			if (beginSliceIndex % 8 != 4) {
				throw new IllegalOperationError
			}
			_slices.writeDouble(0)
			_slices.writeUnsignedInt(position)
			return beginSliceIndex
		}
		public function endBlock(beginSliceIndex:uint):void {
			if (_slices.length % 8 != 0) {
				throw new IllegalOperationError
			}
			_slices.writeUnsignedInt(position)
			_slices.position = beginSliceIndex + 8
			const beginPosition:uint = _slices.readUnsignedInt()
			_slices.position = beginSliceIndex
			_slices.writeUnsignedInt(position)
			WriteUtils.write_TYPE_UINT32(this, position - beginPosition)
			_slices.writeUnsignedInt(position)
			_slices.position = _slices.length
			_slices.writeUnsignedInt(position)
		}
		public function toNormal(output:IDataOutput):void {
			if (_slices.length % 8 != 0) {
				throw new IllegalOperationError
			}
			_slices.position = 0
			var begin:uint = 0
			while (_slices.bytesAvailable > 0) {
				var end:uint = _slices.readUnsignedInt()
				if (end > begin) {
					output.writeBytes(this, begin, end - begin)
				} else if (end < begin) {
					throw new IllegalOperationError
				}
				begin = _slices.readUnsignedInt()
			}
			if (begin < length) {
				output.writeBytes(this, begin)
			}
		}
	}
}

把 WritingBuffer 标记为 @private

把 WritingBuffer 标记为 @private


package text
{
	import flash.system.Capabilities;
	import org.flixel.*;

	public class TextState extends FlxState
	{
		override public function create():void
		{
			var os:String = Capabilities.version.substr(0, 3);
			var isMobile:Boolean = false;

			if (os == "AND") {
				isMobile = true;
			} else {
				var mobileSystems:Array = new Array("Windows SmartPhone", "Windows PocketPC", "Windows Mobile");
				isMobile = (mobileSystems.indexOf(Capabilities.os) != -1)
			}

			var helloWorld:FlxText = new FlxText(10, 10, 620, String(isMobile))
			helloWorld.setFormat("system", 16, FlxG.WHITE, "center", FlxG.PINK)
			add(helloWorld)

			var leftAlignedText:FlxText = new FlxText(10, 70, 150, "This text is left-aligned")
			leftAlignedText.setFormat("system", 16, FlxG.RED, "left")
			add(leftAlignedText)

			var rightAlignedText:FlxText = new FlxText(480, 70, 150, "This text is right-aligned")
			rightAlignedText.setFormat("system", 16, FlxG.GREEN, "right")
			add(rightAlignedText)

			var abouFlixel:FlxText = new FlxText(10, 140, 620, "Flixel is an open source game-making library that is completely free for personal or commercial use. Written entirely in ActionScript 3 by Adam “Atomic” Saltsman, and designed to be used with free development tools, Flixel is easy to learn, extend and customize.")
			abouFlixel.setFormat("system", 15, FlxG.BLUE, "left")
			add(abouFlixel)
		}

	}
}

package text
{
	import org.flixel.*;

	public class TextState extends FlxState
	{
		override public function create():void
		{
			var helloWorld:FlxText = new FlxText(10, 10, 620, "Hello World!")
			helloWorld.setFormat("system", 16, FlxG.WHITE, "center", FlxG.PINK)
			add(helloWorld)

			var leftAlignedText:FlxText = new FlxText(10, 70, 150, "This text is left-aligned")
			leftAlignedText.setFormat("system", 16, FlxG.RED, "left")
			add(leftAlignedText)

			var rightAlignedText:FlxText = new FlxText(480, 70, 150, "This text is right-aligned")
			rightAlignedText.setFormat("system", 16, FlxG.GREEN, "right")
			add(rightAlignedText)

			var abouFlixel:FlxText = new FlxText(10, 140, 620, "Flixel is an open source game-making library that is completely free for personal or commercial use. Written entirely in ActionScript 3 by Adam “Atomic” Saltsman, and designed to be used with free development tools, Flixel is easy to learn, extend and customize.")
			abouFlixel.setFormat("system", 15, FlxG.BLUE, "left")
			add(abouFlixel)
		}

	}
}

Reset text test changes

Reset text test changes


package
{
    import flash.display.Sprite;
    import flash.text.TextField;
    import flash.net.Socket;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.SecurityErrorEvent;
    import flash.utils.ByteArray;
    import flash.utils.setTimeout;

    [SWF(width="400", height="300")]
    public class swfcat extends Sprite
    {
        /* David's bridge (nickname eRYaZuvY02FpExln) that also serves a
           crossdomain policy. */
        private const DEFAULT_TOR_ADDR:Object = {
            // host: "173.255.221.44", 3VXRyxz67OeRoqHn
            host: "69.164.193.231",
            port: 9001
        };

        // Milliseconds.
        private const FACILITATOR_POLL_INTERVAL:int = 10000;

        // Socket to facilitator.
        private var s_f:Socket;

        private var output_text:TextField;

        private var fac_addr:Object;

        public function puts(s:String):void
        {
            output_text.appendText(s + "\n");
            output_text.scrollV = output_text.maxScrollV;
        }

        public function swfcat()
        {
            output_text = new TextField();
            output_text.width = 400;
            output_text.height = 300;
            output_text.background = true;
            output_text.backgroundColor = 0x001f0f;
            output_text.textColor = 0x44CC44;
            addChild(output_text);

            puts("Starting.");
            // Wait until the query string parameters are loaded.
            this.loaderInfo.addEventListener(Event.COMPLETE, loaderinfo_complete);
        }

        private function loaderinfo_complete(e:Event):void
        {
            var fac_spec:String;

            puts("Parameters loaded.");
            fac_spec = this.loaderInfo.parameters["facilitator"];
            if (!fac_spec) {
                puts("Error: no \"facilitator\" specification provided.");
                return;
            }
            puts("Facilitator spec: \"" + fac_spec + "\"");
            fac_addr = parse_addr_spec(fac_spec);
            if (!fac_addr) {
                puts("Error: Facilitator spec must be in the form \"host:port\".");
                return;
            }

            main();
        }

        /* The main logic begins here, after start-up issues are taken care of. */
        private function main():void
        {
            s_f = new Socket();

            s_f.addEventListener(Event.CONNECT, fac_connected);
            s_f.addEventListener(Event.CLOSE, function (e:Event):void {
                puts("Facilitator: closed connection.");
                setTimeout(main, FACILITATOR_POLL_INTERVAL);
            });
            s_f.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Facilitator: I/O error: " + e.text + ".");
            });
            s_f.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Facilitator: security error: " + e.text + ".");
            });

            puts("Facilitator: connecting to " + fac_addr.host + ":" + fac_addr.port + ".");
            s_f.connect(fac_addr.host, fac_addr.port);
        }

        private function fac_connected(e:Event):void
        {
            puts("Facilitator: connected.");

            s_f.addEventListener(ProgressEvent.SOCKET_DATA, fac_data);

            s_f.writeUTFBytes("GET / HTTP/1.0\r\n\r\n");
        }

        private function fac_data(e:ProgressEvent):void
        {
            var client_spec:String;
            var client_addr:Object;
            var proxy_pair:Object;

            client_spec = s_f.readMultiByte(e.bytesLoaded, "utf-8");
            puts("Facilitator: got \"" + client_spec + "\"");

            client_addr = parse_addr_spec(client_spec);
            if (!client_addr) {
                puts("Error: Client spec must be in the form \"host:port\".");
                return;
            }
            if (client_addr.host == "0.0.0.0" && client_addr.port == 0) {
                puts("Error: Facilitator has no clients.");
                return;
            }

            proxy_pair = new ProxyPair(this, client_addr, DEFAULT_TOR_ADDR);
            proxy_pair.connect();
        }

        /* Parse an address in the form "host:port". Returns an Object with
           keys "host" (String) and "port" (int). Returns null on error. */
        private static function parse_addr_spec(spec:String):Object
        {
            var parts:Array;
            var addr:Object;

            parts = spec.split(":", 2);
            if (parts.length != 2 || !parseInt(parts[1]))
                return null;
            addr = {}
            addr.host = parts[0];
            addr.port = parseInt(parts[1]);

            return addr;
        }
    }
}

import flash.display.Sprite;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.net.Socket;
import flash.utils.ByteArray;

/* An instance of a client-relay connection. */
class ProxyPair
{
    // Address ({host, port}) of client.
    private var addr_c:Object;
    // Address ({host, port}) of relay.
    private var addr_r:Object;

    // Socket to client.
    private var s_c:Socket;
    // Socket to relay.
    private var s_r:Socket;

    // Parent swfcat, for UI updates.
    private var ui:swfcat;

    private function log(msg:String):void
    {
        ui.puts(id() + ": " + msg)
    }

    // String describing this pair for output.
    private function id():String
    {
        return "<" + this.addr_c.host + ":" + this.addr_c.port +
            "," + this.addr_r.host + ":" + this.addr_r.port + ">";
    }

    public function ProxyPair(ui:swfcat, addr_c:Object, addr_r:Object)
    {
        this.ui = ui;
        this.addr_c = addr_c;
        this.addr_r = addr_r;
    }

    public function connect():void
    {
        s_r = new Socket();

        s_r.addEventListener(Event.CONNECT, tor_connected);
        s_r.addEventListener(Event.CLOSE, function (e:Event):void {
            log("Tor: closed.");
            if (s_c.connected)
                s_c.close();
        });
        s_r.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
            log("Tor: I/O error: " + e.text + ".");
            if (s_c.connected)
                s_c.close();
        });
        s_r.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
            log("Tor: security error: " + e.text + ".");
            if (s_c.connected)
                s_c.close();
        });

        log("Tor: connecting to " + addr_r.host + ":" + addr_r.port + ".");
        s_r.connect(addr_r.host, addr_r.port);
    }

    private function tor_connected(e:Event):void
    {
        log("Tor: connected.");

        s_c = new Socket();

        s_c.addEventListener(Event.CONNECT, client_connected);
        s_c.addEventListener(Event.CLOSE, function (e:Event):void {
            log("Client: closed.");
            if (s_r.connected)
                s_r.close();
        });
        s_c.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
            log("Client: I/O error: " + e.text + ".");
            if (s_r.connected)
                s_r.close();
        });
        s_c.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
            log("Client: security error: " + e.text + ".");
            if (s_r.connected)
                s_r.close();
        });

        log("Client: connecting to " + addr_c.host + ":" + addr_c.port + ".");
        s_c.connect(addr_c.host, addr_c.port);
    }

    private function client_connected(e:Event):void
    {
        log("Client: connected.");

        s_r.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
            var bytes:ByteArray = new ByteArray();
            s_r.readBytes(bytes, 0, e.bytesLoaded);
            log("Tor: read " + bytes.length + ".");
            s_c.writeBytes(bytes);
        });
        s_c.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
            var bytes:ByteArray = new ByteArray();
            s_c.readBytes(bytes, 0, e.bytesLoaded);
            log("Client: read " + bytes.length + ".");
            s_r.writeBytes(bytes);
        });
    }
}

package
{
    import flash.display.Sprite;
    import flash.text.TextField;
    import flash.net.Socket;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.SecurityErrorEvent;
    import flash.utils.ByteArray;
    import flash.utils.setTimeout;

    [SWF(width="640", height="480")]
    public class swfcat extends Sprite
    {
        /* David's bridge (nickname eRYaZuvY02FpExln) that also serves a
           crossdomain policy. */
        private const DEFAULT_TOR_ADDR:Object = {
            // host: "173.255.221.44", 3VXRyxz67OeRoqHn
            host: "69.164.193.231",
            port: 9001
        };

        // Milliseconds.
        private const FACILITATOR_POLL_INTERVAL:int = 10000;

        // Socket to facilitator.
        private var s_f:Socket;

        private var output_text:TextField;

        private var fac_addr:Object;

        public function puts(s:String):void
        {
            output_text.appendText(s + "\n");
            output_text.scrollV = output_text.maxScrollV;
        }

        public function swfcat()
        {
            output_text = new TextField();
            output_text.width = 640;
            output_text.height = 480;
            output_text.background = true;
            output_text.backgroundColor = 0x001f0f;
            output_text.textColor = 0x44CC44;
            addChild(output_text);

            puts("Starting.");
            // Wait until the query string parameters are loaded.
            this.loaderInfo.addEventListener(Event.COMPLETE, loaderinfo_complete);
        }

        private function loaderinfo_complete(e:Event):void
        {
            var fac_spec:String;

            puts("Parameters loaded.");
            fac_spec = this.loaderInfo.parameters["facilitator"];
            if (!fac_spec) {
                puts("Error: no \"facilitator\" specification provided.");
                return;
            }
            puts("Facilitator spec: \"" + fac_spec + "\"");
            fac_addr = parse_addr_spec(fac_spec);
            if (!fac_addr) {
                puts("Error: Facilitator spec must be in the form \"host:port\".");
                return;
            }

            main();
        }

        /* The main logic begins here, after start-up issues are taken care of. */
        private function main():void
        {
            s_f = new Socket();

            s_f.addEventListener(Event.CONNECT, fac_connected);
            s_f.addEventListener(Event.CLOSE, function (e:Event):void {
                puts("Facilitator: closed connection.");
                setTimeout(main, FACILITATOR_POLL_INTERVAL);
            });
            s_f.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Facilitator: I/O error: " + e.text + ".");
            });
            s_f.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Facilitator: security error: " + e.text + ".");
            });

            puts("Facilitator: connecting to " + fac_addr.host + ":" + fac_addr.port + ".");
            s_f.connect(fac_addr.host, fac_addr.port);
        }

        private function fac_connected(e:Event):void
        {
            puts("Facilitator: connected.");

            s_f.addEventListener(ProgressEvent.SOCKET_DATA, fac_data);

            s_f.writeUTFBytes("GET / HTTP/1.0\r\n\r\n");
        }

        private function fac_data(e:ProgressEvent):void
        {
            var client_spec:String;
            var client_addr:Object;
            var proxy_pair:Object;

            client_spec = s_f.readMultiByte(e.bytesLoaded, "utf-8");
            puts("Facilitator: got \"" + client_spec + "\"");

            client_addr = parse_addr_spec(client_spec);
            if (!client_addr) {
                puts("Error: Client spec must be in the form \"host:port\".");
                return;
            }
            if (client_addr.host == "0.0.0.0" && client_addr.port == 0) {
                puts("Error: Facilitator has no clients.");
                return;
            }

            proxy_pair = new ProxyPair(this, client_addr, DEFAULT_TOR_ADDR);
            proxy_pair.connect();
        }

        /* Parse an address in the form "host:port". Returns an Object with
           keys "host" (String) and "port" (int). Returns null on error. */
        private static function parse_addr_spec(spec:String):Object
        {
            var parts:Array;
            var addr:Object;

            parts = spec.split(":", 2);
            if (parts.length != 2 || !parseInt(parts[1]))
                return null;
            addr = {}
            addr.host = parts[0];
            addr.port = parseInt(parts[1]);

            return addr;
        }
    }
}

import flash.display.Sprite;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.net.Socket;
import flash.utils.ByteArray;

/* An instance of a client-relay connection. */
class ProxyPair
{
    // Address ({host, port}) of client.
    private var addr_c:Object;
    // Address ({host, port}) of relay.
    private var addr_r:Object;

    // Socket to client.
    private var s_c:Socket;
    // Socket to relay.
    private var s_r:Socket;

    // Parent swfcat, for UI updates.
    private var ui:swfcat;

    private function log(msg:String):void
    {
        ui.puts(id() + ": " + msg)
    }

    // String describing this pair for output.
    private function id():String
    {
        return "<" + this.addr_c.host + ":" + this.addr_c.port +
            "," + this.addr_r.host + ":" + this.addr_r.port + ">";
    }

    public function ProxyPair(ui:swfcat, addr_c:Object, addr_r:Object)
    {
        this.ui = ui;
        this.addr_c = addr_c;
        this.addr_r = addr_r;
    }

    public function connect():void
    {
        s_r = new Socket();

        s_r.addEventListener(Event.CONNECT, tor_connected);
        s_r.addEventListener(Event.CLOSE, function (e:Event):void {
            log("Tor: closed.");
            if (s_c.connected)
                s_c.close();
        });
        s_r.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
            log("Tor: I/O error: " + e.text + ".");
            if (s_c.connected)
                s_c.close();
        });
        s_r.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
            log("Tor: security error: " + e.text + ".");
            if (s_c.connected)
                s_c.close();
        });

        log("Tor: connecting to " + addr_r.host + ":" + addr_r.port + ".");
        s_r.connect(addr_r.host, addr_r.port);
    }

    private function tor_connected(e:Event):void
    {
        log("Tor: connected.");

        s_c = new Socket();

        s_c.addEventListener(Event.CONNECT, client_connected);
        s_c.addEventListener(Event.CLOSE, function (e:Event):void {
            log("Client: closed.");
            if (s_r.connected)
                s_r.close();
        });
        s_c.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
            log("Client: I/O error: " + e.text + ".");
            if (s_r.connected)
                s_r.close();
        });
        s_c.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
            log("Client: security error: " + e.text + ".");
            if (s_r.connected)
                s_r.close();
        });

        log("Client: connecting to " + addr_c.host + ":" + addr_c.port + ".");
        s_c.connect(addr_c.host, addr_c.port);
    }

    private function client_connected(e:Event):void
    {
        log("Client: connected.");

        s_r.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
            var bytes:ByteArray = new ByteArray();
            s_r.readBytes(bytes, 0, e.bytesLoaded);
            log("Tor: read " + bytes.length + ".");
            s_c.writeBytes(bytes);
        });
        s_c.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
            var bytes:ByteArray = new ByteArray();
            s_c.readBytes(bytes, 0, e.bytesLoaded);
            log("Client: read " + bytes.length + ".");
            s_r.writeBytes(bytes);
        });
    }
}

Increase size of movie to 640×480 from 400×300.


////////////////////////////////////////////////////////////////////////////////
//
//  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.display.IBitmapDrawable;
import flash.utils.*;
import flash.net.*;
import flash.events.*;
import flash.display.*;
import flash.geom.Matrix;

/**
 *  WaitForLayoutManager - waits for LayoutManager to complete validation
 *  MXML attributes:
 *  timeout (optional)
 */
public class WaitForLayoutManager extends TestStep
{

	private var eventListenerListening:Boolean = false;

	/**
	 *  Test the value of a property, log result if failure.
	 */
	override public function execute(root:DisplayObject, context:UnitTester, testCase:TestCase, testResult:TestResult):Boolean
	{

		this.root = root;
		this.context = context;
		this.testCase = testCase;
		this.testResult = testResult;

		waitEvent = eventName;
		waitTarget = target;

		var actualTarget:Object = context.stringToObject(target);
		if (actualTarget)
		{
			actualTarget.addEventListener(eventName, eventListener);
			eventListenerListening = true;
		}

		if (numEvents < numExpectedEvents )
		{

			testCase.setExpirationTime (getTimer() + timeout);
			if (!eventListenerListening)
			{
				actualTarget.addEventListener(eventName, eventListener);
				eventListenerListening = true;
				testCase.cleanupAsserts.push(this);
			}
			doStep();
			waitEvent = eventName;
			waitTarget = target;
			return false;
		}
		else
		{
			testCase.setExpirationTime (0);
			stepComplete();
			return true;
		}

		return super.execute(root, context, testCase, testResult);
	}

	/**
	 *  Test the value of a property, log result if failure.
	 */
	override protected function doStep():void
	{
		var actualTarget:Object = context.stringToObject(target);
		if (!actualTarget)
		{
			testResult.doFail("Target " + target + " not found");
			return;
		}

	}

	public function doTimeout ():void
	{
		testResult.doFail("Timeout waiting for " + waitEvent + " on " + target);

	}

	/**
	 *  The object to set a property on
	 */
	private var target:String = "mx.core::UIComponentGlobals.mx_internal:layoutManager";

	/**
	 *  The event to wait for
	 */
	private var eventName:String = "updateComplete";

	/**
	 *  Storage for numEvents
	 */
	protected var numEvents:int = 0;

	/**
	 *  Number of expected events (must be > 0), use AssertNoEvent for 0.
	 *  Set to -1 if you want to see at least one event and don't care if there's more.
	 */
	public var numExpectedEvents:int = 1;

	/**
	 *  The event object
	 */
	private var lastEvent:Event;

	/**
	 *	The event listener
	 */
	protected function eventListener(event:Event):void
	{
		testCase.setExpirationTime(0);

		lastEvent = event;
		numEvents++;

		waitEventHandler (event);

	}

	/**
	 *  Test the value of a property, log result if failure.
	 */
	public function cleanup():void
	{
		var actualTarget:Object = context.stringToObject(target);
		if (actualTarget)	// might be null if object was killed
			actualTarget.removeEventListener(eventName, eventListener);
	}

	/**
	 *  customize string representation
	 */
	override public function toString():String
	{
		var s:String = "WaitForLayoutManager";
		return s;
	}

	/**
	 *  The method that gets called back when the event we're waiting on fires
	 */
	override protected function waitEventHandler(event:Event):void
	{

		// we can rely on eventListener to update lastEvent and numEvents

		// keep waiting if there aren't enough events
		if (numExpectedEvents != -1 && numEvents < numExpectedEvents)
		{
			return;
		}

		if (numExpectedEvents == numEvents)
		{
			cleanup();
			testCase.setExpirationTime (0);
			stepComplete();
			return;

		}

		waitEvent = eventName;
		waitTarget = target;
		super.waitEventHandler(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.display.IBitmapDrawable;
import flash.utils.*;
import flash.net.*;
import flash.events.*;
import flash.display.*;
import flash.geom.Matrix;

/**
 *  WaitForLayoutManager - waits for LayoutManager to complete validation
 *  MXML attributes:
 *  timeout (optional)
 */
public class WaitForLayoutManager extends TestStep
{

	private var eventListenerListening:Boolean = false;

	/**
	 *  Test the value of a property, log result if failure.
	 */
	override public function execute(root:DisplayObject, context:UnitTester, testCase:TestCase, testResult:TestResult):Boolean
	{

		this.root = root;
		this.context = context;
		this.testCase = testCase;
		this.testResult = testResult;

		waitEvent = eventName;
		waitTarget = target;

		var actualTarget:Object = context.stringToObject(target);
		if (actualTarget)
		{
			if (actualTarget.isInvalid())
			{
				actualTarget.addEventListener(eventName, eventListener);
				eventListenerListening = true;
			}
			else
			{
				return true;
			}
		}

		if (numEvents < numExpectedEvents )
		{

			testCase.setExpirationTime (getTimer() + timeout);
			if (!eventListenerListening)
			{
				actualTarget.addEventListener(eventName, eventListener);
				eventListenerListening = true;
				testCase.cleanupAsserts.push(this);
			}
			doStep();
			waitEvent = eventName;
			waitTarget = target;
			return false;
		}
		else
		{
			testCase.setExpirationTime (0);
			stepComplete();
			return true;
		}

		return super.execute(root, context, testCase, testResult);
	}

	/**
	 *  Test the value of a property, log result if failure.
	 */
	override protected function doStep():void
	{
		var actualTarget:Object = context.stringToObject(target);
		if (!actualTarget)
		{
			testResult.doFail("Target " + target + " not found");
			return;
		}

	}

	public function doTimeout ():void
	{
		testResult.doFail("Timeout waiting for " + waitEvent + " on " + target);

	}

	/**
	 *  The object to set a property on
	 */
	private var target:String = "mx.core::UIComponentGlobals.mx_internal:layoutManager";

	/**
	 *  The event to wait for
	 */
	private var eventName:String = "updateComplete";

	/**
	 *  Storage for numEvents
	 */
	protected var numEvents:int = 0;

	/**
	 *  Number of expected events (must be > 0), use AssertNoEvent for 0.
	 *  Set to -1 if you want to see at least one event and don't care if there's more.
	 */
	public var numExpectedEvents:int = 1;

	/**
	 *  The event object
	 */
	private var lastEvent:Event;

	/**
	 *	The event listener
	 */
	protected function eventListener(event:Event):void
	{
		testCase.setExpirationTime(0);

		lastEvent = event;
		numEvents++;

		waitEventHandler (event);

	}

	/**
	 *  Test the value of a property, log result if failure.
	 */
	public function cleanup():void
	{
		var actualTarget:Object = context.stringToObject(target);
		if (actualTarget)	// might be null if object was killed
			actualTarget.removeEventListener(eventName, eventListener);
	}

	/**
	 *  customize string representation
	 */
	override public function toString():String
	{
		var s:String = "WaitForLayoutManager";
		return s;
	}

	/**
	 *  The method that gets called back when the event we're waiting on fires
	 */
	override protected function waitEventHandler(event:Event):void
	{

		// we can rely on eventListener to update lastEvent and numEvents

		// keep waiting if there aren't enough events
		if (numExpectedEvents != -1 && numEvents < numExpectedEvents)
		{
			return;
		}

		if (numExpectedEvents == numEvents)
		{
			cleanup();
			testCase.setExpirationTime (0);
			stepComplete();
			return;

		}

		waitEvent = eventName;
		waitTarget = target;
		super.waitEventHandler(event);

	}
}

}

Make WaitForLayoutManager smarter. It checks to see if it needs to wait or not.

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


package aerys.minko.type.data
{
	import aerys.minko.type.Signal;
	import aerys.minko.type.enum.DataProviderUsage;

	import flash.utils.Dictionary;

	public class DataBindings
	{
		private var _numProviders					: uint 						= 0;
		private var _providers						: Vector.<IDataProvider> 	= new <IDataProvider>[];
		private var _bindingNames					: Vector.<String>			= new Vector.<String>();

		private var _bindingNameToValue				: Object					= {};
		private var _bindingNameToChangedSignal		: Object					= {};

		private var _bindingNameToProvider			: Dictionary				= new Dictionary();
		private var _providerToBindingNames			: Dictionary				= new Dictionary(); // dic[Vector.<String>[]]
		private var _attributeToProviders			: Dictionary				= new Dictionary(); // dic[Vector.<IDataProvider>[]]
		private var _attributeToProvidersAttrNames	: Dictionary				= new Dictionary(); // dic[Vector.<String>[]]

		public function get numProviders() : uint
		{
			return _numProviders;
		}

		public function get numProperties() : uint
		{
			return _bindingNames.length;
		}

		public function DataBindings()
		{
		}

		public function contains(dataProvider : IDataProvider) : Boolean
		{
			return _providers.indexOf(dataProvider) != -1;
		}

		public function addProvider(provider : IDataProvider) : void
		{
			if (_providerToBindingNames[provider])
				throw new Error('This provider is already bound.');

			var providerBindingNames	: Vector.<String>	= new Vector.<String>();
			var dataDescriptor			: Object			= provider.dataDescriptor;

			provider.changed.add(providerChangedHandler);

			for (var attrName : String in dataDescriptor)
			{
				// if this provider attribute is also a dataprovider, let's also bind it
				var bindingName	: String			= dataDescriptor[attrName];
				var attribute	: Object			= provider[attrName]
				var dpAttribute	: IMonitoredData	= attribute as IMonitoredData;

				if (_bindingNames.indexOf(bindingName) != -1)
					throw new Error(
						'Another Dataprovider is already declaring the \'' + bindingName + '\' property.'
					);

				_bindingNameToProvider[bindingName] = provider;

				if (dpAttribute != null)
				{
					dpAttribute.changed.add(providerPropertyChangedHandler);

					_attributeToProviders[dpAttribute]			||= new <IDataProvider>[];
					_attributeToProvidersAttrNames[dpAttribute]	||= new <String>[];

					_attributeToProviders[dpAttribute].push(provider);
					_attributeToProvidersAttrNames[dpAttribute].push(attrName);
				}

				_bindingNameToValue[bindingName] = attribute;

				providerBindingNames.push(bindingName);
				_bindingNames.push(bindingName);

				if (_bindingNameToChangedSignal[bindingName])
					_bindingNameToChangedSignal[bindingName].execute(this, bindingName, attribute);
			}

			_providerToBindingNames[provider] = providerBindingNames;
			_providers.push(provider);
			++_numProviders;
		}

		public function removeProvider(provider : IDataProvider) : void
		{
			var bindingNames : Vector.<String> = _providerToBindingNames[provider];

			if (bindingNames == null)
				throw new ArgumentError('Unkown provider.');

			for each (var bindingName : String in bindingNames)
			{
				var indexOf : int = _bindingNames.indexOf(bindingName);

				_bindingNames.splice(indexOf, 1);

				if (_bindingNameToValue[bindingName] is IDataProvider)
					IDataProvider(_bindingNameToValue[bindingName]).changed.remove(providerPropertyChangedHandler);

				delete _bindingNameToValue[bindingName];
				delete _bindingNameToProvider[bindingName];
			}

			var attributesToDelete : Vector.<Object> = new Vector.<Object>();

			for (var attribute : Object in _attributeToProviders)
			{
				var providers		: Vector.<IDataProvider>	= _attributeToProviders[attribute];
				var attrNames		: Vector.<String>			= _attributeToProvidersAttrNames[attribute];
				var indexOfProvider	: int						= providers.indexOf(provider);

				if (indexOfProvider != -1)
				{
					providers.splice(indexOfProvider, 1);
					attrNames.splice(indexOfProvider, 1);
				}

				if (providers.length == 0)
					attributesToDelete.push(attribute);
			}

			for (var attributeToDelete : Object in attributesToDelete)
			{
				delete _attributeToProviders[attributeToDelete];
				delete _attributeToProvidersAttrNames[attributeToDelete];
			}

			provider.changed.remove(providerChangedHandler);

			--_numProviders;
			_providers.splice(_providers.indexOf(provider), 1);

			delete _providerToBindingNames[provider];

			for each (bindingName in bindingNames)
				if (_bindingNameToChangedSignal[bindingName])
					_bindingNameToChangedSignal[bindingName].execute(this, bindingName, null);
		}

		public function removeAllProviders() : void
		{
			var numProviders : uint = this.numProviders;

			for (var providerId : uint = 0; providerId < numProviders; ++providerId)
				removeProvider(getProviderAt(providerId));
		}

		public function hasCallback(bindingName	: String,
									callback	: Function) : Boolean
		{
			var signal : Signal = _bindingNameToChangedSignal[bindingName];

			return signal != null && signal.hasCallback(callback);
		}

		public function addCallback(bindingName : String,
									callback	: Function) : void
		{
			_bindingNameToChangedSignal[bindingName] ||=
				new Signal('DataBindings.changed[' + bindingName + ']');

			Signal(_bindingNameToChangedSignal[bindingName]).add(callback);
		}

		public function removeCallback(bindingName	: String,
									   callback		: Function) : void
		{
			var signal : Signal = _bindingNameToChangedSignal[bindingName];
			if (!signal)
				throw new ArgumentError('Unkown property \'' + bindingName + '\'.');

			signal.remove(callback);

			if (signal.numCallbacks == 0)
				delete _bindingNameToChangedSignal[bindingName];
		}

		public function getProviderAt(index : uint) : IDataProvider
		{
			return _providers[index];
		}

		public function getProviderByBindingName(bindingName : String) : IDataProvider
		{
			if (_bindingNameToProvider[bindingName] == null)
				throw new ArgumentError('Unkown property \'' + bindingName + '\'.');

			return _bindingNameToProvider[bindingName];
		}

		public function propertyExists(bindingName : String) : Boolean
		{
			return _bindingNameToValue.hasOwnProperty(bindingName);
		}

		public function getProperty(bindingName : String) : *
		{
			return _bindingNameToValue[bindingName];
		}

		public function getPropertyName(bindingIndex : uint) : String
		{
			if (bindingIndex > numProperties)
				throw new ArgumentError('No such binding');

			return _bindingNames[bindingIndex];
		}

		public function copySharedProvidersFrom(source : DataBindings) : void
		{
			var numProviders : uint = source._providers.length;

			for (var providerId : uint = 0; providerId < numProviders; ++providerId)
			{
				var provider : IDataProvider = source._providers[providerId];

				if (provider.usage == DataProviderUsage.SHARED)
					addProvider(provider);
			}
		}

		private function providerChangedHandler(source : IDataProvider, attributeName : String) : void
		{
			if (attributeName == null)
			{
				throw new Error('DataProviders must change one property at a time.');
			}
			else if (attributeName == 'dataDescriptor')
			{
				removeProvider(source);
				addProvider(source);
			}
			else
			{
				var bindingName : String		= source.dataDescriptor[attributeName];
				var oldDpValue	: IDataProvider	= _bindingNameToValue[bindingName] as IDataProvider;
				var newValue	: Object		= source[attributeName];
				var newDpValue	: IDataProvider	= newValue as IDataProvider;

				// we are replacing a data provider. We must remove listeners and related mapping keys
				if (oldDpValue != null)
				{
					oldDpValue.changed.remove(providerPropertyChangedHandler);

					var providers	: Vector.<IDataProvider>	= _attributeToProviders[oldDpValue];
					var attrNames	: Vector.<String>			= _attributeToProvidersAttrNames[oldDpValue];

					if (providers.length == 1)
					{
						delete _attributeToProviders[oldDpValue];
						delete _attributeToProvidersAttrNames[oldDpValue];
					}
					else
					{
						var index : uint = providers.indexOf(source);
						providers.splice(index, 1);
						attrNames.splice(index, 1);
					}
				}

				// the new value for this key is a dataprovider, we must listen changes.
				if (newDpValue != null)
				{
					newDpValue.changed.add(providerPropertyChangedHandler);

					_attributeToProviders[newDpValue]			||= new <IDataProvider>[];
					_attributeToProvidersAttrNames[newDpValue]	||= new <String>[];

					_attributeToProviders[newDpValue].push(source);
					_attributeToProvidersAttrNames[newDpValue].push(attributeName);
				}

				_bindingNameToValue[bindingName] = newValue;

				if (_bindingNameToChangedSignal[bindingName])
					_bindingNameToChangedSignal[bindingName].execute(this, bindingName, newValue);
			}
		}

		private function providerPropertyChangedHandler(source : IMonitoredData, key : String) : void
		{
			var providers		: Vector.<IDataProvider>	= _attributeToProviders[source];
			var attrNames		: Vector.<String>			= _attributeToProvidersAttrNames[source];
			var numProviders	: uint						= providers.length;

			for (var providerId : uint = 0; providerId < numProviders; ++providerId)
			{
				var provider	: IDataProvider	= providers[providerId];
				var attrName	: String		= attrNames[providerId];
				var bindingName : String		= provider.dataDescriptor[attrName];

				if (_bindingNameToChangedSignal[bindingName])
					_bindingNameToChangedSignal[bindingName].execute(this, bindingName, source);
			}
		}
	}
}

package aerys.minko.type.data
{
	import aerys.minko.type.Signal;
	import aerys.minko.type.enum.DataProviderUsage;

	import flash.utils.Dictionary;

	public class DataBindings
	{
		private var _numProviders					: uint 						= 0;
		private var _providers						: Vector.<IDataProvider> 	= new <IDataProvider>[];
		private var _bindingNames					: Vector.<String>			= new Vector.<String>();

		private var _bindingNameToValue				: Object					= {};
		private var _bindingNameToChangedSignal		: Object					= {};

		private var _bindingNameToProvider			: Dictionary				= new Dictionary();
		private var _providerToBindingNames			: Dictionary				= new Dictionary(); // dic[Vector.<String>[]]
		private var _attributeToProviders			: Dictionary				= new Dictionary(); // dic[Vector.<IDataProvider>[]]
		private var _attributeToProvidersAttrNames	: Dictionary				= new Dictionary(); // dic[Vector.<String>[]]

		public function get numProviders() : uint
		{
			return _numProviders;
		}

		public function get numProperties() : uint
		{
			return _bindingNames.length;
		}

		public function DataBindings()
		{
		}

		public function contains(dataProvider : IDataProvider) : Boolean
		{
			return _providers.indexOf(dataProvider) != -1;
		}

		public function addProvider(provider : IDataProvider) : void
		{
			if (_providerToBindingNames[provider])
				throw new Error('This provider is already bound.');

			var providerBindingNames	: Vector.<String>	= new Vector.<String>();
			var dataDescriptor			: Object			= provider.dataDescriptor;

			provider.changed.add(providerChangedHandler);

			for (var attrName : String in dataDescriptor)
			{
				// if this provider attribute is also a dataprovider, let's also bind it
				var bindingName	: String			= dataDescriptor[attrName];
				var attribute	: Object			= provider[attrName]
				var dpAttribute	: IMonitoredData	= attribute as IMonitoredData;

				if (_bindingNames.indexOf(bindingName) != -1)
					throw new Error(
						'Another Dataprovider is already declaring the \'' + bindingName + '\' property.'
					);

				_bindingNameToProvider[bindingName] = provider;

				if (dpAttribute != null)
				{
					dpAttribute.changed.add(providerPropertyChangedHandler);

					_attributeToProviders[dpAttribute]			||= new <IDataProvider>[];
					_attributeToProvidersAttrNames[dpAttribute]	||= new <String>[];

					_attributeToProviders[dpAttribute].push(provider);
					_attributeToProvidersAttrNames[dpAttribute].push(attrName);
				}

				_bindingNameToValue[bindingName] = attribute;

				providerBindingNames.push(bindingName);
				_bindingNames.push(bindingName);

				if (_bindingNameToChangedSignal[bindingName])
					_bindingNameToChangedSignal[bindingName].execute(this, bindingName, attribute);
			}

			_providerToBindingNames[provider] = providerBindingNames;
			_providers.push(provider);
			++_numProviders;
		}

		public function removeProvider(provider : IDataProvider) : void
		{
			var bindingNames : Vector.<String> = _providerToBindingNames[provider];

			if (bindingNames == null)
				throw new ArgumentError('Unkown provider.');

			for each (var bindingName : String in bindingNames)
			{
				var indexOf : int = _bindingNames.indexOf(bindingName);

				_bindingNames.splice(indexOf, 1);

				if (_bindingNameToValue[bindingName] is IMonitoredData)
					IMonitoredData(_bindingNameToValue[bindingName]).changed.remove(providerPropertyChangedHandler);

				delete _bindingNameToValue[bindingName];
				delete _bindingNameToProvider[bindingName];
			}

			var attributesToDelete : Vector.<Object> = new Vector.<Object>();

			for (var attribute : Object in _attributeToProviders)
			{
				var providers		: Vector.<IDataProvider>	= _attributeToProviders[attribute];
				var attrNames		: Vector.<String>			= _attributeToProvidersAttrNames[attribute];
				var indexOfProvider	: int						= providers.indexOf(provider);

				if (indexOfProvider != -1)
				{
					providers.splice(indexOfProvider, 1);
					attrNames.splice(indexOfProvider, 1);
				}

				if (providers.length == 0)
					attributesToDelete.push(attribute);
			}

			for (var attributeToDelete : Object in attributesToDelete)
			{
				delete _attributeToProviders[attributeToDelete];
				delete _attributeToProvidersAttrNames[attributeToDelete];
			}

			provider.changed.remove(providerChangedHandler);

			--_numProviders;
			_providers.splice(_providers.indexOf(provider), 1);

			delete _providerToBindingNames[provider];

			for each (bindingName in bindingNames)
				if (_bindingNameToChangedSignal[bindingName])
					_bindingNameToChangedSignal[bindingName].execute(this, bindingName, null);
		}

		public function removeAllProviders() : void
		{
			var numProviders : uint = this.numProviders;

			for (var providerId : uint = 0; providerId < numProviders; ++providerId)
				removeProvider(getProviderAt(providerId));
		}

		public function hasCallback(bindingName	: String,
									callback	: Function) : Boolean
		{
			var signal : Signal = _bindingNameToChangedSignal[bindingName];

			return signal != null && signal.hasCallback(callback);
		}

		public function addCallback(bindingName : String,
									callback	: Function) : void
		{
			_bindingNameToChangedSignal[bindingName] ||=
				new Signal('DataBindings.changed[' + bindingName + ']');

			Signal(_bindingNameToChangedSignal[bindingName]).add(callback);
		}

		public function removeCallback(bindingName	: String,
									   callback		: Function) : void
		{
			var signal : Signal = _bindingNameToChangedSignal[bindingName];
			if (!signal)
				throw new ArgumentError('Unkown property \'' + bindingName + '\'.');

			signal.remove(callback);

			if (signal.numCallbacks == 0)
				delete _bindingNameToChangedSignal[bindingName];
		}

		public function getProviderAt(index : uint) : IDataProvider
		{
			return _providers[index];
		}

		public function getProviderByBindingName(bindingName : String) : IDataProvider
		{
			if (_bindingNameToProvider[bindingName] == null)
				throw new ArgumentError('Unkown property \'' + bindingName + '\'.');

			return _bindingNameToProvider[bindingName];
		}

		public function propertyExists(bindingName : String) : Boolean
		{
			return _bindingNameToValue.hasOwnProperty(bindingName);
		}

		public function getProperty(bindingName : String) : *
		{
			return _bindingNameToValue[bindingName];
		}

		public function getPropertyName(bindingIndex : uint) : String
		{
			if (bindingIndex > numProperties)
				throw new ArgumentError('No such binding');

			return _bindingNames[bindingIndex];
		}

		public function copySharedProvidersFrom(source : DataBindings) : void
		{
			var numProviders : uint = source._providers.length;

			for (var providerId : uint = 0; providerId < numProviders; ++providerId)
			{
				var provider : IDataProvider = source._providers[providerId];

				if (provider.usage == DataProviderUsage.SHARED)
					addProvider(provider);
			}
		}

		private function providerChangedHandler(source : IDataProvider, attributeName : String) : void
		{
			if (attributeName == null)
			{
				throw new Error('DataProviders must change one property at a time.');
			}
			else if (attributeName == 'dataDescriptor')
			{
				removeProvider(source);
				addProvider(source);
			}
			else
			{
				var bindingName : String		= source.dataDescriptor[attributeName];
				var oldDpValue	: IDataProvider	= _bindingNameToValue[bindingName] as IDataProvider;
				var newValue	: Object		= source[attributeName];
				var newDpValue	: IDataProvider	= newValue as IDataProvider;

				// we are replacing a data provider. We must remove listeners and related mapping keys
				if (oldDpValue != null)
				{
					oldDpValue.changed.remove(providerPropertyChangedHandler);

					var providers	: Vector.<IDataProvider>	= _attributeToProviders[oldDpValue];
					var attrNames	: Vector.<String>			= _attributeToProvidersAttrNames[oldDpValue];

					if (providers.length == 1)
					{
						delete _attributeToProviders[oldDpValue];
						delete _attributeToProvidersAttrNames[oldDpValue];
					}
					else
					{
						var index : uint = providers.indexOf(source);
						providers.splice(index, 1);
						attrNames.splice(index, 1);
					}
				}

				// the new value for this key is a dataprovider, we must listen changes.
				if (newDpValue != null)
				{
					newDpValue.changed.add(providerPropertyChangedHandler);

					_attributeToProviders[newDpValue]			||= new <IDataProvider>[];
					_attributeToProvidersAttrNames[newDpValue]	||= new <String>[];

					_attributeToProviders[newDpValue].push(source);
					_attributeToProvidersAttrNames[newDpValue].push(attributeName);
				}

				_bindingNameToValue[bindingName] = newValue;

				if (_bindingNameToChangedSignal[bindingName])
					_bindingNameToChangedSignal[bindingName].execute(this, bindingName, newValue);
			}
		}

		private function providerPropertyChangedHandler(source : IMonitoredData, key : String) : void
		{
			var providers		: Vector.<IDataProvider>	= _attributeToProviders[source];
			var attrNames		: Vector.<String>			= _attributeToProvidersAttrNames[source];
			var numProviders	: uint						= providers.length;

			for (var providerId : uint = 0; providerId < numProviders; ++providerId)
			{
				var provider	: IDataProvider	= providers[providerId];
				var attrName	: String		= attrNames[providerId];
				var bindingName : String		= provider.dataDescriptor[attrName];

				if (_bindingNameToChangedSignal[bindingName])
					_bindingNameToChangedSignal[bindingName].execute(this, bindingName, source);
			}
		}
	}
}

Fix minor bug

Fix minor bug


//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.flex {

import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.events.IEventDispatcher;

import flash.geom.Point;
import flash.geom.Rectangle;

import mx.controls.Menu;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IListItemRenderer;
import mx.controls.menuClasses.IMenuItemRenderer;
import mx.controls.menuClasses.MenuListData;

import mx.core.mx_internal;
import mx.core.Application;
import mx.core.ClassFactory;
import mx.core.IFlexDisplayObject;
import mx.core.ScrollPolicy;

import mx.events.MenuEvent;

import mx.managers.PopUpManager;

import mx.utils.ObjectUtil;

import flexlib.controls.ScrollableArrowMenu;

import com.threerings.util.CommandEvent;

import com.threerings.flex.PopUpUtil;

import com.threerings.flex.menuClasses.CommandMenuItemRenderer;
import com.threerings.flex.menuClasses.CommandListData;

use namespace mx_internal;

/**
 * A pretty standard menu that can submit CommandEvents if menu items have "command" and possibly
 * "arg" properties. Commands are submitted to controllers for processing. Alternatively, you may
 * specify "callback" properties that specify a function closure to call, with the "arg" property
 * containing either a single arg or an array of args.
 *
 * Another property now supported is "iconObject", which is an already-instantiated
 * IFlexDisplayObject to use as the icon. This will only be applied if the "icon" property
 * (which specifies a Class) is not used.
 *
 * Example dataProvider array:
 * [ { label: "Go home", icon: homeIconClass,
 *     command: Controller.GO_HOME, arg: homeId },
 *   { type: "separator"},
     { label: "Crazytown", callback: setCrazy, arg: [ true, false ] },
 *   { label: "Other places", children: subMenuArray }
 * ];
 *
 * See "Defining menu structure and data" in the Flex manual for the full list.
 */
public class CommandMenu extends Menu
{
    /**
     * Factory method to create a command menu.
     *
     * @param items an array of menu items.
     * @param dispatcher an override event dispatcher to use for command events, rather than
     * our parent.
     */
    public static function createMenu (
        items :Array, dispatcher :IEventDispatcher = null) :CommandMenu
    {
        var menu :CommandMenu = new CommandMenu();
        menu.owner = DisplayObjectContainer(Application.application);
        menu.tabEnabled = false;
        menu.showRoot = true;
        menu.setDispatcher(dispatcher);
        Menu.popUpMenu(menu, null, items); // does not actually pop up, but needed.
        return menu;
    }

    public function CommandMenu ()
    {
        super();

        itemRenderer = new ClassFactory(CommandMenuItemRenderer);

        verticalScrollPolicy = ScrollPolicy.OFF;
        addEventListener(MenuEvent.ITEM_CLICK, itemClicked);
    }

    /**
     * Configures the event dispatcher to be used when dispatching this menu's command events.
     * Normally they will be dispatched on our parent (usually the SystemManager or something).
     */
    public function setDispatcher (dispatcher :IEventDispatcher) :void
    {
        _dispatcher = dispatcher;
    }

    /**
     * Sets a Rectangle (in stage coords) that the menu will attempt to keep submenus positioned
     * within.  If a submenu is too large to fit, it will position it in the lower right corner
     * of this Rectangle (or top if popping upwards, or left if popping leftwards).   This
     * value defaults to the stage bounds.
     */
    public function setBounds (fitting :Rectangle) :void
    {
        _fitting = fitting;
    }

    /**
     * Actually pop up the menu. This can be used instead of show().
     */
    public function popUp (
        trigger :DisplayObject, popUpwards :Boolean = false, popLeftwards :Boolean = false) :void
    {
        _upping = popUpwards;
        _lefting = popLeftwards;
        var r :Rectangle = trigger.getBounds(trigger.stage);
        show(_lefting ? r.left : r.right, _upping ? r.top : r.bottom);
    }

    /**
     * Shows the menu at the specified mouse coordinates.
     */
    public function popUpAt (
        mx :int, my :int, popUpwards :Boolean = false, popLeftwards :Boolean = false) :void
    {
        _upping = popUpwards;
        _lefting = popLeftwards;
        show(mx, my);
    }

    /**
     * Shows the menu at the current mouse location.
     */
    public function popUpAtMouse (popUpwards :Boolean = false, popLeftwards :Boolean = false) :void
    {
        _upping = popUpwards;
        _lefting = popLeftwards;
        show(); // our show, with no args, pops at the mouse
    }

    /**
     * Just like our superclass's show(), except that when invoked with no args, causes the menu to
     * show at the current mouse location instead of the top-left corner of the application.
     * Also, we ensure that the resulting menu is in-bounds.
     */
    override public function show (xShow :Object = null, yShow :Object = null) :void
    {
        if (xShow == null) {
            xShow = DisplayObject(Application.application).mouseX;
        }
        if (yShow == null) {
            yShow = DisplayObject(Application.application).mouseY;
        }

        super.show(xShow, yShow);

        // reposition now that we know our size
        if (_lefting) {
            y = x - getExplicitOrMeasuredWidth();
        }
        if (_upping) {
            y = y - getExplicitOrMeasuredHeight();
        }

        PopUpUtil.fitInRect(this, _fitting || screen);
    }

    /**
     * Callback for MenuEvent.ITEM_CLICK.
     */
    protected function itemClicked (event :MenuEvent) :void
    {
        var arg :Object = getItemProp(event.item, "arg");
        var cmdOrFn :Object = getItemProp(event.item, "command");
        if (cmdOrFn == null) {
            cmdOrFn = getItemProp(event.item, "callback");
        }
        if (cmdOrFn != null) {
            event.stopImmediatePropagation();
            CommandEvent.dispatch(_dispatcher == null ? mx_internal::parentDisplayObject :
                                  _dispatcher, cmdOrFn, arg);
        }
        // else: no warning. There may be non-command menu items mixed in.
    }

    // from ScrollableArrowMenu..
    override mx_internal function openSubMenu (row :IListItemRenderer) :void
    {
        supposedToLoseFocus = true;

        var r :Menu = getRootMenu();
        var menu :CommandMenu;

        // check to see if the menu exists, if not create it
        if (!IMenuItemRenderer(row).menu) {
            // the only differences between this method and the original method in mx.controls.Menu
            // are these few lines.
            menu = new CommandMenu();
            menu.maxHeight = this.maxHeight;
            menu.verticalScrollPolicy = this.verticalScrollPolicy;
            menu.variableRowHeight = this.variableRowHeight;

            menu.parentMenu = this;
            menu.owner = this;
            menu.showRoot = showRoot;
            menu.dataDescriptor = r.dataDescriptor;
            menu.styleName = r;
            menu.labelField = r.labelField;
            menu.labelFunction = r.labelFunction;
            menu.iconField = r.iconField;
            menu.iconFunction = r.iconFunction;
            menu.itemRenderer = r.itemRenderer;
            menu.rowHeight = r.rowHeight;
            menu.scaleY = r.scaleY;
            menu.scaleX = r.scaleX;

            // if there's data and it has children then add the items
            if (row.data && _dataDescriptor.isBranch(row.data) &&
                    _dataDescriptor.hasChildren(row.data)) {
                menu.dataProvider = _dataDescriptor.getChildren(row.data);
            }
            menu.sourceMenuBar = sourceMenuBar;
            menu.sourceMenuBarItem = sourceMenuBarItem;

            IMenuItemRenderer(row).menu = menu;
            PopUpManager.addPopUp(menu, r, false);
        }

        super.openSubMenu(row);

        // if we're lefting, upping or fitting make sure our submenu does so as well
        var submenu :Menu = IMenuItemRenderer(row).menu;
        if (_lefting) {
            submenu.x -= submenu.getExplicitOrMeasuredWidth();
        }
        if (_upping) {
            var displayObj :DisplayObject = DisplayObject(row);
            var rowLoc :Point = displayObj.localToGlobal(new Point(row.x, row.y));
            submenu.y = rowLoc.y - submenu.getExplicitOrMeasuredHeight() + displayObj.height;
        }

        PopUpUtil.fitInRect(submenu, _fitting || screen);
    }

    // from List
    override protected function makeListData (data :Object, uid :String, rowNum :int) :BaseListData
    {
        // Oh, FFS.
        // We need to set up these "maxMeasuredIconWidth" fields on the MenuListData, but our
        // superclass has made those variables private.
        // We can get the values out of another MenuListData, so we just always call super()
        // to create one of those, and if we need to make a CommandListData, we construct one
        // from the fields in the MenuListData.

        var menuListData :MenuListData = super.makeListData(data, uid, rowNum) as MenuListData;

        var iconObject :IFlexDisplayObject = getItemProp(data, "iconObject") as IFlexDisplayObject;
        if (iconObject != null) {
            var cmdListData :CommandListData = new CommandListData(menuListData.label,
                menuListData.icon, iconObject, labelField, uid, this, rowNum);

            cmdListData.maxMeasuredIconWidth = menuListData.maxMeasuredIconWidth;
            cmdListData.maxMeasuredTypeIconWidth = menuListData.maxMeasuredTypeIconWidth;
            cmdListData.maxMeasuredBranchIconWidth = menuListData.maxMeasuredBranchIconWidth;
            cmdListData.useTwoColumns = menuListData.useTwoColumns;
            return cmdListData;
        }

        return menuListData;
    }

    /**
     * Get the specified property for the specified item, if any.  Somewhat similar to bits in the
     * DefaultDataDescriptor.
     */
    protected function getItemProp (item :Object, prop :String) :Object
    {
        try {
            if (item is XML) {
                return String((item as XML).attribute(prop));

            } else if (prop in item) {
                return item[prop];
            }

        } catch (e :Error) {
            // alas; fall through
        }
        return null;
    }

    protected var _dispatcher :IEventDispatcher;
    protected var _lefting :Boolean = false;
    protected var _upping :Boolean = false;
    protected var _fitting :Rectangle = null;
}
}

//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.flex {

import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;

import flash.events.Event;
import flash.events.IEventDispatcher;

import flash.geom.Point;
import flash.geom.Rectangle;

import mx.controls.Menu;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IListItemRenderer;
import mx.controls.menuClasses.IMenuItemRenderer;
import mx.controls.menuClasses.MenuListData;

import mx.core.mx_internal;
import mx.core.Application;
import mx.core.ClassFactory;
import mx.core.IFlexDisplayObject;
import mx.core.ScrollPolicy;

import mx.events.MenuEvent;

import mx.managers.PopUpManager;

import mx.utils.ObjectUtil;

import flexlib.controls.ScrollableArrowMenu;

import com.threerings.util.CommandEvent;

import com.threerings.flex.PopUpUtil;

import com.threerings.flex.menuClasses.CommandMenuItemRenderer;
import com.threerings.flex.menuClasses.CommandListData;

use namespace mx_internal;

/**
 * A pretty standard menu that can submit CommandEvents if menu items have "command" and possibly
 * "arg" properties. Commands are submitted to controllers for processing. Alternatively, you may
 * specify "callback" properties that specify a function closure to call, with the "arg" property
 * containing either a single arg or an array of args.
 *
 * Another property now supported is "iconObject", which is an already-instantiated
 * IFlexDisplayObject to use as the icon. This will only be applied if the "icon" property
 * (which specifies a Class) is not used.
 *
 * Example dataProvider array:
 * [ { label: "Go home", icon: homeIconClass,
 *     command: Controller.GO_HOME, arg: homeId },
 *   { type: "separator"},
     { label: "Crazytown", callback: setCrazy, arg: [ true, false ] },
 *   { label: "Other places", children: subMenuArray }
 * ];
 *
 * See "Defining menu structure and data" in the Flex manual for the full list.
 */
public class CommandMenu extends Menu
{
    /**
     * Factory method to create a command menu.
     *
     * @param items an array of menu items.
     * @param dispatcher an override event dispatcher to use for command events, rather than
     * our parent.
     */
    public static function createMenu (
        items :Array, dispatcher :IEventDispatcher = null) :CommandMenu
    {
        var menu :CommandMenu = new CommandMenu();
        menu.owner = DisplayObjectContainer(Application.application);
        menu.tabEnabled = false;
        menu.showRoot = true;
        menu.setDispatcher(dispatcher);
        Menu.popUpMenu(menu, null, items); // does not actually pop up, but needed.
        return menu;
    }

    /**
     * The mx.controls.Menu class overrides setting and getting the verticalScrollPolicy
     * basically setting the verticalScrollPolicy did nothing, and getting it always returned
     * ScrollPolicy.OFF.  So that's not going to work if we want the menu to scroll. Here we
     * reinstate the verticalScrollPolicy setter, and keep a local copy of the value in a
     * protected variable _verticalScrollPolicy.
     *
     * This setter is basically a copy of what ScrollControlBase and ListBase do.
     */
    override public function set verticalScrollPolicy (value :String) :void
    {
        var newPolicy :String = value.toLowerCase();

        itemsSizeChanged = true;

        if (_verticalScrollPolicy != newPolicy)
        {
            _verticalScrollPolicy = newPolicy;
            dispatchEvent(new Event("verticalScrollPolicyChanged"));
        }

        invalidateDisplayList();
    }

    override public function get verticalScrollPolicy () :String
    {
        return _verticalScrollPolicy;
    }

    public function CommandMenu ()
    {
        super();

        itemRenderer = new ClassFactory(CommandMenuItemRenderer);

        verticalScrollPolicy = ScrollPolicy.OFF;
        addEventListener(MenuEvent.ITEM_CLICK, itemClicked);
    }

    /**
     * Configures the event dispatcher to be used when dispatching this menu's command events.
     * Normally they will be dispatched on our parent (usually the SystemManager or something).
     */
    public function setDispatcher (dispatcher :IEventDispatcher) :void
    {
        _dispatcher = dispatcher;
    }

    /**
     * Sets a Rectangle (in stage coords) that the menu will attempt to keep submenus positioned
     * within.  If a submenu is too large to fit, it will position it in the lower right corner
     * of this Rectangle (or top if popping upwards, or left if popping leftwards).   This
     * value defaults to the stage bounds.
     */
    public function setBounds (fitting :Rectangle) :void
    {
        _fitting = fitting;
    }

    /**
     * Actually pop up the menu. This can be used instead of show().
     */
    public function popUp (
        trigger :DisplayObject, popUpwards :Boolean = false, popLeftwards :Boolean = false) :void
    {
        _upping = popUpwards;
        _lefting = popLeftwards;
        var r :Rectangle = trigger.getBounds(trigger.stage);
        show(_lefting ? r.left : r.right, _upping ? r.top : r.bottom);
    }

    /**
     * Shows the menu at the specified mouse coordinates.
     */
    public function popUpAt (
        mx :int, my :int, popUpwards :Boolean = false, popLeftwards :Boolean = false) :void
    {
        _upping = popUpwards;
        _lefting = popLeftwards;
        show(mx, my);
    }

    /**
     * Shows the menu at the current mouse location.
     */
    public function popUpAtMouse (popUpwards :Boolean = false, popLeftwards :Boolean = false) :void
    {
        _upping = popUpwards;
        _lefting = popLeftwards;
        show(); // our show, with no args, pops at the mouse
    }

    /**
     * Just like our superclass's show(), except that when invoked with no args, causes the menu to
     * show at the current mouse location instead of the top-left corner of the application.
     * Also, we ensure that the resulting menu is in-bounds.
     */
    override public function show (xShow :Object = null, yShow :Object = null) :void
    {
        if (xShow == null) {
            xShow = DisplayObject(Application.application).mouseX;
        }
        if (yShow == null) {
            yShow = DisplayObject(Application.application).mouseY;
        }

        super.show(xShow, yShow);

        // reposition now that we know our size
        if (_lefting) {
            y = x - getExplicitOrMeasuredWidth();
        }
        if (_upping) {
            y = y - getExplicitOrMeasuredHeight();
        }

        PopUpUtil.fitInRect(this, _fitting || screen);
    }

    /**
     * The Menu class overrode configureScrollBars() and made the function do nothing.  That means
     * the scrollbars don't know how to draw themselves, so here we reinstate configureScrollBars.
     * This is basically a copy of the same method from the mx.controls.List class.
     */
    override protected function configureScrollBars () :void
    {
        var rowCount :int = listItems.length;
        if (rowCount == 0) {
            return;
        }

        // if there is more than one row and it is a partial row we don't count it
        if (rowCount > 1 &&
            rowInfo[rowCount - 1].y + rowInfo[rowCount - 1].height > listContent.height) {
            rowCount--;
        }

        // offset, when added to rowCount, is hte index of the dataProvider item for that row.
        // IOW, row 10 in listItems is showing dataProvider item 10 + verticalScrollPosition -
        // lockedRowCount - 1
        var offset :int = verticalScrollPosition - lockedRowCount - 1;

        // don't count filler rows at the bottom either.
        var fillerRows :int = 0;
        while (rowCount > 0 && listItems[rowCount - 1].length == 0)
        {
            if (collection && rowCount + offset >= collection.length) {
                rowCount--;
                fillerRows++;
            } else {
                break;
            }
        }

        var colCount :int = listItems[0].length;
        var oldHorizontalScrollBar :Object = horizontalScrollBar;
        var oldVerticalScrollBar :Object = verticalScrollBar;
        var roundedWidth :int = Math.round(unscaledWidth);
        var length :int = collection ? collection.length - lockedRowCount : 0;
        var numRows :int = rowCount - lockedRowCount;

        setScrollBarProperties(Math.round(listContent.width), roundedWidth, length, numRows);
        maxVerticalScrollPosition = Math.max(length - numRows, 0);
    }

    /**
     * Callback for MenuEvent.ITEM_CLICK.
     */
    protected function itemClicked (event :MenuEvent) :void
    {
        var arg :Object = getItemProp(event.item, "arg");
        var cmdOrFn :Object = getItemProp(event.item, "command");
        if (cmdOrFn == null) {
            cmdOrFn = getItemProp(event.item, "callback");
        }
        if (cmdOrFn != null) {
            event.stopImmediatePropagation();
            CommandEvent.dispatch(_dispatcher == null ? mx_internal::parentDisplayObject :
                                  _dispatcher, cmdOrFn, arg);
        }
        // else: no warning. There may be non-command menu items mixed in.
    }

    // from ScrollableArrowMenu..
    override mx_internal function openSubMenu (row :IListItemRenderer) :void
    {
        supposedToLoseFocus = true;

        var r :Menu = getRootMenu();
        var menu :CommandMenu;

        // check to see if the menu exists, if not create it
        if (!IMenuItemRenderer(row).menu) {
            // the only differences between this method and the original method in mx.controls.Menu
            // are these few lines.
            menu = new CommandMenu();
            menu.maxHeight = this.maxHeight;
            menu.verticalScrollPolicy = this.verticalScrollPolicy;
            menu.variableRowHeight = this.variableRowHeight;

            menu.parentMenu = this;
            menu.owner = this;
            menu.showRoot = showRoot;
            menu.dataDescriptor = r.dataDescriptor;
            menu.styleName = r;
            menu.labelField = r.labelField;
            menu.labelFunction = r.labelFunction;
            menu.iconField = r.iconField;
            menu.iconFunction = r.iconFunction;
            menu.itemRenderer = r.itemRenderer;
            menu.rowHeight = r.rowHeight;
            menu.scaleY = r.scaleY;
            menu.scaleX = r.scaleX;

            // if there's data and it has children then add the items
            if (row.data && _dataDescriptor.isBranch(row.data) &&
                    _dataDescriptor.hasChildren(row.data)) {
                menu.dataProvider = _dataDescriptor.getChildren(row.data);
            }
            menu.sourceMenuBar = sourceMenuBar;
            menu.sourceMenuBarItem = sourceMenuBarItem;

            IMenuItemRenderer(row).menu = menu;
            PopUpManager.addPopUp(menu, r, false);
        }

        super.openSubMenu(row);

        // if we're lefting, upping or fitting make sure our submenu does so as well
        var submenu :Menu = IMenuItemRenderer(row).menu;
        if (_lefting) {
            submenu.x -= submenu.getExplicitOrMeasuredWidth();
        }
        if (_upping) {
            var displayObj :DisplayObject = DisplayObject(row);
            var rowLoc :Point = displayObj.localToGlobal(new Point(row.x, row.y));
            submenu.y = rowLoc.y - submenu.getExplicitOrMeasuredHeight() + displayObj.height;
        }

        PopUpUtil.fitInRect(submenu, _fitting || screen);
    }

    override protected function measure () :void
    {
        super.measure();

        if (measuredHeight > this.maxHeight) {
            measuredHeight = this.maxHeight;
        }

        if (verticalScrollPolicy == ScrollPolicy.ON || verticalScrollPolicy == ScrollPolicy.AUTO) {
            if (verticalScrollBar) {
                measuredMinWidth = measuredWidth = measuredWidth + verticalScrollBar.minWidth;
            }
        }

        commitProperties();
    }

    // from List
    override protected function makeListData (data :Object, uid :String, rowNum :int) :BaseListData
    {
        // Oh, FFS.
        // We need to set up these "maxMeasuredIconWidth" fields on the MenuListData, but our
        // superclass has made those variables private.
        // We can get the values out of another MenuListData, so we just always call super()
        // to create one of those, and if we need to make a CommandListData, we construct one
        // from the fields in the MenuListData.

        var menuListData :MenuListData = super.makeListData(data, uid, rowNum) as MenuListData;

        var iconObject :IFlexDisplayObject = getItemProp(data, "iconObject") as IFlexDisplayObject;
        if (iconObject != null) {
            var cmdListData :CommandListData = new CommandListData(menuListData.label,
                menuListData.icon, iconObject, labelField, uid, this, rowNum);

            cmdListData.maxMeasuredIconWidth = menuListData.maxMeasuredIconWidth;
            cmdListData.maxMeasuredTypeIconWidth = menuListData.maxMeasuredTypeIconWidth;
            cmdListData.maxMeasuredBranchIconWidth = menuListData.maxMeasuredBranchIconWidth;
            cmdListData.useTwoColumns = menuListData.useTwoColumns;
            return cmdListData;
        }

        return menuListData;
    }

    /**
     * Get the specified property for the specified item, if any.  Somewhat similar to bits in the
     * DefaultDataDescriptor.
     */
    protected function getItemProp (item :Object, prop :String) :Object
    {
        try {
            if (item is XML) {
                return String((item as XML).attribute(prop));

            } else if (prop in item) {
                return item[prop];
            }

        } catch (e :Error) {
            // alas; fall through
        }
        return null;
    }

    protected var _dispatcher :IEventDispatcher;
    protected var _lefting :Boolean = false;
    protected var _upping :Boolean = false;
    protected var _fitting :Rectangle = null;
    protected var _verticalScrollPolicy :String;
}
}

Bring back the scrollable menu.  However, we leave the default as not scrolling, and only do
scrolling of any kind of the verticalScrollPolicy is set to ScrollPolicy.AUTO or ScrollPolicy.ON,
and the maxHeight has been set.

git-svn-id: b675b909355d5cf946977f44a8ec5a6ceb3782e4@525 ed5b42cb-e716-0410-a449-f6a68f950b19