/*
    Реализация спецификаций 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.microedition.*;
import malik.emulator.microedition.lcdui.*;

public class Form extends ScrollingScreen
{
    private static final class Clear extends Object implements Runnable
    {
        private final int index;
        private final Item[] items;

        public Clear(Item[] items, int index) {
            this.index = index;
            this.items = items;
        }

        public void run() {
            Item[] list;
            for(int f = index, i = (list = items).length; i-- > 0; )
            {
                Item current = list[i];
                if(i == f) current.onFocusLost();
                if(current.visible)
                {
                    current.visible = false;
                    current.onHide();
                }
            }
        }
    }

    private static final class Delete extends Object implements Runnable
    {
        private final boolean hasFocus;
        private final Item deleted;

        public Delete(Item deleted, boolean hasFocus) {
            this.hasFocus = hasFocus;
            this.deleted = deleted;
        }

        public void run() {
            Item current = deleted;
            if(hasFocus) current.onFocusLost();
            if(current.visible)
            {
                current.visible = false;
                current.onHide();
            }
        }
    }

    class Helper extends ScrollingScreen.Helper
    {
        public Helper() {
        }

        public int size() {
            int count = super.size();
            Item focused = (Form.this).getFocused();
            return count + (focused != null ? focused.getCommands().size() : 0);
        }

        public Command commandAt(int index) {
            int len;
            Item focused;
            Command.Owner commands;
            return
                (focused = (Form.this).getFocused()) == null ? super.commandAt(index) :
                index < (len = (commands = focused.getCommands()).size()) ? commands.commandAt(index) : super.commandAt(index - len)
            ;
        }

        public Command defaultCommand() {
            Command result;
            Item focused = (Form.this).getFocused();
            return focused == null || (result = focused.getCommands().defaultCommand()) == null ? super.defaultCommand() : result;
        }

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

        protected final void servicePlaceItems() {
            (Form.this).placeItems();
        }
    }

    private boolean placeItemsNeeded;
    private int focused;
    private int count;
    private final int[] rect0;
    private final int[] rect1;
    private final int[] rect2;
    private Item[] items;
    ItemStateListener listener;
    private final Object monitor;

    public Form(String title) {
        this(title, null, false, null, null);
    }

    public Form(String title, Item[] items) {
        this(title, null, false, null, items);
    }

    public Form(String title, Ticker ticker, boolean fullScreen, ScrollBarStyle style, Item[] items) {
        super(title, ticker, fullScreen, SCROLL_BOTH, style);
        int len = items == null ? 0 : items.length;
        int error;
        int[] monitor;
        Item[] list;
        this.placeItemsNeeded = true;
        this.count = len;
        this.rect0 = monitor = new int[4];
        this.rect1 = new int[4];
        this.rect2 = new int[4];
        this.items = list = new Item[getOptimalLength(len)];
        this.monitor = monitor;
        if(len <= 0) return;
        error = 0;
        synchronized(Item.OWNER_MONITOR)
        {
            label0:
            {
                int i;
                label1:
                {
                    for(i = 0; i < len; i++)
                    {
                        Item current;
                        if((current = items[i]) == null)
                        {
                            error = 1;
                            break label1;
                        }
                        if(current.owner != null)
                        {
                            error = 2;
                            break label1;
                        }
                        (list[i] = current).owner = this;
                    }
                    break label0;
                }
                for(; i-- > 0; list[i].owner = null);
            }
        }
        switch(error)
        {
        case 1:
            throw new NullPointerException("Form: один из элементов аргумента items равен нулевой ссылке.");
        case 2:
            throw new IllegalStateException("Form: один из элементов аргумента items уже принадлежит экрану или аргумент items содержит равные элементы.");
        }
    }

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

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

    public void insert(int itemIndex, Item item) {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int len;
                Item[] list;
                if(itemIndex < 0 || itemIndex > (len = count))
                {
                    error = 1;
                    break label0;
                }
                if(item == null)
                {
                    error = 2;
                    break label0;
                }
                synchronized(Item.OWNER_MONITOR)
                {
                    label1:
                    {
                        if(item.owner != null)
                        {
                            error = 3;
                            break label1;
                        }
                        item.owner = this;
                    }
                }
                if(error != 0) break label0;
                if(len == (list = items).length) Array.copy(list, 0, list = items = new Item[(len << 1) + 1], 0, len);
                if(itemIndex < len) Array.copy(list, itemIndex, list, itemIndex + 1, len - itemIndex);
                list[itemIndex] = item;
                placeItemsNeeded = true;
                count = ++len;
                if(len > 1)
                {
                    int activeIndex;
                    if((activeIndex = focused) >= itemIndex) focused = activeIndex + 1;
                }
                requestPaint(CLIENT);
            }
        }
        switch(error)
        {
        case 1:
            throw new IndexOutOfBoundsException("Form.insert: аргумент itemIndex выходит из диапазона.");
        case 2:
            throw new NullPointerException("Form.insert: аргумент item равен нулевой ссылке.");
        case 3:
            throw new IllegalStateException("Form.insert: аргумент item уже принадлежит экрану.");
        }
    }

    public void delete(int itemIndex) {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                boolean hasFocus;
                int len;
                Item[] list;
                Item deleted;
                if(itemIndex < 0 || itemIndex > (len = count - 1))
                {
                    error = 1;
                    break label0;
                }
                (deleted = (list = items)[itemIndex]).owner = null;
                hasFocus = len <= 0;
                if(itemIndex < len) Array.copy(list, itemIndex + 1, list, itemIndex, len - itemIndex);
                list[len] = null;
                placeItemsNeeded = true;
                count = len;
                if(len > 0)
                {
                    int activeIndex;
                    if((activeIndex = focused) == itemIndex)
                    {
                        if(activeIndex >= len) activeIndex = len - 1;
                        focused = activeIndex;
                        hasFocus = true;
                        placeCommands();
                    }
                    else if(activeIndex > itemIndex)
                    {
                        focused = activeIndex - 1;
                    }
                }
                requestAction(new Delete(deleted, hasFocus));
                requestPaintAll();
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("Form.delete: аргумент itemIndex выходит из диапазона.");
        }
    }

    public void deleteAll() {
        synchronized(monitor)
        {
            int len;
            if((len = count) > 0)
            {
                int indexOfFocused = focused;
                Item[] cleared = new Item[len];
                Item[] list = items;
                placeItemsNeeded = true;
                focused = 0;
                count = 0;
                for(int i = len; i-- > 0; list[i].owner = null);
                Array.copy(list, 0, cleared, 0, len);
                Array.fill(list, 0, len, null);
                placeCommands();
                requestAction(new Clear(cleared, indexOfFocused));
                requestPaintAll();
            }
        }
    }

    public void set(int itemIndex, Item item) {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                boolean hasFocus;
                Item[] list;
                Item deleted;
                if(itemIndex < 0 || itemIndex >= count)
                {
                    error = 1;
                    break label0;
                }
                if(item == null)
                {
                    error = 2;
                    break label0;
                }
                synchronized(Item.OWNER_MONITOR)
                {
                    label1:
                    {
                        if(item.owner != null)
                        {
                            error = 3;
                            break label1;
                        }
                        item.owner = this;
                    }
                }
                if(error != 0) break label0;
                (deleted = (list = items)[itemIndex]).owner = null;
                hasFocus = focused == itemIndex;
                list[itemIndex] = item;
                if(hasFocus) placeCommands();
                placeItemsNeeded = true;
                requestAction(new Delete(deleted, hasFocus));
                requestPaintAll();
            }
        }
        switch(error)
        {
        case 1:
            throw new IndexOutOfBoundsException("Form.set: аргумент itemIndex выходит из диапазона.");
        case 2:
            throw new NullPointerException("Form.set: аргумент item равен нулевой ссылке.");
        case 3:
            throw new IllegalStateException("Form.set: аргумент item уже принадлежит экрану.");
        }
    }

    public void setItemStateListener(ItemStateListener listener) {
        this.listener = listener;
    }

    public int size() {
        return count;
    }

    public int append(Item item) {
        int result;
        int error;
        if(item == null)
        {
            throw new NullPointerException("Form.append: аргумент item равен нулевой ссылке.");
        }
        error = 0;
        synchronized(Item.OWNER_MONITOR)
        {
            label0:
            {
                if(item.owner != null)
                {
                    error = 1;
                    break label0;
                }
                item.owner = this;
            }
        }
        if(error == 1)
        {
            throw new IllegalStateException("Form.append: аргумент item уже принадлежит экрану.");
        }
        synchronized(monitor)
        {
            Item[] list;
            if((result = count) == (list = items).length) Array.copy(list, 0, list = items = new Item[(result << 1) + 1], 0, result);
            list[result] = item;
            count = result + 1;
            placeItemsNeeded = true;
            requestPaint(CLIENT);
        }
        return result;
    }

    public int append(String text) {
        return append(new StringItem(null, text, Item.PLAIN));
    }

    public int append(Image image) {
        return append(new ImageItem(null, image, Item.LAYOUT_DEFAULT, null, Item.PLAIN));
    }

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

    void paint(ScreenGraphics render) {
        int len;
        int clipLeft = render.getRestrictedLeft();
        int clipTop = render.getRestrictedTop();
        int clipWidth = render.getRestrictedWidth();
        int clipHeight = render.getRestrictedHeight();
        int translateX = render.getTranslateX() + render.getStartPointX();
        int translateY = render.getTranslateY() + render.getStartPointY();
        Item[] list;
        clipLeft -= translateX;
        clipTop -= translateY;
        synchronized(monitor)
        {
            Array.copy(items, 0, list = new Item[len = count], 0, len);
        }
        for(int i = 0; i < len; i++)
        {
            Item current;
            if((current = list[i]).visible)
            {
                int left;
                int top;
                render.reset();
                render.translate(translateX, translateY);
                render.clipRect(left = current.left, top = current.top, current.width, current.height);
                render.clipRect(clipLeft, clipTop, clipWidth, clipHeight);
                render.translate(left, top);
                current.onPaint(render);
            }
        }
    }

    void setFocus(final Item item, Display parent) {
        parent.callSerially(new Runnable() {
            public void run() {
                (Form.this).setFocus(item);
            }
        });
    }

    void onShow() {
        int viewportWidth = getApplicationWidth();
        int viewportHeight = getApplicationHeight();
        changeItemVisibility(super.horz.getPosition(), super.vert.getPosition(), viewportWidth, viewportHeight);
        moveFocus(Item.DIR_NONE, viewportWidth, viewportHeight, false);
    }

    void onHide() {
        int len;
        Item[] list;
        Item focused;
        if((focused = getFocused()) != null) focused.onFocusLost();
        synchronized(monitor)
        {
            Array.copy(items, 0, list = new Item[len = count], 0, len);
        }
        for(int i = 0; i < len; i++)
        {
            Item current;
            if((current = list[i]).visible)
            {
                current.visible = false;
                current.onHide();
            }
        }
    }

    void onSizeChanged(int width, int height) {
        synchronized(monitor)
        {
            placeItemsNeeded = true;
        }
        super.onSizeChanged(width, height);
    }

    void onCommandAction(Command command) {
        Item focused;
        if((focused = getFocused()) != null && focused.getCommands().isMyOwnedCommand(command))
        {
            focused.onCommandAction(command);
            return;
        }
        super.onCommandAction(command);
    }

    void onClientKeyboardEvent(KeyboardEvent event) {
        boolean moved = false;
        int key = event.getKey();
        int action = event.getAction();
        int viewportWidth = getApplicationWidth();
        int viewportHeight = getApplicationHeight();
        DeviceSettings settings = DeviceManager.getInstance().getSettings();
        Item activeItem = null;
        if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_LEFT))
        {
            if((action == KeyboardEvent.ACTION_KEY_PRESSED || action == KeyboardEvent.ACTION_KEY_REPEATED) && !moveFocus(Item.DIR_LEFT, viewportWidth, viewportHeight, false))
            {
                synchronized(monitor)
                {
                    int activeIndex;
                    Item[] list = items;
                    if((activeIndex = focused) > 0)
                    {
                        moved = true;
                        activeItem = list[activeIndex];
                        focused = activeIndex - 1;
                        placeCommands();
                        requestPaintAll();
                    }
                }
                if(moved)
                {
                    activeItem.onFocusLost();
                    moveFocus(Item.DIR_LEFT, viewportWidth, viewportHeight, true);
                } else
                {
                    super.horz.scroll(-16);
                }
            }
            return;
        }
        if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_RIGHT))
        {
            if((action == KeyboardEvent.ACTION_KEY_PRESSED || action == KeyboardEvent.ACTION_KEY_REPEATED) && !moveFocus(Item.DIR_RIGHT, viewportWidth, viewportHeight, false))
            {
                synchronized(monitor)
                {
                    int activeIndex;
                    Item[] list = items;
                    if((activeIndex = focused) < count - 1)
                    {
                        moved = true;
                        activeItem = list[activeIndex];
                        focused = activeIndex + 1;
                        placeCommands();
                        requestPaintAll();
                    }
                }
                if(moved)
                {
                    activeItem.onFocusLost();
                    moveFocus(Item.DIR_RIGHT, viewportWidth, viewportHeight, true);
                } else
                {
                    super.horz.scroll(+16);
                }
            }
            return;
        }
        if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_UP))
        {
            if((action == KeyboardEvent.ACTION_KEY_PRESSED || action == KeyboardEvent.ACTION_KEY_REPEATED) && !moveFocus(Item.DIR_UP, viewportWidth, viewportHeight, false))
            {
                synchronized(monitor)
                {
                    int activeIndex;
                    if((activeIndex = focused) > 0)
                    {
                        int lnCount = 0;
                        Item[] list;
                        activeItem = (list = items)[activeIndex];
                        for(; activeIndex >= 0; activeIndex--)
                        {
                            Item current;
                            if((current = list[activeIndex]).newLine) lnCount++;
                            if(lnCount == 2)
                            {
                                if(current.visible)
                                {
                                    moved = true;
                                    focused = activeIndex;
                                    placeCommands();
                                    requestPaintAll();
                                }
                                break;
                            }
                        }
                    }
                }
                if(moved)
                {
                    activeItem.onFocusLost();
                    moveFocus(Item.DIR_UP, viewportWidth, viewportHeight, true);
                } else
                {
                    super.vert.scroll(-Item.LABEL_FONT.getHeight());
                }
            }
            return;
        }
        if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_DOWN))
        {
            if((action == KeyboardEvent.ACTION_KEY_PRESSED || action == KeyboardEvent.ACTION_KEY_REPEATED) && !moveFocus(Item.DIR_DOWN, viewportWidth, viewportHeight, false))
            {
                synchronized(monitor)
                {
                    int len;
                    int activeIndex;
                    if((activeIndex = focused) < (len = count) - 1)
                    {
                        Item[] list;
                        activeItem = (list = items)[activeIndex++];
                        for(; activeIndex < len; activeIndex++)
                        {
                            Item current;
                            if((current = list[activeIndex]).newLine)
                            {
                                if(current.visible)
                                {
                                    moved = true;
                                    focused = activeIndex;
                                    placeCommands();
                                    requestPaintAll();
                                }
                                break;
                            }
                        }
                    }
                }
                if(moved)
                {
                    activeItem.onFocusLost();
                    moveFocus(Item.DIR_DOWN, viewportWidth, viewportHeight, true);
                } else
                {
                    super.vert.scroll(+Item.LABEL_FONT.getHeight());
                }
            }
            return;
        }
        if((activeItem = getFocused()) != null) activeItem.onKeyboardEvent(event);
    }

    void onClientPointerEvent(PointerEvent event) {
        Item activeItem = null;
        Item focusedItem = null;
        if(event.getAction() == PointerEvent.ACTION_POINTER_PRESSED)
        {
            int x = event.getX();
            int y = event.getY();
            synchronized(monitor)
            {
                Item[] list;
                activeItem = (list = items)[focused];
                for(int i = count; i-- > 0; )
                {
                    int left;
                    int top;
                    Item current = list[i];
                    if(x >= (left = current.left) && x < left + current.width && y >= (top = current.top) && y < top + current.height)
                    {
                        focusedItem = current;
                        focused = i;
                        placeCommands();
                        requestPaintAll();
                        break;
                    }
                }
            }
        }
        if(activeItem != null && focusedItem != null && activeItem != focusedItem)
        {
            activeItem.onFocusLost();
            moveFocus(Item.DIR_NONE, getApplicationWidth(), getApplicationHeight(), true);
        }
        if((activeItem = getFocused()) != null)
        {
            int[] rect = rect0;
            activeItem.writeContentRectangleTo(rect);
            event.translate(activeItem.left + rect[0], activeItem.top + rect[1]);
            activeItem.onPointerEvent(event);
        }
    }

    void onClientScroll(int positionHorizontal, int positionVertical, int clientWidth, int clientHeight) {
        changeItemVisibility(positionHorizontal, positionVertical, clientWidth, clientHeight);
    }

    boolean focusSupported() {
        return true;
    }

    boolean isFocused(Item item) {
        return item != null && item == items[focused];
    }

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

    final void requestStateChanged(final Item item) {
        requestAction(item == null ? null : new Runnable() {
            public void run() {
                ItemStateListener listener;
                if((listener = (Form.this).listener) != null) listener.itemStateChanged(item);
            }
        });
    }

    final void requestInvalidate() {
        synchronized(monitor)
        {
            placeItemsNeeded = true;
        }
        requestPaint(CLIENT);
    }

    final strictfp void placeItems() {
        boolean needed;
        int len;
        int clientWidth;
        int clientHeight;
        int scrollWidth;
        int scrollHeight;
        int[] layouts;
        int[] prefWidths;
        int[] prefHeights;
        Item[] list;
        Item current;
        ScrollBar horz;
        ScrollBar vert;
        synchronized(monitor)
        {
            if(needed = placeItemsNeeded) placeItemsNeeded = false;
        }
        if(!needed) return;
        clientWidth = getApplicationWidth();
        clientHeight = getApplicationHeight();
        scrollWidth = clientWidth;
        scrollHeight = 0;
        synchronized(monitor)
        {
            Array.copy(items, 0, list = new Item[len = count], 0, len);
        }
        layouts = new int[len];
        prefWidths = new int[len];
        prefHeights = new int[len];
        for(int i = 0; i < len; i++)
        {
            int layout;
            int minWidth;
            int prefWidth;
            (current = list[i]).resetContentSize();
            layouts[i] = (layout = current.suppliedLayout()) == Item.LAYOUT_DEFAULT ? (layout = current.getDefaultLayout()) : layout;
            prefWidths[i] = prefWidth = current.suppliedPreferredWidth();
            prefHeights[i] = current.suppliedPreferredHeight();
            current.newLine = false;
            current.minimumWidth = minWidth = current.computeMinimumWidth();
            current.minimumHeight = current.computeMinimumHeight();
            if((layout & Item.LAYOUT_SHRINK) != 0)
            {
                current.width = minWidth;
            } else
            {
                if(minWidth >= prefWidth)
                {
                    prefWidth = current.computePreferredWidth(-1);
                    if(minWidth > prefWidth) prefWidth = minWidth;
                }
                current.width = prefWidth;
            }
        }
        for(int rowHeight, rowEnd, rowBegin = 0; rowBegin < len; scrollHeight += rowHeight, rowBegin = rowEnd)
        {
            /* определение содержимого строки */
            int left;
            int rowWidth;
            int alignment;
            int currentAlignment;
            int layout = layouts[rowEnd = rowBegin];
            (current = list[rowEnd++]).newLine = true;
            currentAlignment = (alignment = layout & Item.LAYOUT_CENTER) != 0 ? alignment : Item.LAYOUT_LEFT;
            if((rowWidth = current.width) < clientWidth) for(int newWidth; !current.hasRowBreakAfter(layout, currentAlignment) && rowEnd < len; rowWidth = newWidth, rowEnd++)
            {
                layout = layouts[rowEnd];
                current = list[rowEnd];
                newWidth = rowWidth + current.width;
                if(current.hasRowBreakBefore(layout, currentAlignment) || newWidth < 0 || newWidth > clientWidth) break;
            }
            /* расширение усаживаемых по ширине элементов */
            if(rowWidth < clientWidth)
            {
                double difference = 0.d;
                for(int i = rowBegin; i < rowEnd; i++)
                {
                    if((layouts[i] & Item.LAYOUT_SHRINK) != 0)
                    {
                        int minWidth = (current = list[i]).minimumWidth;
                        int prefWidth = prefWidths[i];
                        if(minWidth >= prefWidth)
                        {
                            prefWidth = current.computePreferredWidth(-1);
                            if(minWidth > prefWidth) prefWidth = minWidth;
                        }
                        prefWidths[i] = prefWidth;
                        difference += (double) (prefWidth - minWidth);
                    }
                }
                if(difference > 0.d)
                {
                    double koefficient;
                    if((koefficient = (double) (clientWidth - rowWidth) / difference) > 1.d) koefficient = 1.d;
                    for(int i = rowBegin; i < rowEnd; i++) if((layouts[i] & Item.LAYOUT_SHRINK) != 0)
                    {
                        int delta = (int) (koefficient * (double) (prefWidths[i] - (current = list[i]).minimumWidth));
                        current.width += delta;
                        rowWidth += delta;
                    }
                }
            }
            /* расширение растягиваемых по ширине элементов */
            if(rowWidth < clientWidth)
            {
                int count = 0;
                for(int i = rowBegin; i < rowEnd; i++) if((layouts[i] & Item.LAYOUT_EXPAND) != 0) count++;
                if(count > 0)
                {
                    int delta = (clientWidth - rowWidth) / count;
                    for(int i = rowBegin; i < rowEnd; i++) if((layouts[i] & Item.LAYOUT_EXPAND) != 0)
                    {
                        current = list[i];
                        if(count <= 1)
                        {
                            delta = clientWidth - rowWidth;
                            current.width += delta;
                            rowWidth += delta;
                            break;
                        }
                        current.width += delta;
                        rowWidth += delta;
                        count--;
                    }
                }
            }
            /* определение высоты строки */
            rowHeight = 0;
            for(int i = rowBegin; i < rowEnd; i++)
            {
                int height;
                current = list[i];
                if((layouts[i] & Item.LAYOUT_VSHRINK) != 0)
                {
                    current.height = height = current.minimumHeight;
                } else
                {
                    int minHeight = current.minimumHeight;
                    int prefHeight = prefHeights[i];
                    if(minHeight >= prefHeight)
                    {
                        prefHeight = current.computePreferredHeight(current.width);
                        if(minHeight > prefHeight) prefHeight = minHeight;
                    }
                    current.height = height = prefHeight;
                }
                if(rowHeight < height) rowHeight = height;
            }
            /* расширение растягиваемых по высоте элементов */
            for(int i = rowBegin; i < rowEnd; i++)
            {
                current = list[i];
                if(((layout = layouts[i]) & Item.LAYOUT_VEXPAND) != 0)
                {
                    int minWidth;
                    int maxWidth;
                    int prefWidth;
                    current.height = rowHeight;
                    if((layout & (Item.LAYOUT_SHRINK | Item.LAYOUT_EXPAND)) == 0 && (prefWidth = prefWidths[i]) <= (minWidth = current.minimumWidth))
                    {
                        int delta;
                        prefWidth = current.computePreferredWidth(rowHeight);
                        if(minWidth > prefWidth) prefWidth = minWidth;
                        if((maxWidth = current.width) < prefWidth) prefWidth = maxWidth;
                        delta = prefWidth - maxWidth;
                        current.width += delta;
                        rowWidth += delta;
                    }
                }
                current.onSizeChanged();
            }
            /* размещение элементов строки */
            switch(currentAlignment)
            {
            default:
                left = 0;
                break;
            case Item.LAYOUT_CENTER:
                left = (clientWidth - rowWidth) >> 1;
                break;
            case Item.LAYOUT_RIGHT:
                left = clientWidth - rowWidth;
                break;
            }
            if(left < 0) left = 0;
            for(int i = rowBegin; i < rowEnd; left += current.width, i++)
            {
                (current = list[i]).left = left;
                switch((layouts[i] & Item.LAYOUT_VCENTER) >> 4)
                {
                case Item.LAYOUT_TOP >> 4:
                    current.top = scrollHeight;
                    break;
                case Item.LAYOUT_VCENTER >> 4:
                    current.top = scrollHeight + ((rowHeight - current.height) >> 1);
                    break;
                default:
                    current.top = scrollHeight + (rowHeight - current.height);
                    break;
                }
            }
            if(scrollWidth < rowWidth) scrollWidth = rowWidth;
        }
        /* обновление диапазонов прокрутки */
        (horz = super.horz).setRange(scrollWidth);
        (vert = super.vert).setRange(scrollHeight);
        /* прокрутка формы */
        moveFocus(Item.DIR_NONE, clientWidth, clientHeight, false);
        /* определение видимости элементов */
        changeItemVisibility(horz.getPosition(), vert.getPosition(), clientWidth, clientHeight);
    }

    final void setFocus(Item item) {
        Item activeItem = null;
        Item focusedItem = null;
        synchronized(monitor)
        {
            int index;
            Item[] list;
            activeItem = (list = items)[focused];
            if((index = Array.findf(list, 0, item)) < count)
            {
                focusedItem = item;
                focused = index;
                placeCommands();
                requestPaintAll();
            }
        }
        if(activeItem != null && focusedItem != null && activeItem != focusedItem)
        {
            placeItems();
            activeItem.onFocusLost();
            moveFocus(Item.DIR_NONE, getApplicationWidth(), getApplicationHeight(), true);
        }
    }

    final Item getFocused() {
        return items[focused];
    }

    private void changeItemVisibility(int left, int top, int width, int height) {
        int len;
        Item[] list;
        synchronized(monitor)
        {
            Array.copy(items, 0, list = new Item[len = count], 0, len);
        }
        for(int i = 0; i < len; i++)
        {
            boolean visibility;
            int l;
            int t;
            int w;
            int h;
            Item current = list[i];
            l = current.left;
            t = current.top;
            w = current.width;
            h = current.height;
            visibility = left < l + w && top < t + h && left + width > l && top + height > t;
            if(current.visible && !visibility)
            {
                current.visible = false;
                current.onHide();
            }
            else if(!current.visible && visibility)
            {
                current.visible = true;
                current.onShow();
            }
        }
    }

    private boolean moveFocus(int direction, int viewportWidth, int viewportHeight, boolean initialFocus) {
        Item focused;
        if((focused = getFocused()) != null)
        {
            int viewportLeft = super.horz.getPosition();
            int viewportTop = super.vert.getPosition();
            int contentLeft;
            int contentTop;
            int focusedLeft = focused.left;
            int focusedTop = focused.top;
            int[] viewportRect = rect0;
            int[] contentRect = rect1;
            int[] visibleRect = rect2;
            if(initialFocus)
            {
                int focusedWidth = focused.width;
                int focusedHeight = focused.height;
                int focusedRight = focusedLeft + focusedWidth;
                int focusedBottom = focusedTop + focusedHeight;
                if(focusedWidth > viewportWidth)
                {
                    if(viewportLeft + viewportWidth > focusedRight && (viewportLeft = focusedRight - viewportWidth) < 0) viewportLeft = 0;
                    if(viewportLeft < focusedLeft) viewportLeft = focusedLeft;
                } else
                {
                    if(viewportLeft + viewportWidth < focusedRight) viewportLeft = focusedRight - viewportWidth;
                    if(viewportLeft > focusedLeft) viewportLeft = focusedLeft;
                }
                if(focusedHeight > viewportHeight)
                {
                    if(viewportTop + viewportHeight > focusedBottom && (viewportTop = focusedBottom - viewportHeight) < 0) viewportTop = 0;
                    if(viewportTop < focusedTop) viewportTop = focusedTop;
                } else
                {
                    if(viewportTop + viewportHeight < focusedBottom) viewportTop = focusedBottom - viewportHeight;
                    if(viewportTop > focusedTop) viewportTop = focusedTop;
                }
            }
            focused.writeContentRectangleTo(contentRect);
            contentLeft = focusedLeft + contentRect[0];
            contentTop = focusedTop + contentRect[1];
            viewportRect[0] = viewportLeft;
            viewportRect[1] = viewportTop;
            viewportRect[2] = viewportLeft + viewportWidth;
            viewportRect[3] = viewportTop + viewportHeight;
            contentRect[2] += focusedLeft;
            contentRect[3] += focusedTop;
            visibleRect[0] = Math.max(viewportRect[0], contentLeft);
            visibleRect[1] = Math.max(viewportRect[1], contentTop);
            visibleRect[2] = Math.min(viewportRect[2], contentRect[2]);
            visibleRect[3] = Math.min(viewportRect[3], contentRect[3]);
            if((visibleRect[2] -= visibleRect[0]) < 0) visibleRect[2] = 0;
            if((visibleRect[3] -= visibleRect[1]) < 0) visibleRect[3] = 0;
            visibleRect[0] -= contentLeft;
            visibleRect[1] -= contentTop;
            if(focused.onFocusMove(direction, viewportWidth, viewportHeight, visibleRect))
            {
                int left = contentLeft + visibleRect[0];
                int top = contentTop + visibleRect[1];
                int right = left + visibleRect[2];
                int bottom = top + visibleRect[3];
                if(viewportLeft + viewportWidth < right) viewportLeft = right - viewportWidth;
                if(viewportTop + viewportHeight < bottom) viewportTop = bottom - viewportHeight;
                if(viewportLeft > left) viewportLeft = left;
                if(viewportTop > top) viewportTop = top;
                super.scrollTo(viewportLeft, viewportTop);
                return true;
            }
            if(initialFocus) super.scrollTo(viewportLeft, viewportTop);
        }
        return false;
    }
}
