In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

Forked from: バネの動きの練習 の続き2

/**
 * Copyright rxr01676 ( http://wonderfl.net/user/rxr01676 )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/AbE6
 */

// このアプリケーションの説明 
// 画面をマウスでクリックするとマウスカーソルの動きを青のボールが追いかけます
// 緑のボールは青のボールとバネでつながれているかのような動作をします
// マウスボタンを押している間、緑のボールはまっすぐ飛んでいきます
// マウスボタンを離すと緑のボールは青のボールに再び引き寄せられます
// 
// メニューの各項目についての説明
// root
// 青のボールが過去にどの位置にあったかをあらわします
// nフレーム前:x座標,y座標という形式です
//
// node
// 現在の緑のボールの位置(x,y)、速度(vx,vy)とバネの原点(ox,oy)をあらわします
// 緑のボールが画面上で動いている時は running 
// バネに引っ張られている時は pulled と表示されます
// 
// keyboard
// IME Processing : IMEで入力している時 YES と表示されます
// 押されていないキーは T, 押されているキーは _ とマークされます  
// IME入力中はキーボードの状態が画面に反映されません
// 日本語112キーボードを元にしているので環境によっては存在しないキーもあります
//
// キーとメニューコマンドの対応表
// 'UP' 'I' 一つ上の項目を選択
// 'DOWN' 'K' 一つ下の項目を選択            
// 'RIGHT' 'L' 選択されている項目を開く
// 'LEFT' 'J' 選択されている項目を閉じる
// 'ENTER' 'O' 全ての項目を開く
// 'BACKSPACE' 'P' 全ての項目を閉じる
// 'HOME' 'U' 初期状態に戻す
// 'SCROLLLOCK' 'ctrl - M' メニューへのキー操作をロックする(もう一度押すと解除)
//
// このコードの中で改変・使用しているコード
// yuugurenoteさんの バネの動きの練習
// Manish Jethaniさんの sprintf
// Richard Lordさんの keypoll
//
// 開発メモ
// forked from rxr01676's forked from: バネの動きの練習
// forked from yuugurenote's バネの動きの練習
// クラス化 - isRunning - toText
// バネの根元を表現するクラス - springRootSprite
// マウスボタンを押している間、バネの束縛を受けない - xPulling
// 直近300フレーム分のspringRootSpriteの位置を保存し、6フレーム分をピックアップして表示
// 形式は nフレーム前:x位置,y位置 - _storeState
// FPSを表示 - xTimer - FPS_REFRESH_RATIO FPSの数値を更新する頻度(回/秒)
// 初期化部分の修正 画面内のどこかをクリックするとバネの運動を開始する - xActivateRoot
// sprintfにパッチ - floatのゼロ埋めフラグ(flagZeroPad)が無効だった仕様を修正
// キーイベントを表示 - KeyboadMapper
// シンプルなメニュー - 矢印キーでアイテムの選択と内容の表示 - ConfigTextField
// Richard Lordさんのkeypollクラスを使ってENTER_FRAMEイベント中のキー検出テスト
// 同時押しもok - KeyWatcher 
//* 日本語IME関連のキーを押すと特定のキーが押しっぱなしの状態であると判定されることがある
//* ローカルな環境では一度ウィンドウの外にフォーカスを移すとキーを押された状態が解消される 
//* ブラウザ上ではフォーカスを移動させてもキーの状態はクリアされない
// IMEの状態を調べる - ImeWatcher
// メニュー周辺のコードの整理 - ConfigCollectorManager
// ENTER-全開、BACKSPACE-全閉、HOME-初期状態に
//* 「押しっぱなしのキーコード」がkeypollに残る問題の解
// keypollにパッチを当てる - 英数,全角/半角,カナキーを拾わないように
// 0xF0から0xF7までのキーコードを全て無視するように変更
// またIMEは「押しっぱなしのキーコード」0xE5を発生させるのでこれもマスク
// 「Alt」+「漢字」の0x19もマスクする 
//* IMEについてのメモ
// IMEの実行時はどのキーを押してもKEY_DOWNイベントにはキーコード0xE5(IME_PROCESS)が送られる
// よってどのようなキーが押されたかKEY_DOWNイベントでは把握できない(keypollは動作しない)
// IME_PROCESSをチェックすることで、キーが押された時IMEの影響下にあったかどうか調べることはできる
// - KeyWatcher.notifyKeyDown
// keypall自体はマスク処理をせず全てのキーコードを受け入れ、keypallを所有するKeyWatcherが
// IME_PROCESS終了時にkeypallの状態をゼロクリアする仕様に変更
// メニューバーを実装 - configMenuBar
// メニューの一番上の項目で'UP'か'I'キーを押すと、グループ名(main)が最上段に出る
// キーを離すと消える - configMenu.notifyKeyDown( MenuMapper.ITEM_MENUBAR, keyState ) 
// オブジェクトをメニューと結びつける処理を整理 - configMenu.addItem( ... )
// メニューにsomethingオブジェクトを付け加えるまでの流れ(暫定)
/*

1. MenuMapperに固有の名前を登録しておく
class MenuMapper extends Object {
    ...
    public static const ITEM_SOMETHING:String = 'hoge';
    ...
}

2. 最小のテンプレート
class something extends Object {
    public var value:Number = 0;
    
    public var confCol:ConfigCollector = new ConfigCollector(MenuMapper.ITEM_SOMETHING, toText);
    
    public function something(v:Number = 0):void {
        value = v;
    }
    
    public function toText():String {
        var s:String = '';
        s += String(value)+'\n'; 
        return s;        
    }
    
    public function update():void {
        value += 0.01;
        confCol.collect();
    }
}

3. インスタンスを生成し、ENTER_FRAMEでupdateを呼ぶ
public class AS111113_01 extends Sprite {
...
    public var some1:something = new something(0);
    ...

    private function xEnter(e:Event):void {
        ...
        some1.update();
        ...
    }
}
    
4. メニューにアイテムを追加する
class ConfigTextField extends TextField {
...
    private function addItemsToMenu():void {
        ...
        configMenu.addItem(MenuMapper.ITEM_SOMETHING, MenuMapper.GROUP_MAIN);
        ...
    }
}
*/
// ConfigCollectorのフラグが増えてきたのでデバッグモードを作る
// isDebugging:Boolean = true フラグが見える
// メニュー全体のロックとは別に各項目ごとにロック-アンロック( 'M' or 'END' キー )
// - getItemState( ConfigCollector.CM_LOCK, name )
// - setItemState( ConfigCollector.CM_LOCK, name )
// メニューへの操作をスプライトのプロパティへ反映する
// root node 
// メニューで項目を選択 -> スプライトの色を黄色に
// メニューで項目をロック -> スプライトをその場で固定
// - ConfigCollector.isSelected .isLocked
// メニューの初期設定部分の整理
// - configMenu.setItemState(ConfigCollector.CM_NAKED, true, MenuMapper.ITEM_MENUSTATUS);    
// メニューロックキーをctrl-Mに変更
// スプライト選択時の動作を修正
// ConfigCollectorを機能ごとに分けて継承する
// MultiConfigCollector - インスタンスが複数生成されるオブジェクトをメニューに加える時に使用する
// ParamsControlManager - エディット可能なオブジェクトをメニューに加える時に使用する
// ParamController - ParamsControlManagerを親に持ち、オブジェクトへパラメータの変化を通知する
// mySprite(node)のfrictionパラメータを増減する
// カンマ or テンキー+  値を増加
// ピリオド or テンキー-  値を増加
// - configMenu.setItemState( ParamsControlManager.CM_ADD, true, MenuMapper.ITEM_NODE );
// numpadをメニューに追加、keyboardに記号キーを追加
//* Keyboard.NUMPAD_ENTERはKeyboard.ENTERに化ける
// ブラウザでうまく拾えなかったnumpadのキーがあった
// 検出されたキーコードをメモ
// 'Enter' -> 0x0D KeyLocation.STANDARD
// 'NumLock' -> 0x90 KeyLocation.STANDARD
// '-' -> 0xBD KeyLocation.NUM_PAD
// テストしたブラウザ - Chrome 15.0.874.121 m
// ブラウザ上でもnumpadの-キーで値を減少できるように修正
// nodeを展開した状態で 'Right'(or 'L') を押すとパラメータにフォーカスが移動
// パラメータを選択して+-キーで値が増減する
// configMenu -> ConfigCollectorManager -> paramsControlManager -> ParamController
// という順にメッセージを投げる
// オブジェクト側はParamControllerから来るメッセージを待つ
// sprintfにパッチ - 0に近い負数で符号が+になる仕様を修正
// bitmapDataの中身を見るクラス - BitmapDataInspector
// ParamModifier - 値チェックとステップ幅を管理
// 選択した項目のみ明るく - selectBeginCharIndex ,selectEndCharIndex
// 画面の左上スミの4x4ピクセルの領域をbyteArrayに取り込んでダンプ - BitmapDataInspector 
// テキストのカラー化 - ConfigCollector.colored( ... )
// (px,py)をtopleftとした12x12の正方領域のピクセルのカラーを抽出し、色付きの文字列として表示
//*ヘビーな処理 bmpdataを開いた状態ではローカルでFPS=17前後
// メニュー内のカーソルの動きを少し柔軟にする willLeft willRight willPrior willNext
//*クラスの継承についてのメモ
// 規約:クラスを継承する時、コンストラクタに引数があるなら必ず親のコンストラクタを呼ばなければならない
/*エラーが出る
class Finder extends mySprite {
    public function Finder(bs:Sprite) {
    }
}
col: 18:Error: No default constructor found in base class AS111113_01.as$0:mySprite.
*/
/*エラーは出ない
class Finder extends mySprite {
    public function Finder(bs:Sprite) {
        super(bs);    
    }
} 
*/
/*エラーは出ない
class somethingElse extends something {
    public function somethingElse() {}
}
*/
// 規約:クラスを継承する時、上書きするプロパティはprivateで宣言しなければならない
/*エラーが出る
class SpriteEx extends Sprite {
    public const MAPPED_NAME:String = MenuMapper.NOENTRY;
    ...
    
class mySprite extends SpriteEx {
    public const MAPPED_NAME:String = MenuMapper.ITEM_NODE;
    ...
    
col: 15 Error: A conflict exists with inherited definition AS111113_01.as$0
:SpriteEx.MAPPED_NAME in namespace public.
*/
/*エラーが出ない
class SpriteEx extends Sprite {
    private const MAPPED_NAME:String = MenuMapper.NOENTRY;
    ...
    
class mySprite extends SpriteEx {
    private const MAPPED_NAME:String = MenuMapper.ITEM_NODE;
    ...
    
*/
/*'public化'するにはgetterを使う
class SpriteEx extends Sprite {
    private const _MAPPED_NAME:String = MenuMapper.NOENTRY;
    
    public function get MAPPED_NAME():String {
        return _MAPPED_NAME;
    }
    ...
    
class mySprite extends SpriteEx {
    private const _MAPPED_NAME:String = MenuMapper.ITEM_NODE;
    
    override public function get MAPPED_NAME():String {
        return _MAPPED_NAME;
    }
    ...
*/ 
//* getterをoverrideしないとmySpriteのインスタンスは親の(SpriteExの)プロパティ値を返す
//色つき文字で表示する代わりに、スプライトを使う - lattice
//ローカルな環境では、ほぼFPS=60付近に回復した
//メニュー項目のカラー化の続き - ConfigCollector.coSprintf  cosprintf
//cosprintfをsprintfと統合
//sprintf( format:String, ...:args ) の呼び出しの際、args[0],args[1]がArray型の時
//cosprintfとして機能し、それ以外の場合は従来のsprintfと同様に機能することにして互換性を保つ
//*cosprintfはソースから削除
//使用例
/*通常のsprintfとして使う
var s:String = sprintf( 'Font: %s = %#x %s = %d', 'color' , 0xFFFFFF, 'size', 12 );
trace(s);
// result //
//Font: color = 0xffffff size = 12
*/
/*書式指定子によって置き換えられた文字列の先頭と終端のインデックスを得ることができる
var ar:Array = [0, 1, 0, 0];
var t:String = sprintf( 'Font: %s = %#x %s = %d', ar, ['color' , 0xFFFFFF, 'size', 12] );
trace(t);
var bn:uint = ar.shift();
var ed:uint = ar.shift()+bn;
var u:String = sprintf( '\'%#x\' charIndex : begin = %d , end = %d.' , 0xFFFFFF, bn, ed );
trace(u);
// result //
//Font: color = 0xffffff size = 12
//'0xffffff' charIndex : begin = 14 , end = 22.
*/
//色の定数を一つのクラスにまとめる - ColorMapper
//メニューカーソルカラー化 - MenuHeaderCursor
//メニュー項目のマークを変更、カーソルのカラー化は延期
/*メニュー項目のマークの意味

+node    「node」は閉じている。「node」上にメニューカーソルはない。
-node    「node」は展開中。「node」上にメニューカーソルはない。

+[ node ]  「node」は閉じている。「node」上にメニューカーソルがある。
-[ node >  「node」は展開中。「node」上にメニューカーソルがあり、かつ「node」にはユーザーが変更可能なパラメータがある。
-[ node   「node」は展開中。メニューカーソルは「node」内の変更可能なパラメータ上のどこかに移動している。

-[ root ]  「root」は展開中。「root」上にメニューカーソルがある。「root」にはユーザーが変更可能なパラメータはない。

*/
/*カラーリング
MENU_DEFAULT:uint = 0x909090 // デフォルトの色
MENU_SELECTEDITEM:uint = 0xD0FFD0; // 選択された項目の色
MENU_HEADER_SELECTED:uint = 0x00A03F; // 選択された項目のヘッダ部の色
MENU_CURSOR_DEFAULT:uint = 0x10D03F; // ヘッダ部にあるメニューカーソルの色
*/
//パラメータのカラー化 - paramCoor
//ParamModifierとParamControllerのコードを整理
//rootのtracing属性をメニューに追加 - BooleanModifier
//rootのrecording属性をメニューに追加
//recording中は、直近300フレーム分のマウスの位置を記録し続ける
//rootのreplaying属性をメニューに追加
/*replayingをyesに変更すると、記録したマウスの位置が繰り返し再生される
再生中:
frictionやspringなどのパラメータは変更できる
recordingとtracingが自動的にnoに
recordingとtracingは変更できない
制限:
少なくとも300フレーム分の記録がなければ再生できない(修正済み)
終了:
replayingをnoにすると再生が終了し
recordingとtracingは再生以前の状態に戻る
*/
//編集可能なパラメータはparam[変数名]ToStrメソッドでテキスト化される
//param[変数名]ToStrメソッドをパターン化するために、これらのメソッドへ
//呼び出し側のParamControllerへの参照を与える仕様に変更
//逆順再生だったバグを修正
//300フレームに満たない短いループも再生できるように修正 - CyclerEx
//rootのreceiving属性をメニューに追加
//receiving=yes -> 円運動に切り替わる - BaseMotion
//rootから動きの記録、再生機能を別のクラスに分離する - MotionRecorder
//位置の受け渡しをクラス化する - PosSender PosReceiver PosStream
//マウスの位置をsenderに、bitmaploupeの観測点をreceiverにしてテスト
//PosSender PosReceiver PosStreamの3つ組をPosConnectionと呼び
//それぞれに大域名をつけて管理する - PosConnectionMapper
//パラメータのテキスト化部分のテンプレート - ParamToTextTemplate 
//noを選択したとき、微妙に文字の色を暗く - MENU_PARAM_NO:uint = 0x0B864B;
//coSprintfのバグフィックス
// × conCoor = conCoorStack.pop();
// ○ conCoor = conCoorStack.shift();
//画面の右端、下端でlattice.drawColoredLattice()がインデックスエラーを起こすバグ
// -> BitmapDataInspector.updatePos()で範囲チェックを入れてフィックス
// 束縛する点 - BindingPos   束縛される点 - BindedPos
//位置の受け渡しをpoint型で行うように書き換え

package {
    import flash.display.Sprite;
    import flash.display.Graphics;
    import flash.display.Bitmap;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.display.BitmapData;
    import flash.events.MouseEvent;
    import flash.events.TimerEvent;    
    import flash.events.Event;
    import flash.geom.ColorTransform;
    import flash.filters.BlurFilter;
    import flash.geom.Point;
    import flash.utils.Timer;
    import flash.utils.getTimer;
    import flash.display.StageScaleMode;
    import flash.system.IME;
    
    [SWF(width=465,height=465,backgroundColor=0x000000,frameRate=60)]

    public class AS111113_01 extends Sprite {
        public var fps:Number = 0.0;

        public var sw:Number=stage.stageWidth;
        public var sh:Number=stage.stageHeight;
        public var myColor:ColorTransform = new ColorTransform();
        public var viewBmd:BitmapData = new BitmapData(sw, sh, false, 0x000000);
        public var bitmapDataInsp:BitmapDataInspector = new BitmapDataInspector(viewBmd);
        public var viewBmp:Bitmap=new Bitmap(viewBmd);
        public var configTF:ConfigTextField = new ConfigTextField();
        public var confCol:ConfigCollector = new ConfigCollector(MenuMapper.ITEM_STAGE, toText);

//        public var some1:something = new something(0);
//        public var some2:something = new something(1000);

        public var posSender:PosSender = new PosSender(PosConnectionMapper.SENDER_MOUSE);
        public var posStream:PosStream = new PosStream(PosConnectionMapper.STREAM_STAGE_BITMAPINSP);
        public var bindingPoint:BindingPoint = new BindingPoint();

        public var iw:ImeWatcher = new ImeWatcher();
        public var mr:MotionRecorder = new MotionRecorder();
        
        public var lattice:Lattice;
        public var _mySprite:mySprite;
        public var _springRoot:SpringRootSprite;
        private var activatedRoot:Boolean = false;
        
        private const FPS_REFRESH_RATIO:int = 5;
        private var _timer:Timer = new Timer(1000 / FPS_REFRESH_RATIO);
        private var _preTimes:Vector.<int> = new Vector.<int>;
        private var _totalTime:int = 0;
        private var _drawCounts:Vector.<int> = new Vector.<int>;
        private var _drawCount:int = 0;
        private var _totalDrawCount:int = 0;        

        public function AS111113_01() {
            addChild(viewBmp);
            stage.scaleMode = StageScaleMode.NO_SCALE; //    import flash.display.StageScaleMode; 
            
            myColor.redMultiplier=0.9;
            myColor.greenMultiplier=0.9;
            myColor.blueMultiplier=0.9;
            
            _springRoot = new SpringRootSprite(this);
            _springRoot.motionRecoder = mr;                    
            addChild(_springRoot);
            
            _mySprite = new mySprite(this);
            addChild(_mySprite);
            
            lattice = new Lattice(this);
            addChild(lattice);
            
            configTF.textColor = ColorMapper.MENU_DEFAULT;
            configTF.selectable = false;
            configTF.autoSize = TextFieldAutoSize.LEFT; //    import flash.text.TextFieldAutoSize;
            addChild(configTF);
            
            lattice.textField = configTF;
            lattice.inspector = bitmapDataInsp;
            
            setupInitialConnections();

            _timer.addEventListener(TimerEvent.TIMER, xTimer);
            var _now:int = getTimer();
            for (var i:int = 0; i < FPS_REFRESH_RATIO; i++) {
                _drawCounts.unshift(0);
                _preTimes.unshift(_now);     }

            _timer.start();
            
            stage.addEventListener(Event.ENTER_FRAME, xEnter);
            stage.addEventListener(MouseEvent.CLICK, xActivateRoot);
            
        }
        
        public function setupInitialConnections():void {
            PosConnectionMapper.initialize();
            PosConnectionMapper.setConnection(PosConnectionMapper.STREAM_STAGE_BITMAPINSP,
                                                PosConnectionMapper.SENDER_MOUSE,
                                                PosConnectionMapper.RECEIVER_LOUPE);
            posSender.send = updateBindingPoint;
            bindingPoint.attachPosSenderByName(PosConnectionMapper.SENDER_MOUSE);
            posSender.isActive = true;
        }
        
        public function updateStreams():void {
            posStream.updateSender();
            posStream.updateReceiver();
        }
        
        public function updateBindingPoint():void {
                bindingPoint.x = mouseX;
                bindingPoint.y = mouseY;
        }
        
        public function xActivateRoot(e:Event):void {
            if (!activatedRoot) {
                _springRoot.bind(_mySprite);
                _springRoot.isTracing = true;
                _springRoot.isRecording = true;
                activatedRoot = true;                
                stage.removeEventListener(MouseEvent.CLICK, xActivateRoot); 
            }
        }

        public function xTimer(te:TimerEvent):void {
            var _now:int = getTimer();
            _totalTime = _now - _preTimes.pop();
            _preTimes.unshift(_now);

            _totalDrawCount += _drawCount;
            _totalDrawCount -= _drawCounts.pop();
            _drawCounts.unshift(_drawCount);
            _drawCount = 0;
            
            if ( _totalTime > 0 ) {
                fps = Number(_totalDrawCount)*1000.0 / Number(_totalTime);
            }
        }

        public function toText():String {
            var s:String = '';
            s += sprintf( ' FPS: %06.2f\n', fps);
//            if (activatedRoot) { s += ' Activated\n'; } else { s += '\n'; }
            return s;
        }
        
        private function xEnter(e:Event):void {
//            some1.update();
//            some2.update();

            iw.update();
            bitmapDataInsp.update();
            
            updateStreams();
            
            _springRoot.updateState();
            mr.record();
            
            _mySprite.updateState();
            lattice.updateState();

            confCol.collect();
            configTF.updateContents();
            
            viewBmd.draw(stage,null,myColor);
            viewBmd.applyFilter(viewBmd, viewBmd.rect, new Point(0,0), new BlurFilter(8, 8, 4));
            _drawCount++;                           
        }
    }
}

import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;
import flash.ui.KeyLocation;
import flash.events.MouseEvent;
import flash.filters.BlurFilter;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.text.TextField;
import flash.utils.Dictionary;
import flash.system.Capabilities;
import flash.system.IME;
import flash.system.IMEConversionMode;
import flash.display.BitmapData;
import flash.text.TextFormat;
import flash.utils.ByteArray;


class SpriteEx extends Sprite {
    private const _MAPPED_NAME:String = MenuMapper.NOENTRY;
    
    public function get MAPPED_NAME():String {
        return _MAPPED_NAME;
    }
    
    public var wasSelected:Boolean = false;
    public var isSelected:Boolean = false;
    
    public var normalColor:uint = ColorMapper.BALL_DEFAULT;
    public var selectedColor:uint = 0xFFFF7F;
    
    public var _stageWidth:int = 0;
    public var _stageHeight:int = 0;
    
    public var bottomSprite:Sprite;

    public var posX:Number = 0;
    public var posY:Number = 0;
    
    public function SpriteEx(bs:Sprite) {
        bottomSprite = bs;
        _stageWidth = this.bottomSprite.stage.stageWidth;
        _stageHeight = this.bottomSprite.stage.stageHeight;
        
        firstDraw();

        registerInMenuMapper();
        
        this.addEventListener(Event.ADDED, this.xAdded);
    }
    
    public function registerInMenuMapper():void { }
        
    public function firstDraw():void { }
    
    public function updateColor(confCol:ConfigCollector):void {
        isSelected = confCol.isSelected;
        if (wasSelected != confCol.isSelected) {
            graphics.clear();
            if (confCol.isSelected) { 
                graphics.beginFill(normalColor, 1);
                graphics.drawCircle(0, 0, 7);
                graphics.drawCircle(0,0,5);
            }
            else {
                graphics.beginFill(normalColor, 1);
                graphics.drawCircle(0,0,5);
            }
            graphics.endFill();
        }
        wasSelected = confCol.isSelected;
    }
    
    public function xAdded(e:Event):void { }
    
}

class SpringRootSprite extends SpriteEx {
    private const _MAPPED_NAME:String = MenuMapper.ITEM_ROOT;
    override public function get MAPPED_NAME():String {
        return _MAPPED_NAME;
    }    
    public var posSender:PosSender = new PosSender( PosConnectionMapper.SENDER_ROOT, sendPx, sendPy );
    
    public var motionRecoder:MotionRecorder;// = new MotionRecorder();
    public var receivePx:Function = null;
    public var receivePy:Function = null;
    public function sendPx():Number { return posX; }
    public function sendPy():Number { return posY; }
    
    public var paramsCol:ParamsControlManager; 
    
    public var paramIsTracing:ParamController;
    public function get isTracing():Boolean {
        return motionRecoder.isTracing;
    }
    public function set isTracing(state:Boolean):void {
        motionRecoder.isTracing = state;
    }
    
    public var paramIsRecording:ParamController;
    public function get isRecording():Boolean {
        return motionRecoder.isRecording;
    }
    public function set isRecording(state:Boolean):void {
        motionRecoder.isRecording = state;
    }
    
    public var paramIsReplaying:ParamController;
    public function get isReplaying():Boolean {
        return motionRecoder.isReplaying;
    }
    public function set isReplaying(state:Boolean):void {
        motionRecoder.isReplaying = state;
    }
    
    public var baseMotion:BaseMotion = new BaseMotion();

    public var wasReceiving:Boolean = false;
    public var _isReceiving:Boolean = false;
    public var paramIsReceiving:ParamController;
    public function get isReceiving():Boolean {
        return _isReceiving;
    }
    public function set isReceiving(state:Boolean):void {
        if ( wasReceiving == state ) { return; }
        wasReceiving = state;
        _isReceiving = state;
        if (state == true) { startReceive(); }
        else { endReceive(); }
    }
    
    public var isPulling:Boolean = false;
    public var hasChild:Boolean = false;
    public var childSprite:mySprite;
    
    public function SpringRootSprite(bs:Sprite) {
        super(bs);
    }

    override public function registerInMenuMapper():void {
        paramsCol = new ParamsControlManager(MAPPED_NAME, toText, this);
        paramIsTracing = new ParamController(MAPPED_NAME, 'isTracing', ParamToTextTemplate.BooleanToYesNo, this);
        paramIsRecording = new ParamController(MAPPED_NAME, 'isRecording', ParamToTextTemplate.BooleanToYesNo, this);
        paramIsReplaying = new ParamController(MAPPED_NAME, 'isReplaying', ParamToTextTemplate.BooleanToYesNo, this);
        paramIsReceiving = new ParamController(MAPPED_NAME, 'isReceiving', ParamToTextTemplate.BooleanToYesNo, this);
        
        paramIsTracing.setModifier( new BooleanModifier( false ) );
        paramIsRecording.setModifier( new BooleanModifier( false ) );
        paramIsReplaying.setModifier( new BooleanModifier( false ) );
        paramIsReceiving.setModifier( new BooleanModifier( false ) );
        
        paramIsReplaying.isProtected = true;
    }
    
    override public function firstDraw():void { 
        normalColor = ColorMapper.ROOT_DEFAULT;

        this.graphics.beginFill(normalColor,1);
        this.graphics.drawCircle(0,0,5);
        this.graphics.endFill();        
    }
    
    public override function xAdded(e:Event):void {
        super.xAdded(e);
        
        
        
        motionRecoder.paramIsRecording = this.paramIsRecording;
        motionRecoder.paramIsReplaying = this.paramIsReplaying;
        motionRecoder.paramIsTracing = this.paramIsTracing;
        motionRecoder.receivePx = this.sendPx;
        motionRecoder.receivePy = this.sendPy;
        
        baseMotion.offsetX = _stageWidth / 2;
        baseMotion.offsetY = _stageHeight / 2;        

        stage.addEventListener(MouseEvent.MOUSE_DOWN, xPulling);
        stage.addEventListener(MouseEvent.MOUSE_UP, xPulling);
        stage.addEventListener(MouseEvent.MOUSE_OVER, xPulling);
                
        this.posX = _stageWidth * 2/ 3 - this.width / 2;
        this.posY = _stageHeight / 2 - this.height / 2;
      
        this.x = this.posX;
        this.y = this.posY;
    }

    public function xPulling(me:MouseEvent):void { 
        this.isPulling = !me.buttonDown;
    }

    public function startReceive():void {
        isReplaying = false;
        paramIsReplaying.isProtected = true;        
//        isRecording = false;
//        paramIsRecording.isProtected = true;        
        motionRecoder.pre_isTracing = isTracing;
        isTracing = false;
        paramIsTracing.isProtected = true;
    }
    public function endReceive():void {
        paramIsReplaying.isProtected = false;
//        paramIsRecording.isProtected = false;
        paramIsTracing.isProtected = false;
        isTracing = motionRecoder.pre_isTracing;
    }
    
    public function updatePos():void {
//        posX = sender.posX;
//        posY = sender.posY;
    }
        
    public function updateState():void {
        if (!paramsCol.isLocked) {
            if (this.isReplaying) {
                this.posX = motionRecoder.sendPx();
                this.posY = motionRecoder.sendPy();
                motionRecoder.moveToNextFrame();
            }
            else {
                if (this.isReceiving) {
                    this.posX = baseMotion.posX;
                    this.posY = baseMotion.posY;
                    baseMotion.next();
                }
                else {
                    if(this.isTracing) {
                    this.posX = bottomSprite.mouseX;
                    this.posY = bottomSprite.mouseY;
                    }
                }
            }
        }

        this.x=this.posX;
        this.y=this.posY;
        
        if( this.hasChild ){
            childSprite.oriX=this.posX;
            childSprite.oriY=this.posY;
        
            childSprite.isPulled = this.isPulling;
        }
        
        updateColor(paramsCol);
        
//        motionRecoder.record();
    }
    
    public function toText():String {
        var s:String = '';
        
        s = paramsCol.coSprintf(s, ' tracing: %s\t recording: %s   \t replaying: %s \t receiving: %s \n', 
            new ParamCoor(paramIsTracing), new ParamCoor(paramIsRecording), new ParamCoor(paramIsReplaying), new ParamCoor(paramIsReceiving) );
        if (isReplaying) {
            s += sprintf(' mouse pos at frame: %03d/%03d \n', motionRecoder.playCycler.dist+1, motionRecoder.recordCycler.loopLength);
        }
        if (isRecording) {
            s += sprintf(' total recorded frame # %03d \n', motionRecoder.recordCycler.loopLength );
            if ( motionRecoder.recordCycler.loopLength == motionRecoder.TOTAL_POINTS) {
                s += ' buffer full. disposed first recorded frame.\n';
            }
        }
        s += sprintf(' %03d : x=%+010.3f \t y=%+010.3f   \t', 0, motionRecoder._memPts[motionRecoder._topInd[0]].x, motionRecoder._memPts[motionRecoder._topInd[0]].y);
        s += sprintf(' %03d : x=%+010.3f \t y=%+010.3f   \n', 5, motionRecoder._memPts[motionRecoder._topInd[5]].x, motionRecoder._memPts[motionRecoder._topInd[5]].y);
        s += sprintf(' %03d : x=%+010.3f \t y=%+010.3f   \t', 10, motionRecoder._memPts[motionRecoder._topInd[10]].x, motionRecoder._memPts[motionRecoder._topInd[10]].y);
        s += sprintf(' %03d : x=%+010.3f \t y=%+010.3f   \n', 15, motionRecoder._memPts[motionRecoder._topInd[15]].x, motionRecoder._memPts[motionRecoder._topInd[15]].y);
        s += sprintf(' %03d : x=%+010.3f \t y=%+010.3f   \t', 20, motionRecoder._memPts[motionRecoder._topInd[20]].x, motionRecoder._memPts[motionRecoder._topInd[20]].y);
        s += sprintf(' %03d : x=%+010.3f \t y=%+010.3f   \n', 25, motionRecoder._memPts[motionRecoder._topInd[25]].x, motionRecoder._memPts[motionRecoder._topInd[25]].y);
        
        return s;
    }
    
    public function bind(ms:mySprite):void {
        childSprite = ms;
        this.hasChild = true;
        this.isPulling = true;
    }
}
    
class mySprite extends SpriteEx {
    private const _MAPPED_NAME:String = MenuMapper.ITEM_NODE;
    
    override public function get MAPPED_NAME():String {
        return _MAPPED_NAME;
    }
    
    public var posReciver:PosReceiver = new PosReceiver( PosConnectionMapper.RECEIVER_NODE_ORI );
    
    private var _preX:Number=0;
    private var _preY:Number=0;
    
    public var oriX:Number=0;
    public var oriY:Number=0;

    public var speedX:Number=0;
    public var speedY:Number=0;
    public var minRunningSpeed:Number=0.01;
    public var isRunning:Boolean=false;
    public var isPulled:Boolean=false;
    public var spring:Number = 0.1;
//    public var spring:Number=0.01;
    
    public var friction:Number=0.89;
//    public var friction:Number = 0.08;
    
    public var paramsCol:ParamsControlManager;
    public var paramFr:ParamController;
    public var paramSp:ParamController;
    
    private function _updateRunningState():void {
        if (this.isRunning) {
            if (Math.abs(this.speedX)<this.minRunningSpeed && Math.abs(this.speedY)<this.minRunningSpeed) {
                this.isRunning=false;
                this._preX=Math.round(this.x);
                this._preY=Math.round(this.y);
            }
        }
        else {
             if (this._preX != Math.round(this.x) || this._preY != Math.round(this.y)) {
                this.isRunning=true;
            }
        }
    }
    
    public function updateSpeed():void {
        var myIntX:Number=this.oriX-this.posX;
        var myIntY:Number=this.oriY-this.posY;

        this.speedX+=myIntX*this.spring;
        this.speedY+=myIntY*this.spring;
        this.speedX*=this.friction;
        this.speedY*=this.friction;
    }

    public function toText():String {
        var s:String = '';
        s = paramsCol.coSprintf( s, ' friction:%s\t spring:%s\n', new ParamCoor( paramFr ), new ParamCoor( paramSp ) );        
        s += sprintf(' Running: %s \tPulled: %s \n', isRunning ? 'yes' : 'no', isPulled ? 'yes' : 'no');
        s += sprintf(' x=%+010.3f    \ty=%+010.3f    \tox=%+010.3f    \toy=%+010.3f\n', x, y, oriX, oriY);
        s += sprintf(' vx=%+010.5f \tvy=%+010.5f', speedX, speedY);
        s += sprintf('\t\tdist=%+010.3f\n', Math.sqrt((posX - oriX)*(posX - oriX) +(posY - oriY)*(posY - oriY)));
    
        return s;
    }
    
    public function paramFrictionToStr(pm:ParamController):String {
        if (Math.abs(Number(friction)) < 0.0005) { friction = 0; }
        return ParamToTextTemplate.NumberToFormatText(pm);;
    }

    override public function registerInMenuMapper():void {
        paramsCol = new ParamsControlManager(MAPPED_NAME, toText, this);
        paramFr = new ParamController(MAPPED_NAME, 'friction', paramFrictionToStr, this);
        paramSp = new ParamController(MAPPED_NAME, 'spring', ParamToTextTemplate.NumberToFormatText, this);    
        paramFr.setModifier( new ParamModifier( 0.890, -1.000, +1.500, 0.005, true ) );
        paramSp.setModifier( new ParamModifier( 0.100, -1.000, +1.000, 0.001, true ) );        
    }
    
    override public function firstDraw():void {
        this.graphics.beginFill(normalColor,1);
        this.graphics.drawCircle(0,0,5);
        this.graphics.endFill();
        
        this.posX = _stageWidth / 3 - this.width / 2;
        this.posY = _stageHeight / 2 - this.height / 2;
        
        this.x = this.posX;
        this.y = this.posY;
        
        this._preX = this.posX;
        this._preY = this.posY;
        
        this.oriX = this.posX;
        this.oriY = this.posY;        
    }
    
    public function mySprite(bs:Sprite) {
        super(bs);
    }
    
    public override function xAdded(e:Event):void {
        super.xAdded(e);
    }
    
    public function updateState():void {
        
        if( !paramsCol.isLocked ) {
            if(this.isPulled){ this.updateSpeed(); }      

            this.posX+=this.speedX;
            this.posY+=this.speedY;
        
            this.x=this.posX;
            this.y=this.posY;
        
            this._updateRunningState();
        }
        
        this.updateColor(paramsCol);
        
        paramsCol.collect();
                
        }
}

class Lattice extends SpriteEx {
    private const _MAPPED_NAME:String = MenuMapper.ITEM_LATTICE;
    
    public var textField:TextField;
    public var inspector:BitmapDataInspector;
    
    public var bandWidth:int = 4;
    public var boxWidth:uint = 11;
    public var boxColumns:uint = 12;
    public var boxLines:uint = 12;
    
    public var wasDrawed:Boolean = false;
    
    override public function get MAPPED_NAME():String {
        return _MAPPED_NAME;
    }    
    
    public var confCol:ConfigCollector;
    
    public function Lattice(bs:Sprite) {
        super(bs);    
    }
    override public function registerInMenuMapper():void 
    {
        confCol = new ParamsControlManager(MAPPED_NAME, toText, this);
    }
    
    public function drawLattice():void {
        this.graphics.beginFill(normalColor, 1);
        
        var px:uint = bandWidth;
        var py:uint = bandWidth;
        var dist:uint = bandWidth + boxWidth;
        for ( var i:uint = 0; i < boxLines; i++) {
            px = bandWidth;
            for ( var j:uint = 0; j < boxColumns; j++) {
                this.graphics.drawRect(px, py, boxWidth, boxWidth);
                px += dist;
            }
            py += dist;
        }
        this.graphics.endFill();
        
        wasDrawed = true;
    }
    
    public function drawColoredLattice():void {
        if (inspector == null) { return };
        
        var px:uint = bandWidth;
        var py:uint = bandWidth;
        var dist:uint = bandWidth + boxWidth;
        var offset:uint = 0;
        var index:uint = 0;
        
        for ( var i:uint = 0; i < boxLines; i++) {
            px = bandWidth;
            for ( var j:uint = 0; j < boxColumns; j++) {
                index = offset + j;
                if( inspector.vectorArray.length > index ) {
                    this.graphics.beginFill(inspector.vectorArray[index], 1);
                    this.graphics.drawRect(px, py, boxWidth, boxWidth);
                    this.graphics.endFill();
                }
                px += dist;
            }
            py += dist;
            offset += boxColumns;
        }
        
        wasDrawed = true;
    }
    
    override public function firstDraw():void 
    {
        // drawLattice();
        
        this.posX = 0;
        this.posY = 281;
        
        this.x = this.posX;
        this.y = this.posY;
    }
    
    public function toText():String {
        var s:String = '';
        s += sprintf( 'bitmap vectorArray length = %d \n' , inspector.vectorArray.length );
        s += sprintf( 'stage width: %d  px: %d  stage height: %d  py: %d \n', this._stageWidth, inspector.px, this._stageHeight, inspector.py);
        s += sprintf( 'presentating width: %d  height: %d \n', 
            ((this._stageWidth - inspector.px) < boxColumns) ? (this._stageWidth - inspector.px) : boxColumns,
            ((this._stageHeight- inspector.py) < boxLines )? (this._stageHeight- inspector.py) : boxLines );
        
        return s;
    }
    
    public function updateState():void {
        this.x = 0;
        this.y = textField.y + textField.height-4;
        if (confCol.isExpanded) {
//            if (wasDrawed) { return; }
            if (inspector != null) {
                inspector.updateVectorArray();
                graphics.clear();
                drawColoredLattice();
            }
            else {
                graphics.clear();
                drawLattice();
            }
        }
        else { 
            graphics.clear();
            wasDrawed = false;
        }
    }
    
    
} 

class Finder extends mySprite {
    private const _MAPPED_NAME:String = MenuMapper.ITEM_FINDER;
    
    override public function get MAPPED_NAME():String {
        return _MAPPED_NAME;
    }    
    
    public function Finder(bs:Sprite) {
        super(bs);    
    }
    override public function registerInMenuMapper():void 
    {
        
    }
    
} 



class BitmapDataInspector extends Object {
    public var bitmapData:BitmapData;
    public var rect:Rectangle;
    public var px:int = 39;
    public var py:int = 8;
    
    public var posReceiver:PosReceiver = new PosReceiver(PosConnectionMapper.RECEIVER_LOUPE);
    public var bindedPoint:BindedPoint = new BindedPoint();
    
    public var paramsMan:ParamsControlManager = new ParamsControlManager( MenuMapper.ITEM_BITMAPDATA, ToText, this );
    public var paramX:ParamController = new ParamController( MenuMapper.ITEM_BITMAPDATA,'px' ,paramXToStr, this );
    public var paramY:ParamController = new ParamController( MenuMapper.ITEM_BITMAPDATA, 'py' , paramYToStr, this );
    
    public var tarRect4:Rectangle = new Rectangle(0, 0, 4, 4);
    public var tarRect12:Rectangle = new Rectangle(0, 0, 12, 12);
    
    public var byteArray:ByteArray = new ByteArray();
    public var vectorArray:Vector.<uint> = new Vector.<uint>;

    public function BitmapDataInspector( bd:BitmapData ):void {
        bitmapData = bd;
        rect = bitmapData.rect;
        
        setupInitialConnections();
        paramX.setModifier( new ParamModifier(0, -465, 465, 1) );
        paramY.setModifier( new ParamModifier(0, -465, 465, 1) );

        
        for ( var i:uint = 0; i < tarRect4.width * tarRect4.height; i++) {
            byteArray.writeUnsignedInt(0);
        }
        
        for ( i = 0; i < tarRect12.width * tarRect12.height; i++) {
            vectorArray.push(0);
        }
    }
    
    public function setupInitialConnections():void {
        bindedPoint.posReceiver = this.posReceiver;
    }
    
    
    public function updateVectorArray():void {
        tarRect12.x = px;
        tarRect12.y = py;
        vectorArray = bitmapData.getVector(tarRect12);
    }
    
    public function ToText():String {
        var c:uint = bitmapData.getPixel32(px, py);
        var s:String = '';
        var sc:String = '';
        
        s = paramsMan.coSprintf(s, ' pixelColor at( x:%6s  y:%6s ) = ', new ParamCoor( paramX ), new ParamCoor( paramY ) );
        sc = "■";
        s = paramsMan.colored( s, sc, c );
        s += sprintf(' 0x%08X \n', c );
/*        
        byteArray = bitmapData.getPixels(tarRect4);
        s += byteArrayToText( byteArray );
*/        
/*        
        tarRect12.x = px;
        tarRect12.y = py;
        vectorArray = bitmapData.getVector(tarRect12);
        var offset:uint = 0;
        for ( var i:uint = 0; i < tarRect12.height; i++ ) {
            s += ' ';
            for ( var j:uint = 0; j < tarRect12.width; j++) {
                var obj:Object = new Object();
                obj['color'] = vectorArray[offset + j];
//                obj['bold'] = true;
                obj['size'] = 14;
                obj['letterSpacing'] = 1.25;
//                obj['underline'] = true;
                
                s = paramsMan.setTextFormatsProperties( s, sc , obj );
            }
            offset += tarRect12.width;
            s += '\n';
        }
*/        
        return s;
    }
    
    public function paramXToStr(pm:ParamController):String {
        var s:String = '';
        s += sprintf(' %s%d', paramX.isSelected ? paramX.cursor : '  ', px );
        
        return s;
    }

    public function paramYToStr(pm:ParamController):String {
        var s:String = '';
        s += sprintf(' %s%d', paramY.isSelected ? paramY.cursor : '  ', py );
        
        return s;
    }

    public function update():void {
        var _px:int = bindedPoint.x;
        if ( _px + tarRect12.width <= StageMapper.WIDTH ) { px = _px; }
        var _py:int = bindedPoint.y;
        if ( _py + tarRect12.height <= StageMapper.HEIGHT ) { py = _py; }            
        
        paramsMan.collect();
    }
}

class something extends Object {
    public var value:Number;
    
    public var confCol:ConfigCollector = new ConfigCollector(MenuMapper.ITEM_SOMETHING, toText);
    
    public function something() {
        value = 0;
    }
    
    public function toText():String {
        var s:String = '';
        s += String(value)+'\n'; 
        return s;        
    }
    
    public function update():void {
        value += 0.01
        confCol.collect();
    }
}

class somethingElse extends something {
    public function somethingElse() {}
}

class BaseMotion extends Object { 
    private const CIRCLE_POINTS:int = 360;
    public var cycler:CyclerEx = new CyclerEx(CIRCLE_POINTS);    
    public var Pts:Vector.<Point> = new Vector.<Point>(CIRCLE_POINTS);
    public var RCos:Vector.<Number> = new Vector.<Number>(CIRCLE_POINTS);
    public var RSin:Vector.<Number> = new Vector.<Number>(CIRCLE_POINTS);
    
    public var radius:Number = 50;
    public var offsetX:Number = 0;
    public var offsetY:Number = 0;
    
    
    public function BaseMotion():void { 
        for ( var i:int = 0; i < CIRCLE_POINTS; i++ ) { 
            Pts[i] = new Point(0, 0); 
            RCos[i] = Math.cos( Number(i) / Math.PI );
            RSin[i] = Math.sin( Number(i) / Math.PI );
        }
        cycler.beginIndex = 0;
        cycler.endIndex = CIRCLE_POINTS - 1;
        cycler.moveToBegin();
        
        generate();
    }
    
    public function generate():void {
        for ( var i:int = 0; i < CIRCLE_POINTS; i++ ) {
            Pts[i].x = radius * RCos[i];
            Pts[i].y = radius * RSin[i];
        }
    }
    
    public function get posX():Number { 
        return Pts[cycler.index].x + offsetX;
    }
    public function get posY():Number { 
        return Pts[cycler.index].y + offsetY;
    }
    
    public function next():void {
        cycler.next();
    }
}

class MotionRecorder extends Object { 
    public const TOTAL_POINTS:int = 360;
    public const TOP_POINTS:int = 30;
    public var _memPts:Vector.<Point> = new Vector.<Point>(TOTAL_POINTS);
    public var _topInd:Vector.<int> = new Vector.<int>;
    public var recordCycler:CyclerEx = new CyclerEx(TOTAL_POINTS);    
    public var playCycler:CyclerEx = new CyclerEx(TOTAL_POINTS);
    
    public var receivePx:Function = null;
    public var receivePy:Function = null;
    public function sendPx():Number { 
        return _memPts[playCycler.index].x;
    }
    public function sendPy():Number { 
        return _memPts[playCycler.index].y;
    }
    public function moveToNextFrame():void {
        playCycler.next();    
    }

    public var pre_isTracing:Boolean = false;
    public var isTracing:Boolean = false;
    public var paramIsTracing:ParamController;
    
    public var pre_isRecording:Boolean = false;
    public var isRecording:Boolean = false;
    public var paramIsRecording:ParamController;

    public var wasReplaying:Boolean = false;
    public var _isReplaying:Boolean = false;
    public var paramIsReplaying:ParamController;
    public function get isReplaying():Boolean {
        return _isReplaying;
    }
    
    public function set isReplaying(state:Boolean):void {
        if ( wasReplaying == state ) { return; }
        wasReplaying = state;
        _isReplaying = state;
        if (state == true) { beforeReplay(); }
        else { afterReplay(); }
    }
    
    public function MotionRecorder():void { 
        for ( var i:int = 0; i < TOTAL_POINTS; i++ ) { _memPts[i] = new Point(0,0); }
        for ( i = 0; i < TOP_POINTS; i++ ) { _topInd.unshift(i); }
        recordCycler.beginIndex = _topInd[0];
        recordCycler.endIndex = _topInd[0];        
    }
    
    public function beforeReplay():void { 
        pre_isRecording = isRecording;
        isRecording = false;
        if( paramIsRecording != null ) { paramIsRecording.isProtected = true; }
        pre_isTracing = isTracing;
        isTracing = false;
        paramIsTracing.isProtected = true;
        
        playCycler.drain( recordCycler );
        playCycler.moveToBegin();
    }
    
    public function afterReplay():void { 
        if( paramIsRecording != null ) { paramIsRecording.isProtected = false; }
        isRecording = pre_isRecording;
        paramIsTracing.isProtected = false;
        isTracing = pre_isTracing;
    }    
    
    public function record():void {
        if (!isRecording) { return; }
        recordCycler.forward()
        var tc:int = recordCycler.endIndex;
        _topInd.unshift(tc);
        _topInd.pop();
        
        _memPts[tc].x = receivePx();
        _memPts[tc].y = receivePy();
        
        if( paramIsReplaying != null ) { paramIsReplaying.isProtected = false; }
    }       
}

class Cycler extends Object {
    public var index:int = 0;
    
    public function get atBegin():Boolean {
        return (index == beginIndex);
    }
    
    public function get atEnd():Boolean {
        return (index == endIndex);
    }

    public function get dist():int {
        var result:int = index - beginIndex;
        if (result < 0) { result += length; }
        return result;
    }
    
    public var _beginIndex:int = 0;
    public function get beginIndex():int {
        return _beginIndex;
    }
    public function set beginIndex(z:int):void {
        _beginIndex = modLength(z);
        if (_beginIndex == 0) { endIndex = length - 1; }
        else { endIndex = _beginIndex - 1; }
    }
    
    public var _endIndex:int = 0;
    public function get endIndex():int {
        return _endIndex;
    }
    public function set endIndex(z:int):void {
        _endIndex = modLength(z);
    }
    
    public var length:int = 1;
    public function Cycler( _length:int, _index:int = 0 ) {
        if (_length > 0) { length = _length; }
        beginIndex = _index;
    }
    public function moveToBegin():void { index = beginIndex; }
    public function moveToEnd():void { index = endIndex; }
    public function next():void {
        index++;
        if (index == length) { index = 0; }
    }
    public function prior():void {
        index--;
        if (index == -1) { index = length - 1; }
    }
    public function skip(d:int):void {
        index = modLength( index + d );
    }    

    public function modLength( z:int ):int { 
        var result:int = ( z % length);
        if (result < 0) { result += length;  }
        return result;
    }
    
    public function drain( tar:Cycler ):void {
        if ( this.length == tar.length ) {
            this.beginIndex = tar.beginIndex;
            this.endIndex = tar.endIndex;
            this.index = tar.index;
        }
    }
}

class CyclerEx extends Cycler {
    public function get loopLength():int {
        var result:int = 0;
        if (_endIndex >= _beginIndex) { result = _endIndex - _beginIndex + 1; }
        else { result = length - _beginIndex + _endIndex + 1; }
        return result;
    }
    
    override public function set beginIndex(z:int):void {
        _beginIndex = modLength(z);
    }
    
    public function CyclerEx( _length:int, _index:int = 0 ) {
        super( _length, _index );
        endIndex = _beginIndex;
    }
    
    public function forward():void {
        _endIndex++;
        if (_endIndex == length) { _endIndex = 0; }
        if (_endIndex == _beginIndex) { 
            _beginIndex++;
            if ( _beginIndex == length ) {
                _beginIndex = 0;
            }
        }
    }
    
    override public function next():void 
    {
        if ( index == _endIndex ) { index = _beginIndex; return }
        super.next();
    }
    
    override public function prior():void 
    {
        if ( index == _beginIndex ) { index = _endIndex; return }
        super.prior();
    }
    
}

class PosStream extends Object {
    public var mappedName:String;
    private var _posSender:PosSender;
    private var _posReceiver:PosReceiver;
    public function PosStream(_mappedName:String):void {
        mappedName = _mappedName;
        PosConnectionMapper.addMember(this);
    }
    public function set posSender(ref:PosSender):void {
        if ( ref == null ) { removeSender(); }
        else {
            _posSender = ref;
            _posSender.parentStream = this;  }
        updateSenderProperties();
//        if( _posReceiver != null ){ updateReceiverProperties(); }
    }
    public function get posSender():PosSender {
        return _posSender;
    }
    public function set posReceiver(ref:PosReceiver):void {
        if ( ref == null ) { removeReceiver(); }
        else { 
            _posReceiver = ref;
            _posReceiver.parentStream = this;
            updateReceiverProperties();
        }
    }
    public function get posReceiver():PosReceiver {
        return _posReceiver;
    }
    public function updateReceiverProperties():void {
        if ( _posSender == null ) { 
            _posReceiver.eat = null;
            _posReceiver.isActive = false;
            return; 
        }
        updateMethods();
    }
    public function updateSenderProperties():void { 
        if ( _posReceiver == null ) {
            _posSender.isActive = false;
            return;
        }
        updateMethods();
    }
    public function updateMethods():void {
//        _posReceiver.eatX = _posSender.feedX;
//        _posReceiver.eatY = _posSender.feedY;
        _posReceiver.eat = _posSender.feed;        
    }
    public function removeReceiver():void {
        if (_posReceiver == null) { return; }
        _posReceiver.parentStream = null;
        _posReceiver = null;
        if ( _posSender == null ) { return; }
        _posSender.isActive = false;
    }
    public function removeSender():void {
        if (_posSender == null ) { return; }
        _posSender.parentStream = null;
        _posSender = null;
        if ( _posReceiver == null ) { return; }
        _posReceiver.isActive = false;
    }
    public function updateSender():void {
        if ( _posSender != null ) { 
            if ( _posSender.isActive ) {
                _posSender.send();
            }
        }
    }
    public function updateReceiver():void {
        if ( _posReceiver != null ) {
            if ( _posReceiver.isActive ) {
                _posReceiver.receive();
            }
        }
    }
}
class PosReceiver extends Object {
    public var mappedName:String;
    public var _isActive:Boolean = true;
    public var parentStream:PosStream = null;
    public function PosReceiver(_mappedName:String, _receive:Function = null):void {
        mappedName = _mappedName;
        receive = _receive;
        PosConnectionMapper.addMember(this);
    }
    public function get isActive():Boolean {
        if ( eat == null ) { return false; }
        return _isActive;
    }
    public function set isActive(state:Boolean):void {
        _isActive = state;
    }
    public var eat:Function = null;
    public var receive:Function = null;
    public function affect( _pos:Point ):void {
        if (eat == null) { return; }
        eat(_pos);
    }
}
class PosSender extends Object {
    public var mappedName:String;
    public var _isActive:Boolean = true;
    public var parentStream:PosStream = null;
    public function PosSender(_mappedName:String, _sendMethod:Function = null, _feedMethod:Function = null ):void {
        mappedName = _mappedName;
//        feedX = _feedX;
//        feedY = _feedY;
        _feed = _feedMethod;
        _send = _sendMethod;
        PosConnectionMapper.addMember(this);
    }
    public function get isActive():Boolean {
//        if (( feedX == null ) || ( feedY == null )) { return false; }
//        if ( feed == null ) { return false; }
        return _isActive;
    }
    public function set isActive(state:Boolean):void {
        _isActive = state;
    }
    public var _feed:Function = null;
    public function set feed(method:Function):void {
        _feed = method;
        notifyParentStream();
    }
    public function get feed():Function {
        return _feed;
    }
    public var _send:Function = null;
    public function set send(method:Function):void {
        _send = method;
        notifyParentStream();
    }
    public function get send():Function {
        return _send;
    }
    public function notifyParentStream():void {
        if (parentStream == null) { return; }
        parentStream.updateSenderProperties();
    }
}
class BindedPoint extends Point {
    public var _posReceiver:PosReceiver;
    public function set posReceiver(ref:PosReceiver):void {
        _posReceiver = ref;
        _posReceiver.receive = updatePos;
    }
    public function attachPosReceiverByName(_mappedName:String):Boolean {
        var ref:* = PosConnectionMapper.nameToReceiver[_mappedName];
        if ( ref == undefined ) { return false; }
        posReceiver = ref;
        return true;
    }
    public function BindedPoint( _initPosX:Number=0,_initPosY:Number=0 ):void {
        super(_initPosX, _initPosY);
    }
    public function updatePos():void {
        _posReceiver.affect(this);
    }
}
class BindingPoint extends Point {
    public var _posSender:PosSender;
    public function set posSender(ref:PosSender):void {
        _posSender = ref;
        _posSender.feed = feed;
    }
    public function attachPosSenderByName(_mappedName:String):Boolean {
        var ref:* = PosConnectionMapper.nameToSender[_mappedName];
        if ( ref == undefined ) { return false; }
        posSender = ref;
        return true;
    }
    public function BindingPoint( _initPosX:Number = 0,_initPosY:Number = 0 ):void {
        super(_initPosX, _initPosY);
    }
    public function feed( _pos:Point ):void {
        _pos.x = this.x;
        _pos.y = this.y;
    }
    
    // BindingPos.posSender updates BindingPos when posSender.send() is invoked.
    
}



/// sprintf

/**
 *    Copyright (c) 2008. Adobe Systems Incorporated.
 *    All rights reserved.
 *
 *    Redistribution and use in source and binary forms, with or without
 *    modification, are permitted provided that the following conditions
 *    are met:
 *
 *      * Redistributions of source code must retain the above copyright
 *        notice, this list of conditions and the following disclaimer.
 *      * Redistributions in binary form must reproduce the above copyright
 *        notice, this list of conditions and the following disclaimer in
 *        the documentation and/or other materials provided with the
 *        distribution.
 *      * Neither the name of Adobe Systems Incorporated nor the names of
 *        its contributors may be used to endorse or promote products derived
 *        from this software without specific prior written permission.
 *
 *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 *    OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 *    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 *    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 *    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/*  sprintf(3) implementation in ActionScript 3.0.
 *
 *  Author:  Manish Jethani (manish.jethani@gmail.com)
 *  Date:    April 3, 2006
 *  Version: 0.1
 *
 *  Copyright (c) 2006 Manish Jethani
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a
 *  copy of this software and associated documentation files (the "Software"),
 *  to deal in the Software without restriction, including without limitation
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *  and/or sell copies of the Software, and to permit persons to whom the
 *  Software is furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 *  DEALINGS IN THE SOFTWARE.  
 */
/*  sprintf(3) implementation in ActionScript 3.0.
 *
 *  http://www.die.net/doc/linux/man/man3/sprintf.3.html
 *
 *  The following flags are supported: '#', '0', '-', '+'
 *
 *  Field widths are fully supported.  '*' is not supported.
 *
 *  Precision is supported except one difference from the standard: for an
 *  explicit precision of 0 and a result string of "0", the output is "0"
 *  instead of an empty string.
 *
 *  Length modifiers are not supported.
 *
 *  The following conversion specifiers are supported: 'd', 'i', 'o', 'u', 'x',
 *  'X', 'f', 'F', 'c', 's', '%'
 *
 *  Report bugs to manish.jethani@gmail.com
 */
function sprintf(format:String, ... args):String
{
// patch cosprintf    
    function setCharIndexes(ind:Array, ori:String,add:String):void {
        if (ind.shift() > 0) { 
            ind.push(ori.length); 
            ind.push(add.length);
        }
    }
// end of patch    
    
// patch cosprintf
    var indexes:Array=null;
    if ( args[0] is Array ) { 
        indexes = args[0];
        args = args[1];
    }
// end of patch     
    
    var result:String = "";

    var length:int = format.length;
    var next:*;
    var str:String;
    for (var i:int = 0; i < length; i++)
    {
        var c:String = format.charAt(i);

        if (c == "%")
        {
            var pastFieldWidth:Boolean = false;
            var pastFlags:Boolean = false;

            var flagAlternateForm:Boolean = false;
            var flagZeroPad:Boolean = false;
            var flagLeftJustify:Boolean = false;
            var flagSpace:Boolean = false;
            var flagSign:Boolean = false;

            var fieldWidth:String = "";
            var precision:String = "";

            c = format.charAt(++i);

            while (c != "d"
                && c != "i"
                && c != "o"
                && c != "u"
                && c != "x"
                && c != "X"
                && c != "f"
                && c != "F"
                && c != "c"
                && c != "s"
                && c != "%")
            {
                if (!pastFlags)
                {
                    if (!flagAlternateForm && c == "#")
                        flagAlternateForm = true;
                    else if (!flagZeroPad && c == "0")
                        flagZeroPad = true;
                    else if (!flagLeftJustify && c == "-")
                        flagLeftJustify = true;
                    else if (!flagSpace && c == " ")
                        flagSpace = true;
                    else if (!flagSign && c == "+")
                        flagSign = true;
                    else
                        pastFlags = true;
                }

                if (!pastFieldWidth && c == ".")
                {
                    pastFlags = true;
                    pastFieldWidth = true;

                    c = format.charAt(++i);
                    continue;
                }

                if (pastFlags)
                {
                    if (!pastFieldWidth)
                        fieldWidth += c;
                    else
                        precision += c;
                }

                c = format.charAt(++i);
            }

            switch (c)
            {
            case "d":
            case "i":
                next = args.shift();
                str = String(Math.abs(int(next)));

                if (precision != "")
                    str = leftPad(str, int(precision), "0");

                if (int(next) < 0)
                    str = "-" + str;
                else if (flagSign && int(next) >= 0)
                    str = "+" + str;

                if (fieldWidth != "")
                {
                    if (flagLeftJustify)
                        str = rightPad(str, int(fieldWidth));
                    else if (flagZeroPad && precision == "")
                        str = leftPad(str, int(fieldWidth), "0");
                    else
                        str = leftPad(str, int(fieldWidth));
                }

//patch cosprintf
                if(indexes != null){ setCharIndexes(indexes,result,str); }
//end of patch                   
                
                result += str;
                break;

            case "o":
                next = args.shift();
                str = uint(next).toString(8);

                if (flagAlternateForm && str != "0")
                    str = "0" + str;

                if (precision != "")
                    str = leftPad(str, int(precision), "0");

                if (fieldWidth != "")
                {
                    if (flagLeftJustify)
                        str = rightPad(str, int(fieldWidth));
                    else if (flagZeroPad && precision == "")
                        str = leftPad(str, int(fieldWidth), "0");
                    else
                        str = leftPad(str, int(fieldWidth));
                }

//patch cosprintf
                if(indexes != null){ setCharIndexes(indexes,result,str); }
//end of patch                   

                result += str;
                break;

            case "u":
                next = args.shift();
                str = uint(next).toString(10);

                if (precision != "")
                    str = leftPad(str, int(precision), "0");

                if (fieldWidth != "")
                {
                    if (flagLeftJustify)
                        str = rightPad(str, int(fieldWidth));
                    else if (flagZeroPad && precision == "")
                        str = leftPad(str, int(fieldWidth), "0");
                    else
                        str = leftPad(str, int(fieldWidth));
                }

//patch cosprintf
                if(indexes != null){ setCharIndexes(indexes,result,str); }
//end of patch                   

                result += str;
                break;

            case "X":
                var capitalise:Boolean = true;
            case "x":
                next = args.shift();
                str = uint(next).toString(16);

                if (precision != "")
                    str = leftPad(str, int(precision), "0");

                var prepend:Boolean = flagAlternateForm && uint(next) != 0;

                if (fieldWidth != "" && !flagLeftJustify
                        && flagZeroPad && precision == "")
                    str = leftPad(str, prepend
                            ? int(fieldWidth) - 2 : int(fieldWidth), "0");

                if (prepend)
                    str = "0x" + str;

                if (fieldWidth != "")
                {
                    if (flagLeftJustify)
                        str = rightPad(str, int(fieldWidth));
                    else
                        str = leftPad(str, int(fieldWidth));
                }

                if (capitalise)
                    str = str.toUpperCase();

//patch cosprintf
                if(indexes != null){ setCharIndexes(indexes,result,str); }
//end of patch                   
                    
                result += str;
                break;

            case "f":
            case "F":
                next = args.shift();
                str = Math.abs(Number(next)).toFixed(
                        precision != "" ?  int(precision) : 6);
/* original
                if (int(next) < 0)
                    str = "-" + str;
                else if (flagSign && int(next) >= 0)
                    str = "+" + str;
*/
                   
// patch                    
                var require_NSign:Boolean = ( next < 0 );
                var require_PSign:Boolean = ( flagSign && next >= 0 );
                
                if(!flagZeroPad){    
                    if (require_NSign)
                        str = "-" + str;
                    else if (require_PSign)
                        str = "+" + str;
                }
// end of patch

                if (flagAlternateForm && str.indexOf(".") == -1)
                    str += ".";

                if (fieldWidth != "")
                {
/* original
                    if (flagLeftJustify)
                        str = rightPad(str, int(fieldWidth));
                    else if (flagZeroPad && precision == "")
                        str = leftPad(str, int(fieldWidth), "0");
                    else
                        str = leftPad(str, int(fieldWidth));
*/


// patch                    
                    if (flagLeftJustify)
                        str = rightPad(str, int(fieldWidth));
                    else if (flagZeroPad)
                        if( require_NSign || require_PSign )
                            str = leftPad(str, int(fieldWidth) - 1, "0");  // add Sign after ZeroPad
                        else
                            str = leftPad(str, int(fieldWidth) , "0");                    
                    else
                        str = leftPad(str, int(fieldWidth));
// end of patch
                }

// patch                    
                if(flagZeroPad){    
                    if (require_NSign)
                        str = "-" + str;
                    else if (require_PSign)
                        str = "+" + str;
                }
// end of patch

//patch cosprintf
                if(indexes != null){ setCharIndexes(indexes,result,str); }
//end of patch                   

                result += str;
                break;

            case "c":
                next = args.shift();
                str = String.fromCharCode(int(next));

                if (fieldWidth != "")
                {
                    if (flagLeftJustify)
                        str = rightPad(str, int(fieldWidth));
                    else
                        str = leftPad(str, int(fieldWidth));
                }

//patch cosprintf
                if(indexes != null){ setCharIndexes(indexes,result,str); }
//end of patch                   

                result += str;
                break;

            case "s":
                next = args.shift();
                str = String(next);

                if (precision != "")
                    str = str.substring(0, int(precision));

                if (fieldWidth != "")
                {
                    if (flagLeftJustify)
                        str = rightPad(str, int(fieldWidth));
                    else
                        str = leftPad(str, int(fieldWidth));
                }

//patch cosprintf
                if(indexes != null){ setCharIndexes(indexes,result,str); }
//end of patch                   
                
                result += str;
                break;

            case "%":
                result += "%";
            }
        }
        else
        {
            result += c;
        }
    }

    return result;
}
// Private functions
function leftPad(source:String, targetLength:int, padChar:String = " "):String
{
    if (source.length < targetLength)
    {
        var padding:String = "";

        while (padding.length + source.length < targetLength)
            padding += padChar;

        return padding + source;
    }

    return source;
}

function rightPad(source:String, targetLength:int, padChar:String = " "):String
{
    while (source.length < targetLength)
        source += padChar;

    return source;
}


/// keypoll

/*
 * Author: Richard Lord
 * Copyright (c) Big Room Ventures Ltd. 2007-2008
 * Version: 1.0.3
 * 
 * Licence Agreement
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.display.DisplayObject;
import flash.utils.ByteArray;
/**
 * <p>Games often need to get the current state of various keys in order to respond to user input. 
 * This is not the same as responding to key down and key up events, but is rather a case of discovering 
 * if a particular key is currently pressed.</p>
 * 
 * <p>In Actionscript 2 this was a simple matter of calling Key.isDown() with the appropriate key code. 
 * But in Actionscript 3 Key.isDown no longer exists and the only intrinsic way to react to the keyboard 
 * is via the keyUp and keyDown events.</p>
 * 
 * <p>The KeyPoll class rectifies this. It has isDown and isUp methods, each taking a key code as a 
 * parameter and returning a Boolean.</p>
 */
class KeyPoll
{
/* original    
    private var states:ByteArray;
*/
// patch
    public var states:ByteArray;
// end of patch
    private var dispObj:DisplayObject;
    
    /**
     * Constructor
     * 
     * @param stage A display object on which to listen for keyboard events.
     * To catch all key events, this should be a reference to the stage.
     */
/* original
     public function KeyPoll( stage:DisplayObject )
*/

// patch
     public function KeyPoll()
// end of patch
    {
        states = new ByteArray();
        states.writeUnsignedInt( 0 );
        states.writeUnsignedInt( 0 );
        states.writeUnsignedInt( 0 );
        states.writeUnsignedInt( 0 );
        states.writeUnsignedInt( 0 );
        states.writeUnsignedInt( 0 );
        states.writeUnsignedInt( 0 );
        states.writeUnsignedInt( 0 );

/* original
        dispObj = stage;

        dispObj.addEventListener( KeyboardEvent.KEY_DOWN, keyDownListener, false, 0, true );
        dispObj.addEventListener( KeyboardEvent.KEY_UP, keyUpListener, false, 0, true );
        dispObj.addEventListener( Event.ACTIVATE, activateListener, false, 0, true );
        dispObj.addEventListener( Event.DEACTIVATE, deactivateListener, false, 0, true );
*/
    }
    
    public var isInIMEProcess:Boolean = false;

// patch
    public function notifyKeyDown( keyCode:uint ): void {

//        if ( (keyCode ) == 0x19 ) { return; } // ignore Alt+Kanji
//        if ( (keyCode ) == 0xE5 ) { return; } // ignore IME_PROCESS
//        if ( (keyCode & 0xF8) == 0xF0 ) { return; } // ignore HANKAKU ZENKAKU EISU KANA keys
        
        states[ keyCode >>> 3 ] |= 1 << (keyCode & 7);
    }  
    public function notifyKeyUp( keyCode:uint ): void {

//        if ( (keyCode ) == 0x19 ) { return; } // ignore Alt+Kanji
//        if ( (keyCode ) == 0xE5 ) { return; } // ignore IME_PROCESS
//        if ( (keyCode & 0xF8) == 0xF0 ) { return; } // ignore HANKAKU ZENKAKU EISU KANA keys
        
        states[ keyCode >>> 3 ] &= ~(1 << (keyCode & 7));
    }  

    public function clearStates():void
    {
        for ( var i:int = 0; i < 32; ++i )
        {
            states[ i ] = 0;
        }
    }

// end of patch

    private function keyDownListener( ev:KeyboardEvent ):void
    {
        states[ ev.keyCode >>> 3 ] |= 1 << (ev.keyCode & 7);
    }

    
    private function keyUpListener( ev:KeyboardEvent ):void
    {
        states[ ev.keyCode >>> 3 ] &= ~(1 << (ev.keyCode & 7));
    }
    
    private function activateListener( ev:Event ):void
    {
        for ( var i:int = 0; i < 32; ++i )
        {
            states[ i ] = 0;
        }
    }

    private function deactivateListener( ev:Event ):void
    {
        for ( var i:int = 0; i < 32; ++i )
        {
            states[ i ] = 0;
        }
    }
    /**
     * To test whether a key is down.
     *
     * @param keyCode code for the key to test.
     *
     * @return true if the key is down, false otherwise.
     *
     * @see #isUp()
     */
    public function isDown( keyCode:uint ):Boolean
    {
        return ( states[ keyCode >>> 3 ] & (1 << (keyCode & 7)) ) != 0;
    }
    
    /**
     * To test whether a key is up.
     *
     * @param keyCode code for the key to test.
     *
     * @return true if the key is up, false otherwise.
     *
     * @see #isDown()
     */
    public function isUp( keyCode:uint ):Boolean
    {
        return ( states[ keyCode >>> 3 ] & (1 << (keyCode & 7)) ) == 0;
    }

}

function byteArrayToText( states:ByteArray ):String {
        var s:String = ''
        s += '\t\t+0\t+1\t+2\t+3\t+4\t+5\t+6\t+7\t\t+8\t+9\t+A\t+B\t+C\t+D\t+E\t+F\n' 
        
        var bytesize:uint = states.length; 
        var rest:uint = ( states.length % 16 )
        for ( var i:int = 0; i < bytesize-rest; i+=16 ) {
            s += sprintf( ' %04X:\t%02X\t%02X\t%02X\t%02X\t%02X\t%02X\t%02X\t%02X\t', 
                i, states[i], states[i + 1], states[i + 2], states[i + 3], states[i + 4], states[i + 5], states[i + 6], states[i + 7]);
            s += sprintf( '\t%02X\t%02X\t%02X\t%02X\t%02X\t%02X\t%02X\t%02X\n', 
                states[i + 8], states[i + 9], states[i + 10], states[i + 11], states[i + 12], states[i + 13], states[i + 14], states[i + 15]);
        }
        if (rest > 0) {
            s += sprintf( ' %04X:', i );
            for ( i; i < bytesize; i++ ) { s += sprintf('\t%02X', states[i]); }
            s += '\n';
        }
        
        return s;
}


/// mapper section

class KeyboardMapper extends Object {
    public static const ZERO : uint = 48;
    public static const ONE : uint = 49;
    public static const TWO : uint = 50;
    public static const THREE : uint = 51;
    public static const FOUR : uint = 52;
    public static const FIVE : uint = 53;
    public static const SIX : uint = 54;
    public static const SEVEN : uint = 55;
    public static const EIGHT : uint = 56;
    public static const NINE : uint = 57;
    
    public static const A : uint = 65;
    public static const B : uint = 66;
    public static const C : uint = 67;
    public static const D : uint = 68;
    public static const E : uint = 69;
    public static const F : uint = 70;
    public static const G : uint = 71;
    public static const H : uint = 72;
    public static const I : uint = 73;
    public static const J : uint = 74;
    public static const K : uint = 75;
    public static const L : uint = 76;
    public static const M : uint = 77;
    public static const N : uint = 78;
    public static const O : uint = 79;
    public static const P : uint = 80;
    public static const Q : uint = 81;
    public static const R : uint = 82;
    public static const S : uint = 83;
    public static const T : uint = 84;
    public static const U : uint = 85;
    public static const V : uint = 86;
    public static const W : uint = 87;
    public static const X : uint = 88;
    public static const Y : uint = 89;
    public static const Z : uint = 90;
    
    // windows 112-keyboard
    public static const ALT : uint = 0x12;
    
    public static const BREAK : uint = 0x13;
    public static const KANJI : uint = 0x19;
    
    public static const HENKAN : uint = 0x1C;
    public static const MUHENKAN : uint = 0x1D;
    
    public static const WIN_LEFT : uint = 0x5B;
    public static const WIND_RIGHT : uint = 0x5C;
    public static const WIN_POP : uint = 0x5D;
    
    public static const NUM_LOCK : uint = 0x90;
    public static const SCROLL_LOCK : uint = 0x91;

    public static const COLON : uint = 0xBA; // :
    public static const SEMICOLON : uint = 0xBB; // ;
    public static const COMMA : uint = 0xBC; // ,
    public static const MINUS : uint = 0xBD; // -
    public static const PERIOD : uint = 0xBE; // .
    public static const SLASH : uint = 0xBF; // /
    
    public static const AT_MARK : uint = 0xC0; // @

    public static const BRA : uint = 0xDB; // [
    public static const YEN : uint = 0xDC; 
    public static const KET : uint = 0xDD; // ]
    public static const WEDGE : uint = 0xDE; // ^
    
    public static const BACK_SLASH : uint = 0xE2; 
    
    public static const IME_PROCESS : uint = 0xE5;
    public static const EISU : uint = 0xF0;
    public static const HIRAGANA : uint = 0xF2;
    public static const KATAKANA : uint = 0xF2;
    public static const ROMA : uint = 0xF2;
    public static const HANKAKU : uint = 0xF3;
    public static const ZENKAKU : uint = 0xF4;

    
    public var keyState:KeyState = new KeyState();
    public var keyCodeToStr:Object = new Object();
    public var keyLocationToStr:Object = new Object();
    
    public function toText():String {

        var s:String = sprintf(' type: %s\n', keyState.type);
        s += sprintf(' keyCode: 0x%02X   \tKeyboard.%s    \n', 
            keyState.keyCode, keyCodeToStr[keyState.keyCode]);
        s += sprintf(' ctrl:%4s\talt:%4s\tshift:%4s\n',
            ( keyState.ctrlKey ? 'down' : 'up' ), 
            ( keyState.altKey ? 'down' : 'up' ),  
            ( keyState.shiftKey ? 'down' : 'up' ));
        s += sprintf(' KeyLocation.%s\n' ,
            keyLocationToStr[keyState.keyLocation] );
        s += sprintf(' charCode: 0x%02X     \tchar:  %s    \n', 
            keyState.charCode, 
            ( (keyState.charCode > 0x20) && ( keyState.charCode < 0x7F ) ) ?  //printable char
                String.fromCharCode(keyState.charCode) : ' ' );
        
        return s;
    }

    
    public function KeyboardMapper():void {
        setSpecialKeyCodes();
        setKeyLocations();
    }
    
    
    private function setKeyLocations():void {
        keyLocationToStr[KeyLocation.LEFT] = 'LEFT';
        keyLocationToStr[KeyLocation.NUM_PAD] = 'NUM_PAD';
        keyLocationToStr[KeyLocation.RIGHT] = 'RIGHT';
        keyLocationToStr[KeyLocation.STANDARD] = 'STANDARD';
    }
    
    private function setSpecialKeyCodes():void {
        keyCodeToStr[Keyboard.BACKSPACE] = 'BACKSPACE';
        keyCodeToStr[Keyboard.CAPS_LOCK] = 'CAPS_LOCK';
        keyCodeToStr[Keyboard.CONTROL] = 'CONTROL';
        keyCodeToStr[Keyboard.DELETE] = 'DELETE';
        keyCodeToStr[Keyboard.DOWN] = 'DOWN';
        keyCodeToStr[Keyboard.END] = 'END';
        keyCodeToStr[Keyboard.ENTER] = 'ENTER';
        keyCodeToStr[Keyboard.ESCAPE] = 'ESCAPE';
        keyCodeToStr[Keyboard.F1] = 'F1';
        keyCodeToStr[Keyboard.F10] = 'F10';
        keyCodeToStr[Keyboard.F11] = 'F11';
        keyCodeToStr[Keyboard.F12] = 'F12';
        keyCodeToStr[Keyboard.F13] = 'F13';
        keyCodeToStr[Keyboard.F14] = 'F14';
        keyCodeToStr[Keyboard.F15] = 'F15';
        keyCodeToStr[Keyboard.F2] = 'F2';
        keyCodeToStr[Keyboard.F3] = 'F3';
        keyCodeToStr[Keyboard.F4] = 'F4';
        keyCodeToStr[Keyboard.F5] = 'F5';
        keyCodeToStr[Keyboard.F6] = 'F6';
        keyCodeToStr[Keyboard.F7] = 'F7';
        keyCodeToStr[Keyboard.F8] = 'F8';
        keyCodeToStr[Keyboard.F9] = 'F9';
        keyCodeToStr[Keyboard.HOME] = 'HOME';
        keyCodeToStr[Keyboard.INSERT] = 'INSERT';
        keyCodeToStr[Keyboard.LEFT] = 'LEFT';
        keyCodeToStr[Keyboard.NUMPAD_0] = 'NUMPAD_0';
        keyCodeToStr[Keyboard.NUMPAD_1] = 'NUMPAD_1';
        keyCodeToStr[Keyboard.NUMPAD_2] = 'NUMPAD_2';
        keyCodeToStr[Keyboard.NUMPAD_3] = 'NUMPAD_3';
        keyCodeToStr[Keyboard.NUMPAD_4] = 'NUMPAD_4';
        keyCodeToStr[Keyboard.NUMPAD_5] = 'NUMPAD_5';
        keyCodeToStr[Keyboard.NUMPAD_6] = 'NUMPAD_6';
        keyCodeToStr[Keyboard.NUMPAD_7] = 'NUMPAD_7';
        keyCodeToStr[Keyboard.NUMPAD_8] = 'NUMPAD_8';
        keyCodeToStr[Keyboard.NUMPAD_9] = 'NUMPAD_9';
        keyCodeToStr[Keyboard.NUMPAD_ADD] = 'NUMPAD_ADD';
        keyCodeToStr[Keyboard.NUMPAD_DECIMAL] = 'NUMPAD_DECIMAL';
        keyCodeToStr[Keyboard.NUMPAD_DIVIDE] = 'NUMPAD_DIVIDE';
        keyCodeToStr[Keyboard.NUMPAD_ENTER] = 'NUMPAD_ENTER';
        keyCodeToStr[Keyboard.NUMPAD_MULTIPLY] = 'NUMPAD_MULTIPLY';
        keyCodeToStr[Keyboard.NUMPAD_SUBTRACT] = 'NUMPAD_SUBTRACT';
        keyCodeToStr[Keyboard.PAGE_DOWN] = 'PAGE_DOWN';
        keyCodeToStr[Keyboard.PAGE_UP] = 'PAGE_UP';
        keyCodeToStr[Keyboard.RIGHT] = 'RIGHT';
        keyCodeToStr[Keyboard.SHIFT] = 'SHIFT';
        keyCodeToStr[Keyboard.SPACE] = 'SPACE';
        keyCodeToStr[Keyboard.TAB] = 'TAB';
        keyCodeToStr[Keyboard.UP] = 'UP';
    }    
}

class MenuMapper extends Object {
    public static var nameToMenuItem:Object = new Object();
    public static var nameToMenuGroup:Object = new Object();
    public static const NOENTRY:String = 'noentry';    
    
    public static const GROUP_MAIN:String = 'main';
    public static const GROUP_KEY:String = 'key';
    
    public static const ITEM_STAGE:String = 'stage';
    public static const ITEM_BITMAPDATA:String = 'bitmapdata';
    public static const ITEM_LATTICE:String = 'bitmaploupe';
    
    public static const ITEM_ROOT:String = 'root';
    public static const ITEM_NODE:String = 'node';
    public static const ITEM_FINDER:String = 'finder';
    
    
    public static const ITEM_IME:String = 'ime';
    public static const ITEM_KEYBOARD:String = 'keyboard';
    public static const ITEM_NUMPAD:String = 'numpad';
    
    public static const ITEM_KEYPOLL:String = 'keypoll';
    public static const ITEM_KEYEVENT:String = 'keyevent';
    public static const ITEM_MENUSTATUS:String = 'menu';
    public static const ITEM_MENUGROUPS:String = 'menugroups';
    public static const ITEM_MENUBAR:String = 'menubar';
    
    public static const ITEM_SOMETHING:String = 'hoge';
}

class ColorMapper extends Object {
    // Syntax Coloring
    static public var SYNTAX_COMMENTDOC:uint = 0x8C8C8C;    // // /* */
    static public var SYNTAX_COMMENTLINEDOC:uint = 0x008000 // ///
    static public var SYNTAX_CHARACTER:uint = 0xC28EBD;     // 'px' 'py'
    static public var SYNTAX_STRING:uint = 0xa31515;        // "■"
    static public var SYNTAX_WORD:uint = 0xF9F386;          // null false true
    static public var SYNTAX_WORD2:uint = 0x45A0FF;         // void int String Object 
    static public var SYNTAX_WORD3:uint = 0xD54C02;         // static public
    static public var SYNTAX_WORD5:uint = 0x88F7EE;         // this super
    static public var SYNTAX_GLOBALCLASS:uint = 0xF39C64;   // class var extends
    static public var SYNTAX_NUMBER:uint = 0x7FFF7F;        // 1 2.50 0xFFFFFF
    static public var SYNTAX_LINENUMBER:uint = 0xD0D0D0;    //  
    static public var SYNTAX_SELECTIONBACK:uint = 0x663333  //
    static public var SYNTAX_FOREGROUND:uint = 0xFFFFFF;    //
    static public var SYNTAX_BACKGROUND:uint = 0x000000;    //

    static public var SYNTAX_CURRENTLINEBACK:uint = 0x111111;
    
    // ConfigMenu
    static public var MENU_DEFAULT:uint = 0x909090
    static public var MENU_SELECTEDITEM:uint = 0xC0E0C0;
    static public var MENU_HEADER_SELECTED:uint = 0x00A03F;
    static public var MENU_CURSOR_DEFAULT:uint = 0x10D03F;
    static public var MENU_CURSOR_EXPANDED:uint = 0x10D03F;
    static public var MENU_PARAM_DEFAULT:uint = 0x10D03F;
    static public var MENU_PARAM_YES:uint = 0x10D03F;
    static public var MENU_PARAM_NO:uint = 0x0B864B;
    
//    static public var MENU_SELECTEDCURSOR:uint = 0xF7663C;
    
    
    // ball
    static public var BALL_DEFAULT:uint = 0x00FF7F;
    static public var BALL_SELECTED:uint = 0xFFFF7F;

    // SpringRoot
    static public var ROOT_DEFAULT:uint = 0x7F7FFF;
    
    // SpringNode
    static public var NODE_DEFAULT:uint = 0x00FF7F;    
} 

class PosConnectionMapper extends Object {
    static public var nameToStream:Object = new Object();
    static public var nameToSender:Object = new Object();
    static public var nameToReceiver:Object = new Object();
    
    static public const NOT_CHANGE:String = 'not_change';
    static public const NONE:String = 'none';
    static public const STREAM_STAGE_BITMAPINSP:String = 'stream_stage_bitmapinsp';
    static public const SENDER_MOUSE:String = 'sender_mouse';
    static public const SENDER_ROOT:String = 'sender_root';
    static public const RECEIVER_LOUPE:String = 'receiver_loupe';
    static public const RECEIVER_NODE_ORI:String = 'receiver_node_ori';
    
    
    static public function initialize():void {
        nameToSender[NONE] = null;
    }
    
    static public function addMember(mem:*):void {
        if ( mem is PosStream ) { nameToStream[mem.mappedName] = mem;return }
        if ( mem is PosSender ) { nameToSender[mem.mappedName] = mem;return }
        if ( mem is PosReceiver ) { nameToReceiver[mem.mappedName] = mem;return }
    }
    
    static public function setConnection(streamName:String, 
                                    senderName:String=NOT_CHANGE, 
                                    receiverName:String=NOT_CHANGE ):void {
        var _posStream:PosStream = nameToStream[streamName];
        
        var _posSender:PosSender;
        if ( senderName == NOT_CHANGE ) { _posSender = _posStream.posSender; }
        else { _posSender = nameToSender[senderName]; }
        
        var _posReceiver:PosReceiver
        if ( receiverName == NOT_CHANGE ) { _posReceiver = _posStream.posReceiver; }
        else { _posReceiver = nameToReceiver[receiverName]; }
        
        _posStream.posSender = _posSender;
        _posStream.posReceiver = _posReceiver;
    }    
}

class StageMapper extends Object {
    static public const WIDTH:uint = 465;
    static public const HEIGHT:uint = 465;  
}

/// key section

class KeyState extends Object {
    public var type:String;
    public var charCode:uint = 0;
    public var keyCode:uint = 0;
    public var keyLocation:uint = 0;
    public var ctrlKey:Boolean = false;
    public var altKey:Boolean = false;
    public var shiftKey:Boolean = false;
    public function KeyState( ke:KeyboardEvent = null ):void {
        if ( ke != null ) { drain(ke) }
    }
    public function drain( ke:KeyboardEvent ):void {
        type = ke.type;
        charCode = ke.charCode;
        keyCode = ke.keyCode;
        keyLocation = ke.keyLocation;
        ctrlKey = ke.ctrlKey;
        altKey = ke.altKey;
        shiftKey = ke.shiftKey;
    }
    public function clone( ks:KeyState ):void {
        type = ks.type;
        charCode = ks.charCode;
        keyCode = ks.keyCode;
        keyLocation = ks.keyLocation;
        ctrlKey = ks.ctrlKey;
        altKey = ks.altKey;
        shiftKey = ks.shiftKey;
    }
    
}

class KeyboardRecorder extends Object {
    private const TOTAL_POINTS:int = 300;
    private const TOP_POINTS:int = 30;
    private var _memPts:Vector.<KeyState> = new Vector.<KeyState>(TOTAL_POINTS);
    private var _topInd:Vector.<int> = new Vector.<int>;//(TOP_POINTS);
    
    public function KeyboardRecoder(ksp:Function):void {
        for ( var i:int = 0; i < TOTAL_POINTS; i++ ) { _memPts[i] = new KeyState(); }
        for ( i = 0; i < TOP_POINTS; i++ ) { _topInd.unshift(i); }
    }
    
    private function _storeState(ke:KeyboardEvent):void {
        var tc:int = _topInd[0];
        tc++;
        if(tc==TOTAL_POINTS){tc=0;}
        _topInd.unshift(tc);
        _topInd.pop();
        
        _memPts[tc].drain(ke)
    }
      
    public function record(ke:KeyboardEvent):void {
        _storeState(ke)
    }

}

class ImeWatcher extends Object {
    private var _hasIME:Boolean = false;
    private var _IMEenabled:Boolean = false;
    private var _IMEconversionMode:String

    public var ConfCol:ConfigCollector = new ConfigCollector( MenuMapper.ITEM_IME , ToText );
    public function ImeWatcher():void {
    }

    public function ToText():String {
        var s:String = '';
        s += sprintf('** System has %sIME.\n', _hasIME ? '' : 'no ' ); 
        if ( _hasIME ) {
            s += sprintf( ' enabled: %s\n', _IMEenabled ? 'yes' : 'no' );
            s += sprintf( ' conversionMode: %s\n', _IMEconversionMode );
        }
        return s;
    }
    public function update(): void {
        if ( Capabilities.hasIME ) { 
            _hasIME = Capabilities.hasIME;
            _IMEenabled = IME.enabled;
            _IMEconversionMode = IME.conversionMode;  }

        ConfCol.collect();
    }
}

class KeyWatcher extends Object {
    public var isInIMEProcess:Boolean = false;
    public var isKeyDown:Boolean = false;
    public var key:KeyPoll;
    public var keyState:KeyState = new KeyState();
    public var keyCode:uint = 0;
    public var ConfCol:ConfigCollector = new ConfigCollector( MenuMapper.ITEM_KEYBOARD , ToText );
    public var numpadConfCol:ConfigCollector = new ConfigCollector( MenuMapper.ITEM_NUMPAD , numpadToText );
    
    public var keypollConfCol:ConfigCollector = new ConfigCollector( MenuMapper.ITEM_KEYPOLL , keypollToText );
    public function KeyWatcher():void {
        key = new KeyPoll();
//        ConfCol.isNaked = true;
    }

    public function notifyKeyDown( ks:KeyState ): void {
        isKeyDown = true;
        keyCode = ks.keyCode;
        if (keyCode == KeyboardMapper.IME_PROCESS) {
            isInIMEProcess = true;
        };
        if (isInIMEProcess) {
            if (keyCode != KeyboardMapper.IME_PROCESS) {
                isInIMEProcess = false;
                key.clearStates();
            }
        }
        key.notifyKeyDown(keyCode);
    }  

    public function notifyKeyUp( ks:KeyState ): void {
        isKeyDown = false;
        keyCode = ks.keyCode;
        key.notifyKeyUp(keyCode);
    }      

    public function numpadToText():String {
        var s:String = '';
        s += sprintf( ' Num:%s ',Keyboard.numLock ? '_' : 'T' );
        s += sprintf( ' / :%s ', key.isDown( Keyboard.NUMPAD_DIVIDE ) ? '_' : 'T' );
        s += sprintf( ' *:%s\n', key.isDown( Keyboard.NUMPAD_MULTIPLY ) ? '_' : 'T' );
        s += sprintf( ' 7:%s ', key.isDown( Keyboard.NUMPAD_7 ) ? '_' : 'T' );
        s += sprintf( ' 8:%s ', key.isDown( Keyboard.NUMPAD_8 ) ? '_' : 'T' );
        s += sprintf( ' 9:%s ', key.isDown( Keyboard.NUMPAD_9 ) ? '_' : 'T' );
        s += sprintf( ' - :%s \n', key.isDown( Keyboard.NUMPAD_SUBTRACT ) ? '_' : 'T' );
        s += sprintf( ' 4:%s ', key.isDown( Keyboard.NUMPAD_4 ) ? '_' : 'T' );
        s += sprintf( ' 5:%s ', key.isDown( Keyboard.NUMPAD_5 ) ? '_' : 'T' );
        s += sprintf( ' 6:%s ', key.isDown( Keyboard.NUMPAD_6 ) ? '_' : 'T' );
        s += sprintf( ' +:%s \n', key.isDown( Keyboard.NUMPAD_ADD ) ? '_' : 'T' );
        s += sprintf( ' 1:%s ', key.isDown( Keyboard.NUMPAD_1 ) ? '_' : 'T' );
        s += sprintf( ' 2:%s ', key.isDown( Keyboard.NUMPAD_2 ) ? '_' : 'T' );
        s += sprintf( ' 3:%s ', key.isDown( Keyboard.NUMPAD_3 ) ? '_' : 'T' );
        s += sprintf( ' RET:%s \n', key.isDown( Keyboard.NUMPAD_ENTER ) ? '_' : 'T' );
        s += sprintf( ' 0:%s ', key.isDown( Keyboard.NUMPAD_0 ) ? '_' : 'T' );
        s += sprintf( ' . :%s \n', key.isDown( Keyboard.NUMPAD_DECIMAL ) ? '_' : 'T' );
        
        return s;
    }
    
    public function ToText():String {
        var s:String = '';
        s += sprintf( ' IME Processing: %s\n', isInIMEProcess ? 'YES' : 'no');
        s += sprintf( ' F1:%s ', key.isDown( Keyboard.F1 ) ? '_' : 'T' );
        s += sprintf( 'F2:%s ', key.isDown( Keyboard.F2 ) ? '_' : 'T' );
        s += sprintf( 'F3:%s ', key.isDown( Keyboard.F3 ) ? '_' : 'T' );
        s += sprintf( 'F4:%s ', key.isDown( Keyboard.F4 ) ? '_' : 'T' );
        s += sprintf( 'F5:%s ', key.isDown( Keyboard.F5 ) ? '_' : 'T' );
        s += sprintf( 'F6:%s ', key.isDown( Keyboard.F6 ) ? '_' : 'T' );
        s += sprintf( 'F7:%s ', key.isDown( Keyboard.F7 ) ? '_' : 'T' );
        s += sprintf( 'F8:%s ', key.isDown( Keyboard.F8 ) ? '_' : 'T' );
        s += sprintf( 'F9:%s ', key.isDown( Keyboard.F9 ) ? '_' : 'T' );
        s += sprintf( 'F10:%s ', key.isDown( Keyboard.F10 ) ? '_' : 'T' );
        s += sprintf( 'F11:%s ', key.isDown( Keyboard.F11 ) ? '_' : 'T' );
        s += sprintf( 'F12:%s\n', key.isDown( Keyboard.F12 ) ? '_' : 'T' );
        
        s += sprintf( ' ESC:%s ', key.isDown( Keyboard.ESCAPE ) ? '_' : 'T' );
        s += sprintf( '1:%s ', key.isDown( KeyboardMapper.ONE ) ? '_' : 'T' );
        s += sprintf( '2:%s ', key.isDown( KeyboardMapper.TWO ) ? '_' : 'T' );
        s += sprintf( '3:%s ', key.isDown( KeyboardMapper.THREE ) ? '_' : 'T' );
        s += sprintf( '4:%s ', key.isDown( KeyboardMapper.FOUR ) ? '_' : 'T' );
        s += sprintf( '5:%s ', key.isDown( KeyboardMapper.FIVE ) ? '_' : 'T' );
        s += sprintf( '6:%s ', key.isDown( KeyboardMapper.SIX ) ? '_' : 'T' );
        s += sprintf( '7:%s ', key.isDown( KeyboardMapper.SEVEN ) ? '_' : 'T' );
        s += sprintf( '8:%s ', key.isDown( KeyboardMapper.EIGHT ) ? '_' : 'T' );
        s += sprintf( '9:%s ', key.isDown( KeyboardMapper.NINE ) ? '_' : 'T' );
        s += sprintf( '0:%s ', key.isDown( KeyboardMapper.ZERO ) ? '_' : 'T' );

        s += sprintf( '- :%s ', key.isDown( KeyboardMapper.MINUS ) ? '_' : 'T' );
        s += sprintf( '^:%s ', key.isDown( KeyboardMapper.WEDGE ) ? '_' : 'T' );
        s += sprintf( '\\ :%s ', key.isDown( KeyboardMapper.YEN ) ? '_' : 'T' );
    
        s += sprintf( 'BKS:%s ', key.isDown( Keyboard.BACKSPACE ) ? '_' : 'T' );
        s += sprintf( 'SCL:%s ', key.isDown( KeyboardMapper.SCROLL_LOCK ) ? '_' : 'T' );
        s += sprintf( 'BRK:%s\n', key.isDown( KeyboardMapper.BREAK ) ? '_' : 'T' );

        s += sprintf( ' TAB:%s ', key.isDown( Keyboard.TAB ) ? '_' : 'T' );
        s += sprintf( 'Q:%s ', key.isDown( KeyboardMapper.Q ) ? '_' : 'T' );
        s += sprintf( 'W:%s ', key.isDown( KeyboardMapper.W ) ? '_' : 'T' );
        s += sprintf( 'E:%s ', key.isDown( KeyboardMapper.E ) ? '_' : 'T' );
        s += sprintf( 'R:%s ', key.isDown( KeyboardMapper.R ) ? '_' : 'T' );
        s += sprintf( 'T:%s ', key.isDown( KeyboardMapper.T ) ? '_' : 'T' );
        s += sprintf( 'Y:%s ', key.isDown( KeyboardMapper.Y ) ? '_' : 'T' );
        s += sprintf( 'U:%s ', key.isDown( KeyboardMapper.U ) ? '_' : 'T' );
        s += sprintf( 'I:%s ', key.isDown( KeyboardMapper.I ) ? '_' : 'T' );
        s += sprintf( 'O:%s ', key.isDown( KeyboardMapper.O ) ? '_' : 'T' );
        s += sprintf( 'P:%s ', key.isDown( KeyboardMapper.P ) ? '_' : 'T' );

        s += sprintf( '@:%s ', key.isDown( KeyboardMapper.AT_MARK ) ? '_' : 'T' );
        s += sprintf( '[ :%s ', key.isDown( KeyboardMapper.BRA ) ? '_' : 'T' );
        s += sprintf( 'INS:%s ', key.isDown( Keyboard.INSERT ) ? '_' : 'T' );
        s += sprintf( 'HOM:%s\n', key.isDown( Keyboard.HOME ) ? '_' : 'T' );
        
        s += sprintf( ' CAP:%s ', key.isDown( Keyboard.CAPS_LOCK ) ? '_' : 'T' );
        s += sprintf( 'A:%s ', key.isDown( KeyboardMapper.A ) ? '_' : 'T' );
        s += sprintf( 'S:%s ', key.isDown( KeyboardMapper.S ) ? '_' : 'T' );
        s += sprintf( 'D:%s ', key.isDown( KeyboardMapper.D ) ? '_' : 'T' );
        s += sprintf( 'F:%s ', key.isDown( KeyboardMapper.F ) ? '_' : 'T' );
        s += sprintf( 'G:%s ', key.isDown( KeyboardMapper.G ) ? '_' : 'T' );
        s += sprintf( 'H:%s ', key.isDown( KeyboardMapper.H ) ? '_' : 'T' );
        s += sprintf( 'J:%s ', key.isDown( KeyboardMapper.J ) ? '_' : 'T' );
        s += sprintf( 'K:%s ', key.isDown( KeyboardMapper.K ) ? '_' : 'T' );
        s += sprintf( 'L:%s ', key.isDown( KeyboardMapper.L ) ? '_' : 'T' );
        s += sprintf( '; :%s ', key.isDown( KeyboardMapper.SEMICOLON ) ? '_' : 'T' );
        s += sprintf( ': :%s ', key.isDown( KeyboardMapper.COLON ) ? '_' : 'T' );
        s += sprintf( '] :%s ', key.isDown( KeyboardMapper.KET ) ? '_' : 'T' );
        
        s += sprintf( 'RET:%s ', key.isDown( Keyboard.ENTER ) ? '_' : 'T' );
        s += sprintf( 'DEL:%s ', key.isDown( Keyboard.DELETE ) ? '_' : 'T' );
        s += sprintf( 'END:%s\n', key.isDown( Keyboard.END ) ? '_' : 'T' );
        
        s += sprintf( ' SFT:%s ', key.isDown( Keyboard.SHIFT ) ? '_' : 'T' );
        s += sprintf( 'Z:%s ', key.isDown( KeyboardMapper.Z ) ? '_' : 'T' );
        s += sprintf( 'X:%s ', key.isDown( KeyboardMapper.X ) ? '_' : 'T' );
        s += sprintf( 'C:%s ', key.isDown( KeyboardMapper.C ) ? '_' : 'T' );
        s += sprintf( 'V:%s ', key.isDown( KeyboardMapper.V ) ? '_' : 'T' );
        s += sprintf( 'B:%s ', key.isDown( KeyboardMapper.B ) ? '_' : 'T' );
        s += sprintf( 'N:%s ', key.isDown( KeyboardMapper.N ) ? '_' : 'T' );
        s += sprintf( 'M:%s ', key.isDown( KeyboardMapper.M ) ? '_' : 'T' );
        s += sprintf( ', :%s ', key.isDown( KeyboardMapper.COMMA ) ? '_' : 'T' );
        s += sprintf( '. :%s ', key.isDown( KeyboardMapper.PERIOD ) ? '_' : 'T' );
        s += sprintf( '/ :%s ', key.isDown( KeyboardMapper.SLASH ) ? '_' : 'T' );
        s += sprintf( '\\ :%s ', key.isDown( KeyboardMapper.BACK_SLASH ) ? '_' : 'T' );
        
        s += sprintf( 'LFT:%s ', key.isDown( Keyboard.LEFT ) ? '_' : 'T' );
        s += sprintf( 'UP:%s ', key.isDown( Keyboard.UP ) ? '_' : 'T' );
        s += sprintf( 'RGT:%s\n', key.isDown( Keyboard.RIGHT ) ? '_' : 'T' );

        s += sprintf( ' CTL:%s ', key.isDown( Keyboard.CONTROL ) ? '_' : 'T' );
        s += sprintf( 'WIN:%s ', key.isDown( KeyboardMapper.WIN_LEFT ) ? '_' : 'T' );
        s += sprintf( 'ALT:%s ', key.isDown( KeyboardMapper.ALT ) ? '_' : 'T' );
        s += sprintf( 'MHN:%s ', key.isDown( KeyboardMapper.MUHENKAN ) ? '_' : 'T' );
        s += sprintf( 'SPC:%s ', key.isDown( Keyboard.SPACE ) ? '_' : 'T' );
        s += sprintf( 'HEN:%s ', key.isDown( KeyboardMapper.HENKAN ) ? '_' : 'T' );
        s += sprintf( 'WIN:%s ', key.isDown( KeyboardMapper.WIND_RIGHT ) ? '_' : 'T' );
        s += sprintf( 'POP:%s ', key.isDown( KeyboardMapper.WIN_POP ) ? '_' : 'T' );
        s += sprintf( 'DWN:%s\n', key.isDown( Keyboard.DOWN ) ? '_' : 'T' );

        s += sprintf( ' IME:%s ', key.isDown( KeyboardMapper.IME_PROCESS ) ? '_' : 'T' );
        s += sprintf( 'HAN:%s ', key.isDown( KeyboardMapper.HANKAKU ) ? '_' : 'T' );
        s += sprintf( 'ZEN:%s ', key.isDown( KeyboardMapper.ZENKAKU ) ? '_' : 'T' );
        s += sprintf( 'KANJI:%s ', key.isDown( KeyboardMapper.KANJI ) ? '_' : 'T' );
        s += sprintf( 'EISU:%s ', key.isDown( KeyboardMapper.EISU ) ? '_' : 'T' );
        s += sprintf( 'HIRA:%s\n', key.isDown( KeyboardMapper.HIRAGANA ) ? '_' : 'T' );
//        s += '\n';

//        s += key.toText();
        return s;
    }
    
    public function keypollToText():String {
        var s:String = ''
        s += '\t\t+0\t+1\t+2\t+3\t+4\t+5\t+6\t+7\t\t+8\t+9\t+A\t+B\t+C\t+D\t+E\t+F\n' 
        
        for ( var i:int = 0; i < 32; i+=16 ) {
            s += sprintf( ' %04X:\t%02X\t%02X\t%02X\t%02X\t%02X\t%02X\t%02X\t%02X\t', 
                i, key.states[i], key.states[i + 1], key.states[i + 2], key.states[i + 3], key.states[i + 4], key.states[i + 5], key.states[i + 6], key.states[i + 7]);
            s += sprintf( '\t%02X\t%02X\t%02X\t%02X\t%02X\t%02X\t%02X\t%02X\n', 
                key.states[i + 8], key.states[i + 9], key.states[i + 10], key.states[i + 11], key.states[i + 12], key.states[i + 13], key.states[i + 14], key.states[i + 15]);
        }
        
//        if ( isKeyDown ){
        
        if ( (keyCode) == KeyboardMapper.KANJI ) {
            s += sprintf(' ** keyCode 0x%02X is ALT+KANJI.\n', keyCode);
            return s;
        }
        
        if ( (keyCode) == KeyboardMapper.IME_PROCESS ) {
            s += sprintf(' ** keyCode 0x%02X is IME_PROCESS.\n', keyCode);
            return s;
        }
        
        if ( (keyCode & 0xF8) == 0xF0 ) {
            s += sprintf(' ** keyCode 0x%02X is Special key.\n', keyCode);
            return s;
        }

        if (!isInIMEProcess) {
            s += sprintf(' keyCode 0x%02X turned to 0x%02X, stacked at states[0x%02X].\n' , 
            keyCode, 1 << (keyCode & 7), keyCode >>> 3);
        }
        else {
            s += ' ** IME processing...\n'
        }
        return s;
        
    }

    public function update(): void {
        ConfCol.collect();
        numpadConfCol.collect();
        keypollConfCol.collect();
    }

}


/// menu section

class ConCoor extends Object {
    public var content:*;
    public var coordinate:Object;
    public var cc:ConfigCollector;
    public function preConvert(confCol:ConfigCollector):void {}
    public function afterConvert(beginIndex:uint, endIndex:uint):void {}
    
    public function ConCoor(con:*, coor:Object = null):void {
        content = con;
        coordinate = coor;
    }
}

class MenuCursor extends ConCoor {
    private var _isSelected:Boolean = false;
    
    public function MenuCursor(con:*, coor:Object = null):void {
        super(con, coor);
    }

    override public function preConvert(confCol:ConfigCollector):void {
        cc = confCol;
        if ( confCol.isSelected ) {
            _isSelected = true;
            this.content = '[' + String(this.content) + ']';
        }
        else { _isSelected = false; }
    }
    
    override public function afterConvert(beginIndex:uint, endIndex:uint):void 
    {
        if (cc == null) { return; }
        if (_isSelected) {
            var tc:textCoordinator = new textCoordinator();
            tc.beginCharIndex = beginIndex;
            tc.endCharIndex = beginIndex+1;
            tc.property = 'color';
            tc.param = ColorMapper.MENU_CURSOR_DEFAULT;
            cc.commander.textCoordinators.unshift(tc);
           
            var tc2:textCoordinator = new textCoordinator();
            tc2.beginCharIndex = endIndex-1;
            tc2.endCharIndex = endIndex;
            tc2.property = 'color';
            tc2.param = ColorMapper.MENU_CURSOR_DEFAULT;
            cc.commander.textCoordinators.unshift(tc2);
            
        }            
    }
}

class MenuHeaderCursor extends ConCoor {
    private var _isFocused:Boolean = false;
    
    public function MenuHeaderCursor(con:*, coor:Object = null):void {
        super(con, coor);
    }

    override public function preConvert(confCol:ConfigCollector):void {
        cc = confCol;
        if ( cc.isSelected ) {
            _isFocused = true;
            if ( cc._canFocusOut ) {
                if(cc._isExpanded) {
                this.content = '[ ' + String(this.content) + (cc.hasParams ? ' >' : ' ]');
                }
                else {
                    this.content = '[ ' + String(this.content) + ' ]';
                }
            }
            else {
                this.content = '[ ' + String(this.content)
            }
        }
        else { _isFocused = false; }
    }
    
    override public function afterConvert(beginIndex:uint, endIndex:uint):void 
    {
        if (cc == null) { return; }
        if (!_isFocused) { return; }
        
        var tfs:TextFormatStack = new TextFormatStack();
        tfs.beginEndIndexes.push(beginIndex + 1);
        tfs.beginEndIndexes.push(endIndex - (cc.canFocusOut? 1 : 0));
        
        var textFormat:TextFormat = new TextFormat();
        textFormat.color = ColorMapper.MENU_HEADER_SELECTED;
        tfs.textFormats.push(textFormat);

        cc.commander.textCoordinators.unshift(tfs);    
        
        var tc:textCoordinator = new textCoordinator();
        tc.beginCharIndex = beginIndex;
        tc.endCharIndex = beginIndex+1;
        tc.property = 'color';
        if(cc._isExpanded){
            tc.param = ColorMapper.MENU_CURSOR_DEFAULT;
        }
        else {
            tc.param = ColorMapper.MENU_CURSOR_DEFAULT;
        }
        cc.commander.textCoordinators.unshift(tc);
           
        if (cc.canFocusOut) {
            var tc2:textCoordinator = new textCoordinator();
            tc2.beginCharIndex = endIndex-1;
            tc2.endCharIndex = endIndex;
            tc2.property = 'color';
            if(cc._isExpanded){
                tc2.param = ColorMapper.MENU_CURSOR_DEFAULT;
            }
            else {
                tc2.param = ColorMapper.MENU_CURSOR_DEFAULT;
            }
            cc.commander.textCoordinators.unshift(tc2);
        }
    }
}

class ParamCoor extends ConCoor {
    public var paramCol:ParamController=null;
    public function ParamCoor(con:*, coor:Object = null):void {
        super(con, coor);
        if ( con is ParamController ) { paramCol = con; }
    }    
    
    override public function preConvert(confCol:ConfigCollector):void 
    {
        cc = confCol;
        if ( paramCol != null ) {
            this.content = paramCol.config;
        }
    }
    
    override public function afterConvert(beginIndex:uint, endIndex:uint):void 
    {
        if (( paramCol == null ) || ( cc == null )) { return; }
        if ( !cc.isSelected ) { return; }
        var tfs:TextFormatStack = new TextFormatStack();
        tfs.beginEndIndexes.push(beginIndex);
        tfs.beginEndIndexes.push(endIndex);
        
        var textFormat:TextFormat = new TextFormat();
        textFormat.color = paramCol.textColor;
        tfs.textFormats.push(textFormat);
        
        cc.commander.textCoordinators.unshift(tfs);    
    }
    
}

class ConfigCollector extends Object {
    public var owner:Object;
    
    private var _isEditable:Boolean = false;
    
    public function get isEditable():Boolean {
        return _isEditable;
    }
    
    public function get hasParams():Boolean {
        return false;
    }    
    
    public var commander:ConfigMenu = null;
    public var topCharIndex:uint;
    public var currentCharIndex:uint;
    
    public var header:String = '';
    public var config:String = '';
    
    public static const CM_EXPAND:String = 'isExpanded'
    public var _isExpanded:Boolean = false;
    
    public function get isExpanded():Boolean {
        return this._isExpanded;
    }

    public function set isExpanded(state:Boolean):void {
        
        
        this._isExpanded=state
    }

    public var _willNext:Boolean;
    
    public function set willNext(state:Boolean):void {
        _willNext = state;
    }
    
    public var _willPrior:Boolean;
    
    public function set willPrior(state:Boolean):void {
        _willPrior = state;
    }
    
    public var _willRight:Boolean;
    
    public function set willRight(state:Boolean):void {
        _willRight = state;
        isExpanded = state;
    }

    public var _willLeft:Boolean;
    
    public function set willLeft(state:Boolean):void {
        _willLeft = state;
        isExpanded = !state;
    }
    
    public function get isSelectable():Boolean {
        return ((!isNaked) && (isVisible))
    }
    
    public function get canFocusIn():Boolean {
        return isSelectable;
    }
    
    public var _canFocusOut:Boolean = true;    
    public function get canFocusOut():Boolean {
        return _canFocusOut;
    }

    public function set canFocusOut(state:Boolean):void {
        _canFocusOut = state;
    }
    
    public var isDebugging:Boolean = false; // debbuging mode
    
    public static const CM_SELECT:String = 'isSelected';
    public var isSelected:Boolean = false;
    
    public static const CM_NAKED:String = 'isNaked';
    public var isNaked:Boolean = false;     // display config only

    public static const CM_VISIBLE:String = 'isVisible';
    public var isVisible:Boolean = true;
    
    public static const CM_LOCK:String = 'isLocked';    
    public var isLocked:Boolean = false;
    
    public function setThisToMenuMapper( mappedName:String ):void {
        if ( mappedName != MenuMapper.NOENTRY )
        {
            MenuMapper.nameToMenuItem[header] = this;
            // in case of Owner is not singleton, use MultiConfigCollector // 
        }
    }
    
    public function ConfigCollector( mappedName:String, collectmethod:Function, ownerRef:Object = null ) { 
        this.header = mappedName;
        this.owner = ownerRef;
        // Owner's states are collected through collectmethod() after ENTER_FRAME event occured. 
        this.collectmethod = collectmethod;
        
        setThisToMenuMapper( mappedName );
    }
    
    public function initParams():void {}
    
    public function toString():String {
        return header;    
    }
    
    public function flagsToStr(cc:ConfigCollector):String {
        return sprintf( '(%s%s%s%s%s) ',
            cc.isExpanded ? 'e' : '_' , 
            cc.isSelected ? 's' : '_' , 
            cc.isNaked ? 'n' : '_' , 
            cc.isVisible ? 'v' : '_' , 
            cc.isLocked ? 'l' : '_')
    }
    
    public function toDebugStr():String {
        var s:String = '';
        if (isNaked) { s += header+'\n'; }        
        s += flagsToStr( this );
        return s;    
    }
    
    public function headerToText():String {
        var h:String = header;
        var s:String = '';

        if (!isVisible) { return '' }
        if (!isNaked) {

            s = coSprintf( s, '%s%s%s%s',
                isExpanded ? '-' : '+',
                new MenuHeaderCursor( h ),
//                (isSelected && canFocusOut) ? '[' + h + ']' : ' ' + h + ' ',
                (isExpanded && !isSelected) ? '' : '',
                isLocked ? ' o' : '  ' );

/*                
            if ((this.isSelected)&&(this.canFocusOut)) { s += '[' + h + ']'; } else { s += ' ' + h + ' '; }
            if (this.isLocked) { s += ' o'; } else { s += '  '; } 
            if (this.isExpanded) { s = '-'+s; } else { s = '+'+s; }
*/            
            s += '\n';
        }
        return s;
    }
    
    public function configToText():String {
        config = collectConfig();
        return config;
    }
    
    public function colored(preText:String, coloringText:String, color:uint):String {
        var tc:textCoordinator = new textCoordinator();
        tc.beginCharIndex = currentCharIndex + preText.length;
        tc.endCharIndex = tc.beginCharIndex + coloringText.length;
        tc.property = 'color';
        tc.param = color;
        if ( commander != null ) { commander.textCoordinators.unshift(tc) };
        return preText + coloringText;
    }
    
    public function setTextFormatsProperties(preText:String, coloringText:String, nameToValue:Object):String {
        var tc:textCooridinatorEx = new textCooridinatorEx();
        tc.beginCharIndex = currentCharIndex + preText.length;
        tc.endCharIndex = tc.beginCharIndex + coloringText.length;
        tc.nameToValue = nameToValue;
        if ( commander != null ) { commander.textCoordinators.unshift(tc) };
        return preText + coloringText;
    }
    
    public function coSprintf(preText:String, format:String, ... args):String {
        var charPosIndexes:Array = new Array();
        var conCoorStack:Array = new Array();
        var conCoor:ConCoor;
        
        for (var i:int = 0; i < args.length; i++) { 
            if ( args[i] is ConCoor ) { 
                conCoor = args[i];
                conCoor.preConvert(this);
                
                conCoorStack.push(conCoor);
                args[i] = conCoor.content;
                
                charPosIndexes.push(1); // command 'return content index' to sprintf 
            }
            else {
                charPosIndexes.push(0);
            }
        }
        
        var s:String = preText + sprintf(format, charPosIndexes, args);
        
        var offsetIndex:uint;
        var beginIndex:uint;
        var endIndex:uint;
        
        if ( commander != null ) {
            offsetIndex = currentCharIndex + preText.length;
            
            while ( conCoorStack.length > 0 ) { 
                conCoor = conCoorStack.shift();
                
                beginIndex = offsetIndex + charPosIndexes.shift();
                endIndex = beginIndex + charPosIndexes.shift();
                
                conCoor.afterConvert(beginIndex, endIndex);
                
/*              var tc:textCoordinator = new textCoordinator();
                tc.beginCharIndex = beginIndex;
                tc.endCharIndex = endIndex;
                tc.property = 'color';
                tc.param = 0xFF0000;
                commander.textCoordinators.unshift(tc);
*/                
            }
        }
        
        return s;  
    }
    
    public function toText():String {
        var s:String = '';
        currentCharIndex = topCharIndex;
        s += headerToText();
        currentCharIndex = topCharIndex + s.length;
        if (this.isDebugging) { s += toDebugStr() + '\n'; }
        currentCharIndex = topCharIndex + s.length;
        s += configToText();
        return s; 
    }
    
    public function collectConfig():String {
        var s:String = '';
        if (this.collectmethod == null) { return s; }
        if (!this.isExpanded) { return s; }
        s += this.collectmethod();
        return s;
    }

    public var collectmethod:Function = null;
    public function collect():void {
//        config = collectConfig();
    }
    
    // Commands are sent to Owner through notifyKeyDown( ks:KeyState ) after keyDown event occured
    public var notifyKeyDown:Function=null; 
    public var notifyKeyUp:Function=null; 
}

class EditableConfigCollector extends ConfigCollector {
    public var parentParam:ParamController = null;
    public var childParam:ParamController = null;
    public var lastParam:ParamController = null;
    
    public static const CM_SUB:String = 'willSubtract';    
    public var _willSubtract:Boolean = false;
    public function set willSubtract(state:Boolean):void {
        _willSubtract = state;
    }
    public function get willSubtract():Boolean {
        return _willSubtract;
    }

    public static const CM_ADD:String = 'willAdd';    
    public var _willAdd:Boolean = false;
    public function set willAdd(state:Boolean):void {
        _willAdd = state;
    }
    public function get willAdd():Boolean {
        return _willAdd;
    }
    
    public function executeAdd():void {}
    public function executeSubtract():void {}
    
    public function EditableConfigCollector( mappedName:String, collectmethod:Function, ownerRef:Object = null ) {
        super( mappedName, collectmethod, ownerRef ); 
    }
    
    override public function get hasParams():Boolean {
        return !( childParam == null );
    }
    
    override public function set isExpanded(state:Boolean):void {
        this._isExpanded=state
        if ( hasParams ) {
            childParam.isExpanded = state;  // this method is called recursively. if a child has a state, its all children have the same state. 
        }
    }

}

class MultiConfigCollector extends ConfigCollector {
    public var theFirst:MultiConfigCollector = null;
    public var successor:MultiConfigCollector = null;

    public function MultiConfigCollector( mappedName:String, collectmethod:Function, ownerRef:Object = null ) {
        this.theFirst = this;
        super( mappedName, collectmethod, ownerRef ); 
    }
    
    override public function setThisToMenuMapper( mappedName:String ):void {
        if ( mappedName != MenuMapper.NOENTRY )
        {
            if ( MenuMapper.nameToMenuItem[header] == undefined ) {
                MenuMapper.nameToMenuItem[header] = this;
            }

            // in case of Owner is not singelton
            else {
                var pre:MultiConfigCollector = MenuMapper.nameToMenuItem[header];  
                pre.successor = this;
                this.theFirst = pre.theFirst;
                MenuMapper.nameToMenuItem[header] = this; // any header correspods to the last generated ConfigCollector on the MenuMap.
                
            }
        }
    }

    override public function get isExpanded():Boolean {
        return theFirst._isExpanded;
    }

    override public function set isExpanded(state:Boolean):void {
        theFirst._isExpanded=state
    }
    
    override public function toDebugStr():String 
    {
        var s:String = '';
        s += super.toDebugStr();
        s += sprintf( '(%s%s) ', (this == theFirst) ? 'f' : '_' , (successor == null) ? 'b' : '_' );
        if (this != theFirst) { s += flagsToStr( theFirst ); }
        return s;
    }

    override public function configToText():String {
        var s:String = '';
        var cc:MultiConfigCollector = this.theFirst;
        if (this.isExpanded) {
            while ( cc != null ) 
            {
                s += cc.config; 
                cc = cc.successor;
            }
        }
        return s; 
    }
    
}

class ParamsControlManager extends EditableConfigCollector {
    public var watchingParam:ParamController = null;
    
    override public function get isEditable():Boolean {
        if ( watchingParam != null ) { return ( watchingParam.isSelected ); }
        return false;
    }
    
    public function ParamsControlManager( mappedName:String, collectmethod:Function, ownerRef:Object = null ) {
        super( mappedName, collectmethod, ownerRef );  
    }

    override public function set willSubtract(state:Boolean):void {
        if ( isEditable ) { 
            watchingParam._willSubtract = true;
            watchingParam.executeSubtract();  }
        else { _willSubtract = state; }
    }
    override public function get willSubtract():Boolean {
        if ( watchingParam != null ) { return watchingParam._willSubtract; }
        else { return _willSubtract; }
    }
    
    override public function set willAdd(state:Boolean):void {
        if ( isEditable ) { 
            watchingParam._willAdd = true;
            watchingParam.executeAdd();
        }
        else { _willAdd = state; }
    }
    override public function get willAdd():Boolean {
        if ( watchingParam != null ) { return watchingParam._willAdd; }
        else { return _willAdd; }
    }
    
    override public function set willNext(value:Boolean):void {
        if (isEditable) {
            canFocusOut = false;
            nextParam();
        }
        else { _willNext = true; }
    }

    override public function set willPrior(value:Boolean):void {
        if (isEditable) {
            canFocusOut = false;
            priorParam();
        }
        else { _willPrior = true; }
    }

    override public function set willRight(value:Boolean):void {
        if (isEditable) {
            canFocusOut = false;
            nextParam();
        }
        else( isExpanded = value )
    }
    
    override public function set willLeft(value:Boolean):void {
        if (isEditable) {
            canFocusOut = false;
            priorParam();
            _willLeft = false;
        }
        else{ isExpanded = !value };
    }
    
    
    public function addParam():void {
        
    }
    
    public function subtractParam():void {
    }
    
    public function nextParam():void {
        if (watchingParam.childParam != null) {
                watchingParam.isSelected = false;
                watchingParam = watchingParam.childParam;                
                watchingParam.isSelected = true;
        }
    }
    
    public function priorParam():void {
        if (watchingParam.parentParam != null) {
                watchingParam.isSelected = false;
                watchingParam = watchingParam.parentParam;                
                watchingParam.isSelected = true;
        }
        else { 
            this.watchingParam.isSelected = false;
            this.canFocusOut = true;
            this._willPrior = false;
        }
    }    
    
    override public function set isExpanded(state:Boolean):void 
    {
        if (state == true) {
            if (this._isExpanded) {
                if (this.hasParams) {
                    this.watchingParam.isSelected = true;
                    this.canFocusOut = false;
                }
                return;
            }
            else {
                this._isExpanded = true;
                if (this.hasParams) {
                    this.childParam.isExpanded = true; 
                }
                return;
            }
        }
        
        if (state == false) {
            if (this.hasParams) {
                if (this.watchingParam.isSelected) {
                    this.watchingParam.isSelected = false;
                    this.canFocusOut = true;
                    return;
                }
                else {
                    this.childParam.isExpanded = false;
                    this._isExpanded = false;
                    return;
                }
            }
            this._isExpanded = false;
        }
    }
    
    public function addChildParam(param:ParamController):void {
        if (childParam == null) {
            childParam = param;
            childParam.parentParam = null;  
            lastParam = param;
            watchingParam = param ;
        }
        else {
            lastParam.childParam = param;
            param.parentParam = lastParam;
            lastParam = param;
        }
    }

    override public function configToText():String {
        if (this.childParam != null) {
            var paramCol:ParamController = childParam;
            while (paramCol != null) {
                paramCol.updateConfig();
                paramCol = paramCol.childParam;
            }
        }        

        var s:String = super.configToText();
        
        config = s;
        return config;
    }
}

class ParamController extends EditableConfigCollector {
    public var hasParent:Boolean = false;
    public var hasCorrectParam:Boolean = false;
    
    public var isProtected:Boolean = false;
    public var paramName:String = '';
    public var modifier:Modifier = null;
    
    public var cursor:String = '> ';
    public var textColor:uint = ColorMapper.MENU_PARAM_DEFAULT;
    
    public function get ownerValue():* {
        if (!hasCorrectParam) { return undefined; }
        return owner[paramName];
    }
    
    public function set ownerValue(v:*):void {
        if (!hasCorrectParam) { return ; }
        owner[paramName] = v;
    }

    override public function collectConfig():String {
        var s:String = '';
        if (this.collectmethod == null) { return s; }
        if (!this.isExpanded) { return s; }
        s += this.collectmethod(this);
        return s;
    }    
    
    public function updateConfig():void {
        config = collectConfig();
    }
    
    public function setModifier(mo:Modifier):void {
        modifier = mo;
    }
    
    override public function executeAdd():void     {
        if ( modifier != null) { 
            if (!isProtected) { modifier.addParam(this); }
        }
    }
    
    override public function executeSubtract():void {
        if ( modifier != null) {
            if (!isProtected) { modifier.subtractParam(this); }
        }
    }
    
    // if a ConfigCollector has same owner and same header( = mappedName ) to 'this', then the ConfigCollector is the parent of 'this'.
    public function findParentConfigCollector():Boolean {
        var cc:ParamsControlManager = MenuMapper.nameToMenuItem[header];
        if ( cc == null ) { return false; } // bad end. perhaps header name is incorrect. 
        if ( cc.owner     == this.owner ) 
            { cc.addChildParam(this); return true; } // normal end. 'this' param generated from 'this' owner after the last generated config.
/*
        cc = cc.theFirst;
        while ( cc != null ) { 
            if ( cc.owner == this.owner ) { 
                cc.addChildParam(this); return true; // in case that owner not always generates param after config.
            }
            cc = cc.successor;
        }
        // if 'this' param found no parent, theFirst becomes to 'this' parent.  
        cc.theFirst.addChildParam(this);
*/        
        return true;
    }
    public function ParamController( mappedName:String, propertyName:String, collectmethod:Function, ownerRef:Object = null ):void {
        super(MenuMapper.NOENTRY, collectmethod, ownerRef);
        this.header = mappedName; 
        this.paramName = propertyName;
        hasParent = findParentConfigCollector();
        hasCorrectParam = owner.hasOwnProperty(paramName);
    }

    
}

class ParamToTextTemplate extends Object { 
    static public function BooleanToYesNo(pm:ParamController):String {
        var s:String = '';
        s += sprintf(' %s%s', pm.isSelected ? pm.cursor : ' ', pm.ownerValue ? 'yes' : 'no');
        pm.textColor = pm.ownerValue ? ColorMapper.MENU_PARAM_YES : ColorMapper.MENU_PARAM_NO;
        return s;
    }
    static public function NumberToFormatText(pm:ParamController, format:String = ' %s%+07.3f'):String {
        var s:String = '';
        s += sprintf( format, pm.isSelected ? pm.cursor : ' ', pm.ownerValue );
        return s;
    }
}

class Modifier extends Object {
    private var _isStopAuto:Boolean = true;
    public function get isStopAuto():Boolean {
        return _isStopAuto;
    }
    public function set isStopAuto(state:Boolean):void {
        _isStopAuto = state;
    }
    
    public function setInitialCondition( paramCol:ParamController ):void { }
    public function addParam( paramCol:ParamController ):void { }
    public function subtractParam( paramCol:ParamController ):void { }
}

class ParamModifier extends Modifier {
    public var initial:Number=0;
    public var min:Number=-1;
    public var max:Number=1;
    public var step:Number=0.1;
    public function ParamModifier( i:Number=0, mn:Number=-1, mx:Number=1, st:Number=0.1, auto:Boolean=true ):void {
        initial = i;
        min = mn;
        max = mx;
        step = st;
        isStopAuto = auto;
    }

    
    
    override public function addParam( paramCol:ParamController ):void {
        var value:Number = paramCol.ownerValue;
        value += step;
        if (value > max) { value = max; }
        paramCol.ownerValue = value;
        paramCol._willAdd = !isStopAuto;
    }
    override public function subtractParam( paramCol:ParamController ):void {
        var value:Number = paramCol.ownerValue;
        value -= step;
        if (min > value) { value = min; }
        paramCol.ownerValue = value;
        paramCol._willAdd = !isStopAuto;
    }
    
}

class BooleanModifier extends Modifier {
    public var state:Boolean = false;
    public function BooleanModifier( st:Boolean = false , auto:Boolean = true ):void {
        state = st;
        isStopAuto = auto;
    }
    override public function addParam( paramCol:ParamController ):void {
        var bool:Boolean = paramCol.ownerValue;
        paramCol.ownerValue = !bool;
    }
    
    override public function subtractParam( paramCol:ParamController ):void {
        var bool:Boolean = paramCol.ownerValue;
        paramCol.ownerValue = !bool;
    }
}

class textCoordinator extends Object {
    public var property:String;
    public var beginCharIndex:uint;
    public var endCharIndex:uint;
    public var param:*;
    public function ToText():String {
        var s:String = '';
        if ( property == 'color' ) {
            s = sprintf('property: %s  beginIndex: %d  endIndex: %d  param: 0x%X \n', property, beginCharIndex, endCharIndex, uint(param) ); 
        }
        else { s = sprintf('property: %s  beginIndex: %d  endIndex: %d  param: %s \n', property, beginCharIndex, endCharIndex, String(param) ); }
        return s;
    }
    public function apply( tf:TextField ):void {
        var textFormat:TextFormat = new TextFormat();
        textFormat[property] = param;
        tf.setTextFormat( textFormat, beginCharIndex, endCharIndex );
    }
}

class textCooridinatorEx extends textCoordinator {
    public var nameToValue:Object;
    override public function apply( tf:TextField ):void {
        var textFormat:TextFormat = new TextFormat();
        for (var str:String in nameToValue) {
            textFormat[str] = nameToValue[str];
        }
        tf.setTextFormat( textFormat, beginCharIndex, endCharIndex );
    }
}

class TextFormatStack extends textCoordinator {
    public var beginEndIndexes:Array=new Array();
    public var textFormats:Array=new Array();
    override public function apply( tf:TextField ):void {
        while (textFormats.length > 0) {
            tf.setTextFormat( textFormats.shift(), beginEndIndexes.shift(), beginEndIndexes.shift() );
        }
    }
}

class ConfigMenu extends Object {
    public var menuBar:ConfigMenuBar;
    
    public var groupList:Vector.<ConfigCollectorManager> = new Vector.<ConfigCollectorManager>;
    
    public var textCoordinators:Vector.<textCoordinator> = new Vector.<textCoordinator>;

    public var confCol:ConfigCollector = new ConfigCollector( MenuMapper.ITEM_MENUSTATUS , ToText );    
    
    public function ConfigMenu( he:String = '' ):void {
        header = he;
        menuBar = new ConfigMenuBar(this);
    }
    
    public function ToText():String {
        var s:String = '';
        if ( isLocked ) { s += ' ** Locked ** (Unlock - \'ctrl-M\')\n' };
        s += String(textCoordinators.length)+'\n';
        if ( textCoordinators.length > 0 ) {
            for each( var tc:textCoordinator in textCoordinators ) {
                s += tc.ToText() + '\n';
            }
        }
        
//        s += sprintf(' %s - selected \n', watchList[itemIndex].header);
//        s += sprintf(' %s - expanded \n', watchList.filter(_isExpanded).join());

        return s;
    }

    public function addGroup( ccm:ConfigCollectorManager ):Boolean {
        var success:Boolean = false;
        groupList.push( ccm );
        var g:int = groupCount - 1;
        groupNameToGroupIndex[ ccm.header ] = g;
        var c:int = ccm.itemCount;
        if ( c > 0 ) {
            for ( var i:int = 0; i < c; i++ ) {
                itemNameToGroupItemIndex[ ccm.watchList[i].header ] = [g, i]; 
            }
        }
        menuBar.groupNames.push( ccm.header );
        ccm.commander = this;
        
        return success;
    }
    
    public function addItem( mappedName:String, groupName:String = '' ):Boolean {
        var cc:* = MenuMapper.nameToMenuItem[mappedName];   // get reference of the configCollector correspond to the mappedName
        if ( cc == undefined ) { return false; }
        return watch( cc, groupName );
    }

    public function get Item():ConfigCollector {
        if ( groupCount > 0 ) {
            if ( groupList[ groupIndex ].itemCount > 0 ) {
                return groupList[ groupIndex ].item
            }
        }
        return null;
    }
    
    public function initGroups():void {
        if ( groupCount > 0) {
            for each(var group:ConfigCollectorManager in groupList) { group.initItems(); }
            groupList[ groupIndex ].isSelected = true;
        }
        menuBar.confCol.isExpanded = true;
        menuBar.confCol.isNaked = true;
    }
    
    public function notifyKeyDown( itemName:String, ks:KeyState ):void {
        if ( itemName == MenuMapper.ITEM_MENUBAR ) { menuBar.notifyKeyDown(ks); }
        else {
            var i:* = itemNameToGroupItemIndex[itemName];
            if ( i == undefined ) { return; }
            var notify:Function = groupList[i[0]].watchList[i[1]].notifyKeyDown;
            if ( notify != null ) { notify(ks); }
        }
    }

    public function notifyKeyUp( itemName:String, ks:KeyState ):void {
        if ( itemName == MenuMapper.ITEM_MENUBAR ) { menuBar.notifyKeyUp(ks); }
        else {
            var i:* = itemNameToGroupItemIndex[itemName];
            if ( i == undefined ) { return; }
            var notify:Function = groupList[i[0]].watchList[i[1]].notifyKeyUp;
            if ( notify != null ) { notify(ks); }
        }
    }

    public function find( name:String ):int {
        var index:* = groupNameToGroupIndex[ name ];
        if ( index == undefined ) { return NOT_FOUND; }
        return index;
    }    

    public function moveToItem( name:String ):Boolean {
        var result:Boolean = false;
        var i:*= itemNameToGroupItemIndex[ name ];
        if ( i == undefined ) { return false; }
        groupIndex = i[0];
        itemIndex = i[1];
        result = true;
        return result;
    }
    
//  same as ConfigCollectorManager propertyname
    public function get itemIndex(): int {
        if ( groupList.length > 0 ) {
            return groupList[groupIndex].itemIndex;
        }
        else return NOT_FOUND;
    }

    public function set itemIndex(index:int):void {
        if ( groupList.length > 0 ) {
            groupList[groupIndex].itemIndex = index;
        }
    }

    public function get isLocked(): Boolean {
        if ( groupList.length > 0 ) {
            return groupList[groupIndex].isLocked;
        }
        else return false;
    }

    public function set isLocked(state:Boolean):void {
        if ( groupList.length > 0 ) {
            groupList[groupIndex].isLocked = state;
        }
    }
    
//  same as ConfigCollectorManager methodname
    public function watch( cc:ConfigCollector, groupName:String = '' ):Boolean {
        var success:Boolean = false;
        if ( groupList.length > 0 ) {
            var i:int;

            if ( groupName == '' ) { i = groupIndex; }
            else {
                i = find(groupName);
                if ( i == NOT_FOUND ) { return false; }
            }
            groupList[i].watch(cc);
            itemNameToGroupItemIndex[ cc.header ] = [i, groupList[i].itemCount - 1];
            success = true;
            cc.commander = this;
        }
        return success;
    }

    public function nextItem():int {
        if ( groupList.length > 0 ) {
            return groupList[groupIndex].next();
        }
        return ConfigCollectorManager.NOT_FOUND;
    }

    public function priorItem():int {
        if ( groupList.length > 0 ) {
            return groupList[groupIndex].prior();
        }
        return ConfigCollectorManager.NOT_FOUND;        
    }
    
    public function expandItem( name:String = '', state:Boolean = true ):Boolean {
        var success:Boolean = false;
        if ( groupList.length > 0 ) {
            success = groupList[groupIndex].expandItem( name, state );
        }
        return success;
    }
    
    public function getItemState( command:String, itemName:String ):Boolean {
        var result:Boolean = false;
        if ( groupList.length > 0 ) {
            var i:* = itemNameToGroupItemIndex[itemName];
            if ( i == undefined ) { return false; }
            result = groupList[i[0]].getItemState( command, itemName );
        }
        return result;
    }
    
    public function setItemState( command:String, state:Boolean, itemName:String='' ):Boolean {
        var result:Boolean = false;
        if ( groupList.length > 0 ) {
            if ( itemName == '' ) {
                result = groupList[groupIndex].setItemState( command, state, itemName );
                return result
            }
            var i:* = itemNameToGroupItemIndex[itemName];
            if ( i == undefined ) { return false; }
            result = groupList[i[0]].setItemState( command, state, itemName );
        }
        return result;
    }
    
    public function lockItem( name:String = '', state:Boolean = true ):Boolean {
        var success:Boolean = false;
        if ( groupList.length > 0 ) {
            success = groupList[groupIndex].lockItem( name, state );
        }
        return success;
    }
    
    public function unlockItem( name:String = '', state:Boolean = true ):Boolean {
        return lockItem( name, !state );
    }
    
    public function collapseItem( name:String = '', state:Boolean = true ):Boolean {
        return expandItem( name, !state );
    }

    public function expandAll():void {
        groupList[groupIndex].expandAll();
    }

    public function enter():void {
        groupList[groupIndex].setItemState(ConfigCollector.CM_EXPAND, true);
        groupList[groupIndex].setItemState(ConfigCollector.CM_EXPAND, true);
    }
    
    public function collapseAll():void {
        groupList[groupIndex].collapseAll(); 
    }

    public function exit():void {
        groupList[groupIndex].setItemState(ConfigCollector.CM_EXPAND, false);
        groupList[groupIndex].setItemState(ConfigCollector.CM_EXPAND, false);
    }
    
    public function left():void { 
        if (groupCount > 0) {
            groupList[groupIndex].left();
        }
    }
    
    public function right():void {
        if (groupCount > 0) {
            groupList[groupIndex].right();
        }
    }
    
    
    public function get selectBeginCharIndex():uint {
        return groupList[groupIndex].beginCharIndexes[itemIndex];
    }
    
    public function get selectEndCharIndex():uint {
        return groupList[groupIndex].endCharIndexes[itemIndex];
    }
    
    public function reportConfig(): String {
        this.confCol.collect();
        menuBar.confCol.collect();
        
        for each(var group:ConfigCollectorManager in groupList) { group.confCol.collect(); }

        var s:String='';
        s += menuBar.confCol.toText();
        
        if ( groupCount > 0 ) {
            groupList[groupIndex].topCharIndex = s.length;
            s += groupList[groupIndex].reportConfig();
        }
        
        return s;
   }

//    all the same to ConfigCollectorManager definition ( renamed item -> group, Item -> Group )
    public var header:String = '';
    
    public static const NOT_FOUND : int = -1;    
    
//    public var isLocked:Boolean = false;
    
    public var isSelected:Boolean = false ;
   
    protected var _groupIndex:int = 0;
    protected var _groupCount:int = 0;

    public var groupNameToGroupIndex:Object = new Object();
    public var itemNameToGroupItemIndex:Object = new Object();
    
    public function set groupIndex( n:int ):void {
        if ( groupList.length > 0 ) { 
            if ( ( 0 <= n ) && ( n < groupCount) ) { 
                groupList[_groupIndex].isSelected = false;
                _groupIndex = n;
                groupList[_groupIndex].isSelected = true;
            }
        }
    }

    public function get groupIndex():int { 
        return _groupIndex;
    }
    
    public function get groupCount():int {
        return groupList.length;
    }
    
}

class ConfigMenuBar extends Object {
    public var isActive:Boolean = false;
    public var configMenu:ConfigMenu;
    public var confCol:ConfigCollector = new ConfigCollector( MenuMapper.ITEM_MENUBAR , ToText );
    public var groupNames:Vector.<String> = new Vector.<String>
    public function ToText():String {
        var s:String = '';
        if (isActive) {
            for each( var name:String in groupNames ) { s += name + '\t'; };
            s += '\n';
        }
//        var n:Array = configMenu.itemNameToGroupItemIndex[MenuMapper.ITEM_KEYBOARD];
//        s += String(n);
        return s; 
    }
    public function ConfigMenuBar( cm:ConfigMenu ):void {
        configMenu = cm;
    }
    public function notifyKeyDown( ks:KeyState ):void {
//        if (ks.keyCode == Keyboard.UP||ks.keyCode==KeyboardMapper.I) { isActive = true; }
    }
    public function notifyKeyUp( ks:KeyState ):void {
//        if (ks.keyCode == Keyboard.UP||ks.keyCode==KeyboardMapper.I) { isActive = false; }
    }    
}

class ConfigCollectorManager extends Object {
    public var header:String;
    public var _commander:ConfigMenu = null;
    
    public function set commander(cm:ConfigMenu):void {
        _commander = cm;
        if (itemCount > 0) {
            for each(var ccm:ConfigCollector in watchList) { ccm.commander = cm; }
        }
    }
    
    public function get commander():ConfigMenu {
        return _commander;
    }
    
    public static const NOT_FOUND : int = -1;
    
    public var _isLocked:Boolean = false;
    
    public var isSelected:Boolean = false;
    
    private var _isSelectable:Boolean = false;
   
    public function get isSelectable():Boolean {
        return _isSelectable;
    }
    
    protected var _itemIndex:int = 0;
    protected var _itemCount:int = 0;    

    public var nameToItemIndex:Object = new Object();
    public var watchList:Vector.<ConfigCollector> = new Vector.<ConfigCollector>;
    public var topCharIndex:uint=0;
    public var beginCharIndexes:Vector.<uint> = new Vector.<uint>;
    public var endCharIndexes:Vector.<uint> = new Vector.<uint>;
    
    public var confCol:MultiConfigCollector = new MultiConfigCollector( MenuMapper.ITEM_MENUGROUPS , ToText );    

    public function ConfigCollectorManager( groupName:String = '' ):void {
        if (header != '') {
            this.header = groupName;
            MenuMapper.nameToMenuGroup[header] = this;
        }
//        header = he;
    }
    
    public function watch( cc:ConfigCollector ):void {
        watchList.push( cc );
        beginCharIndexes.push( 0 );
        endCharIndexes.push( 0 );
        nameToItemIndex[ cc.header ] = itemCount - 1;
        cc.commander = this.commander;
    }

    public function initItems():void {
        if ( itemCount > 0) {
            for each(var item:ConfigCollector in watchList) { item.initParams(); }
            var i:int = _getTopSelectableItemIndex();
            if (i != NOT_FOUND) { itemIndex = i; }
            _isSelectable = true;
        }
        
    }
    
    public function set itemIndex( n:int ):void {
        if ( watchList.length > 0 ) { 
            if ( ( 0 <= n ) && ( n < itemCount) ) { 
                watchList[_itemIndex].isSelected = false;
                _itemIndex = n;
                watchList[_itemIndex].isSelected = true;
            }
        }
    }

    public function get isLocked():Boolean {
        return _isLocked;
    }
    
    public function set isLocked(state:Boolean):void {
//        changeStateAll( ConfigCollector.CM_LOCK, state );
        _isLocked = state;
    }
    
    public function get itemIndex():int { 
        return _itemIndex;
    }
    
    public function get itemCount():int {
        return watchList.length;
    }
    
    private function _getTopSelectableItemIndex():int {
        for (var i:int = 0; i < itemCount; i++ ) {
            if (watchList[i].isSelectable) { return i; }
        }
        return NOT_FOUND;
    }
    
    private function _getNextSelectableItemIndex():int {
        for (var i:int = itemIndex+1; i < itemCount; i++ ) {
            if (watchList[i].isSelectable) { return i; }
        }
        return NOT_FOUND;
    }
    
    private function _getPriorSelectableItemIndex():int {
        for (var i:int = itemIndex-1; i >= 0; i-- ) {
            if (watchList[i].isSelectable) { return i; }
        }
        return NOT_FOUND;
    }
    
    public function get canFocusOut():Boolean {
        return watchList[itemIndex].canFocusOut;
    }

    public var isNextMoved:Boolean = false;
    
    public var isPriorMoved:Boolean = false;
    
    public function next():int {
        watchList[itemIndex].willNext = true;
        if (this.canFocusOut) {
            var i:int = _getNextSelectableItemIndex();
            if ( i == NOT_FOUND ) { return NOT_FOUND; }
            itemIndex = i;
            isNextMoved = true; isPriorMoved = false;
        }
        return itemIndex;        
    }

    public function prior():int {
        watchList[itemIndex].willPrior = true;
        if ((this.canFocusOut)&&(watchList[itemIndex]._willPrior)) {
            var i:int = _getPriorSelectableItemIndex();
            if ( i == NOT_FOUND ) { return NOT_FOUND; }
            itemIndex = i;
            isNextMoved = false; isPriorMoved = true;
        }
        return itemIndex;        
    }
    
    public function right():int {
        if ( itemCount > 0 ) {
            watchList[itemIndex].willRight = true;
        }
        return itemIndex; 
    }
    
    public function left():int { 
        if ( itemCount > 0 ) {
            watchList[itemIndex].willLeft = true;
        }
        return itemIndex; 
    }

    public function find( name:String ):int {
        var index:* = nameToItemIndex[ name ];
        if ( index == undefined ) { return NOT_FOUND; }
        return index;
    }
    
    public function get item():ConfigCollector {
        if ( itemCount > 0 ) {
            return watchList[itemIndex];
        }
        else 
        return null;
    }

    public function getItemState( command:String, itemName:String = '' ):Boolean {
        var result:Boolean = false;
        if ( watchList.length > 0 ) {
            if ( itemName == '' ) { 
                result = watchList[itemIndex][command];
            }
            else {
                var i:int = find( itemName );
                if ( i != NOT_FOUND ) {
                    result = watchList[i][command]; 
                }
            }
        }
        return result;
    }
    
    private function defined( i:int, c:String ):Boolean {
        return watchList[i].hasOwnProperty(c);
    }
    
    public function setItemState( command:String, state:Boolean = true, itemName:String = '' ):Boolean {
        var result:Boolean = false;
        if ( watchList.length > 0 ) {
            if ( itemName == '' ) { 
                if(defined(itemIndex, command)){
                    watchList[itemIndex][command] = state;
                    result = watchList[itemIndex][command];
                }
            }
            else {
                var i:int = find( itemName );
                if ( i != NOT_FOUND ) {
                    if(defined(i,command)){
                        watchList[i][command] = state; 
                        result = watchList[i][command];
                    }
                }
            }
        }
        return result;
    }
    
    public function expandItem( name:String = '', state:Boolean = true ):Boolean {
       if (getItemState(ConfigCollector.CM_LOCK,name)) { return false; }
        return setItemState( ConfigCollector.CM_EXPAND, state, name );
    }

    public function collapseItem( name:String = '', state:Boolean = true ):Boolean {
        return expandItem( name, !state );
    }
    
    public function lockItem( name:String, state:Boolean = true ):Boolean {
        return setItemState( ConfigCollector.CM_LOCK, state, name );
    }

    public function unlockItem( name:String, state:Boolean = true ):Boolean {
        return lockItem( name, !state );
    }
    
    public function changeStateAll( command:String, state:Boolean = true ):void {
        for each( var item:ConfigCollector in watchList ) {
            if ( item[command] != state ) { item[command] = state; }
        }
    }
    
    public function expandAll(state:Boolean=true):void {
        for each( var itemName:String in watchList ) {    expandItem( itemName, state );    }
    }

    public function collapseAll(state:Boolean=true):void {
        expandAll(!state);
    }
    
    public function lockAll(state:Boolean=true):void {
        for each( var itemName:String in watchList ) {    lockItem( itemName, state );    }
    }

    public function unlockAll(state:Boolean=true):void {
        lockAll(!state);
    }
    
    public function reportItem( i:int ):String {
        watchList[i].topCharIndex = beginCharIndexes[i];
        return watchList[i].toText();
    }
    
    public function reportConfig(): String {
//        confCol.collect();  // conf are collected by ConfigMenu
        
        var s:String = '';
        for (var i:int = 0; i < watchList.length; i++ ) {
            beginCharIndexes[i] = topCharIndex + s.length;
            s += reportItem(i);
            endCharIndexes[i] = topCharIndex + s.length;
        }
        return s;
    }
    
    public function ToText():String {
        var s:String = '';
        s += sprintf( '%s: item %d  index %d\n', header, itemCount, itemIndex );
        return s;
    }
}

class ConfigTextField extends TextField {
    private var isCritical:Boolean = false;
    private var hasDisposedKeyEvent:Boolean = false;
    
    private var keyState:KeyState = new KeyState();
    private var keyMap:KeyboardMapper = new KeyboardMapper();
    private var keyWatcher:KeyWatcher = new KeyWatcher();

    public var keyConfCol:ConfigCollector = new ConfigCollector( MenuMapper.ITEM_KEYEVENT , keyEventToText );

    private var configMenu:ConfigMenu = new ConfigMenu();
    
    public function ConfigTextField() {
        this.addEventListener(Event.ADDED_TO_STAGE, this.xAdded);
    }

    public function keyEventToText():String {
        return keyMap.toText();
    }

    public function updateContents():void {
        keyWatcher.update();
        keyConfCol.collect();
        this.text = configMenu.reportConfig();
        
        var textFormat:TextFormat = new TextFormat();
        textFormat.color = ColorMapper.MENU_SELECTEDITEM;
        this.setTextFormat(textFormat, configMenu.selectBeginCharIndex, configMenu.selectEndCharIndex);
        
        while ( configMenu.textCoordinators.length > 0 ) {
            var tc:textCoordinator = configMenu.textCoordinators.pop();
//            var tFt:TextFormat = new TextFormat();
//            tFt[tc.property] = tc.param;
//            this.setTextFormat(tFt, tc.beginCharIndex, tc.endCharIndex);
            tc.apply(this);
        }
        
//        configMenu.textCoordinators.splice(0, uint.MAX_VALUE);
    }
    
    public function xKeyDown(ke:KeyboardEvent):void {
        if ( isCritical ) {
            return;
        }
        else {
            isCritical = true;
            keyState.drain(ke);
            keyMap.keyState.clone(keyState);
            keyWatcher.notifyKeyDown(keyState);
            isCritical = false;
        }
        
        var kc:uint = keyState.keyCode;
        var sft:Boolean = keyState.shiftKey;
        var ctrl:Boolean = keyState.ctrlKey;
        
        if (configMenu.menuBar.isActive) { 
            configMenu.notifyKeyDown( MenuMapper.ITEM_MENUBAR, keyState );             
            return; 
        }

        
        if ((kc == KeyboardMapper.SCROLL_LOCK) || 
            (kc == KeyboardMapper.M) && (ctrl == true)) {
                configMenu.isLocked = !configMenu.isLocked; 
                configMenu.expandItem(MenuMapper.ITEM_MENUSTATUS, configMenu.isLocked);return;
            }
        if (configMenu.isLocked) { return; }
        
        if ((kc == Keyboard.NUMPAD_ADD)||(kc == KeyboardMapper.COMMA)) { 
//            configMenu.setItemState( EditableConfigCollector.CM_ADD, true, MenuMapper.ITEM_NODE); 
            configMenu.setItemState( EditableConfigCollector.CM_ADD, true); 
            return; } 
        
        if ((kc == Keyboard.NUMPAD_SUBTRACT)||(kc == KeyboardMapper.MINUS)||(kc== KeyboardMapper.PERIOD)) { 
//            configMenu.setItemState( EditableConfigCollector.CM_SUB, true, MenuMapper.ITEM_NODE); 
            configMenu.setItemState( EditableConfigCollector.CM_SUB, true); 
            return; } 
            
        if ((kc == Keyboard.UP) || (kc == KeyboardMapper.I)) { configMenu.priorItem();return; } 
        if ((kc == Keyboard.DOWN)||(kc == KeyboardMapper.K)) { configMenu.nextItem();return; }
        if ((kc == Keyboard.RIGHT)||(kc == KeyboardMapper.L)) { configMenu.right();return; }
        if ((kc == Keyboard.LEFT)||(kc == KeyboardMapper.J)) { configMenu.left();return; }
        if ((kc == Keyboard.HOME)||(kc == KeyboardMapper.U)) { setHomePositionToMenu();return; }
        if ((kc == Keyboard.ENTER) || (kc == KeyboardMapper.O)) { configMenu.enter();return; }
        if ((kc == Keyboard.BACKSPACE) || (kc == KeyboardMapper.P)) { configMenu.exit();return; }
        if ((kc == Keyboard.END) || (kc == KeyboardMapper.M)) { 
            if ( configMenu.Item.isLocked ) { configMenu.unlockItem(); return; }
            else { configMenu.lockItem();return; }
        }
        
        if(kc==Keyboard.SHIFT){ configMenu.menuBar.isActive = true;return }
            
    }
    
    public function xKeyUp(ke:KeyboardEvent):void {
        if ( isCritical ) {
            return;
        }
        else {
            isCritical = true;
            keyState.drain(ke);
            keyMap.keyState.clone(keyState);
            keyWatcher.notifyKeyUp(keyState);
            isCritical = false;
        }
        
        var kc:uint = keyState.keyCode;
        var sft:Boolean = keyState.shiftKey;

        if (configMenu.isLocked) { return; }
        
        if(kc==Keyboard.SHIFT){ configMenu.menuBar.isActive = false;return }

    }
    
    public function xAdded(e:Event):void {
        stage.addEventListener(KeyboardEvent.KEY_DOWN, xKeyDown);
        stage.addEventListener(KeyboardEvent.KEY_UP, xKeyUp);

        addGroupsToMenu();
        addItemsToMenu();
        setHomePositionToMenu();
    }
    
    private function addGroupsToMenu():void {
        configMenu.addGroup(new ConfigCollectorManager(MenuMapper.GROUP_MAIN));
        configMenu.addGroup(new ConfigCollectorManager(MenuMapper.GROUP_KEY));
    }
    
    private function addItemsToMenu():void {
        configMenu.addItem(MenuMapper.ITEM_STAGE, MenuMapper.GROUP_MAIN);
        configMenu.setItemState(ConfigCollector.CM_NAKED,true,MenuMapper.ITEM_STAGE);        
        
        configMenu.addItem(MenuMapper.ITEM_KEYBOARD, MenuMapper.GROUP_KEY);
        configMenu.addItem(MenuMapper.ITEM_NUMPAD, MenuMapper.GROUP_KEY);
        
        configMenu.addItem(MenuMapper.ITEM_ROOT, MenuMapper.GROUP_MAIN);
        configMenu.addItem(MenuMapper.ITEM_NODE,MenuMapper.GROUP_MAIN);
        configMenu.addItem(MenuMapper.ITEM_IME,MenuMapper.GROUP_KEY);

        configMenu.addItem(MenuMapper.ITEM_KEYPOLL, MenuMapper.GROUP_KEY);
        configMenu.addItem(MenuMapper.ITEM_KEYEVENT, MenuMapper.GROUP_KEY);
        configMenu.addItem(MenuMapper.ITEM_MENUGROUPS, MenuMapper.GROUP_KEY);

        configMenu.addItem(MenuMapper.ITEM_BITMAPDATA, MenuMapper.GROUP_MAIN);
        configMenu.addItem(MenuMapper.ITEM_LATTICE, MenuMapper.GROUP_MAIN);
        
        configMenu.addItem(MenuMapper.ITEM_MENUSTATUS, MenuMapper.GROUP_MAIN);
        configMenu.setItemState(ConfigCollector.CM_NAKED,true,MenuMapper.ITEM_MENUSTATUS);             
        
//        configMenu.addItem(MenuMapper.ITEM_SOMETHING, MenuMapper.GROUP_MAIN);

        configMenu.initGroups();
    }
    
    private function setHomePositionToMenu():void {
        configMenu.groupIndex = 0;

        configMenu.moveToItem( MenuMapper.ITEM_ROOT );
         
        configMenu.collapseAll();

        configMenu.expandItem(MenuMapper.ITEM_STAGE);
//        configMenu.expandItem(MenuMapper.ITEM_ROOT);
//        configMenu.expandItem(MenuMapper.ITEM_NODE);
//        configMenu.expandItem(MenuMapper.ITEM_KEYBOARD);
//        configMenu.expandItem(MenuMapper.ITEM_BITMAPDATA);
//        configMenu.expandItem(MenuMapper.ITEM_LATTICE);
        
//        configMenu.expandItem(MenuMapper.ITEM_MENUGROUPS);
        
    }
   
}