ChoiceGroup.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 class ChoiceGroup extends InteractiveItem implements Choice
{
    static final int ICON_WIDTH = 24;
    static final int ICON_HEIGHT = 24;
    private static final int MARGIN = 3;
    private static final int POPUP_ARROW_SIZE = 9;
    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 ICON_TOP;
    private static final int CHECKBOX_TOP;
    private static final int CHECKBOX_WIDTH;
    private static final int CHECKBOX_HEIGHT;
    private static final int ELEMENT_HEIGHT;
    private static final Command[] MULTIPLE_ACTIONS;
    private static final Command CHECK_COMMAND;
    private 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;
        int height1 = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM).getHeight();
        int height2 = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL).getHeight();
        int height3 = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE).getHeight();
        int heightMax = Math.max(Math.max(height1, height2), Math.max(height3, ICON_HEIGHT)) + 2 * MARGIN;
        MARGIN1 = MARGIN * 2 + w;
        MARGIN2 = MARGIN + ICON_WIDTH + MARGIN1;
        MARGIN3 = MARGIN + ICON_WIDTH + MARGIN;
        ICON_TOP = (heightMax - ICON_HEIGHT) >> 1;
        CHECKBOX_TOP = (heightMax - h) >> 1;
        CHECKBOX_WIDTH = w;
        CHECKBOX_HEIGHT = h;
        ELEMENT_HEIGHT = heightMax;
        MULTIPLE_ACTIONS = new Command[] {
            new Command("Отметить все", Command.ITEM, Integer.MAX_VALUE),
            new Command("Снять все", Command.ITEM, Integer.MAX_VALUE),
            new Command("Переключить все", Command.ITEM, Integer.MAX_VALUE)
        };
        CHECK_COMMAND = new Command("\u2713", Command.OK, 0);
        SELECT_COMMAND = new Command("Выбрать", Command.OK, 0);
    }

    private final byte type;
    private int listWidth;
    private int listHeight;
    private int fitPolicy;
    private int selected;
    private int checked;
    private int focused;
    private int pressed;
    private int count;
    private ChoiceElement[] elements;
    private final ChoiceList popupMenu;
    private final String emptyLabel;
    private final ChoiceElementViewer monitor;

    public ChoiceGroup(String label, int type) {
        this(label, type, null, new String[0], null);
    }

    public ChoiceGroup(String label, int type, String[] elements, Image[] icons) {
        this(label, type, null, elements, icons);
    }

    public ChoiceGroup(String label, int type, String emptyLabel, String[] elements, Image[] icons) {
        super(label);
        int count;
        String[] copyElements;
        ChoiceElement[] choiceElements;
        if(type != POPUP && type != EXCLUSIVE && type != MULTIPLE)
        {
            throw new IllegalArgumentException("ChoiceGroup: аргумент type имеет недопустимое значение.");
        }
        if(elements == null)
        {
            throw new NullPointerException("ChoiceGroup: аргумент elements равен нулевой ссылке.");
        }
        Array.copy(elements, 0, copyElements = new String[count = elements.length], 0, count);
        if(Array.findf(copyElements, 0, null) < count)
        {
            throw new NullPointerException("ChoiceGroup: аргумент elements содержит элементы, равные нулевой ссылке.");
        }
        if(icons != null && icons.length != count)
        {
            throw new IllegalArgumentException("ChoiceGroup: длина аргумента icons отличается от длины аргумента elements.");
        }
        choiceElements = new ChoiceElement[Displayable.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 == POPUP ? 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.popupMenu = type == POPUP ? createPopupMenu() : null;
        this.emptyLabel = emptyLabel == null ? DEFAULT_EMPTY_LABEL : emptyLabel;
        this.monitor = new ChoiceElementViewer();
    }

    public void insert(int elementIndex, String text, Image icon) {
        int error;
        if(text == null)
        {
            throw new NullPointerException("ChoiceGroup.insert: аргумент text равен нулевой ссылке.");
        }
        error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int t;
                int len;
                ChoiceElement[] list;
                ChoiceList menu;
                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 == POPUP ? SELECT_COMMAND : CHECK_COMMAND);
                }
                if(t != POPUP)
                {
                    requestInvalidate();
                }
                else if((menu = popupMenu).isShown())
                {
                    menu.insert(elementIndex, text, icon);
                }
                else if(len <= 1)
                {
                    requestPaint();
                }
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("ChoiceGroup.insert: аргумент elementIndex выходит из диапазона.");
        }
    }

    public void delete(int elementIndex) {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                boolean needRepaint;
                int t;
                int len;
                ChoiceElement[] list;
                ChoiceList menu;
                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;
                    needRepaint = false;
                    if((t = 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;
                        needRepaint = true;
                        selected = activeIndex;
                    }
                    else if(activeIndex > elementIndex)
                    {
                        selected = activeIndex - 1;
                    }
                } else
                {
                    needRepaint = true;
                    super.removeCommand((t = type) == POPUP ? SELECT_COMMAND : CHECK_COMMAND);
                }
                if(t != POPUP)
                {
                    requestInvalidate();
                }
                else if((menu = popupMenu).isShown())
                {
                    menu.delete(elementIndex);
                }
                else if(needRepaint)
                {
                    requestPaint();
                }
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("ChoiceGroup.delete: аргумент elementIndex выходит из диапазона.");
        }
    }

    public void deleteAll() {
        synchronized(monitor)
        {
            int len;
            if((len = count) > 0)
            {
                int t;
                ChoiceList menu;
                selected = 0;
                checked = 0;
                focused = -1;
                pressed = -1;
                count = 0;
                Array.fill(elements, 0, len, null);
                super.removeCommand((t = type) == POPUP ? SELECT_COMMAND : CHECK_COMMAND);
                if(t != POPUP)
                {
                    requestInvalidate();
                }
                else if((menu = popupMenu).isShown())
                {
                    menu.deleteAll();
                }
                else
                {
                    requestPaint();
                }
            }
        }
    }

    public void setFitPolicy(int fitPolicy) {
        if(fitPolicy != TEXT_WRAP_DEFAULT && fitPolicy != TEXT_WRAP_ON && fitPolicy != TEXT_WRAP_OFF)
        {
            throw new IllegalArgumentException("ChoiceGroup.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();
                    }
                    break;
                case MULTIPLE:
                    if((item = list[elementIndex]).selected != selected)
                    {
                        item.selected = selected;
                        requestPaint();
                    }
                    break;
                default:
                    if(selected && this.selected != elementIndex)
                    {
                        ChoiceList menu;
                        this.selected = elementIndex;
                        if((menu = popupMenu).isShown())
                        {
                            menu.setSelectedIndex(elementIndex);
                        } else
                        {
                            requestPaint();
                        }
                    }
                    break;
                }
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("ChoiceGroup.setSelectedIndex: аргумент elementIndex выходит из диапазона.");
        }
    }

    public void setSelectedFlags(boolean[] src) {
        int error;
        if(src == null)
        {
            throw new NullPointerException("ChoiceGroup.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();
                    }
                    break;
                case MULTIPLE:
                    needRepaint = false;
                    for(int i = len; i-- > 0; )
                    {
                        boolean selected;
                        ChoiceElement item;
                        if((item = list[i]).selected != (selected = src[i]))
                        {
                            needRepaint = true;
                            item.selected = selected;
                        }
                    }
                    if(needRepaint) requestPaint();
                    break;
                default:
                    if((elementIndex = Array.findf(src, 0, true)) < len && selected != elementIndex)
                    {
                        ChoiceList menu;
                        selected = elementIndex;
                        if((menu = popupMenu).isShown())
                        {
                            menu.setSelectedIndex(elementIndex);
                        } else
                        {
                            requestPaint();
                        }
                    }
                    break;
                }
            }
        }
        if(error == 1)
        {
            throw new IllegalArgumentException("ChoiceGroup.setSelectedFlags: аргумент src короче списка.");
        }
    }

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

    public void set(int elementIndex, String text, Image icon) {
        int error;
        if(text == null)
        {
            throw new NullPointerException("ChoiceGroup.set: аргумент text равен нулевой ссылке.");
        }
        error = 0;
        synchronized(monitor)
        {
            label0:
            {
                ChoiceList menu;
                ChoiceElement item;
                if(elementIndex < 0 || elementIndex >= count)
                {
                    error = 1;
                    break label0;
                }
                (item = elements[elementIndex]).text = text;
                item.icon = icon;
                if((menu = popupMenu) != null && menu.isShown())
                {
                    menu.setString(elementIndex, text, icon);
                }
                else if(type != POPUP || selected == elementIndex)
                {
                    requestPaint();
                }
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("ChoiceGroup.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 != POPUP ? elements[elementIndex].selected : elementIndex == selected;
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("ChoiceGroup.isSelected: аргумент elementIndex выходит из диапазона.");
        }
        return result;
    }

    public int append(String text, Image icon) {
        int result;
        if(text == null)
        {
            throw new NullPointerException("ChoiceGroup.append: аргумент text равен нулевой ссылке.");
        }
        synchronized(monitor)
        {
            int t;
            int len;
            ChoiceElement[] list;
            ChoiceList menu;
            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 == POPUP ? SELECT_COMMAND : CHECK_COMMAND);
            if(t != POPUP)
            {
                requestInvalidate();
            }
            else if((menu = popupMenu).isShown())
            {
                menu.append(text, icon);
            }
            else if(len <= 1)
            {
                requestPaint();
            }
        }
        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("ChoiceGroup.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 != POPUP)
                {
                    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("ChoiceGroup.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("ChoiceGroup.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("ChoiceGroup.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("ChoiceGroup.getString: аргумент elementIndex выходит из диапазона.");
        }
        return result;
    }

    void paint(Graphics render, int contentWidth, int contentHeight) {
        int type = this.type;
        int top1 = render.getClipY();
        int top2 = render.getClipHeight() + top1 - 1;
        int element1 = top1 / ELEMENT_HEIGHT;
        int element2 = top2 / ELEMENT_HEIGHT;
        listWidth = contentWidth;
        listHeight = contentHeight;
        synchronized(monitor)
        {
            label0:
            {
                int sel;
                int press;
                int count;
                ChoiceElement[] list;
                Font font;
                if((count = this.count) <= 0)
                {
                    render.setColor(RasterCanvas.getSystemColor(0x28));
                    render.setFont(font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_SMALL));
                    render.drawString(MultilinedStringBuilder.truncate(emptyLabel, font, contentWidth), contentWidth >> 1, 0, Graphics.HCENTER | Graphics.TOP);
                    break label0;
                }
                if(type == POPUP)
                {
                    boolean f = super.focused;
                    boolean p = pressed == 0;
                    int x;
                    int y;
                    Image icon;
                    ChoiceElement item = elements[selected];
                    render.setFont(font = item.font);
                    render.setColor(RasterCanvas.getSystemColor(f ? p ? 0x25 : 0x27 : 0x24));
                    render.drawElement(4, f ? p ? 1 : 3 : 0, 0, 0, 0, contentWidth, contentHeight);
                    if((icon = item.icon) != null)
                    {
                        x = MARGIN3 + (p ? 1 : 0);
                        y = ((ELEMENT_HEIGHT - font.getHeight()) >> 1) + (p ? 1 : 0);
                        render.drawStretch(icon, MARGIN + (p ? 1 : 0), ICON_TOP + (p ? 1 : 0), ICON_WIDTH, ICON_HEIGHT, 0);
                        render.drawString(MultilinedStringBuilder.truncate(item.text, font, contentWidth - (MARGIN * 2 + POPUP_ARROW_SIZE + MARGIN3)), x, y, 0);
                    } else
                    {
                        x = MARGIN + (p ? 1 : 0);
                        y = ((ELEMENT_HEIGHT - font.getHeight()) >> 1) + (p ? 1 : 0);
                        render.drawString(MultilinedStringBuilder.truncate(item.text, font, contentWidth - (MARGIN * 3 + POPUP_ARROW_SIZE)), x, y, 0);
                    }
                    x = contentWidth - (POPUP_ARROW_SIZE + MARGIN) + (p ? 1 : 0);
                    y = ((ELEMENT_HEIGHT - POPUP_ARROW_SIZE) >> 1) + (p ? 2 : 1);
                    render.fillTriangle(x, y, x + (POPUP_ARROW_SIZE - 1), y, x + (POPUP_ARROW_SIZE >> 1), y + (POPUP_ARROW_SIZE - 1));
                    break label0;
                }
                sel = super.focused ? selected : -1;
                list = elements;
                press = pressed;
                render.translate(0, ELEMENT_HEIGHT * element1);
                render.setColor(RasterCanvas.getSystemColor(0x28));
                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);
                    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 == SELECT_COMMAND)
        {
            synchronized(monitor)
            {
                showPopupMenu();
            }
            return;
        }
        if(command == CHECK_COMMAND)
        {
            synchronized(monitor)
            {
                int activeIndex;
                int previousIndex;
                switch(type)
                {
                case EXCLUSIVE:
                    if((activeIndex = selected) >= 0 && activeIndex < count && activeIndex != (previousIndex = checked))
                    {
                        ChoiceElement[] list;
                        (list = elements)[previousIndex].selected = false;
                        list[checked = activeIndex].selected = true;
                        super.notifyStateChanged();
                        super.requestPaint();
                    }
                    break;
                case MULTIPLE:
                    if((activeIndex = selected) >= 0 && activeIndex < count)
                    {
                        ChoiceElement item;
                        (item = elements[activeIndex]).selected = !item.selected;
                        super.notifyStateChanged();
                        super.requestPaint();
                    }
                    break;
                }
            }
            return;
        }
        if(command == MULTIPLE_ACTIONS[0])
        {
            synchronized(monitor)
            {
                boolean needRepaint = false;
                ChoiceElement[] list = elements;
                for(int i = count; i-- > 0; )
                {
                    ChoiceElement item;
                    if(!(item = list[i]).selected)
                    {
                        needRepaint = true;
                        item.selected = true;
                    }
                }
                if(needRepaint)
                {
                    super.notifyStateChanged();
                    super.requestPaint();
                }
            }
        }
        if(command == MULTIPLE_ACTIONS[1])
        {
            synchronized(monitor)
            {
                boolean needRepaint = false;
                ChoiceElement[] list = elements;
                for(int i = count; i-- > 0; )
                {
                    ChoiceElement item;
                    if((item = list[i]).selected)
                    {
                        needRepaint = true;
                        item.selected = false;
                    }
                }
                if(needRepaint)
                {
                    super.notifyStateChanged();
                    super.requestPaint();
                }
            }
        }
        if(command == MULTIPLE_ACTIONS[2])
        {
            synchronized(monitor)
            {
                int len;
                if((len = count) > 0)
                {
                    ChoiceElement[] list = elements;
                    for(int i = len; i-- > 0; )
                    {
                        ChoiceElement item;
                        (item = list[i]).selected = !item.selected;
                    }
                    super.notifyStateChanged();
                    super.requestPaint();
                }
            }
        }
        super.onCommandAction(command);
    }

    void onKeyboardEvent(KeyboardEvent event) {
        int key = event.getKey();
        DeviceSettings settings = DeviceManager.getInstance().getSettings();
        if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_POUND))
        {
            if(event.getAction() == KeyboardEvent.ACTION_KEY_PRESSED)
            {
                Font font;
                String text;
                Display parent;
                Displayable owner;
                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 && (owner = this.owner) != null && (parent = owner.getParentDisplay()) != null)
                {
                    screen.getScrollBar().setPosition(0);
                    screen.setFont(font);
                    screen.setText(text);
                    parent.setCurrent(screen);
                }
            }
            return;
        }
        if(key == KeyboardEvent.KEY_SPACE)
        {
            switch(event.getAction())
            {
            case KeyboardEvent.ACTION_KEY_PRESSED:
                pressed = type == POPUP ? 0 : selected;
                requestPaint();
                break;
            case KeyboardEvent.ACTION_KEY_RELEASED:
                switch(type)
                {
                case POPUP:
                    synchronized(monitor)
                    {
                        if(count > 0) showPopupMenu();
                    }
                    break;
                case EXCLUSIVE:
                    synchronized(monitor)
                    {
                        int activeIndex;
                        int previousIndex;
                        ChoiceElement[] list;
                        if((activeIndex = pressed) >= 0 && activeIndex < count && activeIndex != (previousIndex = checked))
                        {
                            (list = elements)[previousIndex].selected = false;
                            list[checked = activeIndex].selected = true;
                            super.notifyStateChanged();
                        }
                    }
                    break;
                case MULTIPLE:
                    synchronized(monitor)
                    {
                        int activeIndex;
                        ChoiceElement item;
                        if((activeIndex = pressed) >= 0 && activeIndex < count)
                        {
                            (item = elements[activeIndex]).selected = !item.selected;
                            super.notifyStateChanged();
                        }
                    }
                    break;
                }
                pressed = -1;
                requestPaint();
                break;
            }
        }
    }

    void onPointerEvent(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(type == POPUP)
                {
                    if((focused = getFocusedElement(event.getX(), event.getY())) == -2)
                    {
                        pressed = 0;
                        requestPaint();
                    }
                    break;
                }
                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;
                        selected = i;
                        requestInvalidate();
                    }
                    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)
                    {
                        selected = i;
                        requestInvalidate();
                    }
                }
            }
            break;
        case PointerEvent.ACTION_POINTER_DRAGGED:
            if(type == POPUP)
            {
                if(focused == -2 && pressed != (pressed = getFocusedElement(event.getX(), event.getY()) == -2 ? 0 : -1)) requestPaint();
                break;
            }
            if((f = focused) >= 0)
            {
                if(pressed != (pressed = getFocusedElement(event.getX(), event.getY()) == f ? f : -1)) requestPaint();
                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)
                {
                    selected = i;
                    requestInvalidate();
                }
            }
            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;
                if(type == POPUP)
                {
                    if(f == -2 && p == 0) synchronized(monitor)
                    {
                        if(count > 0)
                        {
                            showPopupMenu();
                        } else
                        {
                            requestPaint();
                        }
                    }
                    break;
                }
                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)
                        {
                            selected = i;
                            requestInvalidate();
                        }
                    }
                    if(p >= 0)
                    {
                        int s;
                        switch(type)
                        {
                        case EXCLUSIVE:
                            if(p < count && p != (s = checked))
                            {
                                ChoiceElement[] list;
                                (list = elements)[s].selected = false;
                                list[checked = p].selected = true;
                                super.notifyStateChanged();
                            }
                            break;
                        case MULTIPLE:
                            if(p < count)
                            {
                                ChoiceElement item;
                                (item = elements[p]).selected = !item.selected;
                                super.notifyStateChanged();
                            }
                            break;
                        }
                    }
                }
                requestPaint();
            }
            break;
        }
    }

    void onFocusLost() {
        focused = -1;
        pressed = -1;
        super.onFocusLost();
    }

    boolean onFocusMove(int direction, int viewportWidth, int viewportHeight, int[] visibleRectangle) {
        boolean stay;
        int elementIndex;
        if(!super.onFocusMove(direction, viewportWidth, viewportHeight, visibleRectangle))
        {
            elementIndex = 0;
            if(type != POPUP) synchronized(monitor)
            {
                if(direction == DIR_UP)
                {
                    int len;
                    if((len = count) > 0) selected = elementIndex = len - 1;
                } else
                {
                    selected = 0;
                }
            }
            visibleRectangle[LEFT] = 0;
            visibleRectangle[TOP] = elementIndex * ELEMENT_HEIGHT;
            visibleRectangle[WIDTH] = listWidth;
            visibleRectangle[HEIGHT] = ELEMENT_HEIGHT;
            return true;
        }
        if(type == POPUP) return false;
        switch(direction)
        {
        case DIR_NONE:
            elementIndex = selected;
            break;
        case DIR_UP:
            synchronized(monitor)
            {
                if(stay = (elementIndex = selected) > 0) selected = --elementIndex;
            }
            if(!stay) return false;
            requestPaint();
            break;
        case DIR_DOWN:
            synchronized(monitor)
            {
                if(stay = (elementIndex = selected) < count - 1) selected = ++elementIndex;
            }
            if(!stay) return false;
            requestPaint();
            break;
        default:
            return false;
        }
        visibleRectangle[LEFT] = 0;
        visibleRectangle[TOP] = elementIndex * ELEMENT_HEIGHT;
        visibleRectangle[WIDTH] = listWidth;
        visibleRectangle[HEIGHT] = ELEMENT_HEIGHT;
        return true;
    }

    int getMinimumContentWidth() {
        return 100;
    }

    int getMinimumContentHeight() {
        return ELEMENT_HEIGHT * (type == POPUP ? 1 : Math.max(1, count));
    }

    final void setSelected(int elementIndex) {
        synchronized(monitor)
        {
            int lim;
            if(elementIndex > (lim = count - 1)) elementIndex = lim;
            if(elementIndex < 0) elementIndex = 0;
            if(selected != elementIndex)
            {
                selected = elementIndex;
                super.notifyStateChanged();
                super.requestPaint();
            }
        }
    }

    private void showPopupMenu() {
        int position;
        int len = count;
        ChoiceElement[] list;
        ChoiceList menu;
        ScrollBar scroll;
        Display parent;
        Screen screen;
        if((screen = owner) == null || (parent = screen.getParentDisplay()) == null) return;
        position = (scroll = (menu = popupMenu).getScrollBar()).getPosition();
        menu.deleteAll();
        list = elements;
        for(int i = 0; i < len; i++)
        {
            ChoiceElement item = list[i];
            menu.append(item.text, item.icon, item.font);
        }
        if(len > 0) menu.setSelectedIndex(selected);
        scroll.setPosition(position);
        parent.setCurrent(menu);
    }

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

    private ChoiceList createPopupMenu() {
        ChoiceList result;
        final Command select = SELECT_COMMAND;
        Command back = new Command("Назад", Command.BACK, 0);
        (result = new ChoiceList(null, null)).setCommandListener(new CommandListener() {
            public void commandAction(Command command, Displayable screen) {
                Display parent;
                if(command == select) (ChoiceGroup.this).setSelected(((TextMenu) screen).getSelectedIndex());
                if((parent = screen.getParentDisplay()) != null) parent.hideForeground();
            }
        });
        result.addCommand(select);
        result.addCommand(back);
        return result;
    }
}