/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                                                                       *
 *   JavaWorld Library, Copyright 2011 Bryan Chadwick                    *
 *                                                                       *
 *   FILE: ./universe/world/BigBang.java                                 *
 *                                                                       *
 *   This file is part of JavaWorld.                                     *
 *                                                                       *
 *   JavaWorld is free software: you can redistribute it and/or          *
 *   modify it under the terms of the GNU General Public License         *
 *   as published by the Free Software Foundation, either version        *
 *   3 of the License, or (at your option) any later version.            *
 *                                                                       *
 *   JavaWorld is distributed in the hope that it will be useful,        *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of      *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the       *
 *   GNU General Public License for more details.                        *
 *                                                                       *
 *   You should have received a copy of the GNU General Public License   *
 *   along with JavaWorld.  If not, see <http://www.gnu.org/licenses/>.  *
 *                                                                       *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

package universe.world;

import javax.swing.*;

import universe.base.UniverseBase;
import universe.base.Server.WorldShell;
import universe.world.base.*;
import universe.control.*;
import universe.Package;
import java.net.*;
import java.io.*;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.RenderingHints;
import java.awt.event.*;
import image.*;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;

/** A Class representing the creation of a World/System that communicates
 *    by passing messages of some type (<tt>Msg</tt>), and the related methods 
 *    and Function-Objects (call-backs) for drawing the world and handling various
 *    events.  Handlers are parameterized so they are statically checked. 
 *    
 *  <p>The name and types of handlers are given in the table below:<br/><br/>
 *    
 *    <style>
 *       table.mine{ margin-left: 20px; border: 1px solid blue; }
 *       table.mine td, table.mine th{ padding-left:20px; padding-right:5px;  border: 1px solid blue; }
 *       td.event, th.event{ text-align: center; }
 *       .com { color: #AA240F; font-style: italic; }
 *       .keyw { color: #262680; font-weight: bold; }
 *       .useful { color: #1111C0; }
 *       .num { color: #00AA00; }
 *       .str { color: #00AA00; }
 *       .fun { color: #AA5500; }
 *    </style>
 *    <table class="mine">
 *      <tr><th class="event">Event Name</th><th>BigBang Method</th><th>Handler Signature</th><th>Required?</th><tr/>
 *      <tr><td class="event">OnDraw</td><td><tt><span class='fun'>onDraw</span>(OnDraw)</tt></td><td><tt>Scene <span class='fun'>apply</span>(World<Msg> w)</tt></td><td><b><i>yes</i></b></td><tr/>
 *      <tr><td class="event">OnTick</td><td><tt><span class='fun'>onTick</span>(OnTick<Msg>)</tt> or<br/> <tt><span class='fun'>onTick</span>(OnTick<Msg>, <span class="keyw">double</span>)</tt></td><td><tt>World <span class='fun'>apply</span>(World<Msg> w)</tt></td><td>no</td><tr/>
 *      <tr><td class="event">OnMouse</td><td><tt><span class='fun'>onMouse</span>(OnMouse<Msg>)</tt></td><td><tt>World<Msg> <span class='fun'>apply</span>(World<Msg> w, <span class="keyw">int</span> x, <span class="keyw">int</span> y, String what)</tt></td><td>no</td><tr/>
 *      <tr><td class="event">OnKey</td><td><tt><span class='fun'>onKey</span>(OnKey<Msg>)</tt></td><td><tt>World<Msg> <span class='fun'>apply</span>(World<Msg> w, String key)</tt></td><td>no</td><tr/>
 *      <tr><td class="event">OnRelease</td><td><tt><span class='fun'>onRelease</span>(OnRelease<Msg>)</tt></td><td><tt>World<Msg> <span class='fun'>apply</span>(World<Msg> w, String key)</tt></td><td>no</td><tr/>
 *      <tr><td class="event">StopWhen</td><td><tt><span class='fun'>stopWhen</span>(StopWhen)</tt></td><td><tt><span class="keyw">boolean</span> <span class='fun'>apply</span>(World<Msg> w)</tt></td><td>no</td><tr/>
 *      <tr><td class="event">LastScene</td><td><tt><span class='fun'>lastScene</span>(LastScene)</tt></td><td><tt>Scene <span class='fun'>apply</span>(World<Msg> w)</tt></td><td>no</td><tr/>
 *    </table><br/>
 *    </p>
 */
public class BigBang<Msg extends Serializable>{
    static void p(String s){ System.err.println(s); }
    
    /** Default Tick rate for the world: ~33 frames per second */
    public static double DEFAULT_TICK_RATE = 0.03;

    /** Mouse down (button-down) event String */
    public static String MOUSE_DOWN = "button-down";
    /** Mouse up (button-up) event String */
    public static String MOUSE_UP = "button-up";
    /** Mouse window enter (enter) event String */
    public static String MOUSE_ENTER = "enter";
    /** Mouse window leave (leave) event String */
    public static String MOUSE_LEAVE = "leave";
    /** Mouse motion (move) event String */
    public static String MOUSE_MOVE = "move";
    /** Mouse down & move (drag) event String */
    public static String MOUSE_DRAG = "drag";   
    /** Key arrow-up event String */
    public static String KEY_ARROW_UP = "up";   
    /** Key arrow-down event String */
    public static String KEY_ARROW_DOWN = "down";   
    /** Key arrow-left event String */
    public static String KEY_ARROW_LEFT = "left";   
    /** Key arrow-right event String */
    public static String KEY_ARROW_RIGHT = "right";   
    /** Key escape event String */
    public static String KEY_ESCAPE = "escape";   

    
    private World<Msg> initial;
    double time;
    
    // Handlers
    private OnDraw ondraw;
    private OnTick<Msg> ontick;
    private OnMouse<Msg> onmouse;
    private OnKey<Msg> onkey;
    private OnRelease<Msg> onrelease;
    private OnReceive<Msg> onreceive;
    private StopWhen stopwhen;
    private LastScene lastscene;
    private String server = "";
    private String name = "";
    
    
    public BigBang(World<Msg> initial){
        this(initial, 0.05, null, null, null, null,
                null, null, null, null, null, null);
    }
    /** Install a Draw Handler into this BigBang.  The Draw handler
     *    requires an apply method [World<Msg> -> Scene],
     *    though the requirement is checked dynamically when this
     *    method is called. */
    public BigBang<Msg> onDraw(OnDraw ondraw){
        return new BigBang<Msg>(this.initial, this.time,
                ondraw, this.ontick, this.onmouse, this.onkey, this.onrelease,
                this.onreceive, this.stopwhen, this.lastscene, this.server, this.name);
    }
    /** Install a Tick Handler at a tick rate of 1/20th of a second. */
    public BigBang<Msg> onTick(OnTick<Msg> ontick){
        return onTick(ontick, 0.05);
    }
    /** Install a Tick Handler into this BigBang at the given tick
     *    rate (per-seconds).  The Tick handler requires an apply
     *    method [World<Msg> -> World<Msg>], though the requirement is
     *    checked dynamically when this method is called. */
    public BigBang<Msg> onTick(OnTick<Msg> ontick, double time){
        return new BigBang<Msg>(this.initial, this.time,
                this.ondraw, ontick, this.onmouse, this.onkey, this.onrelease,
                this.onreceive, this.stopwhen, this.lastscene, this.server, this.name);
    }
    /** Install a Mouse Handler into this BigBang.  The Mouse handler
     *    requires an apply method [World<Msg> -> World<Msg>], though the
     *    requirement is checked dynamically when this method is
     *    called. */
    public BigBang<Msg> onMouse(OnMouse<Msg> onmouse){
        return new BigBang<Msg>(this.initial, this.time,
                this.ondraw, this.ontick, onmouse, this.onkey, this.onrelease,
                this.onreceive, this.stopwhen, this.lastscene, this.server, this.name);
    }
    /** Install a Key Handler into this BigBang.  The Key handler
     *    requires an apply method [World<Msg> String -> World<Msg>], though
     *    the requirement is checked dynamically when this method is
     *    called. */
    public BigBang<Msg> onKey(OnKey<Msg> onkey){
        return new BigBang<Msg>(this.initial, this.time,
                this.ondraw, this.ontick, this.onmouse, onkey, this.onrelease,
                this.onreceive, this.stopwhen, this.lastscene, this.server, this.name);
    }
    /** Install a Key Release Handler into this BigBang.  The Key
     *    Release handler requires an apply method [World<Msg> String ->
     *    World<Msg>], though the requirement is checked dynamically when
     *    this method is called. */
    public BigBang<Msg> onRelease(OnRelease<Msg> onrelease){
        return new BigBang<Msg>(this.initial, this.time,
                this.ondraw, this.ontick, this.onmouse, this.onkey, onrelease,
                this.onreceive, this.stopwhen, this.lastscene, this.server, this.name);
    }
    public BigBang<Msg> onReceive(OnReceive<Msg> onreceive){
        return new BigBang<Msg>(this.initial, this.time,
                this.ondraw, this.ontick, this.onmouse, this.onkey, this.onrelease,
                onreceive, this.stopwhen, this.lastscene, this.server, this.name);
    }
    /** Install a StopWhen Handler into this BigBang.  The StopWhen
     *    handler requires an apply method [World<?> -> Boolean],
     *    though the requirement is checked dynamically when this
     *    method is called.  The StopWhen handler, if installed is
     *    call to determine whether or not the World/animation/events
     *    should be stopped.  When/if the handler returns true then
     *    all events stop being received and the LastScene handler is
     *    given a chance to draw the final World. */
    public BigBang<Msg> stopWhen(StopWhen stopwhen){
        return new BigBang<Msg>(this.initial, this.time,
                this.ondraw, this.ontick, this.onmouse, this.onkey, this.onrelease,
                this.onreceive, stopwhen, this.lastscene, this.server, this.name);
    }
    /** Install a LastScene Handler into this BigBang.  The LastScene
     *    handler requires an apply method [World<?> -> Scene], though
     *    the requirement is checked dynamically when this method is
     *    called.  After the animation is stopped (StopWhen) the final
     *    World is drawn using the LstScene Handler. */
    public BigBang<Msg> lastScene(LastScene lastscene){
        return new BigBang<Msg>(this.initial, this.time,
                this.ondraw, this.ontick, this.onmouse, this.onkey, this.onrelease,
                this.onreceive, this.stopwhen, lastscene, this.server, this.name);
    }
    /** Install the name of the Universe server to connect to once
     *    {@link BigBang#bigBang bigBang} is called */
    public BigBang<Msg> register(String server){
        return new BigBang<Msg>(this.initial, this.time,
                this.ondraw, this.ontick, this.onmouse, this.onkey, this.onrelease,
                this.onreceive, this.stopwhen, this.lastscene, server, this.name);
    }
    /** Install the name of this client, to be used with the Universe server */
    public BigBang<Msg> name(String name){
        return new BigBang<Msg>(this.initial, this.time,
                this.ondraw, this.ontick, this.onmouse, this.onkey, this.onrelease,
                this.onreceive, this.stopwhen, this.lastscene, this.server, name);
    }
    
    // Private constructors...
    private BigBang(World<Msg> init, double time, 
            OnDraw ondraw, OnTick<Msg> ontick, OnMouse<Msg> onmouse,
            OnKey<Msg> onkey, OnRelease<Msg> onrelease, OnReceive<Msg> onreceive, StopWhen stopwhen,
            LastScene lastscene, String server, String name){
        this.initial = init;
        this.time = time;
        this.ondraw = ondraw;
        this.ontick = ontick;
        this.onmouse = onmouse;
        this.onkey = onkey;
        this.onrelease = onrelease;
        this.onreceive = onreceive;
        this.stopwhen = stopwhen;
        this.lastscene = lastscene;
        this.server = server;
        this.name = name;
    }    
    /** Wrapper for the Draw Handler */
    private Scene doOnDraw(World<Msg> w){
        return this.ondraw.apply(w);
    }
    /** Wrapper for the LastScene Handler */
    private Scene doLastScene(World<Msg> w){
        if(this.lastscene == null)return this.ondraw.apply(w);
        return this.lastscene.apply(w);
    }
    /** Wrapper for the Tick Handler */
    private Package<Msg> doOnTick(World<Msg> w){
        if(this.ontick == null)return null;
        return this.ontick.apply(w);
    }
    /** Wrapper for the Mouse Handler */
    private Package<Msg> doOnMouseEvent(World<Msg> w, int x, int y, String me){
        if(this.onmouse == null)return null;
        return this.onmouse.apply(w, x-BigBang.SPACE, y-BigBang.SPACE, me);
    }
    /** Wrapper for the KeyDown Handler */
    private Package<Msg> doOnKeyEvent(World<Msg> w, String ke){
        if(this.onkey == null || ke.length() == 0)return null;
        return this.onkey.apply(w, ke);
    }
    /** Wrapper for the KeyRelease Handler */
    private Package<Msg> doOnKeyRelease(World<Msg> w, String ke){
        if(this.onkey == null || ke.length() == 0)return null;
        return this.onrelease.apply(w, ke);
    }
    /** Wrapper for the StopWhen Handler */
    private boolean doStopWhen(World<Msg> w){
        if(this.stopwhen == null)return false;
        return this.stopwhen.apply(w);
    }    
    /** Wrapper for the Receive Handler */
    @SuppressWarnings("unchecked")
    private Package<Msg> doOnReceive(World<Msg> w, Object m){
        if(this.onreceive == null)return null;
        return this.onreceive.apply(w, (Msg)m);
    }
    /** Construct and run the animation/interaction system.  For the
     *    Swing version the method returns the final value of the
     *    World after the animation has completed.  The Windqow is
     *    opened as a Modal dialog, so control does not return to the
     *    bigband caller until the window is closed. */
    public World<Msg> bigBang(){
        return this.bigBang("Universe-World");
    }
    
    /** Open a window and run the animation with the given title */
    public World<Msg> bigBang(String title){
        try{
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        }catch(Exception e){}

        if(this.ondraw == null)
            throw new RuntimeException("No World Draw Handler");
        JDialog f = new JDialog((JFrame)null, title, true);
        Scene scn = doOnDraw(this.initial);
        Handler handler = new Handler(this,this.initial, scn,
                new BufferedImage((int)(scn.width()+2*SPACE), (int)(scn.height()+2*SPACE), BufferedImage.TYPE_INT_RGB),
                f, (int)(Math.random()*1000000));
        f.setSize((int)(SPACE*2+Math.max(20, 14+scn.width())),
                (int)(Math.max(20, SPACE*2+31+scn.height())));
        
        f.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        f.setResizable(false);
        f.getContentPane().add(handler);
        f.setVisible(true);
        handler.finish();
        return handler.w;
    }
    
    /** Gap left around the border of the Window */
    private static int SPACE = 5;
        
    /** Handles the nitty-gritty of world updates and interfacing with Swing */
    class Handler extends javax.swing.JComponent
                         implements MouseListener,KeyListener,MouseMotionListener{
        private static final long serialVersionUID = 1L;
        BigBang<Msg> world;
        World<Msg> w;
        Scene scnBuffer;
        BufferedImage buffer;
        Graphics2D graph;
        Timer run;
        TimerTask ticker;
        boolean isRunning = false;
        boolean isDone = false;

        // Specific to the Universe
        long id = 0; 
        Socket sock = null;
        ObjectInputStream inn = null;
        ObjectOutputStream outt = null;
        Thread receiver;

        /** Create a new Handler for all the World's events */        
        Handler(BigBang<Msg> world, World<Msg> ww, Scene scn, BufferedImage buff, JDialog dia, long id){
            this.world = world;
            this.w = ww;
            this.id = id;
            this.scnBuffer = null;
            this.buffer = buff;
            this.graph = buff.createGraphics();
            this.graph.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            this.graph.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
            this.run = new Timer();
            addMouseListener(this);
            if(world.onmouse != null){
                addMouseMotionListener(this);
            }
            if(world.onkey != null)
                dia.addKeyListener(this);
            this.isRunning = true;
            if(world.ontick != null){
                this.run.scheduleAtFixedRate(this.ticker = new TimerTask(){
                    public void run(){ tickAction(); }
                }, 200, (int)(world.time*1000));
            }
            if(BigBang.this.server != null){
                for(int i = 0; this.sock == null && i < 5; i++){
                    try{
                        p("["+i+"] Trying to connect to the universe...");
                        this.sock = new Socket(world.server, UniverseBase.PORT);
                        this.outt = new ObjectOutputStream(this.sock.getOutputStream());
                        this.outt.writeObject(new Connect(world.name, id));
                        this.inn = new ObjectInputStream(this.sock.getInputStream());
                    }catch(IOException e){
                        this.sock = null;
                        this.outt = null;
                        this.inn = null;
                        //p("Exception ["+e+"]");
                        try{ Thread.sleep(200); }catch(Exception ee){}
                    }
                }
                if(this.sock == null){
                    p("** Unable to connect to the universe... running locally");
                }else{
                    p("** Success");
                    this.receiver = new Thread(){
                        public void run(){
                            while(!Handler.this.isDone){
                                try{
                                    Object m = Handler.this.inn.readObject();
                                    if(!(m instanceof Message)){
                                        throw new RuntimeException("Bad Message");
                                    }
                                    Message msg = (Message)m;
                                    if(msg.isTransfer()){
                                        replace(doOnReceive(Handler.this.w, msg.payload()));
                                    }else p("Unknown Message Type");

                                }catch(EOFException e){
                                    return;
                                }catch(Exception e){
                                    if(!Handler.this.isDone)
                                        p("Error Reading Message: "+e);
                                    return;
                                }
                            }
                        }
                    };
                    this.receiver.start();
                }
            }else
                p("** No registration given... running locally");
        }
        /** What to do when we're all done */
        public void finish(){
            this.run.cancel();
            this.isDone = true;
            try{
                this.outt.writeObject(new Disconnect(this.id));
                this.outt.close();
                this.inn.close();
            }catch(Exception e){}
        }
        /** Swing uses a <tt>paint(Graphics)</tt> method to draw the
         *    component (Handler) into the window. */
        public void paint(java.awt.Graphics g){
            Scene curr;
            if(!this.isDone)
                curr = this.world.doOnDraw(this.w);
            else
                curr = this.world.doLastScene(this.w);
            
            if(curr != this.scnBuffer){
                this.scnBuffer = curr;
                this.graph.setColor(Color.white);
                this.graph.fillRect(0,0, this.getWidth(), this.getHeight());
                this.graph.clipRect(SPACE, SPACE, this.buffer.getWidth()-SPACE*2, this.buffer.getHeight()-SPACE*2);
                this.scnBuffer.paint(this.graph,SPACE,SPACE);
            }
            g.drawImage(this.buffer, 0, 0, null);
        }
        /** Rather than Swing timers, we use to java.util.Timer to
         *    provide compatibility with Android (i.e., so the code
         *    for both versions looks the same). */
        public void tickAction(){
            if(!this.isRunning || this.isDone)return;
            replace(this.world.doOnTick(this.w));
        }
        /** Support saving screenshots... */
        JPopupMenu popup = new JPopupMenu("World Options");
        {
            addItem(this.popup, "Save Image...", new ActionListener(){
                public void actionPerformed(ActionEvent e){
                    doSaveAs(Handler.this.scnBuffer);
                    synchronized(Handler.this.popup){ Handler.this.popup.notify(); }
                }
            });
            addItem(this.popup, "Continue",  new ActionListener(){
                public void actionPerformed(ActionEvent e){
                    synchronized(Handler.this.popup){ Handler.this.popup.notify(); }
                }
            });
            this.popup.addPopupMenuListener(new PopupMenuListener(){
                public void popupMenuCanceled(PopupMenuEvent arg0){
                    synchronized(Handler.this.popup){ Handler.this.popup.notify(); }
                }
                public void popupMenuWillBecomeVisible(PopupMenuEvent arg0){}
                public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0){}
            });
        }
        private void addItem(JPopupMenu m, String s, ActionListener l){
            JMenuItem item = new JMenuItem(s);
            if(l != null)
                item.addActionListener(l);
            m.add(item);
        }
        boolean doSaveAs(Scene scn){
            try{
                JFileChooser fc = new JFileChooser();
                int result = fc.showDialog(this, "SaveAs");
                // Check for existence... 
                if(result == JFileChooser.APPROVE_OPTION){
                    this.scnBuffer.toFile(fc.getSelectedFile().getAbsolutePath());
                    return true;
                }
            }catch(Exception e){
                System.err.println("Exception: "+e);
            }
            return false;
        }
        public void mousePressed(final MouseEvent e){
            if(e.isPopupTrigger()){
                // Pause the simulation...
                final Handler that = this;
                final boolean temp = this.isRunning;
                this.isRunning = false;

                // Show the context Menu
                new Thread(){
                    public void run(){
                        Handler.this.popup.show(that, e.getX(), e.getY());
                        try{
                            synchronized(Handler.this.popup){
                                Thread.yield();
                                Handler.this.popup.wait();
                            }
                        }catch(Exception ee){}
                        // Restart the simulation if running...
                        that.isRunning = temp;
                    }
                }.start();
            }else{
                if(this.isRunning && !this.isDone)
                    replace(this.world.doOnMouseEvent(this.w, e.getX(), e.getY(), MOUSE_DOWN));
            }
        }
        /** Mouse click/move/event Methods */
        public void mouseClicked(MouseEvent e){}
        public void mouseEntered(MouseEvent e){ if(this.isRunning && !this.isDone)replace(this.world.doOnMouseEvent(this.w, e.getX(), e.getY(), MOUSE_ENTER)); }
        public void mouseExited(MouseEvent e){ if(this.isRunning && !this.isDone)replace(this.world.doOnMouseEvent(this.w, e.getX(), e.getY(), MOUSE_LEAVE)); }
        public void mouseReleased(MouseEvent e){ if(this.isRunning && !this.isDone)replace(this.world.doOnMouseEvent(this.w, e.getX(), e.getY(), MOUSE_UP)); }
        public void mouseDragged(MouseEvent e){ if(this.isRunning && !this.isDone)replace(this.world.doOnMouseEvent(this.w, e.getX(), e.getY(), MOUSE_DRAG)); }
        public void mouseMoved(MouseEvent e){ if(this.isRunning && !this.isDone)replace(this.world.doOnMouseEvent(this.w, e.getX(), e.getY(), MOUSE_MOVE)); }
        /** Keys are converted to strings to simplify handling */
        public void keyPressed(KeyEvent e){
            if(this.isRunning && !this.isDone)
                replace(this.world.doOnKeyEvent(this.w, convert(e.getKeyCode(), ""+e.getKeyChar())));
        }
        public void keyReleased(KeyEvent e){
            if(this.isRunning && !this.isDone)
                replace(this.world.doOnKeyRelease(this.w, convert(e.getKeyCode(), ""+e.getKeyChar())));
        }
        public void keyTyped(KeyEvent e){
            //if(this.isRunning && !this.done)replace(this.world.doOnKeyEvent(this.w, ""+e.getKeyChar()));
        }
        
        private synchronized void replace(Package<Msg> p){
            if(p == null)return;
            // This isn't enough when mutation is involved...
            if(!this.isRunning || this.isDone)return;
            
            if(this.isRunning && this.world.doStopWhen(w)){
                this.isRunning = false;
                this.isDone = true;
                this.run.cancel();
            }
            
            boolean change = !this.w.equals(p.getWorld());
            this.w = p.getWorld();
            if(change)repaint();
            if(p.hasMsg() && this.sock != null){
                // Deliver Message to the universe
                try{
                    this.outt.writeObject(new WithWorld(new Transfer(this.id, p.getMsg()),
                            new WorldShell(this.world.name,this.id)));
                }catch(IOException e){
                    p("Cannot Send Message ["+p.getMsg().getClass().getName()+"]");
                    p("  ** Exception: "+e);
                }
            }
        }
        private String convert(int code, String ch){
            switch(code){
            case KeyEvent.VK_UP: return KEY_ARROW_UP;
            case KeyEvent.VK_DOWN: return KEY_ARROW_DOWN;
            case KeyEvent.VK_LEFT: return KEY_ARROW_LEFT;
            case KeyEvent.VK_RIGHT: return KEY_ARROW_RIGHT;
            case KeyEvent.VK_ESCAPE: return KEY_ESCAPE;
            default: return ch;
            }
        }
    }
}