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

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

    Это свободная программа: вы можете перераспространять ее и/или изменять
    ее на условиях Меньшей Стандартной общественной лицензии 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 class List extends ScrollingScreen implements Choice
{
    static final int ICON_WIDTH  = 32;
    static final int ICON_HEIGHT = 32;
    private static final int MARGIN = 3;
    private static final int ELEMENT_HEIGHT = ICON_HEIGHT + MARGIN * 2;
    private static final String DEFAULT_EMPTY_LABEL = "(пусто)";

    private static final int MARGIN1;
    private static final int MARGIN2;
    private static final int MARGIN3;
    private static final int CHECKBOX_TOP;
    private static final int CHECKBOX_WIDTH;
    private static final int CHECKBOX_HEIGHT;
    private static final Command[] MULTIPLE_ACTIONS;
    private static final Command CHECK_COMMAND;
    public static final Command SELECT_COMMAND;

    static {
        int i;
        int s = RasterCanvas.getGUIElementSize(15, 0, 0);
        int w = (i = (short) s) <= 0 ? 16 : i > ICON_WIDTH ? ICON_WIDTH : i;
        int h = (i = s >> 16) <= 0 ? 16 : i > ICON_HEIGHT ? ICON_HEIGHT : i;
        MARGIN1 = MARGIN * 2 + w;
        MARGIN2 = MARGIN + ICON_WIDTH + MARGIN1;
        MARGIN3 = MARGIN + ICON_WIDTH + MARGIN;
        CHECKBOX_TOP = (ELEMENT_HEIGHT - h) >> 1;
        CHECKBOX_WIDTH = w;
        CHECKBOX_HEIGHT = h;
        MULTIPLE_ACTIONS = new Command[] {
            new Command("Отметить все", Command.SCREEN, Integer.MIN_VALUE),
            new Command("Снять все", Command.SCREEN, Integer.MIN_VALUE),
            new Command("Переключить все", Command.SCREEN, Integer.MIN_VALUE)
        };
        CHECK_COMMAND = new Command("\u2713", Command.OK, 0);
        SELECT_COMMAND = createScreenCommand("Выбрать", Command.OK, Integer.MIN_VALUE);
    }

    private final byte type;
    private int fitPolicy;
    private int selected;
    private int checked;
    private int focused;
    private int pressed;
    private int count;
    private ChoiceElement[] elements;
    private Command selectCommand;
    private final String emptyLabel;
    private final ChoiceElementViewer monitor;

    public List(String title, int type) {
        this(title, null, false, null, type, null, new String[0], null);
    }

    public List(String title, int type, String[] elements, Image[] icons) {
        this(title, null, false, null, type, null, elements, icons);
    }

    public List(String title, Ticker ticker, boolean fullScreen, ScrollBarStyle style, int type, String emptyLabel, String[] elements, Image[] icons) {
        super(title, ticker, fullScreen, 0, elements == null ? 0 : ELEMENT_HEIGHT * elements.length, SCROLL_VERT, style);
        int count;
        String[] copyElements;
        ChoiceElement[] choiceElements;
        if(type != IMPLICIT && type != EXCLUSIVE && type != MULTIPLE)
        {
            throw new IllegalArgumentException("List: аргумент type имеет недопустимое значение.");
        }
        if(elements == null)
        {
            throw new NullPointerException("List: аргумент elements равен нулевой ссылке.");
        }
        Array.copy(elements, 0, copyElements = new String[count = elements.length], 0, count);
        if(Array.findf(copyElements, 0, null) < count)
        {
            throw new NullPointerException("List: аргумент elements содержит элементы, равные нулевой ссылке.");
        }
        if(icons != null && icons.length != count)
        {
            throw new IllegalArgumentException("List: длина аргумента icons отличается от длины аргумента elements.");
        }
        choiceElements = new ChoiceElement[getOptimalLength(count)];
        for(int i = count; i-- > 0; ) choiceElements[i] = new ChoiceElement(type == EXCLUSIVE && i == 0, copyElements[i], icons != null ? icons[i] : null);
        for(int len = type == MULTIPLE ? MULTIPLE_ACTIONS.length : 0, i = 0; i < len; i++) super.addCommand(MULTIPLE_ACTIONS[i]);
        if(count > 0) super.setDefaultCommand(type == IMPLICIT ? SELECT_COMMAND : CHECK_COMMAND);
        this.type = (byte) type;
        this.fitPolicy = TEXT_WRAP_DEFAULT;
        this.focused = -1;
        this.pressed = -1;
        this.count = count;
        this.elements = choiceElements;
        this.selectCommand = type == IMPLICIT ? SELECT_COMMAND : null;
        this.emptyLabel = emptyLabel == null ? DEFAULT_EMPTY_LABEL : emptyLabel;
        this.monitor = new ChoiceElementViewer();
    }

    public void removeCommand(Command command) {
        synchronized(monitor)
        {
            if(selectCommand == command) selectCommand = null;
        }
        super.removeCommand(command);
    }

    public void insert(int elementIndex, String text, Image icon) {
        int error;
        if(text == null)
        {
            throw new NullPointerException("List.insert: аргумент text равен нулевой ссылке.");
        }
        error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int t;
                int len;
                ChoiceElement[] list;
                if(elementIndex < 0 || elementIndex > (len = count))
                {
                    error = 1;
                    break label0;
                }
                list = elements;
                if(len == list.length) Array.copy(list, 0, list = elements = new ChoiceElement[(len << 1) + 1], 0, len);
                if(elementIndex < len) Array.copy(list, elementIndex, list, elementIndex + 1, len - elementIndex);
                list[elementIndex] = new ChoiceElement((t = type) == EXCLUSIVE && len == 0, text, icon);
                focused = -1;
                pressed = -1;
                count = ++len;
                if(len > 1)
                {
                    int activeIndex;
                    if(t == EXCLUSIVE && (activeIndex = checked) >= elementIndex) checked = activeIndex + 1;
                    if((activeIndex = selected) >= elementIndex) selected = activeIndex + 1;
                } else
                {
                    super.setDefaultCommand(t == IMPLICIT ? selectCommand : CHECK_COMMAND);
                }
                super.vert.setRange(ELEMENT_HEIGHT * len);
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("List.insert: аргумент elementIndex выходит из диапазона.");
        }
    }

    public void delete(int elementIndex) {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int len;
                ChoiceElement[] list;
                if(elementIndex < 0 || elementIndex > (len = count - 1))
                {
                    error = 1;
                    break label0;
                }
                list = elements;
                if(elementIndex < len) Array.copy(list, elementIndex + 1, list, elementIndex, len - elementIndex);
                list[len] = null;
                focused = -1;
                pressed = -1;
                count = len;
                if(len > 0)
                {
                    int activeIndex;
                    if(type == EXCLUSIVE)
                    {
                        if((activeIndex = checked) == elementIndex)
                        {
                            if(activeIndex >= len) activeIndex = len - 1;
                            list[activeIndex].selected = true;
                            checked = activeIndex;
                        }
                        else if(activeIndex > elementIndex)
                        {
                            checked = activeIndex - 1;
                        }
                    }
                    if((activeIndex = selected) == elementIndex)
                    {
                        if(activeIndex >= len) activeIndex = len - 1;
                        selected = activeIndex;
                    }
                    else if(activeIndex > elementIndex)
                    {
                        selected = activeIndex - 1;
                    }
                } else
                {
                    super.removeCommand(type == IMPLICIT ? selectCommand : CHECK_COMMAND);
                }
                super.vert.setRange(ELEMENT_HEIGHT * len);
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("List.delete: аргумент elementIndex выходит из диапазона.");
        }
    }

    public void deleteAll() {
        synchronized(monitor)
        {
            int len;
            if((len = count) > 0)
            {
                selected = 0;
                checked = 0;
                focused = -1;
                pressed = -1;
                count = 0;
                Array.fill(elements, 0, len, null);
                super.removeCommand(type == IMPLICIT ? selectCommand : CHECK_COMMAND);
                super.vert.setRange(0);
            }
        }
    }

    public void setFitPolicy(int fitPolicy) {
        if(fitPolicy != TEXT_WRAP_DEFAULT && fitPolicy != TEXT_WRAP_ON && fitPolicy != TEXT_WRAP_OFF)
        {
            throw new IllegalArgumentException("List.setFitPolicy: аргумент fitPolicy имеет недопустимое значение.");
        }
        this.fitPolicy = fitPolicy;
    }

    public void setSelectedIndex(int elementIndex, boolean selected) {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int activeIndex;
                ChoiceElement item;
                ChoiceElement[] list;
                if(elementIndex < 0 || elementIndex >= count)
                {
                    error = 1;
                    break label0;
                }
                list = elements;
                switch(type)
                {
                case EXCLUSIVE:
                    if(selected && (activeIndex = checked) != elementIndex)
                    {
                        list[activeIndex].selected = false;
                        list[elementIndex].selected = true;
                        checked = elementIndex;
                        requestPaint(CLIENT);
                    }
                    break;
                case MULTIPLE:
                    if((item = list[elementIndex]).selected != selected)
                    {
                        item.selected = selected;
                        requestPaint(CLIENT);
                    }
                    break;
                default:
                    if(selected) correctScrollBarPosition(this.selected = elementIndex);
                    break;
                }
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("List.setSelectedIndex: аргумент elementIndex выходит из диапазона.");
        }
    }

    public void setSelectedFlags(boolean[] src) {
        int error;
        if(src == null)
        {
            throw new NullPointerException("List.setSelectedFlags: аргумент src равен нулевой ссылке.");
        }
        error = 0;
        synchronized(monitor)
        {
            label0:
            {
                boolean needRepaint;
                int len;
                int activeIndex;
                int elementIndex;
                ChoiceElement[] list;
                if(src.length < (len = count))
                {
                    error = 1;
                    break label0;
                }
                list = elements;
                switch(type)
                {
                case EXCLUSIVE:
                    if((elementIndex = Array.findf(src, 0, true)) < len && (activeIndex = checked) != elementIndex)
                    {
                        list[activeIndex].selected = false;
                        list[elementIndex].selected = true;
                        checked = elementIndex;
                        requestPaint(CLIENT);
                    }
                    break;
                case MULTIPLE:
                    needRepaint = false;
                    for(int i = len; i-- > 0; )
                    {
                        boolean selected;
                        ChoiceElement item;
                        if((item = list[i]).selected != (selected = src[i]))
                        {
                            item.selected = selected;
                            needRepaint = true;
                        }
                    }
                    if(needRepaint) requestPaint(CLIENT);
                    break;
                default:
                    if((elementIndex = Array.findf(src, 0, true)) < len) correctScrollBarPosition(selected = elementIndex);
                    break;
                }
            }
        }
        if(error == 1)
        {
            throw new IllegalArgumentException("List.setSelectedFlags: аргумент src короче списка.");
        }
    }

    public void setFont(int elementIndex, Font font) {
        int error = 0;
        if(font == null) font = Font.getDefaultFont();
        synchronized(monitor)
        {
            label0:
            {
                if(elementIndex < 0 || elementIndex >= count)
                {
                    error = 1;
                    break label0;
                }
                elements[elementIndex].font = font;
                requestPaint(CLIENT);
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("List.setFont: аргумент elementIndex выходит из диапазона.");
        }
    }

    public void set(int elementIndex, String text, Image icon) {
        int error;
        if(text == null)
        {
            throw new NullPointerException("List.set: аргумент text равен нулевой ссылке.");
        }
        error = 0;
        synchronized(monitor)
        {
            label0:
            {
                ChoiceElement item;
                if(elementIndex < 0 || elementIndex >= count)
                {
                    error = 1;
                    break label0;
                }
                (item = elements[elementIndex]).text = text;
                item.icon = icon;
                requestPaint(CLIENT);
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("List.set: аргумент elementIndex выходит из диапазона.");
        }
    }

    public boolean isSelected(int elementIndex) {
        boolean result;
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                if(elementIndex < 0 || elementIndex >= count)
                {
                    error = 1;
                    result = false;
                    break label0;
                }
                result = type != IMPLICIT ? elements[elementIndex].selected : elementIndex == selected;
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("List.isSelected: аргумент elementIndex выходит из диапазона.");
        }
        return result;
    }

    public int append(String text, Image icon) {
        int result;
        if(text == null)
        {
            throw new NullPointerException("List.append: аргумент text равен нулевой ссылке.");
        }
        synchronized(monitor)
        {
            int t;
            int len;
            ChoiceElement[] list;
            if((result = len = count) == (list = elements).length) Array.copy(list, 0, list = elements = new ChoiceElement[(len << 1) + 1], 0, len);
            list[len] = new ChoiceElement((t = type) == EXCLUSIVE && len == 0, text, icon);
            focused = -1;
            pressed = -1;
            count = ++len;
            if(len == 1) super.setDefaultCommand(t == IMPLICIT ? selectCommand : CHECK_COMMAND);
            super.vert.setRange(ELEMENT_HEIGHT * len);
        }
        return result;
    }

    public int size() {
        return count;
    }

    public int getFitPolicy() {
        return fitPolicy;
    }

    public int getSelectedIndex() {
        switch(type)
        {
        case EXCLUSIVE:
            return count > 0 ? checked : -1;
        case MULTIPLE:
            return -1;
        default:
            return count > 0 ? selected : -1;
        }
    }

    public int getSelectedFlags(boolean[] dst) {
        int result;
        int error;
        if(dst == null)
        {
            throw new NullPointerException("List.getSelectedFlags: аргумент dst равен нулевой ссылке.");
        }
        error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int len;
                int dstlen;
                ChoiceElement[] list;
                if((dstlen = dst.length) < (len = count))
                {
                    error = 1;
                    result = 0;
                    break label0;
                }
                Array.fill(dst, 0, dstlen, false);
                list = elements;
                if(type != IMPLICIT)
                {
                    result = 0;
                    for(int i = len; i-- > 0; ) if(dst[i] = list[i].selected) result++;
                } else
                {
                    result = 1;
                    dst[selected] = true;
                }
            }
        }
        if(error == 1)
        {
            throw new IllegalArgumentException("List.getSelectedFlags: аргумент dst короче списка.");
        }
        return result;
    }

    public Font getFont(int elementIndex) {
        int error = 0;
        Font result;
        synchronized(monitor)
        {
            label0:
            {
                if(elementIndex < 0 || elementIndex >= count)
                {
                    error = 1;
                    result = null;
                    break label0;
                }
                result = elements[elementIndex].font;
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("List.getFont: аргумент elementIndex выходит из диапазона.");
        }
        return result;
    }

    public Image getImage(int elementIndex) {
        int error = 0;
        Image result;
        synchronized(monitor)
        {
            label0:
            {
                if(elementIndex < 0 || elementIndex >= count)
                {
                    error = 1;
                    result = null;
                    break label0;
                }
                result = elements[elementIndex].icon;
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("List.getImage: аргумент elementIndex выходит из диапазона.");
        }
        return result;
    }

    public String getString(int elementIndex) {
        int error = 0;
        String result;
        synchronized(monitor)
        {
            label0:
            {
                if(elementIndex < 0 || elementIndex >= count)
                {
                    error = 1;
                    result = null;
                    break label0;
                }
                result = elements[elementIndex].text;
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("List.getString: аргумент elementIndex выходит из диапазона.");
        }
        return result;
    }

    public void setSelectCommand(Command command) {
        if(type != IMPLICIT) return;
        synchronized(monitor)
        {
            if(this.selectCommand != command)
            {
                if(count > 0) super.setDefaultCommand(command);
                this.selectCommand = command;
            }
        }
    }

    void paint(ScreenGraphics render) {
        int type = this.type;
        int width = super.getApplicationWidth();
        int top1 = render.getClipY();
        int top2 = render.getClipHeight() + top1 - 1;
        int element1 = top1 / ELEMENT_HEIGHT;
        int element2 = top2 / ELEMENT_HEIGHT;
        render.setColor(RasterCanvas.getSystemColor(0x28));
        synchronized(monitor)
        {
            label0:
            {
                int sel;
                int press;
                int count;
                Font font;
                ChoiceElement[] list;
                if((count = this.count) <= 0)
                {
                    render.setFont(font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_SMALL));
                    render.drawString(MultilinedStringBuilder.truncate(emptyLabel, font, width), width >> 1, 0, Graphics.HCENTER | Graphics.TOP);
                    break label0;
                }
                sel = selected;
                list = elements;
                press = pressed;
                render.translate(0, ELEMENT_HEIGHT * element1);
                for(int i = element1; i <= element2 && i < count; render.translate(0, ELEMENT_HEIGHT), i++)
                {
                    boolean check;
                    int textTop;
                    Image icon;
                    String text;
                    ChoiceElement item;
                    font = (item = list[i]).font;
                    icon = item.icon;
                    text = item.text;
                    check = item.selected;
                    textTop = (ELEMENT_HEIGHT - font.getHeight()) >> 1;
                    if(i == sel) render.drawElement(1, 0, 0, 0, 0, width, ELEMENT_HEIGHT);
                    render.setFont(font);
                    if(type == IMPLICIT)
                    {
                        if(icon != null)
                        {
                            render.drawStretch(icon, MARGIN, MARGIN, ICON_WIDTH, ICON_HEIGHT, 0);
                            render.drawString(MultilinedStringBuilder.truncate(text, font, width - (MARGIN3 + MARGIN)), MARGIN3, textTop, 0);
                        } else
                        {
                            render.drawString(MultilinedStringBuilder.truncate(text, font, width - (MARGIN + MARGIN)), MARGIN, textTop, 0);
                        }
                    } else
                    {
                        render.drawElement(type == EXCLUSIVE ? 12 : 15, check ? 1 : 0, i == press ? 1 : 0, MARGIN, CHECKBOX_TOP, CHECKBOX_WIDTH, CHECKBOX_HEIGHT);
                        if(icon != null)
                        {
                            render.drawStretch(icon, MARGIN1, MARGIN, ICON_WIDTH, ICON_HEIGHT, 0);
                            render.drawString(MultilinedStringBuilder.truncate(text, font, width - (MARGIN2 + MARGIN)), MARGIN2, textTop, 0);
                        } else
                        {
                            render.drawString(MultilinedStringBuilder.truncate(text, font, width - (MARGIN1 + MARGIN)), MARGIN1, textTop, 0);
                        }
                    }
                }
            }
        }
    }

    void onCommandAction(Command command) {
        if(command == CHECK_COMMAND)
        {
            synchronized(monitor)
            {
                int activeIndex;
                switch(type)
                {
                case EXCLUSIVE:
                    if((activeIndex = selected) >= 0 && activeIndex < count)
                    {
                        ChoiceElement[] list;
                        (list = elements)[checked].selected = false;
                        list[checked = activeIndex].selected = true;
                        requestPaint(CLIENT);
                    }
                    break;
                case MULTIPLE:
                    if((activeIndex = selected) >= 0 && activeIndex < count)
                    {
                        ChoiceElement item;
                        (item = elements[activeIndex]).selected = !item.selected;
                        requestPaint(CLIENT);
                    }
                    break;
                }
            }
            return;
        }
        if(command == MULTIPLE_ACTIONS[0])
        {
            synchronized(monitor)
            {
                ChoiceElement[] list = elements;
                for(int i = count; i-- > 0; list[i].selected = true);
                requestPaint(CLIENT);
            }
            return;
        }
        if(command == MULTIPLE_ACTIONS[1])
        {
            synchronized(monitor)
            {
                ChoiceElement[] list = elements;
                for(int i = count; i-- > 0; list[i].selected = false);
                requestPaint(CLIENT);
            }
            return;
        }
        if(command == MULTIPLE_ACTIONS[2])
        {
            synchronized(monitor)
            {
                ChoiceElement item;
                ChoiceElement[] list = elements;
                for(int i = count; i-- > 0; (item = list[i]).selected = !item.selected);
                requestPaint(CLIENT);
            }
            return;
        }
        super.onCommandAction(command);
    }

    void onClientKeyboardEvent(KeyboardEvent event) {
        int key = event.getKey();
        int action = event.getAction();
        DeviceSettings settings = DeviceManager.getInstance().getSettings();
        if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_UP))
        {
            if(action == KeyboardEvent.ACTION_KEY_PRESSED || action == KeyboardEvent.ACTION_KEY_REPEATED)
            {
                synchronized(monitor)
                {
                    int len;
                    if((len = count) > 0) correctScrollBarPosition(selected = (selected + len - 1) % len);
                }
            }
            return;
        }
        if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_DOWN))
        {
            if(action == KeyboardEvent.ACTION_KEY_PRESSED || action == KeyboardEvent.ACTION_KEY_REPEATED)
            {
                synchronized(monitor)
                {
                    int len;
                    if((len = count) > 0) correctScrollBarPosition(selected = (selected + 1) % len);
                }
            }
            return;
        }
        if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_POUND))
        {
            if(action == KeyboardEvent.ACTION_KEY_PRESSED)
            {
                Font font;
                String text;
                Display parent;
                ChoiceElementViewer screen;
                synchronized(screen = monitor)
                {
                    if(count > 0)
                    {
                        ChoiceElement element;
                        text = (element = elements[selected]).text;
                        font = element.font;
                    } else
                    {
                        text = null;
                        font = null;
                    }
                }
                if(text != null && (parent = getParentDisplay()) != null)
                {
                    screen.getScrollBar().setPosition(0);
                    screen.setFont(font);
                    screen.setText(text);
                    parent.setCurrent(screen);
                }
            }
            return;
        }
        if(key == KeyboardEvent.KEY_SPACE) switch(action)
        {
        case KeyboardEvent.ACTION_KEY_PRESSED:
            if(type != IMPLICIT)
            {
                pressed = selected;
                requestPaint(CLIENT);
            }
            break;
        case KeyboardEvent.ACTION_KEY_RELEASED:
            switch(type)
            {
            case EXCLUSIVE:
                synchronized(monitor)
                {
                    int activeIndex;
                    ChoiceElement[] list;
                    if((activeIndex = pressed) >= 0 && activeIndex < count)
                    {
                        (list = elements)[checked].selected = false;
                        list[checked = activeIndex].selected = true;
                    }
                }
                pressed = -1;
                requestPaint(CLIENT);
                break;
            case MULTIPLE:
                synchronized(monitor)
                {
                    int activeIndex;
                    ChoiceElement item;
                    if((activeIndex = pressed) >= 0 && activeIndex < count) (item = elements[activeIndex]).selected = !item.selected;
                }
                pressed = -1;
                requestPaint(CLIENT);
                break;
            }
            break;
        }
    }

    void onClientPointerEvent(PointerEvent event) {
        int c;
        int f;
        int i;
        int y;
        switch(event.getAction())
        {
        case PointerEvent.ACTION_BUTTON_PRESSED:
        case PointerEvent.ACTION_POINTER_PRESSED:
            if(event.getButton() == PointerEvent.BUTTON_MAIN)
            {
                if((focused = f = getFocusedElement(event.getX(), y = event.getY())) >= 0)
                {
                    pressed = f;
                    synchronized(monitor)
                    {
                        c = count;
                        if((i = f) >= c) i = c - 1;
                        if(i < 0) i = 0;
                        correctScrollBarPosition(selected = i);
                    }
                    break;
                }
                if(f == -2) synchronized(monitor)
                {
                    c = count;
                    if((i = y / ELEMENT_HEIGHT) >= c) i = c - 1;
                    if(i < 0) i = 0;
                    if(selected != i) correctScrollBarPosition(selected = i);
                }
            }
            break;
        case PointerEvent.ACTION_POINTER_DRAGGED:
            if((f = focused) >= 0)
            {
                if(pressed != (pressed = getFocusedElement(event.getX(), event.getY()) == f ? f : -1)) requestPaint(CLIENT);
                break;
            }
            if(f == -2) synchronized(monitor)
            {
                c = count;
                if((i = event.getY() / ELEMENT_HEIGHT) >= c) i = c - 1;
                if(i < 0) i = 0;
                if(selected != i) correctScrollBarPosition(selected = i);
            }
            break;
        case PointerEvent.ACTION_POINTER_RELEASED:
        case PointerEvent.ACTION_BUTTON_RELEASED:
            if(event.getButton() == PointerEvent.BUTTON_MAIN && (f = focused) != -1)
            {
                int p = pressed;
                focused = -1;
                pressed = -1;
                synchronized(monitor)
                {
                    if(f == -2)
                    {
                        c = count;
                        if((i = event.getY() / ELEMENT_HEIGHT) >= c) i = c - 1;
                        if(i < 0) i = 0;
                        if(selected != i) correctScrollBarPosition(selected = i);
                    }
                    if(p >= 0)
                    {
                        switch(type)
                        {
                        case EXCLUSIVE:
                            if(p < count)
                            {
                                ChoiceElement[] list;
                                (list = elements)[checked].selected = false;
                                list[checked = p].selected = true;
                            }
                            break;
                        case MULTIPLE:
                            if(p < count)
                            {
                                ChoiceElement item;
                                (item = elements[p]).selected = !item.selected;
                            }
                            break;
                        }
                    }
                }
                requestPaint(CLIENT);
            }
            break;
        }
    }

    private void correctScrollBarPosition(int selected) {
        int pos;
        int page;
        int top1 = ELEMENT_HEIGHT * selected;
        int top2 = ELEMENT_HEIGHT + top1;
        ScrollBar scroll = super.vert;
        if(top1 < (pos = scroll.getPosition()))
        {
            scroll.setPosition(top1);
        }
        else if(top2 > pos + (page = scroll.getPage()))
        {
            scroll.setPosition(top2 - page);
        }
        else
        {
            requestPaint(CLIENT);
        }
    }

    private int getFocusedElement(int x, int y) {
        return x >= 0 && x < getApplicationWidth() && y >= 0 ? type != IMPLICIT && x < MARGIN1 ? y / ELEMENT_HEIGHT : -2 : -1;
    }
}
