Displayable.java

Переключить прокрутку окна
Загрузить этот исходный код

/*
    Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
    и других спецификаций для функционирования компактных приложений на языке
    Java (мидлетов) в среде программного обеспечения Малик Эмулятор.

    Copyright © 2016–2017, 2019–2023 Малик Разработчик

    Это свободная программа: вы можете перераспространять ее и/или изменять
    ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
    в каком она была опубликована Фондом свободного программного обеспечения;
    либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

    Эта программа распространяется в надежде, что она будет полезной,
    но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
    или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
    общественной лицензии GNU.

    Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
    вместе с этой программой. Если это не так, см.
    <https://www.gnu.org/licenses/>.
*/

package javax.microedition.lcdui;

import malik.emulator.application.*;
import malik.emulator.media.graphics.*;
import malik.emulator.microedition.*;
import malik.emulator.microedition.lcdui.*;

public abstract class Displayable extends Object
{
    private static class EmptyCommand extends Command
    {
        public EmptyCommand(String label, int type, int priority) {
            super(label, type, priority);
        }

        public int getPriority() {
            return 0;
        }

        public String getLabel() {
            return "";
        }
    }

    static final byte ALL = -1;
    static final byte NONE = 0;
    static final byte PANEL  = 0x01;
    static final byte CLIENT = 0x02;
    static final byte TITLE  = 0x04;
    static final byte TICKER = 0x08;
    private static final byte FULL_NO_LISTENER   = CLIENT;
    private static final byte FULL_WITH_LISTENER = CLIENT | PANEL;
    private static final byte NORMAL_NO_TIKER    = TITLE | CLIENT | PANEL;
    private static final byte NORMAL_WITH_TICKER = TICKER | TITLE | CLIENT | PANEL;
    private static final int MENU_DISTANCE         = 13; /* расстояние между панелью и меню */
    private static final int MENU_SPIN_WIDTH       = 16; /* ширина кнопки прокрутки */
    private static final int MENU_SPIN_HEIGHT      = 16; /* высота кнопки прокрутки */
    private static final int MENU_SPIN_MARGIN      =  3; /* расстояние между элементами меню и кнопками прокрутки */
    private static final int MENU_INNER_MARGIN     =  5; /* поля внутри меню */
    private static final int MENU_OUTER_MARGIN     =  8; /* отступы слева и справа от краёв экрана */
    private static final int MENU_MAXIMUM_COMMANDS =  7; /* максимальное количество команд в меню */

    private static final int[] COMMAND_TYPES_FOR_SOFT2;
    private static final Command[] PANEL_MENU;
    private static final Command MENU;
    private static final Command EXEC;
    private static final Command BACK;
    private static final Font MENU_FONT;

    static {
        Command exec = createScreenCommand("Выбрать", Command.OK, 0);
        Command back = createScreenCommand("Назад", Command.BACK, 0);
        Command menu = createScreenCommand("Меню", Command.SCREEN, 0);
        COMMAND_TYPES_FOR_SOFT2 = new int[] { Command.BACK, Command.CANCEL, Command.STOP, Command.EXIT };
        PANEL_MENU = new Command[] { null, exec, back };
        MENU = menu;
        EXEC = exec;
        BACK = back;
        MENU_FONT = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
    }

    static int getOptimalLength(int count) {
        int result;
        if(count <= 0) return 1;
        for(result = count; count > 1; result |= (count >>>= 1));
        return result;
    }

    static Command createDefaultCommand(String label, int type, int priority) {
        return new EmptyCommand(label, type, priority) {
            public int getCommandType() {
                return OK;
            }
        };
    }

    static Command createScreenCommand(String label, int type, int priority) {
        return new EmptyCommand(label, type, priority) {
            public int getCommandType() {
                return SCREEN;
            }
        };
    }

    private static int getMenuItemsCount(int length, int height) {
        int max;
        int result;
        if((result = length) > MENU_MAXIMUM_COMMANDS) result = MENU_MAXIMUM_COMMANDS;
        if(result > (max = (height - ((MENU_INNER_MARGIN << 1) + MENU_DISTANCE)) / MENU_FONT.getHeight())) result = max;
        return result;
    }

    private static Command[] copy(Command[] commands) {
        int len;
        Command[] result;
        Array.copy(commands, 0, result = new Command[len = commands.length], 0, len);
        return result;
    }

    private static Command find(Command.Enumeration enumeration, int type, Command ignore) {
        Command result = null;
        for(int priority = Integer.MAX_VALUE, i = enumeration.size(); i-- > 0; )
        {
            int p;
            Command command;
            if((command = enumeration.commandAt(i)) != null && command != ignore && command.type == type && (p = command.priority) <= priority)
            {
                result = command;
                priority = p;
            }
        }
        return result;
    }

    class Helper extends Display.OneShotAction implements Command.Enumeration
    {
        private boolean paintNoUpdate;
        private byte paintElements;
        private int paintLeft;
        private int paintTop;
        private int paintWidth;
        private int paintHeight;
        private int sizeWidth;
        private int sizeHeight;
        private int commandSize;
        private Command[] commandList;
        private Command commandDefault;
        private Image paintBuffer;

        public Helper() {
            this.commandList = new Command[7];
        }

        public int size() {
            return commandSize;
        }

        public Command commandAt(int index) {
            return index >= 0 && index < commandSize ? commandList[index] : null;
        }

        public Command defaultCommand() {
            return commandDefault;
        }

        public final synchronized void justPaint() {
            Displayable owner = Displayable.this;
            paintNoUpdate = true;
            paintElements = Displayable.ALL;
            setPaintRectangle(0, 0, owner.getApplicationWidth(), owner.getApplicationHeight());
        }

        public final void request(Display parent, byte elements) {
            synchronized(this)
            {
                byte prevElements = paintElements;
                paintElements = (byte) (elements | prevElements);
                if((elements & Displayable.CLIENT) != 0)
                {
                    Displayable owner = Displayable.this;
                    if((prevElements & Displayable.CLIENT) == 0)
                    {
                        setPaintRectangle(0, 0, owner.getApplicationWidth(), owner.getApplicationHeight());
                    } else
                    {
                        unionPaintRectangle(0, 0, owner.getApplicationWidth(), owner.getApplicationHeight());
                    }
                }
            }
            request(parent);
        }

        public final void request(Display parent, byte elements, int clipLeft, int clipTop, int clipWidth, int clipHeight, Image clipBuffer) {
            synchronized(this)
            {
                byte prevElements = paintElements;
                paintElements = (byte) (elements | prevElements);
                paintBuffer = clipBuffer;
                if((elements & Displayable.CLIENT) != 0)
                {
                    if((prevElements & Displayable.CLIENT) == 0)
                    {
                        setPaintRectangle(clipLeft, clipTop, clipWidth, clipHeight);
                    } else
                    {
                        unionPaintRectangle(clipLeft, clipTop, clipWidth, clipHeight);
                    }
                }
            }
            request(parent);
        }

        public final void addCommand(Command command) {
            int len;
            Command[] commands;
            if(command == null || command == commandDefault || Array.findf(commands = commandList, 0, command) < (len = commandSize)) return;
            if(len == commands.length) Array.copy(commands, 0, commands = commandList = new Command[(len << 1) + 1], 0, len);
            commands[len++] = command;
            commandSize = len;
        }

        public final void removeCommand(Command command) {
            int len;
            int index;
            Command[] commands;
            if(command == null) return;
            if(command == commandDefault)
            {
                commandDefault = null;
                return;
            }
            if((index = Array.findf(commands = commandList, 0, command)) > (len = commandSize - 1)) return;
            if(index < len) Array.copy(commands, index + 1, commands, index, len - index);
            commands[len] = null;
            commandSize = len;
        }

        public final void setDefaultCommand(Command command) {
            removeCommand(command);
            commandDefault = command;
        }

        protected void execute() {
            serviceSizeChanged();
            servicePaint();
        }

        protected final void servicePaint() {
            boolean update;
            byte elements;
            int clipLeft;
            int clipTop;
            int clipWidth;
            int clipHeight;
            Image clipBuffer;
            Display parent;
            Displayable owner;
            if((parent = (owner = Displayable.this).getParentDisplay()) == null) return;
            synchronized(this)
            {
                update = !paintNoUpdate;
                elements = paintElements;
                clipLeft = paintLeft;
                clipTop = paintTop;
                clipWidth = paintWidth;
                clipHeight = paintHeight;
                clipBuffer = paintBuffer;
                paintNoUpdate = false;
                paintElements = 0;
            }
            owner.eventPaint(parent.graphics(), elements, clipLeft, clipTop, clipWidth, clipHeight, clipBuffer);
            if(update) parent.update();
        }

        protected final void serviceSizeChanged() {
            int width;
            int height;
            Displayable owner;
            if((owner = Displayable.this).getParentDisplay() == null) return;
            width = owner.getApplicationWidth();
            height = owner.getApplicationHeight();
            if(sizeWidth == width && sizeHeight == height) return;
            try
            {
                owner.onSizeChanged(sizeWidth = width, sizeHeight = height);
            }
            catch(RuntimeException e)
            {
                e.printRealStackTrace();
            }
        }

        protected final int availableWidth() {
            return sizeWidth;
        }

        protected final int availableHeight() {
            return sizeHeight;
        }

        private void setPaintRectangle(int clipLeft, int clipTop, int clipWidth, int clipHeight) {
            paintLeft = clipLeft;
            paintTop = clipTop;
            paintWidth = clipWidth;
            paintHeight = clipHeight;
        }

        private void unionPaintRectangle(int clipLeft, int clipTop, int clipWidth, int clipHeight) {
            int tmp;
            int left = paintLeft;
            int top = paintTop;
            int right = left + paintWidth;
            int bottom = top + paintHeight;
            if(left > clipLeft) left = clipLeft;
            if(top > clipTop) top = clipTop;
            if(right < (tmp = clipLeft + clipWidth)) right = tmp;
            if(bottom < (tmp = clipTop + clipHeight)) bottom = tmp;
            paintLeft = left;
            paintTop = top;
            paintWidth = right - left;
            paintHeight = bottom - top;
        }
    }

    private boolean fullScreen;
    private boolean menuOpened;
    private int menuScroll;
    private int menuIndex;
    private int focused;
    private int pressed;
    private Command[] menuCommands;
    private final Command[] panelCommands;
    private CommandListener listener;
    private Ticker ticker;
    private String title;
    private Display parent;
    final Helper helper;

    Displayable(String title, Ticker ticker, boolean fullscreen) {
        this.fullScreen = fullscreen;
        this.menuCommands = new Command[MENU_MAXIMUM_COMMANDS];
        this.panelCommands = new Command[3];
        this.ticker = ticker;
        this.title = title;
        this.helper = createHelper();
    }

    public void addCommand(Command command) {
        if(command == null)
        {
            throw new NullPointerException("Displayable.addCommand: аргумент command равен нулевой ссылке.");
        }
        synchronized(Command.MONITOR)
        {
            helper.addCommand(command);
        }
        placeCommands();
        requestPaintAll();
    }

    public void removeCommand(Command command) {
        if(command == null) return;
        synchronized(Command.MONITOR)
        {
            helper.removeCommand(command);
        }
        placeCommands();
        requestPaintAll();
    }

    public void setCommandListener(CommandListener listener) {
        this.listener = listener;
        requestPaint((byte) (CLIENT | PANEL));
    }

    public void setTicker(Ticker ticker) {
        this.ticker = ticker;
        requestPaint((byte) (TICKER | TITLE | CLIENT));
    }

    public void setTitle(String title) {
        this.title = title;
        requestPaint(TITLE);
    }

    public boolean isShown() {
        return parent != null;
    }

    public int getWidth() {
        return getApplicationWidth();
    }

    public int getHeight() {
        return getApplicationHeight();
    }

    public Ticker getTicker() {
        return ticker;
    }

    public String getTitle() {
        return title;
    }

    protected void sizeChanged(int width, int height) {
    }

    abstract void paint(ScreenGraphics render);

    void paintBackground(ScreenGraphics render, int width, int height, byte visibleElements, byte drawingElements) {
        int elementTop = 0;
        int elementHeight;
        if((visibleElements & TICKER) != 0)
        {
            elementHeight = getTickerHeight();
            if((drawingElements & TICKER) != 0) render.drawElement(13, 0, 0, 0, elementTop, width, elementHeight);
            elementTop += elementHeight;
        }
        if((visibleElements & TITLE) != 0)
        {
            elementHeight = getTitleHeight();
            if((drawingElements & TITLE) != 0) render.drawElement(6, 1, 0, 0, elementTop, width, elementHeight);
            elementTop += elementHeight;
        }
        if((drawingElements &= CLIENT | PANEL) == NONE) return;
        if((visibleElements &= CLIENT | PANEL) == (CLIENT | PANEL))
        {
            if(drawingElements == CLIENT)
            {
                render.setClip(0, elementTop, width, height - elementTop - getPanelHeight());
            }
            else if(drawingElements == PANEL)
            {
                render.setClip(0, height - (elementHeight = getPanelHeight()), width, elementHeight);
            }
        }
        render.drawElement(6, 5, 0, 0, elementTop, width, height - elementTop);
    }

    void paintTicker(ScreenGraphics render, int width, int height, Ticker ticker) {
        render.setColor(RasterCanvas.getSystemColor(0x29));
        render.drawString(ticker.getDisplayedString(), ticker.getPosition(), 2, 0);
    }

    void paintTitle(ScreenGraphics render, int width, int height, String title) {
        render.setColor(RasterCanvas.getSystemColor(0x09));
        render.drawString(MultilinedStringBuilder.truncate(title, render.getFont(), width - 4), 2, 2, 0);
    }

    void paintClient(ScreenGraphics render, int width, int height, int clipLeft, int clipTop, int clipWidth, int clipHeight, Image clipBuffer) {
        int translatedX;
        int translatedY;
        int marginLeft = getMarginLeft();
        int marginTop = getMarginTop();
        int restrictedWidth = width - marginLeft - getMarginRight();
        int restrictedHeight = height - marginTop - getMarginBottom();
        render.translate(marginLeft, marginTop);
        render.restricts(translatedX = render.getTranslateX(), translatedY = render.getTranslateY(), restrictedWidth, restrictedHeight);
        render.setStartPoint(translatedX, translatedY);
        render.setClip(0, 0, restrictedWidth, restrictedHeight);
        if(!menuOpened)
        {
            render.clipRect(clipLeft, clipTop, clipWidth, clipHeight);
        }
        if(clipBuffer != null)
        {
            render.drawImage(clipBuffer, 0, 0, 0);
            return;
        }
        paint(render);
    }

    void paintPanel(ScreenGraphics render, int width, int height, Command[] commands, int pressedCommandIndex) {
        int p1 = width / 3;
        int p2 = width - p1;
        paintCommand(render, 0, 0, p1, height, commands[0], false, pressedCommandIndex == 0);
        paintCommand(render, p1, 0, p2 - p1, height, commands[1], true, pressedCommandIndex == 1);
        paintCommand(render, p2, 0, width - p2, height, commands[2], false, pressedCommandIndex == 2);
    }

    void paintCommand(ScreenGraphics render, int left, int top, int width, int height, Command command, boolean asDefault, boolean asPressed) {
        int x;
        int y;
        int ofs;
        Font font;
        if(command == null) return;
        ofs = asPressed ? 1 : asDefault ? 3 : 0;
        x = left + (width >> 1) + (asPressed ? 1 : 0);
        y = top + (asDefault ? 0 : 1) + (asPressed ? 3 : 2);
        render.setFont(font = asDefault ? getDefaultCommandFont() : getNormalCommandFont());
        render.setColor(RasterCanvas.getSystemColor(0x24 + ofs));
        render.drawElement(4, ofs, 0, left, top, width, height);
        render.drawString(command.getTruncatedLabel(width - 4, font), x, y, Graphics.HCENTER | Graphics.TOP);
    }

    void setFullScreenMode(boolean fullScreen) {
        this.fullScreen = fullScreen;
        requestPaintAll();
    }

    void setDefaultCommand(Command command) {
        synchronized(Command.MONITOR)
        {
            helper.setDefaultCommand(command);
        }
        placeCommands();
        requestPaintAll();
    }

    void onShow() {
    }

    void onHide() {
    }

    void onSizeChanged(int width, int height) {
        sizeChanged(width, height);
    }

    void onCommandAction(Command command) {
        CommandListener listener;
        if(command == null) return;
        if(command == MENU)
        {
            menuOpened = true;
            requestPaintAll();
            return;
        }
        if(command == BACK)
        {
            menuOpened = false;
            requestPaintAll();
            return;
        }
        if((listener = this.listener) != null)
        {
            if(command == EXEC)
            {
                menuOpened = false;
                requestPaintAll();
                if((command = menuCommands[menuIndex]) != null) requestCommandAction(command);
            } else
            {
                listener.commandAction(command, this);
            }
            return;
        }
        if(command == EXEC)
        {
            menuOpened = false;
            requestPaintAll();
        }
    }

    void onPaint(ScreenGraphics render, byte drawingElements, int clipLeft, int clipTop, int clipWidth, int clipHeight, Image clipBuffer) {
        byte visibleElements;
        int totalWidth;
        int totalHeight;
        int tickerHeight;
        int titleHeight;
        int clientHeight;
        int panelHeight;
        Display parent;
        String title = this.title;
        Ticker ticker = this.ticker;
        visibleElements = fullScreen ? listener == null ? FULL_NO_LISTENER : FULL_WITH_LISTENER : ticker == null ? NORMAL_NO_TIKER : NORMAL_WITH_TICKER;
        if((drawingElements &= visibleElements) == 0) return;
        totalWidth = (parent = this.parent).getWidth();
        totalHeight = parent.getHeight();
        tickerHeight = (visibleElements & TICKER) != 0 ? getTickerHeight() : 0;
        titleHeight = (visibleElements & TITLE) != 0 ? getTitleHeight() : 0;
        panelHeight = (visibleElements & PANEL) != 0 ? getPanelHeight() : 0;
        clientHeight = totalHeight - tickerHeight - titleHeight - panelHeight;
        try
        {
            render.reset();
            paintBackground(render, totalWidth, totalHeight, visibleElements, drawingElements);
        }
        catch(RuntimeException e)
        {
            e.printRealStackTrace();
        }
        if((drawingElements & TICKER) != 0 && ticker != null)
        {
            try
            {
                Font font = getTickerFont();
                render.reset();
                render.translate(0, 0);
                render.setClip(0, 0, totalWidth, tickerHeight);
                render.setFont(font);
                ticker.scroll(getTickerScroll(), totalWidth, font);
                paintTicker(render, totalWidth, tickerHeight, ticker);
                requestPaint(TICKER);
            }
            catch(RuntimeException e)
            {
                e.printRealStackTrace();
            }
        }
        if((drawingElements & TITLE) != 0 && title != null)
        {
            try
            {
                Font font = getTitleFont();
                render.reset();
                render.translate(0, tickerHeight);
                render.setClip(0, 0, totalWidth, titleHeight);
                render.setFont(font);
                paintTitle(render, totalWidth, titleHeight, title);
            }
            catch(RuntimeException e)
            {
                e.printRealStackTrace();
            }
        }
        if((drawingElements & PANEL) != 0)
        {
            try
            {
                Command[] commands;
                synchronized(Command.MONITOR)
                {
                    commands = copy(panelCommands);
                }
                render.reset();
                render.translate(0, totalHeight - panelHeight);
                render.setClip(0, 0, totalWidth, panelHeight);
                paintPanel(render, totalWidth, panelHeight, commands, menuOpened ? -1 : pressed - 1);
            }
            catch(RuntimeException e)
            {
                e.printRealStackTrace();
            }
        }
        if((drawingElements & CLIENT) != 0)
        {
            try
            {
                render.reset();
                render.translate(0, tickerHeight + titleHeight);
                render.setClip(0, 0, totalWidth, clientHeight);
                paintClient(render, totalWidth, clientHeight, clipLeft, clipTop, clipWidth, clipHeight, clipBuffer);
            }
            catch(RuntimeException e)
            {
                e.printRealStackTrace();
            }
        }
    }

    boolean onKeyboardEvent(KeyboardEvent event) {
        int key = event.getKey();
        int action = event.getAction();
        DeviceSettings settings = DeviceManager.getInstance().getSettings();
        if((getElements() & PANEL) == 0) return false;
        if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_SOFT1))
        {
            switch(action)
            {
            case KeyboardEvent.ACTION_KEY_PRESSED:
                if(pressed == 0 && panelCommands[0] != null)
                {
                    pressed = 1;
                    requestPaint(PANEL);
                }
                break;
            case KeyboardEvent.ACTION_KEY_RELEASED:
                switch(pressed)
                {
                case 0:
                    break;
                case 1:
                    pressed = 0;
                    requestPaint(PANEL);
                    requestCommandAction(panelCommands[0]);
                    break;
                default:
                    pressed = 0;
                    requestPaint(PANEL);
                    break;
                }
                break;
            }
            return true;
        }
        if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_SELECT))
        {
            switch(action)
            {
            case KeyboardEvent.ACTION_KEY_PRESSED:
                if(pressed == 0 && panelCommands[1] != null)
                {
                    pressed = 2;
                    requestPaint(PANEL);
                }
                break;
            case KeyboardEvent.ACTION_KEY_RELEASED:
                switch(pressed)
                {
                case 0:
                    break;
                case 2:
                    pressed = 0;
                    requestPaint(PANEL);
                    requestCommandAction(panelCommands[1]);
                    break;
                default:
                    pressed = 0;
                    requestPaint(PANEL);
                    break;
                }
                break;
            }
            return true;
        }
        if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_SOFT2))
        {
            switch(action)
            {
            case KeyboardEvent.ACTION_KEY_PRESSED:
                if(pressed == 0 && panelCommands[2] != null)
                {
                    pressed = 3;
                    requestPaint(PANEL);
                }
                break;
            case KeyboardEvent.ACTION_KEY_RELEASED:
                switch(pressed)
                {
                case 0:
                    break;
                case 3:
                    pressed = 0;
                    requestPaint(PANEL);
                    requestCommandAction(panelCommands[2]);
                    break;
                default:
                    pressed = 0;
                    requestPaint(PANEL);
                    break;
                }
                break;
            }
            return true;
        }
        return false;
    }

    boolean onPointerEvent(PointerEvent event) {
        int e;
        int f;
        switch(event.getAction())
        {
        case PointerEvent.ACTION_BUTTON_PRESSED:
        case PointerEvent.ACTION_POINTER_PRESSED:
            if(event.getButton() == PointerEvent.BUTTON_MAIN && (e = getScreenFocusedElement(event.getX(), event.getY())) > 0)
            {
                focused = pressed = e;
                requestPaint(PANEL);
                return true;
            }
            break;
        case PointerEvent.ACTION_POINTER_DRAGGED:
            if((f = focused) > 0)
            {
                if(pressed != (pressed = getScreenFocusedElement(event.getX(), event.getY()) != f ? 0 : f)) requestPaint(PANEL);
                return true;
            }
            break;
        case PointerEvent.ACTION_POINTER_RELEASED:
        case PointerEvent.ACTION_BUTTON_RELEASED:
            if(event.getButton() == PointerEvent.BUTTON_MAIN && focused > 0)
            {
                Command command;
                e = pressed;
                focused = pressed = 0;
                if(e > 0 && e <= 3 && (getElements() & PANEL) != 0 && (command = panelCommands[e - 1]) != null)
                {
                    requestPaint(PANEL);
                    requestCommandAction(command);
                }
                return true;
            }
            break;
        }
        return false;
    }

    boolean isFullScreenMode() {
        return fullScreen;
    }

    float getTickerScroll() {
        return 60.f / DeviceManager.getInstance().getSettings().getMaximumFrequency();
    }

    int getCommandIndexAt(int x, int y, int panelWidth, int panelHeight) {
        if(x >= 0 && x < panelWidth && y >= 0 && y < panelHeight)
        {
            int p1 = panelWidth / 3;
            int p2 = panelWidth - p1;
            return x < p1 ? 0 : x < p2 ? 1 : 2;
        }
        return -1;
    }

    int getMarginLeft() {
        return 0;
    }

    int getMarginTop() {
        return 0;
    }

    int getMarginRight() {
        return 0;
    }

    int getMarginBottom() {
        return 0;
    }

    int getTickerHeight() {
        return getTickerFont().getHeight() + 4;
    }

    int getTitleHeight() {
        return getTitleFont().getHeight() + 4;
    }

    int getPanelHeight() {
        return getDefaultCommandFont().getHeight() + 6;
    }

    Font getTickerFont() {
        return Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_SMALL);
    }

    Font getTitleFont() {
        return Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
    }

    Font getNormalCommandFont() {
        return Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
    }

    Font getDefaultCommandFont() {
        return Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_SMALL);
    }

    Displayable.Helper createHelper() {
        return this.new Helper();
    }

    final void eventShow(Display parent) {
        try
        {
            onShow();
        }
        catch(RuntimeException e)
        {
            e.printRealStackTrace();
        }
        this.parent = parent;
    }

    final void eventHide() {
        this.parent = null;
        try
        {
            onHide();
        }
        catch(RuntimeException e)
        {
            e.printRealStackTrace();
        }
    }

    final void eventKeyboard(KeyboardEvent event) {
        try
        {
            if(menuOpened)
            {
                onKeyboardEventMenu(event);
            } else
            {
                onKeyboardEvent(event);
            }
        }
        catch(RuntimeException e)
        {
            e.printRealStackTrace();
        }
    }

    final void eventPointer(PointerEvent event) {
        try
        {
            if(menuOpened)
            {
                onPointerEventMenu(event);
            } else
            {
                onPointerEvent(event);
            }
        }
        catch(RuntimeException e)
        {
            e.printRealStackTrace();
        }
    }

    final void eventPaint(ScreenGraphics render, byte elements, int clipLeft, int clipTop, int clipWidth, int clipHeight, Image clipBuffer) {
        try
        {
            Display parent = this.parent;
            if(menuOpened & updateMenu())
            {
                int panelHeight = getPanelHeight();
                int screenWidth = parent.getWidth();
                int screenHeight = parent.getHeight();
                try
                {
                    onPaint(render, ALL, clipLeft, clipTop, clipWidth, clipHeight, clipBuffer);
                }
                catch(RuntimeException e)
                {
                    e.printRealStackTrace();
                }
                render.reset();
                render.setARGBColor(0xc0000000);
                render.fillRect(0, 0, screenWidth, screenHeight);
                render.reset();
                render.translate(0, screenHeight - panelHeight);
                render.setClip(0, 0, screenWidth, panelHeight);
                paintPanel(render, screenWidth, panelHeight, copy(PANEL_MENU), pressed - 1);
                render.reset();
                render.setClip(0, 0, screenWidth, screenHeight -= panelHeight);
                paintMenu(render, screenWidth, screenHeight);
            } else
            {
                onPaint(render, elements, clipLeft, clipTop, clipWidth, clipHeight, clipBuffer);
            }
        }
        catch(RuntimeException e)
        {
            e.printRealStackTrace();
        }
    }

    final void requestAction(Runnable action) {
        Display parent;
        if(action != null && (parent = this.parent) != null) parent.callSerially(action);
    }

    final void requestCommandAction(final Command command) {
        requestAction(command == null ? null : new Runnable() {
            public void run() {
                (Displayable.this).onCommandAction(command);
            }
        });
    }

    final void requestPaint(byte elements) {
        Display parent;
        Displayable active;
        if(elements != 0 && (parent = this.parent) != null && (active = parent.getActiveForeground()) != null) active.helper.request(parent, elements);
    }

    final void requestPaintAll() {
        Display parent;
        Displayable active;
        if((parent = this.parent) != null && (active = parent.getActiveForeground()) != null) active.helper.request(parent, ALL);
    }

    final void requestPaintClient(int clipLeft, int clipTop, int clipWidth, int clipHeight) {
        Display parent;
        Displayable active;
        if((parent = this.parent) != null && (active = parent.getActiveForeground()) != null) active.helper.request(parent, CLIENT, clipLeft, clipTop, clipWidth, clipHeight, null);
    }

    final void requestPaintClient(int clipLeft, int clipTop, int clipWidth, int clipHeight, Image clipBuffer) {
        Display parent;
        Displayable active;
        if((parent = this.parent) != null && (active = parent.getActiveForeground()) != null) active.helper.request(parent, CLIENT, clipLeft, clipTop, clipWidth, clipHeight, clipBuffer);
    }

    final void servicePaint() {
        Display parent;
        Displayable active;
        if((parent = this.parent) != null && (active = parent.getActiveForeground()) != null) active.helper.service();
    }

    final void placeCommands() {
        synchronized(Command.MONITOR)
        {
            int len;
            int placed;
            int[] types;
            Command[] c;
            Command left;
            Command right;
            Command center;
            Command.Enumeration list;
            placed = 1;
            if((center = (list = helper).defaultCommand()) == null && (center = find(list, Command.OK, null)) != null) placed++;
            right = null;
            len = (types = COMMAND_TYPES_FOR_SOFT2).length;
            for(int i = 0; i < len; i++) if((right = find(list, types[i], center)) != null)
            {
                placed++;
                break;
            }
            left = null;
            if((len = list.size()) <= placed)
            {
                Array.fill(c = menuCommands, 0, c.length, null);
                for(int i = len == placed ? len : 0; i-- > 0; )
                {
                    Command command;
                    if((command = list.commandAt(i)) != null && command != center && command != right)
                    {
                        left = command;
                        break;
                    }
                }
            } else
            {
                int count;
                if((c = menuCommands).length < (count = len - placed + 1)) c = menuCommands = new Command[getOptimalLength(count)];
                left = MENU;
                {
                    int i = 0;
                    for(int index = 0; index < len; index++)
                    {
                        Command command;
                        if((command = list.commandAt(index)) != null && command != center && command != right) c[i++] = command;
                    }
                    Array.fill(c, count = i, c.length - count, null);
                }
                for(int lim = count - 1, i = 0; i < lim; i++)
                {
                    int j;
                    int jpriority;
                    Command jcommand;
                    jpriority = (jcommand = c[j = i]).priority;
                    for(int k = i + 1; k < count; k++)
                    {
                        int kpriority;
                        Command kcommand;
                        if((kpriority = (kcommand = c[k]).priority) < jpriority)
                        {
                            j = k;
                            jpriority = kpriority;
                            jcommand = kcommand;
                        }
                    }
                    if(i < j)
                    {
                        Array.copy(c, i, c, i + 1, j - i);
                        c[i] = jcommand;
                    }
                }
            }
            (c = panelCommands)[0] = left;
            c[1] = center;
            c[2] = right;
        }
    }

    final boolean isMenuOpened() {
        return menuOpened;
    }

    final byte getElements() {
        return fullScreen ? listener == null ? FULL_NO_LISTENER : FULL_WITH_LISTENER : ticker == null ? NORMAL_NO_TIKER : NORMAL_WITH_TICKER;
    }

    final int getClientHeight() {
        byte elements = getElements();
        int result = getTotalHeight();
        if((elements & TICKER) != 0) result -= getTickerHeight();
        if((elements & TITLE) != 0) result -= getTitleHeight();
        if((elements & PANEL) != 0) result -= getPanelHeight();
        return result;
    }

    final int getTotalWidth() {
        Display parent;
        if((parent = this.parent) == null) parent = DeviceManager.getInstance().getCurrentDisplay();
        return parent.getWidth();
    }

    final int getTotalHeight() {
        Display parent;
        if((parent = this.parent) == null) parent = DeviceManager.getInstance().getCurrentDisplay();
        return parent.getHeight();
    }

    final int getApplicationWidth() {
        return getTotalWidth() - getMarginLeft() - getMarginRight();
    }

    final int getApplicationHeight() {
        return getClientHeight() - getMarginTop() - getMarginBottom();
    }

    final Display getParentDisplay() {
        return parent;
    }

    private void paintMenu(Graphics render, int width, int height) {
        boolean upEnabled;
        boolean downEnabled;
        int itemHeight;
        int itemsWidth;
        int itemsHeight;
        int menuIndex;
        int menuCount;
        int menuScroll;
        int menuLength;
        int menuPressed;
        Command[] commands;
        synchronized(Command.MONITOR)
        {
            commands = copy(menuCommands);
        }
        menuIndex = this.menuIndex;
        menuScroll = this.menuScroll;
        menuLength = Array.findf(commands, 0, null);
        itemsWidth = width - (((MENU_OUTER_MARGIN + MENU_INNER_MARGIN) << 1) + MENU_SPIN_MARGIN + MENU_SPIN_WIDTH);
        itemsHeight = (menuCount = getMenuItemsCount(menuLength, height)) * (itemHeight = MENU_FONT.getHeight());
        render.translate(MENU_OUTER_MARGIN, height - itemsHeight - ((MENU_INNER_MARGIN << 1) + MENU_DISTANCE));
        render.setClip(0, 0, width -= (MENU_OUTER_MARGIN << 1), height = itemsHeight + (MENU_INNER_MARGIN << 1));
        render.setColor(RasterCanvas.getSystemColor(0x04));
        render.fillRect(0, 0, width, height);
        render.translate(MENU_INNER_MARGIN, MENU_INNER_MARGIN);
        render.setClip(0, 0, itemsWidth, itemsHeight);
        render.setFont(MENU_FONT);
        for(int itemTop = 0, itemIndex = menuScroll, i = menuCount; i-- > 0; itemTop += itemHeight, itemIndex++)
        {
            if(itemIndex != menuIndex)
            {
                render.setColor(RasterCanvas.getSystemColor(0x07));
            } else
            {
                render.setColor(RasterCanvas.getSystemColor(0x0d));
                render.fillRect(0, itemTop, itemsWidth, itemHeight);
                render.setColor(RasterCanvas.getSystemColor(0x0e));
            }
            render.drawString(commands[itemIndex].getTruncatedLabel(itemsWidth - 4, MENU_FONT), 2, itemTop, 0);
        }
        menuPressed = pressed;
        render.translate(itemsWidth + MENU_SPIN_MARGIN, 0);
        render.setClip(0, 0, MENU_SPIN_WIDTH, itemsHeight);
        if((upEnabled = menuScroll > 0) | (downEnabled = menuScroll + menuCount < menuLength))
        {
            int menuFocused = focused;
            render.drawElement(14, 0, menuPressed != 4 ? upEnabled || menuFocused == 4 ? 0 : 2 : 1, 0, itemsHeight - (MENU_SPIN_HEIGHT << 1), MENU_SPIN_WIDTH, MENU_SPIN_HEIGHT);
            render.drawElement(14, 1, menuPressed != 5 ? downEnabled || menuFocused == 5 ? 0 : 2 : 1, 0, itemsHeight - MENU_SPIN_HEIGHT, MENU_SPIN_WIDTH, MENU_SPIN_HEIGHT);
        }
    }

    private void onKeyboardEventMenu(KeyboardEvent event) {
        boolean down;
        int key = event.getKey();
        int action = event.getAction();
        DeviceSettings settings = DeviceManager.getInstance().getSettings();
        if(action == KeyboardEvent.ACTION_KEY_PRESSED && ((down = key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_DOWN)) || key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_UP)))
        {
            int menuIndex = this.menuIndex;
            int menuScroll = this.menuScroll;
            int menuLength = Array.findf(menuCommands, 0, null);
            int count = getMenuItemsCount(menuLength, getTotalHeight() - getPanelHeight());
            menuIndex = (down ? menuIndex + 1 : menuIndex + menuLength - 1) % menuLength;
            if(menuIndex < menuScroll) menuScroll = menuIndex;
            if(menuIndex >= menuScroll + count) menuScroll = menuIndex - count + 1;
            if(menuScroll + count > menuLength) menuScroll = menuLength - count;
            if(menuScroll < 0) menuScroll = 0;
            this.menuScroll = menuScroll;
            this.menuIndex = menuIndex;
            requestPaintAll();
            return;
        }
        if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_SOFT1))
        {
            switch(action)
            {
            case KeyboardEvent.ACTION_KEY_PRESSED:
                if(pressed == 0 && PANEL_MENU[0] != null)
                {
                    pressed = 1;
                    requestPaint(PANEL);
                }
                break;
            case KeyboardEvent.ACTION_KEY_RELEASED:
                switch(pressed)
                {
                case 0:
                    break;
                case 1:
                    pressed = 0;
                    requestPaint(PANEL);
                    requestCommandAction(PANEL_MENU[0]);
                    break;
                default:
                    pressed = 0;
                    requestPaint(PANEL);
                    break;
                }
                break;
            }
            return;
        }
        if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_SELECT))
        {
            switch(action)
            {
            case KeyboardEvent.ACTION_KEY_PRESSED:
                if(pressed == 0 && PANEL_MENU[1] != null)
                {
                    pressed = 2;
                    requestPaint(PANEL);
                }
                break;
            case KeyboardEvent.ACTION_KEY_RELEASED:
                switch(pressed)
                {
                case 0:
                    break;
                case 2:
                    pressed = 0;
                    requestPaint(PANEL);
                    requestCommandAction(PANEL_MENU[1]);
                    break;
                default:
                    pressed = 0;
                    requestPaint(PANEL);
                    break;
                }
                break;
            }
            return;
        }
        if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_SOFT2))
        {
            switch(action)
            {
            case KeyboardEvent.ACTION_KEY_PRESSED:
                if(pressed == 0 && PANEL_MENU[2] != null)
                {
                    pressed = 3;
                    requestPaint(PANEL);
                }
                break;
            case KeyboardEvent.ACTION_KEY_RELEASED:
                switch(pressed)
                {
                case 0:
                    break;
                case 3:
                    pressed = 0;
                    requestPaint(PANEL);
                    requestCommandAction(PANEL_MENU[2]);
                    break;
                default:
                    pressed = 0;
                    requestPaint(PANEL);
                    break;
                }
                break;
            }
            return;
        }
    }

    private void onPointerEventMenu(PointerEvent event) {
        int e;
        int f;
        switch(event.getAction())
        {
        case PointerEvent.ACTION_BUTTON_PRESSED:
        case PointerEvent.ACTION_POINTER_PRESSED:
            if(event.getButton() == PointerEvent.BUTTON_MAIN)
            {
                if((e = getMenuFocusedElement(event.getX(), event.getY())) > 0 && e <= 5)
                {
                    int menuScroll = this.menuScroll;
                    int menuLength = Array.findf(menuCommands, 0, null);
                    int menuCount = getMenuPage(menuLength);
                    switch(e)
                    {
                    case 4:
                        if(menuScroll > 0)
                        {
                            this.menuScroll = menuScroll - 1;
                            focused = pressed = 4;
                            requestPaintAll();
                            break;
                        }
                        focused = 9;
                        break;
                    case 5:
                        if(menuScroll + menuCount < menuLength)
                        {
                            this.menuScroll = menuScroll + 1;
                            focused = pressed = 5;
                            requestPaintAll();
                            break;
                        }
                        focused = 9;
                        break;
                    default:
                        focused = pressed = e;
                        requestPaintAll();
                        break;
                    }
                    break;
                }
                if(e == 9)
                {
                    focused = 9;
                    break;
                }
                if(e >= 10)
                {
                    focused = 10;
                    if(menuIndex != (menuIndex = e - 10)) requestPaintAll();
                }
            }
            break;
        case PointerEvent.ACTION_POINTER_DRAGGED:
            if((f = focused) > 0)
            {
                e = getMenuFocusedElement(event.getX(), event.getY());
                if(f > 0 && f <= 5 && pressed != (pressed = e != f ? 0 : f) || f == 10 && e >= 10 && menuIndex != (menuIndex = e - 10)) requestPaintAll();
            }
            break;
        case PointerEvent.ACTION_POINTER_RELEASED:
        case PointerEvent.ACTION_BUTTON_RELEASED:
            if(event.getButton() == PointerEvent.BUTTON_MAIN)
            {
                Command command;
                e = pressed;
                f = focused;
                focused = pressed = 0;
                if(f == 0)
                {
                    menuOpened = false;
                    requestPaintAll();
                    break;
                }
                if(e > 0 && e <= 3 && (command = PANEL_MENU[e - 1]) != null)
                {
                    requestPaintAll();
                    requestCommandAction(command);
                    break;
                }
                requestPaintAll();
            }
            break;
        }
    }

    private boolean updateMenu() {
        int count;
        int menuIndex;
        int menuScroll;
        int menuLength;
        if((menuLength = Array.findf(menuCommands, 0, null)) <= 0) return menuOpened = false;
        count = getMenuPage(menuLength);
        menuIndex = this.menuIndex;
        menuScroll = this.menuScroll;
        if(menuIndex < 0) menuIndex = 0;
        if(menuIndex >= menuLength) menuIndex = menuLength - 1;
        if(menuScroll + count > menuLength) menuScroll = menuLength - count;
        if(menuScroll < 0) menuScroll = 0;
        this.menuScroll = menuScroll;
        this.menuIndex = menuIndex;
        return true;
    }

    private int getScreenFocusedElement(int x, int y) {
        int b;
        int h;
        int w;
        int t;
        if((getElements() & PANEL) == 0) return 0;
        h = getPanelHeight();
        w = getTotalWidth();
        t = getTotalHeight() - h;
        return x >= 0 && x < w && y >= t && y < t + h && (b = getCommandIndexAt(x, y - t, w, h)) >= 0 && b < 3 && panelCommands[b] != null ? b + 1 : 0;
    }

    private int getMenuFocusedElement(int x, int y) {
        int w = getTotalWidth();
        int h = getPanelHeight();
        int l;
        int t = getTotalHeight() - h;
        int itemHeight;
        int itemsWidth;
        int itemsHeight;
        int menuScroll;
        int menuLength;
        if(x >= 0 && x < w && y >= t && y < t + h)
        {
            int b;
            return (b = getCommandIndexAt(x, y - t, w, h)) >= 0 && b < 3 && PANEL_MENU[b] != null ? b + 1 : 0;
        }
        h = t;
        menuScroll = this.menuScroll;
        menuLength = Array.findf(menuCommands, 0, null);
        itemsWidth = w - (((MENU_OUTER_MARGIN + MENU_INNER_MARGIN) << 1) + MENU_SPIN_MARGIN + MENU_SPIN_WIDTH);
        itemsHeight = getMenuItemsCount(menuLength, h) * (itemHeight = MENU_FONT.getHeight());
        l = MENU_OUTER_MARGIN;
        t -= itemsHeight + ((MENU_INNER_MARGIN << 1) + MENU_DISTANCE);
        w -= MENU_OUTER_MARGIN << 1;
        h = itemsHeight + (MENU_INNER_MARGIN << 1);
        if(x < l || x >= l + w || y < t || y >= t + h) return 0;
        l += MENU_INNER_MARGIN;
        t += MENU_INNER_MARGIN;
        w = itemsWidth;
        h = itemsHeight;
        if(x >= l && x < l + w && y >= t && y < t + h) return 10 + menuScroll + (y - t) / itemHeight;
        l += w + MENU_SPIN_MARGIN;
        t += h - (MENU_SPIN_HEIGHT << 1);
        w = MENU_SPIN_WIDTH;
        h = MENU_SPIN_HEIGHT;
        if(x >= l && x < l + w && y >= t && y < t + h) return 4;
        t += MENU_SPIN_HEIGHT;
        if(x >= l && x < l + w && y >= t && y < t + h) return 5;
        return 9;
    }

    private int getMenuPage(int menuLength) {
        return getMenuItemsCount(menuLength, getTotalHeight() - getPanelHeight());
    }
}