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

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

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

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

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


package javax.microedition.lcdui;

import java.lang.ref.*;

public class Form extends Container
{
	private static final class ItemCommandEnumerator extends Object
			implements CommandEnumeration
	{
		private CommandEnumeration formCommands;
		private CommandEnumeration itemCommands;

		ItemCommandEnumerator(CommandEnumeration formCommands, CommandEnumeration itemCommands)
		{
			this.formCommands = formCommands;
			this.itemCommands = itemCommands;
		}

		public int getCommandsCount()
		{
			return formCommands.getCommandsCount() + itemCommands.getCommandsCount();
		}

		public Command getCommand(int index)
		{
			int itemCommandsCount;
			CommandEnumeration formCommands;
			CommandEnumeration itemCommands;
			CommandEnumeration result;
			if(index < 0 || index >= (formCommands = this.formCommands).getCommandsCount() +
					(itemCommandsCount = (itemCommands = this.itemCommands).getCommandsCount()))
			{
				throw new IndexOutOfBoundsException("Form.getCommands: " +
						"индекс выходит из диапазона.");
			}
			if(index < itemCommandsCount)
			{
				result = itemCommands;
			} else
			{
				result = formCommands;
				index -= itemCommandsCount;
			}
			return result.getCommand(index);
		}

		public Command getDefaultCommand()
		{
			Command result;
			return (result = itemCommands.getDefaultCommand()) != null ?
					result : formCommands.getDefaultCommand();
		}

		public Object getMonitor()
		{
			return itemCommands.getMonitor();
		}
	}

	private static final class ItemStateChangedEvent extends Object
			implements Runnable
	{
		private ItemStateListener listener;
		private Item item;

		ItemStateChangedEvent(ItemStateListener listener, Item item)
		{
			this.listener = listener;
			this.item = item;
		}

		public void run()
		{
			listener.itemStateChanged(item);
		}
	}

	private static final class DeleteAllItemsEvent extends Object
			implements Runnable
	{
		private int focusedIndex;
		private int count;
		private Item[] items;
		private Form form;

		DeleteAllItemsEvent(Form form, Item[] items, int count, int focusedIndex)
		{
			this.focusedIndex = focusedIndex;
			this.count = count;
			this.items = items;
			this.form = form;
		}

		public void run()
		{
			int i;
			int curr = focusedIndex;
			Item[] list = items;
			Item item;
			for(i = count; i-- > 0; )
			{
				item = list[i];
				if(curr == i)
				{
					try
					{
						item.onTraverseOut();
					}
					catch(RuntimeException e)
					{
						e.printRealStackTrace();
					}
				}
				if(item.isVisible())
				{
					item.setVisible(false);
					try
					{
						item.onHide();
					}
					catch(RuntimeException e)
					{
						e.printRealStackTrace();
					}
				}
			}
			form.placeItems();
		}
	}

	private static final class RefocusItemEvent extends Object
			implements Runnable
	{
		private boolean focused;
		private Item oldItem;
		private Item newItem;
		private Form form;

		RefocusItemEvent(Form form, boolean deletedItemIsFocused, Item deletedItem,
				Item itemForFocus)
		{
			this.focused = deletedItemIsFocused;
			this.oldItem = deletedItem;
			this.newItem = itemForFocus;
			this.form = form;
		}

		public void run()
		{
			Item item;
			Form form;
			if((item = oldItem) != null)
			{
				if(focused)
				{
					try
					{
						item.onTraverseOut();
					}
					catch(RuntimeException e)
					{
						e.printRealStackTrace();
					}
				}
				if(item.isVisible())
				{
					item.setVisible(false);
					try
					{
						item.onHide();
					}
					catch(RuntimeException e)
					{
						e.printRealStackTrace();
					}
				}
			}
			(form = this.form).placeItems();
			if((item = newItem) != null)
			{
				form.focusItem(item);
			}
		}
	}

	private static final class FocusItemEvent extends Object
			implements Runnable
	{
		private Form form;
		private Item item;

		FocusItemEvent(Form form, Item item)
		{
			this.form = form;
			this.item = item;
		}

		public void run()
		{
			form.focusItem(item);
		}
	}

	private static final class Helper extends WeakReference
			implements ItemStateListener, Runnable
	{
		Helper(Form thisForm)
		{
			super(thisForm);
		}

		public void itemStateChanged(Item item)
		{
			Object thisForm;
			if(!((thisForm = get()) instanceof ItemStateListener))
			{
				return;
			}
			((ItemStateListener) thisForm).itemStateChanged(item);
		}

		public void run()
		{
			Form thisForm;
			if((thisForm = (Form) get()) == null)
			{
				return;
			}
			thisForm.placeItems();
		}
	}

	private static final int LEFT = 0;
	private static final int TOP = 1;
	private static final int WIDTH = 2;
	private static final int HEIGHT = 3;
	private static final int DIR_NONE = CustomItem.NONE;
	private static final int DIR_UP = Canvas.UP;
	private static final int DIR_LEFT = Canvas.LEFT;
	private static final int DIR_RIGHT = Canvas.RIGHT;
	private static final int DIR_DOWN = Canvas.DOWN;

	private static int keyToDirection(int key)
	{
		int keyCode;
		MIDletProxy proxy;
		if((keyCode = (proxy = MIDletProxy.getInstance()).getKeyCode(key)) == 0)
		{
			return DIR_NONE;
		}
		if(proxy.getKeyCodeFor(MIDletProxy.DEVICE_KEY_UP) == keyCode)
		{
			return DIR_UP;
		}
		if(proxy.getKeyCodeFor(MIDletProxy.DEVICE_KEY_LEFT) == keyCode)
		{
			return DIR_LEFT;
		}
		if(proxy.getKeyCodeFor(MIDletProxy.DEVICE_KEY_RIGHT) == keyCode)
		{
			return DIR_RIGHT;
		}
		if(proxy.getKeyCodeFor(MIDletProxy.DEVICE_KEY_DOWN) == keyCode)
		{
			return DIR_DOWN;
		}
		return DIR_NONE;
	}


	private boolean needPlaceItems;
	private boolean stayPlaceItems;
	private boolean firstSizeChanged;
	private int focusedIndex;
	private int count;
	private int[] rect;
	private Item[] items;
	private ItemStateListener listener;
	private Helper helper;
	private Object lock;

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

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

	public Form(String title, Ticker ticker,
			Command[] commands, CommandListener listener,
			Item[] items, ItemStateListener itemlistener)
	{
		this(true, title, ticker, commands, listener, items, itemlistener);
	}

	private Form(boolean system, String title, Ticker ticker,
			Command[] commands, CommandListener listener,
			Item[] items, ItemStateListener itemlistener)
	{
		super(system, title, ticker, commands, null, listener, BOTH);
		int i;
		int j;
		int len;
		int error;
		Item[] list;
		Item item;
		error = 0;
		synchronized(getOwningLock())
		{
			label0:
			{
				list = new Item[Math.max(len = items == null ? 0 : items.length, 1)];
				for(i = len; i-- > 0; )
				{
					if((item = items[i]) == null)
					{
						error = 1;
						break label0;
					}
					if(item.getOwner() != null)
					{
						error = 2;
						break label0;
					}
					for(j = i + 1; j < len; j++)
					{
						if(item == list[j])
						{
							error = 2;
							break label0;
						}
					}
					list[i] = item;
				}
				for(i = len; i-- > 0; )
				{
					list[i].setOwner(this);
				}
			}
		}
		switch(error)
		{
		case 1:
			throw new NullPointerException("Form: " +
					"один из элементов параметра items равен нулевой ссылке.");
		case 2:
			throw new IllegalStateException("Form: " +
					"один из элементов уже принадлежит контейнеру.");
		}
		this.needPlaceItems = true;
		this.firstSizeChanged = true;
		this.count = len;
		this.rect = new int[4];
		this.items = list;
		this.listener = itemlistener;
		this.helper = new Helper(this);
		this.lock = new Object();
	}

	public int getWidth()
	{
		return super.getWidth();
	}

	public int getHeight()
	{
		return super.getHeight();
	}

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

	public void set(int itemIndex, Item item)
	{
		boolean focused;
		int error;
		Item[] list;
		Item old;
		if(item == null)
		{
			throw new NullPointerException("Form.set: " +
					"параметр item равен нулевой ссылке.");
		}
		error = 0;
		synchronized(lock)
		{
			label0:
			{
				if(itemIndex < 0 || itemIndex >= count)
				{
					error = 1;
					break label0;
				}
				old = (list = items)[itemIndex];
				synchronized(getOwningLock())
				{
					label1:
					{
						if(item.getOwner() != null)
						{
							error = 2;
							break label1;
						}
						old.setOwner(null);
						item.setOwner(this);
					}
				}
				if(error == 2)
				{
					break label0;
				}
				list[itemIndex] = item;
				if(focused = focusedIndex == itemIndex)
				{
					focusedIndex = -1;
				}
				callSerially(new RefocusItemEvent(this, focused, old, focused ? item : null));
			}
		}
		switch(error)
		{
		case 1:
			throw new IndexOutOfBoundsException("Form.set: " +
					"параметр itemIndex выходит из диапазона.");
		case 2:
			throw new IllegalStateException("Form.set: " +
					"параметр item уже принадлежит контейнеру.");
		}
	}

	public void insert(int itemIndex, Item item)
	{
		boolean empty;
		int i;
		int len;
		int error;
		Item[] list;
		if(item == null)
		{
			throw new NullPointerException("Form.insert: " +
					"параметр item равен нулевой ссылке.");
		}
		error = 0;
		synchronized(lock)
		{
			label0:
			{
				if(itemIndex < 0 || itemIndex > (len = count))
				{
					error = 1;
					break label0;
				}
				synchronized(getOwningLock())
				{
					label1:
					{
						if(item.getOwner() != null)
						{
							error = 2;
							break label1;
						}
						item.setOwner(this);
					}
				}
				if(error == 2)
				{
					break label0;
				}
				if(len == (list = items).length)
				{
					Array.copy(list, 0, list = items = new Item[(len << 1) + 1], 0, len);
				}
				if((i = len - itemIndex) > 0)
				{
					Array.copy(list, itemIndex, list, itemIndex + 1, i);
				}
				list[itemIndex] = item;
				count = ++len;
				if(empty = len == 1)
				{
					focusedIndex = -1;
				}
				else if((i = focusedIndex) >= itemIndex)
				{
					focusedIndex = i + 1;
				}
				callSerially(new RefocusItemEvent(this, false, null, empty ? item : null));
			}
		}
		switch(error)
		{
		case 1:
			throw new IndexOutOfBoundsException("Form.insert: " +
					"параметр itemIndex выходит из диапазона.");
		case 2:
			throw new IllegalStateException("Form.insert: " +
					"параметр item уже принадлежит контейнеру.");
		}
	}

	public void delete(int itemIndex)
	{
		boolean focused;
		int i;
		int len;
		int error;
		Item[] list;
		Item old;
		Item item;
		error = 0;
		synchronized(lock)
		{
			label0:
			{
				if(itemIndex < 0 || itemIndex >= (len = count))
				{
					error = 1;
					break label0;
				}
				old = (list = items)[itemIndex];
				synchronized(getOwningLock())
				{
					old.setOwner(null);
				}
				if((i = len - itemIndex - 1) > 0)
				{
					Array.copy(list, itemIndex + 1, list, itemIndex, i);
				}
				list[--len] = null;
				count = len;
				item = null;
				if(focused = (i = focusedIndex) == itemIndex)
				{
					focusedIndex = -1;
					item = len > 0 ? list[Math.min(i, len - 1)] : null;
				}
				else if(i > itemIndex)
				{
					focusedIndex = i - 1;
				}
				callSerially(new RefocusItemEvent(this, focused, old, item));
			}
		}
		if(error == 1)
		{
			throw new IndexOutOfBoundsException("Form.delete: " +
					"параметр itemIndex выходит из диапазона.");
		}
	}

	public void deleteAll()
	{
		int i;
		int len;
		Item[] list;
		synchronized(lock)
		{
			if((len = count) > 0)
			{
				i = focusedIndex;
				list = items;
				focusedIndex = 0;
				count = 0;
				items = new Item[1];
				callSerially(new DeleteAllItemsEvent(this, list, len, i));
			}
		}
	}

	public int append(Item item)
	{
		return appendHelper(item);
	}

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

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

	public int size()
	{
		return count;
	}

	public Item get(int itemIndex)
	{
		int error;
		Item result;
		error = 0;
		synchronized(lock)
		{
			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(GraphicsClipRestricted render, ScrollBar horzScrollBar, ScrollBar vertScrollBar)
	{
		int tx = render.getTranslateX();
		int ty = render.getTranslateY();
		int cl = render.getClipX();
		int ct = render.getClipY();
		int cw = render.getClipWidth();
		int ch = render.getClipHeight();
		int i;
		Item[] list;
		Item item;
		synchronized(lock)
		{
			for(list = items, i = count; i-- > 0; )
			{
				if((item = list[i]).isVisible())
				{
					render.reset();
					render.translate(tx, ty);
					render.restrictClipRect(cl, ct, cw, ch);
					render.translate(item.getLeft(), item.getTop());
					render.restrictClipRect(0, 0, item.getWidth(), item.getHeight());
					item.onPaint(render);
				}
			}
		}
	}

	void onShow()
	{
		super.onShow();
		int viewportWidth = getClientWidth();
		ScrollBar horz = getHorzScrollBar();
		ScrollBar vert = getVertScrollBar();
		synchronized(lock)
		{
			if(needPlaceItems)
			{
				needPlaceItems = false;
				place(viewportWidth, horz, vert);
			}
			showAll(horz.getPosition(), vert.getPosition(), viewportWidth, getClientHeight());
			focus();
		}
	}

	void onHide()
	{
		synchronized(lock)
		{
			defocus();
			showAll(0, 0, 0, 0);
		}
		super.onHide();
	}

	void onSizeChanged(int width, int height)
	{
		ScrollBar horz;
		ScrollBar vert;
		label0:
		{
			if(firstSizeChanged)
			{
				firstSizeChanged = false;
				break label0;
			}
			synchronized(lock)
			{
				place(width, horz = getHorzScrollBar(), vert = getVertScrollBar());
				showAll(horz.getPosition(), vert.getPosition(), width, height);
			}
		}
		super.onSizeChanged(width, height);
	}

	void onScroll(ScrollBar horzScrollBar, ScrollBar vertScrollBar)
	{
		synchronized(lock)
		{
			showAll(horzScrollBar.getPosition(), vertScrollBar.getPosition(),
					getClientWidth(), getClientHeight());
		}
	}

	void onKeyPressed(int key, int charCode)
	{
		int direction;
		if((direction = keyToDirection(key)) != DIR_NONE)
		{
			synchronized(lock)
			{
				traverse(direction);
			}
			return;
		}
		if(keyPressedHandled(key, charCode))
		{
			return;
		}
		super.onKeyPressed(key, charCode);
	}

	void onKeyRepeated(int key, int charCode)
	{
		int direction;
		if((direction = keyToDirection(key)) != DIR_NONE)
		{
			synchronized(lock)
			{
				traverse(direction);
			}
			return;
		}
		if(keyRepeatedHandled(key, charCode))
		{
			return;
		}
		super.onKeyRepeated(key, charCode);
	}

	void onKeyReleased(int key)
	{
		if(keyToDirection(key) != DIR_NONE || keyReleasedHandled(key))
		{
			return;
		}
		super.onKeyReleased(key);
	}

	void onClientPointerPressed(int x, int y, int button)
	{
		int next;
		Item nextItem;
		synchronized(lock)
		{
			if((next = getItemIndexUnderPointer(x, y)) >= 0)
			{
				focus(next);
				(nextItem = items[next]).onPointerPressed(
						x - nextItem.getLeft(), y - nextItem.getTop(), button);
			}
		}
	}

	void onClientPointerDragged(int x, int y)
	{
		int curr;
		Item currItem;
		synchronized(lock)
		{
			if((curr = focusedIndex) >= 0 && curr < count)
			{
				(currItem = items[curr]).onPointerDragged(
						x - currItem.getLeft(), y - currItem.getTop());
			}
		}
	}

	void onClientPointerReleased(int x, int y, int button)
	{
		int curr;
		Item currItem;
		synchronized(lock)
		{
			if((curr = focusedIndex) >= 0 && curr < count)
			{
				(currItem = items[curr]).onPointerReleased(
						x - currItem.getLeft(), y - currItem.getTop(), button);
			}
		}
	}

	void onCommandAction(Command command)
	{
		if(commandActionHandled(command))
		{
			return;
		}
		super.onCommandAction(command);
	}

	boolean isFocused(Item item)
	{
		boolean result;
		int curr;
		synchronized(lock)
		{
			result = (curr = focusedIndex) >= 0 && curr < count && items[curr] == item;
		}
		return result;
	}

	CommandEnumeration getCommands()
	{
		int curr;
		Object lock;
		CommandEnumeration result;
		if((lock = this.lock) == null)
		{
			return super.getCommands();
		}
		synchronized(lock)
		{
			result = (curr = focusedIndex) < 0 || curr >= count ? super.getCommands() :
					new ItemCommandEnumerator(super.getCommands(), items[curr].getCommands());
		}
		return result;
	}

	final void setCurrent(Item item, Display display)
	{
		display.setCurrent(this);
		display.callSeriallyEvent(this, new FocusItemEvent(this, item));
	}

	final void notifyPlaceItems()
	{
		if(stayPlaceItems)
		{
			return;
		}
		stayPlaceItems = true;
		callSerially(helper);
	}

	final void notifyItemStateChanged(Item item)
	{
		ItemStateListener listener;
		if((listener = this.listener) == null)
		{
			return;
		}
		callSerially(new ItemStateChangedEvent(listener, item));
	}

	final void focusItem(Item item)
	{
		int next;
		synchronized(lock)
		{
			if((next = indexOf(item)) >= 0)
			{
				if(isReallyShown())
				{
					focus(next);
				} else
				{
					focusedIndex = next;
				}
			}
		}
	}

	final void placeItems()
	{
		int viewportWidth = getClientWidth();
		ScrollBar horz = getHorzScrollBar();
		ScrollBar vert = getVertScrollBar();
		synchronized(lock)
		{
			place(viewportWidth, horz, vert);
			if(isReallyShown())
			{
				showAll(horz.getPosition(), vert.getPosition(), viewportWidth, getClientHeight());
				traverse();
			}
		}
	}

	private void defocus()
	{
		int curr;
		if((curr = focusedIndex) < 0 || curr >= count)
		{
			return;
		}
		try
		{
			items[curr].onTraverseOut();
		}
		catch(RuntimeException e)
		{
			e.printRealStackTrace();
		}
	}

	private void focus()
	{
		int viewportWidth;
		int viewportHeight;
		int curr;
		Item currItem;
		if((curr = focusedIndex) < 0 || curr >= count)
		{
			return;
		}
		viewportWidth = getClientWidth();
		viewportHeight = getClientHeight();
		currItem = items[curr];
		try
		{
			currItem.onTraverseNeedStayOn(
					DIR_NONE, viewportWidth, viewportHeight, getVisibleRectangle(currItem, false,
					DIR_NONE, viewportWidth, viewportHeight));
		}
		catch(RuntimeException e)
		{
			e.printRealStackTrace();
		}
		updateCommands();
		callSeriallyPaintScreen(CLIENT);
	}

	private void focus(int next)
	{
		int viewportWidth;
		int viewportHeight;
		int curr;
		Item[] list = items;
		Item nextItem;
		if((curr = focusedIndex) == next)
		{
			return;
		}
		if(curr >= 0 && curr < count)
		{
			try
			{
				list[curr].onTraverseOut();
			}
			catch(RuntimeException e)
			{
				e.printRealStackTrace();
			}
		}
		viewportWidth = getClientWidth();
		viewportHeight = getClientHeight();
		nextItem = list[next];
		focusedIndex = next;
		try
		{
			nextItem.onTraverseNeedStayOn(
					DIR_NONE, viewportWidth, viewportHeight, getVisibleRectangle(nextItem, false,
					DIR_NONE, viewportWidth, viewportHeight));
		}
		catch(RuntimeException e)
		{
			e.printRealStackTrace();
		}
		updateVisibleRectangle(nextItem, viewportWidth, viewportHeight);
		updateCommands();
		callSeriallyPaintScreen(CLIENT);
	}

	private void traverse()
	{
		int viewportWidth;
		int viewportHeight;
		int curr;
		Item currItem;
		if((curr = focusedIndex) < 0 || curr >= count)
		{
			return;
		}
		viewportWidth = getClientWidth();
		viewportHeight = getClientHeight();
		currItem = items[curr];
		try
		{
			currItem.onTraverseNeedStayOn(
					DIR_NONE, viewportWidth, viewportHeight, getVisibleRectangle(currItem, false,
					DIR_NONE, viewportWidth, viewportHeight));
		}
		catch(RuntimeException e)
		{
			e.printRealStackTrace();
		}
		updateVisibleRectangle(currItem, viewportWidth, viewportHeight);
		updateCommands();
		callSeriallyPaintScreen(CLIENT);
	}

	private void traverse(int direction)
	{
		boolean needStayOn;
		int viewportWidth;
		int viewportHeight;
		int curr;
		int next;
		Item[] list;
		Item currItem;
		Item nextItem;
		if((curr = focusedIndex) < 0 || curr >= count)
		{
			return;
		}
		viewportWidth = getClientWidth();
		viewportHeight = getClientHeight();
		currItem = (list = items)[curr];
		try
		{
			needStayOn = currItem.onTraverseNeedStayOn(
					direction, viewportWidth, viewportHeight, getVisibleRectangle(currItem, true,
					direction, viewportWidth, viewportHeight));
		}
		catch(RuntimeException e)
		{
			e.printRealStackTrace();
			needStayOn = false;
		}
		if(needStayOn || (next = getItemIndexForFocus(curr, direction)) < 0)
		{
			updateVisibleRectangle(currItem, viewportWidth, viewportHeight);
			return;
		}
		try
		{
			currItem.onTraverseOut();
		}
		catch(RuntimeException e)
		{
			e.printRealStackTrace();
		}
		nextItem = list[next];
		focusedIndex = next;
		try
		{
			nextItem.onTraverseNeedStayOn(
					direction, viewportWidth, viewportHeight, getVisibleRectangle(nextItem, false,
					direction, viewportWidth, viewportHeight));
		}
		catch(RuntimeException e)
		{
			e.printRealStackTrace();
		}
		updateVisibleRectangle(nextItem, viewportWidth, viewportHeight);
		updateCommands();
		callSeriallyPaintScreen(CLIENT);
	}

	private void showAll(int viewportLeft, int viewportTop, int viewportWidth, int viewportHeight)
	{
		boolean visible;
		boolean formVisible;
		boolean realVisible;
		int i;
		int tmp;
		int left;
		int top;
		int right;
		int bottom;
		Item[] list;
		Item item;
		formVisible = viewportWidth > 0 && viewportHeight > 0;
		right = (left = viewportLeft) + viewportWidth;
		bottom = (top = viewportTop) + viewportHeight;
		for(list = items, i = count; i-- > 0; )
		{
			item = list[i];
			visible = formVisible &&
					(tmp = item.getLeft()) + item.getWidth() > left && tmp < right &&
					(tmp = item.getTop()) + item.getHeight() > top && tmp < bottom;
			realVisible = item.isVisible();
			item.setVisible(visible);
			if(realVisible && !visible)
			{
				item.setVisible(false);
				try
				{
					item.onHide();
				}
				catch(RuntimeException e)
				{
					e.printRealStackTrace();
				}
			}
			if(visible && !realVisible)
			{
				item.setVisible(true);
				try
				{
					item.onShow();
				}
				catch(RuntimeException e)
				{
					e.printRealStackTrace();
				}
			}
		}
	}

	private void place(int containerClientWidth, ScrollBar horzScrollBar, ScrollBar vertScrollBar)
	{
		int c;
		int p;
		int i;
		int j;
		int len;
		int tmp;
		int left;
		int top;
		int width;
		int iwidth;
		int rowTop;
		int rowHeight;
		int rowFinish;
		int leftWidth;
		int centerIndex;
		int centerLeft;
		int centerWidth;
		int rightIndex;
		int rightLeft;
		int rightWidth;
		int formWidth;
		int formHeight;
		Item[] list;
		Item item;
		stayPlaceItems = false;
		/* вычисляем размеры каждого элемента */
		for(list = items, i = len = count; i-- > 0; )
		{
			list[i].computeSizes(containerClientWidth);
		}
		formWidth = 0;
		formHeight = 0;
		for(rowTop = 0, i = 0; i < len; rowTop += rowHeight, i = rowFinish)
		{
			/* вычисляем индекс конечного элемента на текущей строке элементов */
			for(width = 0, j = i; j < len; width += iwidth, j++)
			{
				/* Очередной элемент будет перенесён на новую строку,
				 * если выполняется хотя бы одно из следующих условий:
				 * 1. У очередного элемента выставлен принудительный перенос на новую строку
				 *    перед ним (Item.LAYOUT_NEWLINE_BEFORE);
				 * 2. У предыдущего элемента выставлен принудительный перенос на новую строку
				 *    после него (Item.LAYOUT_NEWLINE_AFTER);
				 * 3. Очередной элемент не помещается на текущей строке;
				 * 4. Предыдущий элемент выравнен по центру (Item.LAYOUT_CENTER), а
				 *    очередной элемент выравнен по левому краю (Item.LAYOUT_LEFT);
				 * 5. Предыдущий элемент выравнен по павому краю (Item.LAYOUT_RIGHT), а
				 *    очередной элемент не выравнен по правому краю.
				 * Условия проверяются в указанном порядке.
				 */
				iwidth = (item = list[j]).getWidth();
				if(j > i && (((c = item.getRealLayout()) & Item.LAYOUT_NEWLINE_BEFORE) != 0 ||
						((p = list[j - 1].getRealLayout()) & Item.LAYOUT_NEWLINE_AFTER) != 0 ||
						width + iwidth > containerClientWidth ||
						(p &= Item.LAYOUT_CENTER) == Item.LAYOUT_CENTER && (
						(c &= Item.LAYOUT_CENTER) == Item.LAYOUT_LEFT || c == 0) ||
						p == Item.LAYOUT_RIGHT && c != Item.LAYOUT_RIGHT))
				{
					break;
				}
			}
			/* вычисляем высоту в пикселах текущей строки элементов */
			for(rowFinish = j, rowHeight = 0, j = i; j < rowFinish; j++)
			{
				if(rowHeight < (tmp = list[j].getHeight()))
				{
					rowHeight = tmp;
				}
			}
			/* корректируем размеры формы */
			if(formWidth < width)
			{
				formWidth = width;
			}
			formHeight += rowHeight;
			/* распределяем элементы текущей строки элементов следующим образом:
			 * LAYOUT_DEFAULT
			 * LAYOUT_LEFT            LAYOUT_CENTER     LAYOUT_RIGHT
			 * +-----+-----+-----+    +-----+-----+    +-----+-----+    текущая
			 * | [0] | [1] | [2] | -- | [3] | [4] | -- | [5] | [6] | -- строка
			 * +-----+-----+-----+    +-----+-----+    +-----+-----+    элементов
			 * |    leftWidth    |    |centerWidth|    | rightWidth|
			 * +-----------------+    +-----------+    +-----------+
			 * |      centerLeft      |                |           |
			 * +----------------------+                |           |
			 * |               rightLeft               |           |
			 * +---------------------------------------+           |
			 * |               containerClientWidth                |
			 * +---------------------------------------------------+
			 * i = 0
			 * rowFinish = 7
			 * centerIndex = 3
			 * rightIndex = 5
			 */
			for(centerIndex = rowFinish, leftWidth = 0, j = i; j < rowFinish; j++)
			{
				switch((item = list[j]).getRealLayout() & Item.LAYOUT_CENTER)
				{
				default://LAYOUT_LEFT:
					leftWidth += item.getWidth();
					continue;
				case Item.LAYOUT_CENTER:
				case Item.LAYOUT_RIGHT:
					centerIndex = j;
					break;
				}
				break;
			}
			for(rightIndex = rowFinish, centerWidth = 0; j < rowFinish; j++)
			{
				switch((item = list[j]).getRealLayout() & Item.LAYOUT_CENTER)
				{
				case Item.LAYOUT_CENTER:
					centerWidth += item.getWidth();
					continue;
				case Item.LAYOUT_RIGHT:
				default://LAYOUT_LEFT:
					rightIndex = j;
					break;
				}
				break;
			}
			for(rightWidth = 0; j < rowFinish; j++)
			{
				rightWidth += list[j].getWidth();
			}
			centerLeft = Math.max(leftWidth + ((containerClientWidth -
					(leftWidth + centerWidth + rightWidth)) / 2), leftWidth);
			rightLeft = Math.max(containerClientWidth - rightWidth,
					leftWidth + centerWidth);
			/* устанавливаем положение каждого элемента текущей строки элементов */
			for(left = 0, j = i; j < rowFinish; j++)
			{
				if(j == centerIndex)
				{
					left = centerLeft;
				}
				if(j == rightIndex)
				{
					left = rightLeft;
				}
				switch(((item = list[j]).getRealLayout() & Item.LAYOUT_VCENTER) >> 4)
				{
				default://LAYOUT_TOP >> 4:
					top = rowTop;
					break;
				case Item.LAYOUT_VCENTER >> 4:
					top = rowTop + ((rowHeight - item.getHeight()) >> 1);
					break;
				case Item.LAYOUT_BOTTOM >> 4:
					top = rowTop + rowHeight - item.getHeight();
					break;
				}
				item.setPosition(left, top);
				left += item.getWidth();
			}
		}
		horzScrollBar.setVisible(formWidth > containerClientWidth);
		horzScrollBar.setRange(formWidth);
		vertScrollBar.setVisible(true);
		vertScrollBar.setRange(formHeight);
	}

	private void updateVisibleRectangle(Item item, int viewportWidth, int viewportHeight)
	{
		int l;
		int t;
		int x;
		int y;
		int tmp;
		int vrw;
		int vrh;
		int[] visibleRectangle = rect;
		ScrollBar horz = getHorzScrollBar();
		ScrollBar vert = getVertScrollBar();
		visibleRectangle[WIDTH] = vrw = (tmp = visibleRectangle[WIDTH]) < 0 ? 0 :
				(tmp > viewportWidth ? viewportWidth : tmp);
		visibleRectangle[HEIGHT] = vrh = (tmp = visibleRectangle[HEIGHT]) < 0 ? 0 :
			(tmp > viewportHeight ? viewportHeight : tmp);
		x = horz.getPosition() - (l = item.getLeft());
		y = vert.getPosition() - (t = item.getTop());
		x = x > (tmp = visibleRectangle[LEFT]) ? tmp :
				(x < (tmp += vrw - viewportWidth) ? tmp : x);
		y = y > (tmp = visibleRectangle[TOP]) ? tmp :
				(y < (tmp += vrh - viewportHeight) ? tmp : y);
		horz.setPosition(x + l);
		vert.setPosition(y + t);
	}

	private boolean keyPressedHandled(int key, int charCode)
	{
		boolean result;
		int curr;
		Item currItem;
		synchronized(lock)
		{
			label0:
			{
				if((curr = focusedIndex) < 0 || curr >= count ||
						(!(currItem = items[curr]).keyHandling(key)))
				{
					result = false;
					break label0;
				}
				try
				{
					currItem.onKeyPressed(key, charCode);
				}
				catch(RuntimeException e)
				{
					e.printRealStackTrace();
				}
				result = true;
			}
		}
		return result;
	}

	private boolean keyRepeatedHandled(int key, int charCode)
	{
		boolean result;
		int curr;
		Item currItem;
		synchronized(lock)
		{
			label0:
			{
				if((curr = focusedIndex) < 0 || curr >= count ||
						(!(currItem = items[curr]).keyHandling(key)))
				{
					result = false;
					break label0;
				}
				try
				{
					currItem.onKeyRepeated(key, charCode);
				}
				catch(RuntimeException e)
				{
					e.printRealStackTrace();
				}
				result = true;
			}
		}
		return result;
	}

	private boolean keyReleasedHandled(int key)
	{
		boolean result;
		int curr;
		Item currItem;
		synchronized(lock)
		{
			label0:
			{
				if((curr = focusedIndex) < 0 || curr >= count ||
						(!(currItem = items[curr]).keyHandling(key)))
				{
					result = false;
					break label0;
				}
				try
				{
					currItem.onKeyReleased(key);
				}
				catch(RuntimeException e)
				{
					e.printRealStackTrace();
				}
				result = true;
			}
		}
		return result;
	}

	private boolean commandActionHandled(Command command)
	{
		boolean result;
		int curr;
		Item currItem;
		synchronized(lock)
		{
			label0:
			{
				if((curr = focusedIndex) < 0 || curr >= count ||
						(!(currItem = items[curr]).getCommands().hasCommand(command)))
				{
					result = false;
					break label0;
				}
				try
				{
					currItem.onCommandAction(command);
				}
				catch(RuntimeException e)
				{
					e.printRealStackTrace();
				}
				result = true;
			}
		}
		return result;
	}

	private int appendHelper(Item item)
	{
		boolean empty;
		int len;
		int error;
		int result;
		Item[] list;
		if(item == null)
		{
			throw new NullPointerException("Form.append: " +
					"параметр item равен нулевой ссылке.");
		}
		error = 0;
		synchronized(lock)
		{
			label0:
			{
				result = len = count;
				synchronized(getOwningLock())
				{
					label1:
					{
						if(item.getOwner() != null)
						{
							error = 1;
							break label1;
						}
						item.setOwner(this);
					}
				}
				if(error == 1)
				{
					break label0;
				}
				if(len == (list = items).length)
				{
					Array.copy(list, 0, list = items = new Item[(len << 1) + 1], 0, len);
				}
				list[len] = item;
				count = ++len;
				if(empty = len == 1)
				{
					focusedIndex = -1;
				}
				callSerially(new RefocusItemEvent(this, false, null, empty ? item : null));
			}
		}
		if(error == 1)
		{
			throw new IllegalStateException("Form.append: " +
					"параметр item уже принадлежит контейнеру.");
		}
		return result;
	}

	private int indexOf(Item item)
	{
		int i;
		Item[] list;
		for(list = items, i = count; i-- > 0; )
		{
			if(list[i] == item)
			{
				return i;
			}
		}
		return -1;
	}

	private int getItemIndexForFocus(int focusedItemIndex, int direction)
	{
		int i;
		int pos;
		int len;
		int result = -1;
		Item[] list;
		Item current = (list = items)[focusedItemIndex];
		Item item;
		switch(direction)
		{
		case DIR_UP:
			pos = current.getTop();
			for(i = focusedItemIndex; i-- > 0; )
			{
				if((item = list[i]).getTop() + item.getHeight() <= pos)
				{
					result = i;
					break;
				}
			}
			break;
		case DIR_LEFT:
			result = focusedItemIndex - 1;
			break;
		case DIR_RIGHT:
			if(focusedItemIndex < count - 1)
			{
				result = focusedItemIndex + 1;
			}
			break;
		case DIR_DOWN:
			pos = current.getTop() + current.getHeight();
			for(len = count, i = focusedItemIndex + 1; i < len; i++)
			{
				if(list[i].getTop() >= pos)
				{
					result = i;
					break;
				}
			}
			break;
		}
		return result;
	}

	private int getItemIndexUnderPointer(int x, int y)
	{
		int i;
		int tmp;
		Item[] list;
		Item item;
		for(list = items, i = count; i-- > 0; )
		{
			if(x >= (tmp = (item = list[i]).getLeft()) && x < tmp + item.getWidth() &&
					y >= (tmp = item.getTop()) && y < tmp + item.getHeight())
			{
				return i;
			}
		}
		return -1;
	}

	private int[] getVisibleRectangle(Item item, boolean focused,
			int direction, int viewportWidth, int viewportHeight)
	{
		int l;
		int t;
		int l1;
		int t1;
		int tmp;
		int hpos;
		int vpos;
		int itemWidth = item.getWidth();
		int itemHeight = item.getHeight();
		int[] result = rect;
		label0:
		{
			if((!focused) && itemWidth <= viewportWidth && itemHeight <= viewportHeight)
			{
				result[LEFT] = 0;
				result[TOP] = 0;
				result[WIDTH] = itemWidth;
				result[HEIGHT] = itemHeight;
				break label0;
			}
			hpos = getHorzScrollBar().getPosition();
			vpos = getVertScrollBar().getPosition();
			if(focused || direction == DIR_NONE)
			{
				result[LEFT] = l1 = Math.max(l = hpos - item.getLeft(), 0);
				result[TOP] = t1 = Math.max(t = vpos - item.getTop(), 0);
				result[WIDTH] = Math.min(l + viewportWidth, itemWidth) - l1;
				result[HEIGHT] = Math.min(t + viewportHeight, itemHeight) - t1;
				break label0;
			}
			switch(direction)
			{
			case DIR_UP:
				result[LEFT] = tmp = Math.max(hpos - item.getLeft(), 0);
				result[TOP] = Math.max(itemHeight - viewportHeight, 0);
				result[WIDTH] = Math.min(viewportWidth, itemWidth - tmp);
				result[HEIGHT] = Math.min(viewportHeight, itemHeight);
				break;
			case DIR_LEFT:
				result[LEFT] = Math.max(itemWidth - viewportWidth, 0);
				result[TOP] = tmp = Math.max(vpos - item.getTop(), 0);
				result[WIDTH] = Math.min(viewportWidth, itemWidth);
				result[HEIGHT] = Math.min(viewportHeight, itemHeight - tmp);
				break;
			case DIR_RIGHT:
				result[LEFT] = 0;
				result[TOP] = tmp = Math.max(vpos - item.getTop(), 0);
				result[WIDTH] = Math.min(viewportWidth, itemWidth);
				result[HEIGHT] = Math.min(viewportHeight, itemHeight - tmp);
				break;
			case DIR_DOWN:
				result[LEFT] = tmp = Math.max(hpos - item.getLeft(), 0);
				result[TOP] = 0;
				result[WIDTH] = Math.min(viewportWidth, itemWidth - tmp);
				result[HEIGHT] = Math.min(viewportHeight, itemHeight);
				break;
			}
		}
		return result;
	}
}
