/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 */

package glass.rain;

import com.sun.glass.ui.*;
import com.sun.glass.events.*;
import com.sun.glass.ui.DelayedCallback;
import com.sun.glass.ui.Screen.SettingsChangedCallback;
import com.sun.glass.ui.CommonDialogs.ExtensionFilter;
import com.sun.glass.ui.CommonDialogs.Type;
import com.sun.rain.graphics.*;
import com.sun.rain.shapes.*;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ByteOrder;

import java.util.Deque;
import java.util.ArrayDeque;

import java.io.File;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import java.util.Random;

public class Main implements Launchable
{
    static final boolean IS3D = false;
    static final boolean ISDECORATED = true;
    
    static final int WIDTH = 600;
    static final int HEIGHT = 256;
    static final int FPS = 30;
    static final String TITLE = "Glass Rain";
    
    static final int DEFAULT_TRACKING_SIZE = 16;
    
    static final long TimeStart = System.currentTimeMillis();
    static ClipboardAssistance ca;
    
    static MenuBar menubar = null;
    
    static Image resize = null;
    static Image globe = null;
    
    static Window testWindow;

    public static void main(String args[])
    {
        Application.Run(args, new Main());
    }
    
    void createWindow(boolean decorate, boolean accelerated)
    {
        Application app = Application.GetApplication();
        Screen mainScreen = Screen.getMainScreen();
        
        Screen.getScreens();

        int windowMask = Window.UNTITLED;
        if (decorate == true)
        {
            windowMask = Window.TITLED|Window.CLOSABLE|Window.MINIMIZABLE;
        }
        else
        {
            windowMask = Window.CLOSABLE;
        }

        View view = app.createView(getPen(accelerated));
        view.setEventHandler(new MyViewEventHandler());
        
        Window window = app.createWindow(mainScreen, windowMask);
        window.setResizable(true);
        MyWindowEventHandler eventHandler = new MyWindowEventHandler();
        window.setEventHandler(eventHandler);
        eventHandler.addMenus(app, window);
        window.setTitle(((decorate == true) ? "decorated " : "undecorated ") + ((accelerated == false) ? "unaccelerated" : "accelerated"));
        window.setIcon(Application.GetApplication().createPixels(ImageData.GLOBE_WIDTH, ImageData.GLOBE_HEIGHT, IntBuffer.wrap(ImageData.GLOBE_DATA)));
        window.setMinimumSize(128, 64);
        window.setView(view);
        if (!decorate) {
            window.setUndecoratedMoveRectangle(DEFAULT_TRACKING_SIZE);
            window.setUndecoratedResizeRectangle(DEFAULT_TRACKING_SIZE);
        }
        window.setPosition(101, 102);
        int randomSpread = 100;
        window.setContentSize(WIDTH+(int)(2*randomSpread-Math.random()*randomSpread), HEIGHT+(int)(2*randomSpread-Math.random()*randomSpread));
        
        window.setVisible(true);

        System.err.println("Keycode for 'C': " + com.sun.glass.events.KeyEvent.getKeyCodeForChar('C'));

        System.err.println("Multi-click time: " + View.getMultiClickTime());

        System.err.println("Best cursor size for desired size of (100, 100) is: " + Cursor.getBestSize(100, 100));

        ca = new ClipboardAssistance("SYSTEM") {
            @Override
            public void contentChanged() {
                System.out.println(
                        "{clipboard update"
                        + dumpToString()
                        + "\n}clipboard update");
            }
            
            @Override
            public void actionPerformed(int action)
            {
                System.out.println("actionPerformed:" + Clipboard.getActionString(action));
            }
        };


        ca.setData(Clipboard.TEXT_TYPE, "Hello from glass\n");
        ca.setData(Clipboard.URI_TYPE, "http://oracle.com");
//      ca.setData(Clipboard.FILE_LIST_TYPE, new String [] {"/tmp/glass-clipboard/bobok.txt",
//                           "/tmp/glass-clipboard/foo.txt","/tmp/glass-clipboard/goo.txt",});
//        ca.setData(Clipboard.RAW_IMAGE_TYPE, new Pixels(ImageData.GLOBE_WIDTH,
//                                                     ImageData.GLOBE_HEIGHT,
//                                                     ImageData.GLOBE_DATA));
        ca.setSupportedActions(Clipboard.ACTION_COPY_OR_MOVE);
        ca.flush();

        String url = (String)ca.getData(Clipboard.URI_TYPE);

        String [] files = (String [])ca.getData(Clipboard.FILE_LIST_TYPE);

        if (null != files) {
            for (String name:files) {
                System.out.println("FILE IN CLIPBOARD: " + name);
            }
        }
        System.out.println("TEXT TYPE: " + ca.getData(Clipboard.TEXT_TYPE));
        System.out.println("HTML TYPE: " + ca.getData(Clipboard.HTML_TYPE));

        ca.setTargetAction(
                ca.getSupportedSourceActions() == Clipboard.ACTION_MOVE
                ? Clipboard.ACTION_MOVE
                : Clipboard.ACTION_COPY);
    }
    void createDecoratedNormalWindow()
    {
        createWindow(true, false);
    }
    void createUndecoratedNormalWindow()
    {
        createWindow(false, false);
    }
    void createDecoratedGlWindow()
    {
        createWindow(true, true);
    }
    void createUndecoratedGlWindow()
    {
        createWindow(false, true);
    }

    void createUndecoratedShapedWindow()
    {
        Application app = Application.GetApplication();
        Screen mainScreen = Screen.getMainScreen();
        Window window = app.createWindow(mainScreen, 0);
        MyWindowEventHandler eventHandler = new MyWindowEventHandler();
        window.setEventHandler(eventHandler);
        window.setAlpha(0.5f);
        window.setBackground(1.0f, 1.0f, 1.0f);
        window.setShouldStartUndecoratedMove(true);
        eventHandler.addMenus(app, window);
        window.setTitle("shaped");

        View view = app.createView(new ShapedGraphics());
        view.setEventHandler(new ShapedViewEventHandler());
        window.setView(view);

        window.setContentSize(3*this.starSize, 3*this.starSize);
        // TODO: redo
        window.setUndecoratedMoveRectangle(window.getHeight());
        window.setVisible(true);
    }
    
    public void finishLaunching(String args[])
    {
        try
        {
            Application.GetApplication().setEventHandler(new Application.EventHandler() {
                @Override
                public void handleQuitAction(Application app, long time) {
                    app.terminate();
                }
                @Override
                public void handleOpenFilesAction(Application app, long time, String files[]) {
                    System.err.println("app asked to open the following files:");
                    for (String file : files) {
                        System.err.println("    file: "+file);
                    }
                }
            });
            
            SettingsChangedCallback callback = new SettingsChangedCallback()
            {
                @Override
                public void settingsChanged()
                {
                    System.out.println(" Screen.SettingsChangedCallback has been called ");
                }
            };
            Screen.setCallback(callback);

            createStarShape();
            
            // called on native UI thread
            createWindow(ISDECORATED, IS3D);
            //createUndecoratedShapedWindow();

            Timer pulseTimer = Application.GetApplication().createTimer(new Runnable() {
                @Override public void run() {
                    Application.invokeAndWait(new Runnable() {
                        public void run() {
                            final Vector<Window> windows = new Vector<Window>(Window.getWindows());
                            for (Window window : windows) {
                                View view = window.getView();
                                if (view != null) {
                                    View.EventHandler eh = view.getEventHandler();

                                    if (eh instanceof ShapedViewEventHandler) {
                                        ((ShapedViewEventHandler)eh).handleRunLoop(view, System.currentTimeMillis());
                                    } else if (eh instanceof MyViewEventHandler) {
                                        ((MyViewEventHandler)eh).handleRunLoop(view, System.currentTimeMillis());
                                    } else if (eh instanceof TextView) {
                                        ((TextView)eh).handleRunLoop(view, System.currentTimeMillis());
                                    }
                                }
                            }
                        }
                    });
                }
            });
            pulseTimer.start((int)1000/FPS);
        }
        catch (Throwable t)
        {
            t.printStackTrace();
        }
    }

    class ShapedViewEventHandler extends View.EventHandler
    {
        boolean rotate = false;

        public void handleRunLoop(View view, long time)
        {
            // application paint
            Pen pen = view.getPen();
            pen.begin();
            {
                pen.paint(time, view.getWidth(), view.getHeight());
            }
            pen.end();
        }
        
        @Override
        public void handleMouseEvent(View view, long time, int type, int button, int x, int y, int xAbs, int yAbs, int clickCount, int modifiers, boolean isPopupTrigger, boolean isSythesized)
        {
            if ((type == MouseEvent.ENTER) || (type == MouseEvent.UP))
            {
                Cursor cursor = Application.GetApplication().createCursor(Cursor.CURSOR_OPEN_HAND);
                view.getWindow().setCursor(cursor);
            }
            else if (type == MouseEvent.DOWN)
            {
                Cursor cursor = Application.GetApplication().createCursor(Cursor.CURSOR_CLOSED_HAND);
                view.getWindow().setCursor(cursor);
            }
            else if (type == MouseEvent.EXIT)
            {
                Cursor cursor = Application.GetApplication().createCursor(Cursor.CURSOR_DEFAULT);
                view.getWindow().setCursor(cursor);
            }
        }
        
        @Override
        public void handleKeyEvent(View view, long time, int type, int key, char[] chars, int modifiers)
        {
            if (type == KeyEvent.RELEASE)
            {
                this.rotate = !this.rotate;
            }
        }

        public boolean shouldRotate()
        {
            return this.rotate;
        }
    }

    class ShapedGraphics extends Graphics2D
    {
        float angle = 0.0f;
        void paintSomethingCool(Graphics2D graphics, int width, int height)
        {
            ShapedViewEventHandler view = (ShapedViewEventHandler)graphics.getView().getEventHandler();
            
            graphics.push();
            {
                if (view.shouldRotate() == true)
                {
                    this.angle += 0.5f;
                }
                
                graphics.translate(1.5f*starSize, 1.5f*starSize);
                graphics.rotate(this.angle);
                graphics.translate(-1.5f*starSize, -1.5f*starSize);

                graphics.scale(3.0f, 3.0f);
                
                graphics.set(Color.BLUE);
                graphics.fill(star, 0, 0);
            }
            graphics.pop();
        }
        
        @Override
        public void paint(long timeStamp, int width, int height)
        {
            paintSomethingCool(this, width, height);
        }
    }
    
    int starSize = 0;
    Shape star = null;
    float starFollower[] = new float[2];
    void createStarShape()
    {
        int xPoints[] = {55, 67, 109, 73, 83, 55, 27, 37, 1, 43};
        int yPoints[] = {0, 36, 36, 54, 96, 72, 96, 54, 36, 36};

        this.star = new Shape(xPoints[0], yPoints[0]);
        for (int i=1; i<xPoints.length; i++)
        {
            this.star.addLineTo(xPoints[i], yPoints[i]);
        }
        this.starSize = 109;
        starFollower[0] = starFollower[1] = 0f;
    }

    int pastedLife;
    Image pastedImage = null;
    String pastedText = null;

    String inputText = "";

    Font font = new Font("Zapfino", Font.Type.PLAIN, 38);
    Font fontSmall = new Font("Monaco", Font.Type.PLAIN, 8);
    Font fontMonaco = new Font("Monaco", Font.Type.PLAIN, 24);
    Color resizeRectColor = new Color(1.0f, 1.0f, 1.0f, 1.0f);
    public Pen getPen(boolean is3D)
    {
        if (is3D == false)
        {
            return new MyGraphics2D();
        }
        else
        {
            return new MyGraphics3D();
        }
    }
    void paintSomethingCool(Graphics2D graphics, int width, int height)
    {
        MyViewEventHandler view = (MyViewEventHandler)graphics.getView().getEventHandler();
        Window window = graphics.getView().getWindow();

        if (pastedLife > 0)
        {
            pastedLife--;

            graphics.set(Color.WHITE);
            graphics.fillRect(0, 0, width, height);

            if (pastedImage != null)
            {
                graphics.draw(pastedImage, (width-pastedImage.getWidth())/2, (height-pastedImage.getHeight())/2);
            }

            if (pastedText != null)
            {
                Rectangle bounds = this.fontMonaco.getBoundingBox(pastedText);
                graphics.set(this.fontMonaco);
                graphics.set(Color.BLACK);
                graphics.draw(pastedText, (width-bounds.getWidth())/2, (height-bounds.getHeight())/2);
            }

            return;
        }
        pastedImage = null;
        pastedText = null;

        graphics.set(Color.RED);
        graphics.fillRect(0, 0, width, height);

        int margin = DEFAULT_TRACKING_SIZE;
        graphics.set(Color.YELLOW);
        graphics.fillRect(margin, margin, width-margin-margin, height-margin-margin);

        String title = window == null ? "" : window.getTitle();
        Rectangle bounds = this.font.getBoundingBox(title);
        graphics.set(this.font);
        graphics.set(Color.BLACK);
        graphics.draw(title, (width-bounds.getWidth())/2, (0.25f*bounds.getHeight()) + ((bounds.getHeight())/2) - bounds.getHeight());

        graphics.draw(inputText, 100, 100);

        if (globe == null)
        {
            globe = new Image(ImageData.GLOBE_WIDTH, ImageData.GLOBE_HEIGHT, ImageData.GLOBE_DATA, true);
        }

        graphics.draw(globe, width-2*ImageData.GLOBE_WIDTH, ImageData.GLOBE_HEIGHT);
        float fontSmallAscent = this.fontSmall.getMetrics().getAscent();

        graphics.push();
        {
            graphics.translate(1.5f*starSize, 0.5f*starSize);

            graphics.translate(0.5f*starSize, 0.5f*starSize);
            view.angle -= 1; // degrees
            view.angle = (view.angle % 360);
            graphics.rotate(view.angle);
            graphics.translate(-(0.5f*starSize), -(0.5f*starSize));

            graphics.set(Color.BLUE);
            graphics.fill(star, 0, 0);

            graphics.set(this.fontSmall);
            graphics.set(Color.BLACK);
            graphics.draw("   angle: "+(-view.angle), 15, starSize+fontSmallAscent-10);
        }
        graphics.pop();
        
        if (view.position <= margin)
        {
            view.positionDirection = -view.positionDirection;
            view.position = (margin+1);
        }
        if (view.position >= (width-margin-margin))
        {
            view.positionDirection = -view.positionDirection;
            view.position = (width-margin-margin-1);
        }
        graphics.set(Color.BLACK);
        graphics.fillRect(view.position, height-margin-(margin/2), margin, margin/2);
        view.position += view.positionDirection * 10;

        graphics.set(this.fontSmall);
        graphics.set(Color.BLACK);
        for (int i=0; i<height; i+=25)
        {
            graphics.draw(i+":", DEFAULT_TRACKING_SIZE+2, i+fontSmallAscent);
        }

        String timeStr = "time: "+((System.currentTimeMillis()-TimeStart)/1000)+" seconds";
        bounds = this.fontSmall.getBoundingBox(timeStr);
        graphics.set(this.fontSmall);
        graphics.set(Color.BLACK);
        graphics.draw(timeStr, (width-bounds.getWidth())/2.0f, height-(margin-bounds.getHeight())/2.0f);

        if ((window != null) && (window.isFocused() == true) && (starFollower[0] >= 0f))
        {
            graphics.push();
                // center the scaled star under the mouse hot spot
                // anthony: move the star out of the cursor spot to be able to capture the pixel color:
                //          just replaced - with +, and added + 1
            graphics.translate(starFollower[0] + (starSize/16f) + 1, starFollower[1] + (starSize/16f) + 1);
            graphics.scale(0.125f, 0.125f);
            graphics.fill(star, 0f, 0f);
            graphics.pop();
        }
    }
    
    class MyGraphics3D extends Graphics3D
    {
        @Override
        public void paint(long timeStamp, int width, int height)
        {
            //System.out.println("MyGraphics3D.paint: frame=" + frame + ", width=" + width + ", height=" + height);
            paintSomethingCool(this, width, height);
        }
    }
    
    class MyGraphics2D extends Graphics2D
    {
        @Override
        public void paint(long timeStamp, int width, int height)
        {
            //System.out.println("MyGraphics2D.paint: width=" + width + ", height=" + height);
            push();
            {
                paintSomethingCool(this, width, height);
            }
            pop();

            Window host = getView().getWindow();
            if (host != null && (host.isDecorated() == false) && (getView().isInFullscreen() == false))
            {
                if (resize == null)
                {
                    resize = new Image(ImageData.RESIZE_WIDTH, ImageData.RESIZE_HEIGHT, ImageData.RESIZE_DATA, true);
                }
                draw(resize, width - DEFAULT_TRACKING_SIZE, height - DEFAULT_TRACKING_SIZE);
            }
        }
    }

    private static void openPopup(View view, int x, int y) {
        Window owner = view.getWindow();

        Window toplevel = owner;
        while (toplevel.getOwner() != null) {
            toplevel = toplevel.getOwner();
        }

        MyWindowEventHandler mweh = (MyWindowEventHandler)toplevel.getEventHandler();
        if (mweh.popups.isEmpty()) {
            toplevel.grabFocus();
        }

        Window popup = Application.GetApplication().createWindow(owner, owner.getScreen(), Window.UNTITLED);
        TextView textView = new TextView("Monaco") {
            @Override public void handleScrollEvent(View view, long time,
                    int x, int y, int xAbs, int yAbs,
                    double deltaX, double deltaY, int modifiers, int lines, int chars,
                    int defaultLines, int defaultChars, 
                    double xMultiplier, double yMultiplier)
            {
                System.err.println("Handle Scroll event on a popup deltaY=" + deltaY + "    x: " + x + "   y: " + y + "   xAbs: " + xAbs + "   yAbs: " + yAbs);
            }

            @Override public void handleMouseEvent(View view, long time, int type, int button, int x, int y, int xAbs, int yAbs, int clickCount, int modifiers, boolean isPopupTrigger, boolean isSythesized) {
                Window toplevel = view.getWindow();
                while (toplevel.getOwner() != null) {
                    toplevel = toplevel.getOwner();
                }

                if (type == MouseEvent.DOWN) {
                    MyWindowEventHandler mweh = (MyWindowEventHandler)toplevel.getEventHandler();

                    while (mweh.popups.peek() != view.getWindow()) {
                        mweh.popups.pop().close();
                    }
                }

                if (isPopupTrigger) {
                    openPopup(view, xAbs, yAbs);
                }

                if (type == MouseEvent.UP && clickCount == 2) {
                    toplevel.ungrabFocus();
                }

                if (type == MouseEvent.ENTER) {
                    System.err.println("--- Handle MouseEvent.ENTER on a popup window");
                }
                if (type == MouseEvent.EXIT) {
                    System.err.println("--- Handle MouseEvent.EXIT on a popup window");
                }
            }
        };
        textView.setColors(Color.BLACK, Color.LIGHT_GREY);
        textView.setText("Right- or double-click me", 8);

        popup.setView(textView.getView());
        popup.setPosition(x, y);
        popup.setContentSize(64, 128);
        popup.setFocusable(false);

        popup.setVisible(true);

        mweh.popups.push(popup);
    }

    Window openWindow(int level) {
        Application app = Application.GetApplication();
        Window window = app.createWindow(Screen.getMainScreen(), Window.TITLED|Window.CLOSABLE|Window.MINIMIZABLE);
        window.setResizable(true);
        window.setEventHandler(new Window.EventHandler() {
            @Override
            public void handleWindowEvent(Window window, long time, int type) {
                switch (type) {
                    case WindowEvent.CLOSE:
                        window.close();
                        break;
                    case WindowEvent.FOCUS_DISABLED:
                        System.err.println("Got FOCUS_DISABLED.");
                        if (modalWindow != null) {
                            modalWindow.minimize(false);
                            modalWindow.toFront();
                            modalWindow.requestFocus();
                            // Also could be useful to 'blink' the window somehow to
                            // atract user's attention to the modal window
                        }
                        break;
                }
            }
        });
        String desc = "<unknown>";
        float r = 0.0f, g = 0.0f, b = 0.0f;
        int x = 0, y = 0;
        switch (level) {
            case Window.Level.NORMAL:
                desc = "NORMAL";
                g = 1.f;
                break;
            case Window.Level.FLOATING:
                desc = "FLOATING";
                b = 1.f;
                x = 32;
                y = 32;
                break;
            case Window.Level.TOPMOST:
                desc = "TOPMOST";
                r = 1.f;
                x = 64;
                y = 64;
                break;
        }
        window.setTitle(desc);
        window.setPosition(x+50, y+50);
        window.setSize(200, 200);
        window.setBackground(r, g, b);
        window.setLevel(level);
        window.setFocusable(false);
        window.setAlpha(0.75f);
        window.setVisible(true);

        return window;
    }

    class MyViewEventHandler extends View.EventHandler
    {
        int angle = 0;
        float position = 0.0f;
        float positionDirection = 1.0f;
        boolean doDrag = false;

        private Cursor customCursor;

        void paint(View view, long time)
        {
            Pen pen = view.getPen();
            pen.begin();
            {
                pen.paint(time, view.getWidth(), view.getHeight());
            }
            pen.end();
        }
        
        @Override
        public void handleViewEvent(View view, long time, int type)
        {
            switch (type)
            {
                case ViewEvent.REPAINT:
                    // system paint
                    paint(view, time);
                    break;
                case ViewEvent.FULLSCREEN_ENTER:
                    System.err.println("FULLSCREEN_ENTER");
                    break;
                case ViewEvent.FULLSCREEN_EXIT:
                    System.err.println("FULLSCREEN_EXIT");
                    break;
            }
        }
        
        public void handleRunLoop(View view, long time)
        {
            // application paint
            paint(view, time);
        }
        
        @Override public void handleScrollEvent(View view, long time, int x, int y, int xAbs, int yAbs, double deltaX, double deltaY, int modifiers, int lines, int chars, int defaultLines, int defaultChars, double multiplierX, double multiplierY)
        {
            System.err.println("Scroll dX: " + deltaX  + "; dY: " + deltaY + ";     Client coords: " + x + ", " + y + ";   Absolute coords: " + xAbs + ", " + yAbs + ";   lines: " + lines + ";   chars: " + chars + ";   multiplierY: " + multiplierY);
        }

        @Override
        public void handleMouseEvent(View view, long time, int type, int button, int x, int y, int xAbs, int yAbs, int clickCount, int modifiers, boolean isPopupTrigger, boolean isSythesized)
        {
            /*
            if (type == MouseEvent.MOVE) {
                System.err.println("MOVE: x=" + x + "   y=" + y + "   xAbs=" + xAbs + "   yAbs=" + yAbs);
            }
            if (type == MouseEvent.ENTER) {
                System.err.println("ENTER");
            }
            if (type == MouseEvent.EXIT) {
                System.err.println("EXIT");
            }
            */

            if (customCursor == null)
            {
                customCursor = Application.GetApplication().createCursor(ImageData.GLOBE_WIDTH/2, ImageData.GLOBE_HEIGHT/2,
                                                Application.GetApplication().createPixels(
                                                    ImageData.GLOBE_WIDTH,
                                                     ImageData.GLOBE_HEIGHT,
                                                     IntBuffer.wrap(ImageData.GLOBE_DATA)));
            }

            if (type == MouseEvent.WHEEL) {
            }

            if (type == MouseEvent.DOWN) {
                System.err.println("Click count: " + clickCount + ";     Client coords: " + x + ", " + y + ";   Absolute coords: " + xAbs + ", " + yAbs);
                //System.err.println("MouseEvent.DOWN");
                System.err.println("   window.y: " + view.getWindow().getY() + ";   view.y: " + view.getY());
                System.err.println("   button: " + button + ";   modifiers: " + modifiers); 
            }

            if ((type == MouseEvent.ENTER) || (type == MouseEvent.UP))
            {
                Cursor cursor = Application.GetApplication().createCursor(Cursor.CURSOR_CROSSHAIR);
                view.getWindow().setCursor(cursor);
            }
            else if (type == MouseEvent.DOWN)
            {
                view.getWindow().setCursor(customCursor);
            }
            else if (type == MouseEvent.EXIT)
            {
                Cursor cursor = Application.GetApplication().createCursor(Cursor.CURSOR_DEFAULT);
                view.getWindow().setCursor(cursor);
            }
            
            if ((x >= 0) && (x <= view.getWidth()) && (y >= 0) && (y <= view.getHeight())) {
                starFollower[0] = x;
                starFollower[1] = y;
            } else {
                starFollower[0] = -1f;
                starFollower[1] = -1f;
            }

            if ((type == MouseEvent.DOWN) &&
                (x > view.getWidth() - 2 * 32) &&
                (x < view.getWidth() - 32) &&
                (y > 32) &&
                (y < 2 * 32))
            {
                this.doDrag = true;
            }
            else if ((type == MouseEvent.DRAG) && (this.doDrag == true))
            {
                /*
                ByteBuffer pixels = Image.Globe.getPixels();
                pixels.rewind();
                ByteBuffer rawImage = ByteBuffer.allocateDirect(4+4+4+pixels.capacity());
                rawImage.order(ByteOrder.nativeOrder());
                rawImage.rewind();
                rawImage.putInt(Image.Globe.getWidth());
                rawImage.putInt(Image.Globe.getHeight());
                rawImage.putInt(32);
                rawImage.put(pixels);

                //copyToDnd("Globe.png", rawImage);
                */
            }
            else
            {
                this.doDrag = false;
            }

            if (isPopupTrigger && view.getWindow().isFocused()) {
                System.err.println("This mouse event is a popup trigger");

                MyWindowEventHandler mweh = (MyWindowEventHandler)view.getWindow().getEventHandler();
                while (!mweh.popups.isEmpty()) {
                    mweh.popups.pop().close();
                }
                openPopup(view, xAbs, yAbs);
            }
        }

        private final Robot robot = Application.GetApplication().createRobot();

        private final Map<Character, EventLoop> loops =
            new HashMap<Character, EventLoop>();
        {
            loops.put('1', Application.GetApplication().createEventLoop());
            loops.put('2', Application.GetApplication().createEventLoop());
            loops.put('3', Application.GetApplication().createEventLoop());
        }

        private void handleEventLoop(int type, char[] chars, final char c) {
            if ((type == KeyEvent.RELEASE) && (chars.length > 0) && chars[0] == c) {
                EventLoop l = loops.get(c);
                if (l == null) {
                    System.err.println("No such event loop");
                    return;
                }

                switch (l.getState()) {
                    case IDLE:
                        System.err.println("Enter nested event loop " + c + "...");
                        Object r = l.enter();
                        System.err.println("Just exited the nested event loop " + c + " with a return value: " + r);
                        break;
                    case ACTIVE:
                        System.err.println("Leave nested event loop " + c + "...");
                        l.leave(new Object() { @Override public String toString() { return "Nested_return_value_object_for_loop_" + c; } });
                        break;
                    default:
                        System.err.println("The event loop is currently " + l.getState() + ", and as such can be neither entered nor left");
                        break;
                }
            }
        }

        @Override
        public void handleKeyEvent(View view, long time, int type, int key, char[] chars, int modifiers)
        {
            System.out.println("MyView: KeyEvent occurs: " + time + " , " + type + " , " + key +" , <" + new String(chars) + "> , " + modifiers);
            System.out.flush();
            if (type == KeyEvent.PRESS) {
                if (key == KeyEvent.VK_ESCAPE) {
                    inputText = "";
                } else if (key == KeyEvent.VK_M) {
                    Window window = view.getWindow();
                    if (window != null) {
                        window.maximize(!window.isMaximized());
                    }
                } else {
                    inputText = inputText + new String(chars);
                }
            }
            if ((type == KeyEvent.RELEASE) && (chars.length > 0) && (chars[0] == 'f'))
            {
                view.toggleFullscreen(true, true, false);
            }
            if ((type == KeyEvent.RELEASE) && (chars.length > 0) && (chars[0] == 's'))
            {
                printAllScreens();
            }
            if ((type == KeyEvent.RELEASE) && (chars.length > 0) && (chars[0] == 'a'))
            {
                printFocusedWindow();
            }
            if ((type == KeyEvent.RELEASE) && (chars.length > 0) && (chars[0] == 'k'))
            {
                robot.keyPress(KeyEvent.VK_R);
                robot.keyRelease(KeyEvent.VK_R);
            }
            if ((type == KeyEvent.RELEASE) && (chars.length > 0) && (chars[0] == 'c'))
            {
                int x = robot.getMouseX();
                int y = robot.getMouseY();

                int c = robot.getPixelColor(x, y);

                System.err.println("Pixel color at (" + x + ", " + y + ") is: [A, R, G, B] = [" + ((c >> 24) & 0xFF) + ", " + ((c >> 16) & 0xFF) + ", " + ((c >> 8) & 0xFF) + ", " + ((c >> 0) & 0xFF) + "]");
            }
            if ((type == KeyEvent.RELEASE) && (chars.length > 0) && (chars[0] == 'o'))
            {
                Screen screen;
                Window window = view.getWindow();
                if (window != null) {
                    screen = window.getScreen();
                } else {
                    screen = Screen.getMainScreen();
                }

                robot.mouseMove(screen.getWidth() / 2, screen.getHeight() / 2);
                robot.mousePress(1 << 0);
                robot.mouseRelease(1 << 0);
            }
            if ((type == KeyEvent.RELEASE) && (chars.length > 0) && (chars[0] == 'e'))
            {
                Screen screen;
                Window window = view.getWindow();
                if (window != null) {
                    screen = window.getScreen();
                } else {
                    screen = Screen.getMainScreen();
                }

                Pixels pixels = robot.getScreenCapture(0, 0, screen.getWidth(), screen.getHeight());

                int x = robot.getMouseX();
                int y = robot.getMouseY();

                ByteBuffer bb = pixels.asByteBuffer();

                int i = y * pixels.getWidth() + x;
                int c = bb.getInt(i * 4);

                System.err.println("Pixel color at (" + x + ", " + y + ") is: [A, R, G, B] = [" + ((c >> 24) & 0xFF) + ", " + ((c >> 16) & 0xFF) + ", " + ((c >> 8) & 0xFF) + ", " + ((c >> 0) & 0xFF) + "]");

                ca.setData(Clipboard.RAW_IMAGE_TYPE, pixels);
                ca.flush();
            }
            if ((type == KeyEvent.RELEASE) && (chars.length > 0) && (chars[0] == 'n')) {
                System.err.println("Enter nested event loop...");

                Object r = Application.enterNestedEventLoop();

                System.err.println("Just exited the nested event loop with a return value: " + r);
            }
            if ((type == KeyEvent.RELEASE) && (chars.length > 0) && (chars[0] == 'l')) {
                System.err.println("Leave nested event loop...");

                Application.leaveNestedEventLoop(new Object() { @Override public String toString() { return "Nested_return_value_object"; } });
            }
            for (Character c : loops.keySet()) {
                handleEventLoop(type, chars, c);
            }
            if ((type == KeyEvent.RELEASE) && (chars.length > 0) && (chars[0] == 'i')) {
                System.err.println("Minimize/restore");

                view.getWindow().minimize(!view.getWindow().isMinimized());
            }
            if ((type == KeyEvent.RELEASE) && (chars.length > 0) && (chars[0] == 'p')) {
                System.err.println("Put an image onto clipboard");

                byte[] bb = new byte[4 * 32 * 32];
                for (int i = 0; i < 32*32; i++) {
                    // BYTE_BGRA_PRE
                    bb[i*4+0] = (byte)0;
                    bb[i*4+1] = (byte)0;
                    bb[i*4+2] = (byte)255;
                    bb[i*4+3] = (byte)255;
                }

                ca.setData(Clipboard.RAW_IMAGE_TYPE, Application.GetApplication().createPixels(32, 32, ByteBuffer.wrap(bb)));
                ca.flush();
            }
            if ((type == KeyEvent.RELEASE) && (chars.length > 0) && (chars[0] == 'q')) {
                Application.GetApplication().terminate();
            }
            if ((type == KeyEvent.RELEASE) && (chars.length > 0) && (chars[0] == 'w')) {
                System.err.println("Test for memory leaks");

                new Thread() {

                    public void run() {
                        for (int i = 0; i < 300; i++) {
                            Application.GetApplication().invokeLater(new Runnable() {
                                public void run() {
                                    testWindow = openWindow(Window.Level.NORMAL);
                                }
                            });

                            try { Thread.sleep(20); } catch (Exception ex) {}

                            Application.GetApplication().invokeLater(new Runnable() {
                                public void run() {
                                    testWindow.close();
                                    testWindow = null;
                                }
                            });
                        }
                    }
                }.start();
            }
        }

        private void printAllScreens() {
            System.out.println(" All screens: ");
            java.util.List<Screen> screens = Screen.getScreens();
            if (screens != null)  {
                for (Screen screen : screens) {
                    System.out.println(screen);
                }
            }
        }

        private void printFocusedWindow() {
            System.out.println(" Focused window: ");
            System.out.println(Window.getFocusedWindow());
        }
/*
        void handlePastedItems(java.util.Map items)
        {
            //System.err.println("Pasteboard items: "+items);
            
            String text = (String)items.get(Pasteboard.TEXT_TYPE);
            String uri = (String)items.get(Pasteboard.URI_TYPE);
            ByteBuffer imageRaw = (ByteBuffer)items.get(Pasteboard.RAW_IMAGE_TYPE);

            if (text != null)
            {
                System.err.println("Pasteboard item for "+Pasteboard.TEXT_TYPE+": "+text);
                pastedText = text.trim();
                if (pastedText.length() > 50)
                {
                    pastedText = pastedText.substring(0, 50);
                }
                pastedLife = Application.GetApplication().getFps();
            }
            if (uri != null)
            {
                System.err.println("Pasteboard item for "+Pasteboard.URI_TYPE+": "+uri);
                pastedText = uri.trim();
                if (pastedText.length() > 50)
                {
                    pastedText = pastedText.substring(0, 50);
                }
                pastedLife = Application.GetApplication().getFps();
            }
            if (imageRaw != null)
            {
                System.err.println("Pasteboard item for "+Pasteboard.RAW_IMAGE_TYPE+": "+imageRaw+", "+imageRaw.capacity()+" bytes");
                imageRaw.order(ByteOrder.nativeOrder());
                int width = imageRaw.getInt();
                int height = imageRaw.getInt();
                int bpp = imageRaw.getInt();
                pastedImage = new Image(width, height, imageRaw.slice(), true);
                pastedLife = Application.GetApplication().getFps();
            }
        }
*/


        @Override
        public void handleDragStart(View view, int button, int x, int y, int xAbs, int yAbs, ClipboardAssistance dropSourceAssistant) {
            if (button == MouseEvent.BUTTON_LEFT) {
                
                // the order is important! start with simplest representation first!
                if (false) {
                    dropSourceAssistant.setData(Clipboard.TEXT_TYPE, "Globe image\n");
                }

                if (false) {
                    //TODO: On Mac we should probably convert ARGB --> BGRA
                    Pixels img = Application.GetApplication().createPixels(
                        ImageData.GLOBE_WIDTH,
                     ImageData.GLOBE_HEIGHT,
                     IntBuffer.wrap(ImageData.GLOBE_DATA));
                    dropSourceAssistant.setData(Clipboard.RAW_IMAGE_TYPE, img);
                }
                if (true) {
                    dropSourceAssistant.setData(Clipboard.URI_TYPE, "http://oracle.com");
                }
                dropSourceAssistant.setData(Clipboard.TEXT_TYPE, new DelayedCallback() {
                    public Object providedData() {
                        return "Creates data on target request.";
                    }
                });
                dropSourceAssistant.setSupportedActions(Clipboard.ACTION_COPY);
                dropSourceAssistant.flush();
            }
        }

        /**
         * Called by system and notifies about successful data transfer.
         * Delete-on-move functionality should be implemented here.
         * @param performedAction Clipboard.ACTION_COPY, or Clipboard.ACTION_MOVE, or Clipboard.ACTION_REFERENCE
         */
        @Override
        public void handleDragEnd(View view, int performedAction) {
            //Delete-on-move action if any
        }

        @Override
        public int handleDragEnter(View view, int x, int y, int xAbs, int yAbs, int recommendedDropAction, ClipboardAssistance dropTargetAssistant) {
            System.err.println("{handleDragEnter x:" + x + " y:" + y + "[xAbs:" + xAbs + " yAbs:" + yAbs + "]"
                  + dropTargetAssistant.dumpToString()
                  + "}handleDragEnter");
            return recommendedDropAction;
        }

        @Override
        public int handleDragOver(View view, int x, int y, int xAbs, int yAbs, int recommendedDropAction, ClipboardAssistance dropTargetAssistant) {
            System.err.println("handleDragOver x:" + x + " y:" + y + "[xAbs:" + xAbs + " yAbs:" + yAbs + "]");
            int sup = dropTargetAssistant.getSupportedSourceActions();
            if ((recommendedDropAction & sup) == 0) {
                if ((sup & Clipboard.ACTION_MOVE) != 0) {
                    return Clipboard.ACTION_MOVE;
                }
                if ((sup & Clipboard.ACTION_COPY) != 0) {
                    return Clipboard.ACTION_COPY;
                }
                if ((sup & Clipboard.ACTION_REFERENCE) != 0) {
                    return Clipboard.ACTION_REFERENCE;
                }
                return Clipboard.ACTION_NONE;
            }
            return recommendedDropAction;
        }

        @Override
        public void handleDragLeave(View view, ClipboardAssistance dropTargetAssistant) {
            System.err.println("handleDragLeave");
        }

        @Override
        public int handleDragDrop(View view, int x, int y, int xAbs, int yAbs, int recommendedDropAction, ClipboardAssistance dropTargetAssistant) {
            System.err.println("\n{\nRain Demo handleDragDrop x:" + x + " y:" + y + "[xAbs:" + xAbs + " yAbs:" + yAbs + "]"
                  + dropTargetAssistant.dumpToString()
                  + "\n}\n");
            String[] mimes = dropTargetAssistant.getMimeTypes();
            System.err.println("Rain Demo items:");
            for (String mime : mimes) {
                if (mime.equals(Clipboard.RAW_IMAGE_TYPE)) {
                    System.err.println("    RAW_IMAGE_TYPE");
                    pastedText = "Image: ";
                    pastedImage = new Image((Pixels)dropTargetAssistant.getData(Clipboard.RAW_IMAGE_TYPE), true);
                    System.err.println(pastedText);
                } else if( mime.equals(Clipboard.URI_TYPE)) {
                    System.err.println("    URI_TYPE");
                    pastedText = "URLs: ";
                    pastedText += (String)dropTargetAssistant.getData(Clipboard.URI_TYPE);
                    System.err.println(pastedText);
                } else if( mime.equals(Clipboard.FILE_LIST_TYPE)) {
                    System.err.println("    FILE_LIST_TYPE");
                    pastedText = "Files: ";
                    String files[] = (String[])dropTargetAssistant.getData(Clipboard.FILE_LIST_TYPE);
                    if (files.length > 0) {
                        for (int i=0; i<files.length; i++) {
                            pastedText += files[i] + " ";
                        }
                    }
                    System.err.println(pastedText);
                } else if( mime.equals(Clipboard.TEXT_TYPE)) {
                    System.err.println("    TEXT_TYPE");
                    pastedText = "Text: ";
                    pastedText += (String)dropTargetAssistant.getData(Clipboard.TEXT_TYPE);
                    System.err.println(pastedText);
                }
                pastedLife = FPS;
            }
            return Clipboard.ACTION_COPY;
        }
    }

    Window modalWindow;

    class MyWindowEventHandler extends Window.EventHandler
    {
        public final Deque<Window> popups = new ArrayDeque<Window>();

        @Override
        public void handleWindowEvent(Window window,long time, int type) {
            switch (type) {
                case WindowEvent.CLOSE:
                    window.close();
                    if (Window.getWindows().isEmpty()) {
                        Application.GetApplication().terminate();
                    }
                    break;
                case WindowEvent.FOCUS_DISABLED:
                    System.err.println("Got FOCUS_DISABLED.");
                    if (modalWindow != null) {
                        modalWindow.minimize(false);
                        modalWindow.toFront();
                        modalWindow.requestFocus();
                        // Also could be useful to 'blink' the window somehow to
                        // atract user's attention to the modal window
                    }
                    break;
                case WindowEvent.FOCUS_UNGRAB:
                    System.err.println("FOCUS_UNGRAB");
                    while (!popups.isEmpty()) {
                        popups.pop().close();
                    }
                    break;
                case WindowEvent.FOCUS_GAINED:
                    System.err.println("FOCUS_GAINED");
                    break;
                case WindowEvent.FOCUS_LOST:
                    System.err.println("FOCUS_LOST");
                    break;
            }
        }
        
        void testExclusiveModality() {
            final int fontSize = 20;
            final String msg1 = "click to ENTER modality";
            final String msg2 = "click to EXIT modality";
            Application app = Application.GetApplication();
            TextView view = new TextView("Monaco") {
                @Override
                public void handleMouseEvent(View view, long time, int type, int button, int x, int y, int xAbs, int yAbs, int clickCount, int modifiers, boolean isPopupTrigger, boolean isSythesized) {
                    if (type == MouseEvent.DOWN) {
                        Window window = view.getWindow();
                        if (window.isModal() == false) {
                            setText(msg2, fontSize);
                            window.enterModal();
                        } else {
                            window.exitModal();
                            setText(msg1, fontSize);
                        }
                    }
                }
            };
            view.setText(msg1, fontSize);
            
            Screen mainScreen = Screen.getMainScreen();
            Window window = app.createWindow(mainScreen, Window.TITLED|Window.CLOSABLE);
            window.setEventHandler(new MyWindowEventHandler());
            window.setTitle("Modal");
            window.setView(view.getView());
            window.setPosition((mainScreen.getWidth()-window.getWidth())/2, (mainScreen.getHeight()-window.getHeight())/2);
            window.setContentSize(640, 480);
            window.setVisible(true);
        }

        void testDialogModality() {
            Window window = Window.getFocusedWindow();
            Application app = Application.GetApplication();

            final int fontSize = 20;
            final String msg = "click to EXIT dialog";
            TextView view = new TextView("Monaco") {
                @Override
                public void handleMouseEvent(View view, long time, int type, int button, int x, int y, int xAbs, int yAbs, int clickCount, int modifiers, boolean isPopupTrigger, boolean isSythesized) {
                    if (type == MouseEvent.DOWN) {
                        Window window = view.getWindow();
                        window.exitModal();
                    }
                }
            };
            view.setText(msg, fontSize);
            
            Window dialog = app.createWindow(Screen.getMainScreen(), Window.TITLED);
            dialog.setContentSize((int)(0.75f*window.getWidth()), (int)(0.75f*window.getHeight()));
            dialog.setTitle("Dialog");
            dialog.setView(view.getView());
            dialog.setAlpha(0.95f);
            dialog.enterModal(window);
        }
        
        void openOwnedWindow(Window owner) {
            Application app = Application.GetApplication();
            Window window = app.createWindow(owner, Screen.getMainScreen(), Window.TITLED|Window.CLOSABLE|Window.MINIMIZABLE);
            window.setResizable(true);
            window.setEventHandler(new Window.EventHandler() {
                @Override
                public void handleWindowEvent(Window window, long time, int type) {
                    switch (type) {
                        case WindowEvent.CLOSE:
                            System.err.println("Owned window: CLOSE event");
                            window.close();
                            break;
                        case WindowEvent.DESTROY:
                            System.err.println("Owned window: DESTROY event");
                            break;
                    }
                }
            });
            
            window.setTitle("owned window");
            window.setPosition(25, 25);
            window.setMinimumSize(100, 0);
            window.setMaximumSize(Integer.MAX_VALUE, 400);
            window.setSize(200, 200);
            window.setBackground(1.f, 0.f, 1.f);
            window.setVisible(true);
        }
        
        void openPixelsWindow(boolean is3d) {
            Plasma plasma = new Plasma(is3d);
            plasma.start();
        }
        
        private Window childWindow;
        void toggleChildWindow(long parent) {
            Application app = Application.GetApplication();
            if (childWindow == null) {
                childWindow = app.createWindow(parent);
                childWindow.setEventHandler(new Window.EventHandler() {
                    @Override
                    public void handleWindowEvent(Window window, long time, int type) {
                        switch (type) {
                            case WindowEvent.DESTROY:
                                System.err.println("Child window: DESTROY event");
                                break;
                        }
                    }
                });

                childWindow.setPosition(250, 50);
                childWindow.setSize(70, 40);
                childWindow.setBackground(1.f, 0.f, 1.f);
                childWindow.setVisible(true);
            } else {
                childWindow.close();
                childWindow = null;
            }
        }
        
        void openFullscreen(String title, final boolean animate, final boolean keepRatio, final boolean hideCursor) {
            final int fontSize = 20;
            final String msg1 = "click to ENTER fullscreen";
            final String msg2 = "click to EXIT fullscreen";
            Application app = Application.GetApplication();
            TextView view = new TextView("Monaco") {
                @Override
                public void handleMouseEvent(View view, long time, int type, int button, int x, int y, int xAbs, int yAbs, int clickCount, int modifiers, boolean isPopupTrigger, boolean isSythesized) {
                    if (type == MouseEvent.DOWN) {
                        if (view.isInFullscreen() == false) {
                            view.enterFullscreen(animate, keepRatio, hideCursor);
                            setText(msg2, fontSize);
                        } else {
                            view.exitFullscreen(animate);
                            setText(msg1, fontSize);
                        }
                    }
                }
            };
            view.setText(msg1, fontSize);
            view.setColors(Color.BLACK, Color.YELLOW);
            
            Screen mainScreen = Screen.getMainScreen();
            Window window = app.createWindow(mainScreen, Window.TITLED|Window.CLOSABLE);
            window.setEventHandler(new MyWindowEventHandler());
            window.setTitle(title);
            window.setView(view.getView());
            window.setContentSize(640, 480);
            window.setPosition((mainScreen.getWidth()-window.getWidth())/2, (mainScreen.getHeight()-window.getHeight())/2);
            window.setVisible(true);
        }
        
        void openAppModalWindow() {
            Application app = Application.GetApplication();
            for (Window w : Window.getWindows()) {
                // If this were a document modal window, it would block
                // only its relative windows, not all app's windows.
                w.setEnabled(false);
            }

            modalWindow = app.createWindow(Screen.getMainScreen(), Window.TITLED|Window.CLOSABLE|Window.MINIMIZABLE);
            modalWindow.setResizable(true);
            modalWindow.setEventHandler(new Window.EventHandler() {
                @Override
                public void handleWindowEvent(Window window, long time, int type) {
                    switch (type) {
                        case WindowEvent.CLOSE:
                            for (Window w : Window.getWindows()) {
                                if (w != window) {
                                    // If this were a document modal window, it would unblock
                                    // only its relative windows, not all app's windows.
                                    w.setEnabled(true);
                                }
                            }
                            window.close();
                            modalWindow = null;
                            break;
                    }
                }
            });

            modalWindow.setTitle("Modal window. Close it to enable other windows.");
            modalWindow.setPosition(150, 150);
            modalWindow.setSize(300, 200);
            modalWindow.setBackground(0.f, 1.f, 1.f);

            modalWindow.setVisible(true);
            // In real-world the app might start a new thread and wait till the modal
            // dialog closes. But it's per client's desire.
        }

        void testInvokeAPI() {
            System.err.println("Test invoke API:");
            System.err.println("   Call #1");
            Application.invokeAndWait(new java.lang.Runnable() {
                public void run() {
                    System.err.println("      1. invokeAndWait");
                }
            });
            System.err.println("   Call #2");
            Application.invokeLater(new java.lang.Runnable() {
                public void run() {
                    System.err.println("      2. invokeLater");
                }
            });
            System.err.println("   Call #3");
            Application.postOnEventQueue(new java.lang.Runnable() {
                public void run() {
                    System.err.println("      3. postOnEventQueue");
                }
            });
            /*Application.postAtEventQueueHead(new java.lang.Runnable() {
                public void run() {
                    System.err.println("   4. postAtEventQueueHead");
                }
            });*/
            System.err.println("done testing invoke API. Some messages should be printed after this one.");
        }
        
        void openEventsWindow() {
            System.err.println("openEventWindow");
            final int fontSize = 20;
            final String msg = "last event: ";
            Application app = Application.GetApplication();
            final TextView view = new TextView("Monaco") {
                @Override
                public void handleMouseEvent(View view, long time, int type, int button, int x, int y, int xAbs, int yAbs, int clickCount, int modifiers, boolean isPopupTrigger, boolean isSythesized) {
                    //System.err.println("MouseEvent "+MouseEvent.GetTypeString(type));
                    //setText(msg+"MouseEvent "+MouseEvent.GetTypeString(type), fontSize);
                }
            };
            view.setText(msg+"NONE", fontSize);

            try
            {
                Window window = app.createWindow(Screen.getMainScreen(), Window.TITLED|Window.CLOSABLE|Window.MINIMIZABLE);
                window.setResizable(true);
                window.setEventHandler(new Window.EventHandler() {
                    @Override
                    public void handleWindowEvent(Window window, long time, int type) {
                        //System.err.println("WindowEvent "+WindowEvent.GetTypeString(type));
                        //view.setText(msg+"WindowEvent "+WindowEvent.GetTypeString(type), fontSize);
                    }
                });
                window.setTitle("event window");
                window.setView(view.getView());
                window.setPosition(50, 50);
                window.setContentSize(640, 480);
                window.setVisible(true);
            }
            catch (Throwable t)
            {
                t.printStackTrace();
            }
        }
        
        void testFileChooser() {
            //File directory = new File("c:");

            List<String> extensionsAll = new ArrayList<String>();
            extensionsAll.add("*.*");
            ExtensionFilter filterAll = new ExtensionFilter("All files", extensionsAll);

            List<String> extensionsTxt = new ArrayList<String>();
            extensionsTxt.add("*.txt");
            extensionsTxt.add("*.doc");
            extensionsTxt.add("*.bak");
            ExtensionFilter filterTxt = new ExtensionFilter("Text files", extensionsTxt);

            List<ExtensionFilter> filters = new ArrayList<ExtensionFilter>();
            filters.add(filterAll);
            filters.add(filterTxt);

            filters.add(new ExtensionFilter("a files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.a");}}));
            filters.add(new ExtensionFilter("b files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.b");}}));
            filters.add(new ExtensionFilter("c files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.c");}}));
            filters.add(new ExtensionFilter("d files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.d");}}));
            filters.add(new ExtensionFilter("e files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.e");}}));
            filters.add(new ExtensionFilter("f files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.f");}}));
            filters.add(new ExtensionFilter("g files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.g");}}));
            filters.add(new ExtensionFilter("h files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.h");}}));
            filters.add(new ExtensionFilter("i files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.i");}}));
            filters.add(new ExtensionFilter("j files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.j");}}));
            filters.add(new ExtensionFilter("k files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.k");}}));
            filters.add(new ExtensionFilter("l files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.l");}}));
            filters.add(new ExtensionFilter("m files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.m");}}));
            filters.add(new ExtensionFilter("n files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.n");}}));
            filters.add(new ExtensionFilter("o files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.o");}}));
            filters.add(new ExtensionFilter("p files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.p");}}));
            filters.add(new ExtensionFilter("q files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.q");}}));
            filters.add(new ExtensionFilter("r files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.r");}}));
            filters.add(new ExtensionFilter("s files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.s");}}));
            filters.add(new ExtensionFilter("t files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.t");}}));
            filters.add(new ExtensionFilter("u files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.u");}}));
            filters.add(new ExtensionFilter("v files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.v");}}));
            filters.add(new ExtensionFilter("w files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.w");}}));
            filters.add(new ExtensionFilter("x files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.x");}}));
            filters.add(new ExtensionFilter("y files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.y");}}));
            filters.add(new ExtensionFilter("z files - long enough description for this test file extension filter to cause buffer overrun for sure, yeah!!!", new ArrayList<String>(){{add("*.z");}}));

            List<File> files = CommonDialogs.showFileChooser(null/*owner*/, null/*directory*/, "title", Type.OPEN, true, filters);

            System.err.println(" result (size="+files.size()+") {");
            for (File file : files) {
                System.err.println(" "+file);
            }
            System.err.println(" } ");
        }

        void testFolderChooser() {
            File folder = CommonDialogs.showFolderChooser(null, null/*new File("C:\\")*/, "title");

            System.err.println(" result {");
            System.err.println(" "+folder);
            System.err.println(" } ");
        }

        void addMenus(Application app, Window window)
        {
            // one same menubar for all windows
            if (menubar == null)
            {
                menubar = Application.GetApplication().createMenuBar();
                app.installDefaultMenus(menubar);
                
                addMenuMenu(menubar);
                addEventsMenu(menubar);
                addWindowsMenu(menubar, window);
                addFullscreenMenu(menubar);
                addModalityMenu(menubar);
                addCommonDialogsMenu(menubar);
                addPasteMenu(menubar);
            }
            window.setMenuBar(menubar);
        }
        
        void addMenuMenu(MenuBar menubar)
        {
            Application app = Application.GetApplication();
            final Menu menu = app.createMenu("Menu");
            menu.add(app.createMenuItem("Set bottom menu checked", new MenuItem.Callback() {
                @Override
                public void action() {
                    MenuItem item1 =(MenuItem) menu.getItems().get(0);
                    MenuItem item2 =(MenuItem) menu.getItems().get(1);
                    if (item2.isChecked() == true) {
                        item2.setChecked(false);
                        item1.setTitle("Set bottom menu checked");
                    } else {
                        item2.setChecked(true);
                        item1.setTitle("Set bottom menu unchecked");
                    }
                }
            }));
            menu.add(app.createMenuItem("Set top menu disabled", new MenuItem.Callback() {
                @Override
                public void action() {
                    MenuItem item1 =(MenuItem) menu.getItems().get(0);
                    MenuItem item2 =(MenuItem) menu.getItems().get(1);
                    if (item1.isEnabled() == true) {
                        item1.setEnabled(false);
                        item2.setTitle("Set top menu enabled");
                    } else {
                        item1.setEnabled(true);
                        item2.setTitle("Set top menu disabled");
                    }
                }
            }));
            
            menubar.add(menu);
        }

        private Timer timer = Application.GetApplication().createTimer(new Runnable() {
            private int count = 0;
            @Override
            public void run() {
                System.err.println("High-resolution timer tick " + count++);
            }
        });

        void addEventsMenu(MenuBar menubar)
        {
            Application app = Application.GetApplication();
            Menu menu = app.createMenu("Events");
            menu.add(app.createMenuItem("Events window", new MenuItem.Callback() {
                @Override
                public void action() {
                    openEventsWindow();
                }
            }));
            menu.add(MenuItem.Separator);
            menu.add(app.createMenuItem("Start timer", new MenuItem.Callback() {
                @Override
                public void action() {
                    System.err.println("Available periods: " + Timer.getMinPeriod() + ".." + Timer.getMaxPeriod());
                    // It's not quite high pps, but at least reasonable for println()
                    timer.start(Math.min(Math.max(500, Timer.getMinPeriod()), Timer.getMaxPeriod()));
                }
            }));
            menu.add(app.createMenuItem("Stop timer", new MenuItem.Callback() {
                @Override
                public void action() {
                    timer.stop();
                }
            }));
            
            menubar.add(menu);
        }
        
        void addWindowsMenu(MenuBar menubar, final Window window)
        {
            Application app = Application.GetApplication();
            Menu menu = app.createMenu("Windows");
            menu.add(app.createMenuItem("Decorated Unaccelerated window", new MenuItem.Callback()
            {
                @Override
                public void action()
                {
                    createDecoratedNormalWindow();
                }
            }, KeyEvent.VK_1, KeyEvent.MODIFIER_COMMAND));
            menu.add(app.createMenuItem("Undecorated Unaccelerated window", new MenuItem.Callback()
            {
                @Override
                public void action()
                {
                    createUndecoratedNormalWindow();
                }
            }, KeyEvent.VK_2, KeyEvent.MODIFIER_COMMAND));
            menu.add(app.createMenuItem("Decorated Accelerated window", new MenuItem.Callback()
            {
                @Override
                public void action()
                {
                    createDecoratedGlWindow();
                }
            }, KeyEvent.VK_3, KeyEvent.MODIFIER_COMMAND));
            menu.add(app.createMenuItem("Undecorated Accelerated window", new MenuItem.Callback()
            {
                @Override
                public void action()
                {
                    createUndecoratedGlWindow();
                }
            }, KeyEvent.VK_4, KeyEvent.MODIFIER_COMMAND));
            menu.add(app.createMenuItem("Star Shaped window", new MenuItem.Callback()
            {
                @Override
                public void action()
                {
                    createUndecoratedShapedWindow();
                }
            }, KeyEvent.VK_5, KeyEvent.MODIFIER_COMMAND));            menu.add(MenuItem.Separator);
            menu.add(app.createMenuItem("NORMAL window", new MenuItem.Callback() {
                @Override
                public void action() {
                    openWindow(Window.Level.NORMAL);
                }
            }));
            menu.add(app.createMenuItem("FLOATING window", new MenuItem.Callback() {
                @Override
                public void action() {
                    openWindow(Window.Level.FLOATING);
                }
            }));
            menu.add(app.createMenuItem("TOPMOST window", new MenuItem.Callback() {
                @Override
                public void action() {
                    openWindow(Window.Level.TOPMOST);
                }
            }));
            menu.add(MenuItem.Separator);
            menu.add(app.createMenuItem("Owned window", new MenuItem.Callback() {
                @Override
                public void action() {
                    openOwnedWindow(window);
                }
            }));
            menu.add(MenuItem.Separator);
            menu.add(app.createMenuItem("Noise window (pixels upload 2D)", new MenuItem.Callback() {
                @Override
                public void action() {
                    openPixelsWindow(false);
                }
            }));
            menu.add(app.createMenuItem("Noise window (pixels upload 3D)", new MenuItem.Callback() {
                @Override
                public void action() {
                    openPixelsWindow(true);
                }
            }));
            menu.add(MenuItem.Separator);
            menu.add(app.createMenuItem("Minimize", new MenuItem.Callback() {
                @Override
                public void action() {
                    window.minimize(true);
                }
            }));
            menu.add(app.createMenuItem("Maximize", new MenuItem.Callback() {
                @Override
                public void action() {
                    window.maximize(true);
                }
            }));
            menu.add(MenuItem.Separator);
            menu.add(app.createMenuItem("Set Min Size", new MenuItem.Callback() {
                @Override
                public void action() {
                    int width = (int)(1.25f*window.getWidth());
                    int height = (int)(1.25f*window.getHeight());
                    window.setMinimumSize(width, height);
                }
            }));
            menu.add(app.createMenuItem("Set Max Size", new MenuItem.Callback() {
                @Override
                public void action() {
                    int width = (int)(0.75f*window.getWidth());
                    int height = (int)(0.75f*window.getHeight());
                    window.setMaximumSize(width, height);
                }
            }));
            menu.add(app.createMenuItem("Reset Min/Max Size", new MenuItem.Callback() {
                @Override
                public void action() {
                    window.setMinimumSize(Integer.MIN_VALUE, Integer.MIN_VALUE);
                    window.setMaximumSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
                }
            }));
            menu.add(MenuItem.Separator);
            menu.add(app.createMenuItem("Toggle child window", new MenuItem.Callback() {
                @Override
                public void action() {
                    toggleChildWindow(window.getNativeWindow());
                }
            }));
            menu.add(MenuItem.Separator);
            menu.add(app.createMenuItem("Toggle resizable flag", new MenuItem.Callback() {
                @Override
                public void action() {
                    window.setResizable(!window.isResizable());
                }
            }));
            
            menubar.add(menu);
        }
        
        void addFullscreenMenu(MenuBar menubar)
        {
            Application app = Application.GetApplication();
            Menu menu = app.createMenu("Fullscreen");
            menu.add(app.createMenuItem("Plain", new MenuItem.Callback() {
                @Override
                public void action() {
                    openFullscreen("Plain fullscreen window", false, false, false);
                }
            }));
            menu.add(app.createMenuItem("Animating", new MenuItem.Callback() {
                @Override
                public void action() {
                    openFullscreen("Animating fullscreen window", true, false, false);
                }
            }));
            menu.add(app.createMenuItem("Fixed ratio", new MenuItem.Callback() {
                @Override
                public void action() {
                    openFullscreen("Fixed ratio fullscreen window", false, true, false);
                }
            }));
            menu.add(app.createMenuItem("No cursor", new MenuItem.Callback() {
                @Override
                public void action() {
                    openFullscreen("No cursor fullscreen window", false, false, true);
                }
            }));

            menubar.add(menu);        }

        void addModalityMenu(MenuBar menubar)
        {
            Application app = Application.GetApplication();
            Menu menu = app.createMenu("Modality");
            menu.add(app.createMenuItem("Exclusive (proto 1)", new MenuItem.Callback() {
                @Override
                public void action() {
                    testExclusiveModality();
                }
            }));
            menu.add(app.createMenuItem("Dialog/Sheet (proto 1)", new MenuItem.Callback() {
                @Override
                public void action() {
                    testDialogModality();
                }
            }));
            menu.add(MenuItem.Separator);
            menu.add(app.createMenuItem("New application-modal window (proto 2)", new MenuItem.Callback() {
                @Override
                public void action() {
                    openAppModalWindow();
                }
            }));
            menu.add(MenuItem.Separator);
            menu.add(app.createMenuItem("Test invoke API", new MenuItem.Callback() {
                @Override
                public void action() {
                    testInvokeAPI();
                }
            }));
            
            menubar.add(menu);
        }

        void addCommonDialogsMenu(MenuBar menubar)
        {
            Application app = Application.GetApplication();
            Menu menu = app.createMenu("CommonDialogs");
            menu.add(app.createMenuItem("New FileChooser", new MenuItem.Callback() {
                @Override
                public void action() {
                    (new Thread() {
                        @Override
                        public void run() {
                            testFileChooser();
                        }
                    }).start();
                }
            }));
            menu.add(app.createMenuItem("New FolderChooser", new MenuItem.Callback() {
                @Override
                public void action() {
                    (new Thread() {
                        @Override
                            public void run() {
                                testFolderChooser();
                            }
                    }).start();
                }
            }));
            menubar.add(menu);
        }
        
        void addPasteMenu(MenuBar menubar)
        {
            final String string1 = "Hello plain World";
            final String string2 = "Hello custom World";
            Application app = Application.GetApplication();
            Menu menu = app.createMenu("Copy/Paste");
            menu.add(app.createMenuItem("Put \""+string1+"\" on clipboard as plain string", new MenuItem.Callback() {
                @Override
                public void action() {
                    ClipboardAssistance clipboard = new ClipboardAssistance(Clipboard.SYSTEM);
                    clipboard.emptyCache();
                    clipboard.flush();
                    clipboard.setData(Clipboard.TEXT_TYPE, string1);
                    clipboard.flush();
                    clipboard.close();
                }
            }));
            menu.add(app.createMenuItem("Put  \""+string2+"\" on clipboard as custom data", new MenuItem.Callback() {
                @Override
                public void action() {
                    ClipboardAssistance clipboard = new ClipboardAssistance(Clipboard.SYSTEM);
                    clipboard.emptyCache();
                    clipboard.flush();
                    clipboard.setData("glass/custom", string2);
                    clipboard.flush();
                    clipboard.close();
                }
            }));
            menu.add(app.createMenuItem("Put image on clipboard", new MenuItem.Callback() {
                @Override
                public void action() {
                    ClipboardAssistance clipboard = new ClipboardAssistance(Clipboard.SYSTEM);
                    clipboard.emptyCache();
                    clipboard.flush();
                    Pixels img = Application.GetApplication().createPixels(
                        ImageData.GLOBE_WIDTH,
                     ImageData.GLOBE_HEIGHT,
                     IntBuffer.wrap(ImageData.GLOBE_DATA));
                    clipboard.setData(Clipboard.RAW_IMAGE_TYPE, img);
                    clipboard.flush();
                    clipboard.close();
                }
            }));
            menu.add(app.createMenuItem("Get plain text from clipboard", new MenuItem.Callback() {
                @Override
                public void action() {
                    ClipboardAssistance clipboard = new ClipboardAssistance(Clipboard.SYSTEM);
                    String string = (String)clipboard.getData(Clipboard.TEXT_TYPE);
                    if (string != null) {
                        pastedText = string;
                        pastedLife = FPS;
                    } else {
                        pastedText = "[no text on clipboard]";
                        pastedLife = FPS;
                    }
                    clipboard.close();
                }
            }));
            menu.add(app.createMenuItem("Get data from clipboard (as String)", new MenuItem.Callback() {
                @Override
                public void action() {
                    ClipboardAssistance clipboard = new ClipboardAssistance(Clipboard.SYSTEM);
                    Object data = clipboard.getData("glass/custom");
                    String string = null;
                    if (data instanceof String)
                    {
                        string = (String)data;
                    }
                    else if(data instanceof ByteBuffer)
                    {
                        string = new String(((ByteBuffer)data).array());
                    }
                    if (data != null) {
                        pastedText = string;
                        pastedLife = FPS;
                    } else {
                        pastedText = "[no data on clipboard]";
                        pastedLife = FPS;
                    }
                    clipboard.close();
                }
            }));
            menubar.add(menu);
        }
    }
    
    class Noise {

        final boolean is3d;
        Timer timer = null;
        Window window = null;
        ByteBuffer data = null;
        Random random = null;
        
        Noise() {
            this(true);
        }
        Noise(final boolean is3d) {
            this.is3d = is3d;
        }

        void start() {
            Application app = Application.GetApplication();
            this.window = app.createWindow(null, Screen.getMainScreen(), Window.TITLED);
            this.window.setResizable(true);
            this.window.setEventHandler(new Window.EventHandler() {
                @Override
                public void handleWindowEvent(Window window, long time, int type) {
                    switch (type) {
                        case WindowEvent.CLOSE:
                            close();
                            break;
                        case WindowEvent.DESTROY:
                            stop();
                            break;
                    }
                }
            });
            
            Pen pen = null;
            if (this.is3d == true) {
                pen = new NullGraphics3D();
            } else {
                pen = new NullGraphics2D();
            }
            
            View view = app.createView(pen);
            this.window.setView(view);
            
            if (this.is3d == true) {
                this.window.setTitle("pixel upload 3D");
                this.window.setPosition(125, 125);
            } else {
                this.window.setTitle("pixel upload 2D");
                this.window.setPosition(125+512, 125);
            }
            this.window.setSize(500, 250);
            if (this.window.isDecorated() == false)
            {
                this.window.setUndecoratedMoveRectangle(this.window.getHeight());
            }
            this.window.setBackground(1.f, 0.f, 0.f);
            this.window.setVisible(true);
            
            this.random = new Random();
            
            this.timer = Application.GetApplication().createTimer(new Runnable() {
                @Override
                public void run() {
                    animate();
                }
            });
            this.timer.start(1000/30);
        }
        
        void update() {
            this.data.rewind(); {
                for (int i=0; i<this.data.capacity(); i+=4) {
                    int pixel = this.random.nextInt();
                    this.data.putInt(pixel | 0xff000000);
                }
            }
            this.data.rewind();
            
            Pixels pixels = Application.GetApplication().createPixels(this.window.getWidth(), this.window.getHeight(), this.data);
            this.window.getView().uploadPixels(pixels);
        }
        
        synchronized void animate() {
            if (this.window != null) {
                if ((this.data == null) || (this.window.getWidth()*this.window.getHeight()*4 != this.data.capacity())) {
                    this.data = ByteBuffer.allocateDirect(this.window.getWidth()*this.window.getHeight()*4);
                    this.data.order(ByteOrder.nativeOrder());
                    this.data.rewind();
                }
                update();
            }
        }
        
        void close() {
            this.window.close();
            stop();
        }

        synchronized void stop() {
            if (this.timer != null) {
                this.timer.stop();
            }
            this.timer = null;
            this.window = null;
        }
    }
    
    class Plasma extends Noise {
        public Plasma(final boolean is3d) {
            super(is3d);
        }
        @Override void start() {
            super.start();
        }
        @Override void update() {
            super.update();
            // todo
        }
    }
    
    class NullGraphics2D extends Graphics2D {
        @Override public void paint(long timeStamp, int width, int height) {
            set(Color.GREEN);
            fillRect(0, 0, width, height);
        }
    }
    class NullGraphics3D extends Graphics3D {
        @Override public void paint(long timeStamp, int width, int height) {
            set(Color.GREEN);
            fillRect(0, 0, width, height);
        }
    }
}
