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

public class List extends ScrollingScreen
		implements Choice
{
	private static final class Element extends ChoiceElement
	{
		public boolean visible;
		public String truncated;

		Element(boolean selected, Font font, String text, Image icon)
		{
			super(selected, font, text, icon);
		}
	}

	public static final Command SELECT_COMMAND;
	static final int CHECKBOX_WIDTH;
	static final int CHECKBOX_HEIGHT;
	static final Command CHECK_COMMAND;
	private static final int ELEMENT_HEIGHT = 38;
	private static final Command[] SELECT;
	private static final String DEFAULT_EMPTY_LABEL = "(пусто)";

	static
	{
		int i;
		int s = RasterCanvas.getElementOfGUISizes(15, 0, 0);
		SELECT_COMMAND = new Command("Выбрать", Command.OK, 0);
		CHECKBOX_WIDTH = (i = (short) s) <= 0 ? 16 : i;
		CHECKBOX_HEIGHT = (i = s >> 16) <= 0 ? 16 : i;
		CHECK_COMMAND = new Command("\u2713", Command.OK, 0);
		SELECT = new Command[] {
				new Command("Отметить все", Command.SCREEN, 0),
				new Command("Снять все", Command.SCREEN, 0),
				new Command("Переключить все", Command.SCREEN, 0)
		};
	}


	private int type;
	private int fitPolicy;
	private int checkboxFocused;
	private int checkboxPressed;
	private int checkedElementIndex;
	private int focusedElementIndex;
	private int elementsCount;
	private Element[] elements;
	private Command selectCommand;
	private String emptyLabel;
	private Object lock;

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

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

	public List(boolean fullScreenMode, String title, Ticker ticker,
			Command[] commands, Command selectCommand, CommandListener listener,
			int type, String[] elements, Image[] icons)
	{
		this(true, fullScreenMode, title, ticker, commands, selectCommand, listener,
				type, elements, icons, DEFAULT_EMPTY_LABEL);
	}

	public List(boolean fullScreenMode, String title, Ticker ticker,
			Command[] commands, Command selectCommand, CommandListener listener,
			int type, String[] elements, Image[] icons, String emptyLabel)
	{
		this(true, fullScreenMode, title, ticker, commands, selectCommand, listener,
				type, elements, icons, emptyLabel);
	}

	private List(boolean system, boolean fullScreenMode, String title, Ticker ticker,
			Command[] commands, Command selectCommand, CommandListener listener,
			int type, String[] elements, Image[] icons, String emptyLabel)
	{
		super(system, fullScreenMode, title, ticker,
				type == MULTIPLE ? concatCommands(SELECT, commands) : commands,
				elements == null || elements.length == 0 ? null :
				type == IMPLICIT ? selectCommand : CHECK_COMMAND, listener, VERTICAL);
		int i;
		int len;
		String[] lines;
		Element[] list;
		Font font;
		if(type != EXCLUSIVE && type != MULTIPLE && type != IMPLICIT)
		{
			throw new IllegalArgumentException("List: " +
					"недопустимое значение параметра type.");
		}
		if(elements == null)
		{
			throw new NullPointerException("List: " +
					"параметр elements равен нулевой ссылке.");
		}
		Array.copy(elements, 0, lines = new String[len = elements.length], 0, len);
		for(i = len; i-- > 0; )
		{
			if(lines[i] == null)
			{
				throw new NullPointerException("List: " +
						"параметр elements содержит нулевые ссылки.");
			}
		}
		if(icons != null && icons.length != len)
		{
			throw new IllegalArgumentException("List: " +
					"параметр icons имеет длину, отличную от длины параметра elements.");
		}
		getVertScrollBar().setRange(ELEMENT_HEIGHT * len);
		font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM);
		for(list = new Element[i = len]; i-- > 0; )
		{
			list[i] = new Element(type == EXCLUSIVE && i == 0, font,
					lines[i], icons == null ? null : icons[i]);
		}
		this.type = type;
		this.fitPolicy = TEXT_WRAP_DEFAULT;
		this.checkboxFocused = -1;
		this.checkboxPressed = -1;
		this.checkedElementIndex = 0;
		this.focusedElementIndex = 0;
		this.elementsCount = len;
		this.elements = list;
		this.selectCommand = type == IMPLICIT ? selectCommand : null;
		this.emptyLabel = emptyLabel;
		this.lock = new Object();
	}

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

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

	public void setSelectedIndex(int elementIndex, boolean selected)
	{
		int error;
		int checked;
		Element[] list;
		Element elem;
		Element prev;
		error = 0;
		synchronized(lock)
		{
			label0:
			{
				if(elementIndex < 0 || elementIndex >= elementsCount)
				{
					error = 1;
					break label0;
				}
				switch(type)
				{
				case EXCLUSIVE:
					if(elementIndex != (checked = checkedElementIndex))
					{
						checkedElementIndex = elementIndex;
						(elem = (list = elements)[elementIndex]).selected = true;
						(prev = list[checked]).selected = false;
						if(elem.visible || prev.visible)
						{
							callSeriallyPaintScreen(CLIENT);
						}
					}
					break;
				case MULTIPLE:
					if((elem = elements[elementIndex]).selected != selected)
					{
						elem.selected = selected;
						if(elem.visible)
						{
							callSeriallyPaintScreen(CLIENT);
						}
					}
					break;
				case IMPLICIT:
					if(elementIndex != focusedElementIndex)
					{
						focusedElementIndex = elementIndex;
						correctScrollBarPositionToItem();
					}
					break;
				}
			}
		}
		if(error == 1)
		{
			throw new IndexOutOfBoundsException("List.setSelectedIndex: " +
					"параметр elementIndex выходит из диапазона.");
		}
	}

	public void setSelectedFlags(boolean[] selected)
	{
		boolean sel;
		boolean needRepaint;
		int i;
		int len;
		int error;
		int checked;
		int elementIndex;
		Element[] list;
		Element elem;
		Element prev;
		if(selected == null)
		{
			throw new NullPointerException("List.setSelectedFlags: " +
					"параметр selected равен нулевой ссылке.");
		}
		error = 0;
		synchronized(lock)
		{
			label0:
			{
				if(selected.length < (len = elementsCount))
				{
					error = 1;
					break label0;
				}
				switch(type)
				{
				case EXCLUSIVE:
					elementIndex = -1;
					for(i = 0; i < len; i++)
					{
						if(selected[i] == false)
						{
							continue;
						}
						elementIndex = i;
						break;
					}
					if(elementIndex < 0)
					{
						break;
					}
					if(elementIndex != (checked = checkedElementIndex))
					{
						checkedElementIndex = elementIndex;
						(elem = (list = elements)[elementIndex]).selected = true;
						(prev = list[checked]).selected = false;
						if(elem.visible || prev.visible)
						{
							callSeriallyPaintScreen(CLIENT);
						}
					}
					break;
				case MULTIPLE:
					needRepaint = false;
					list = elements;
					for(i = len; i-- > 0; )
					{
						if((elem = list[i]).selected == (sel = selected[i]))
						{
							continue;
						}
						elem.selected = sel;
						if(elem.visible)
						{
							needRepaint = true;
						}
					}
					if(needRepaint)
					{
						callSeriallyPaintScreen(CLIENT);
					}
					break;
				case IMPLICIT:
					elementIndex = -1;
					for(i = 0; i < len; i++)
					{
						if(selected[i] == false)
						{
							continue;
						}
						elementIndex = i;
						break;
					}
					if(elementIndex < 0)
					{
						break;
					}
					if(elementIndex != focusedElementIndex)
					{
						focusedElementIndex = elementIndex;
						correctScrollBarPositionToItem();
					}
					break;
				}
			}
		}
		if(error == 1)
		{
			throw new IllegalArgumentException("List.setSelectedFlags: " +
					"длина параметра selected меньше количества элементов списка.");
		}
	}

	public void setFont(int elementIndex, Font font)
	{
		int error;
		Element elem;
		if(font == null)
		{
			font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM);
		}
		error = 0;
		synchronized(lock)
		{
			label0:
			{
				if(elementIndex < 0 || elementIndex >= elementsCount)
				{
					error = 1;
					break label0;
				}
				if(font != (elem = elements[elementIndex]).font)
				{
					elem.font = font;
					correctTruncated(elem);
					if(elem.visible)
					{
						callSeriallyPaintScreen(CLIENT);
					}
				}
			}
		}
		if(error == 1)
		{
			throw new IndexOutOfBoundsException("List.setFont: " +
					"параметр elementIndex выходит из диапазона.");
		}
	}

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

	public void insert(int elementIndex, String text, Image icon)
	{
		int t;
		int i;
		int len;
		int error;
		Element[] list;
		Element elem;
		if(text == null)
		{
			throw new NullPointerException("List.insert: " +
					"параметр text равен нулевой ссылке.");
		}
		error = 0;
		synchronized(lock)
		{
			label0:
			{
				if(elementIndex < 0 || elementIndex > (len = elementsCount))
				{
					error = 1;
					break label0;
				}
				if(len == (list = elements).length)
				{
					Array.copy(list, 0, list = elements = new Element[(len << 1) + 1], 0, len);
				}
				if((i = len - elementIndex) > 0)
				{
					Array.copy(list, elementIndex, list, elementIndex + 1, i);
				}
				list[elementIndex] = elem = new Element((t = type) == EXCLUSIVE && len == 0,
						Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM),
						text, icon);
				correctTruncated(elem);
				elementsCount = ++len;
				checkboxPressed = -1;
				checkboxFocused = -1;
				if(len > 1)
				{
					if((i = checkedElementIndex) >= elementIndex)
					{
						checkedElementIndex = i + 1;
					}
					if((i = focusedElementIndex) >= elementIndex)
					{
						focusedElementIndex = i + 1;
					}
				} else
				{
					setDefaultCommand(t == IMPLICIT ? selectCommand : CHECK_COMMAND);
				}
				getVertScrollBar().setRange(ELEMENT_HEIGHT * len);
			}
		}
		if(error == 1)
		{
			throw new IndexOutOfBoundsException("List.insert: " +
					"параметр elementIndex выходит из диапазона.");
		}
	}

	public void delete(int elementIndex)
	{
		int i;
		int len;
		int error;
		Element[] list;
		error = 0;
		synchronized(lock)
		{
			label0:
			{
				if(elementIndex < 0 || elementIndex >= (len = elementsCount))
				{
					error = 1;
					break label0;
				}
				list = elements;
				if((i = len - elementIndex - 1) > 0)
				{
					Array.copy(list, elementIndex + 1, list, elementIndex, i);
				}
				list[--len] = null;
				elementsCount = len;
				checkboxPressed = -1;
				checkboxFocused = -1;
				if(len > 0)
				{
					label1:
					{
						if((i = checkedElementIndex) > elementIndex)
						{
							checkedElementIndex = i - 1;
							break label1;
						}
						if(i == elementIndex)
						{
							checkedElementIndex = i = i == len ? i - 1 : i;
							if(type == EXCLUSIVE)
							{
								list[i].selected = true;
							}
						}
					}
					if((i = focusedElementIndex) == elementIndex && i == len || i > elementIndex)
					{
						focusedElementIndex = i - 1;
					}
				} else
				{
					setDefaultCommand(null);
				}
				getVertScrollBar().setRange(ELEMENT_HEIGHT * len);
			}
		}
		if(error == 1)
		{
			throw new IndexOutOfBoundsException("List.delete: " +
					"параметр elementIndex выходит из диапазона.");
		}
	}

	public void deleteAll()
	{
		int i;
		int len;
		Element[] list;
		synchronized(lock)
		{
			if((len = elementsCount) > 0)
			{
				for(list = elements, i = len; i-- > 0; list[i] = null);
				elementsCount = 0;
				checkboxPressed = -1;
				checkboxFocused = -1;
				checkedElementIndex = 0;
				focusedElementIndex = 0;
				setDefaultCommand(null);
				getVertScrollBar().setRange(0);
			}
		}
	}

	public boolean isSelected(int elementIndex)
	{
		boolean result = false;
		int error = 0;
		synchronized(lock)
		{
			label0:
			{
				if(elementIndex < 0 || elementIndex >= elementsCount)
				{
					error = 1;
					break label0;
				}
				switch(type)
				{
				case EXCLUSIVE:
					result = checkedElementIndex == elementIndex;
					break;
				case MULTIPLE:
					result = elements[elementIndex].selected;
					break;
				case IMPLICIT:
					result = focusedElementIndex == elementIndex;
					break;
				}
			}
		}
		if(error == 1)
		{
			throw new IndexOutOfBoundsException("List.isSelected: " +
					"параметр elementIndex выходит из диапазона.");
		}
		return result;
	}

	public int append(String text, Image icon)
	{
		int t;
		int len;
		int result;
		Element[] list;
		Element elem;
		if(text == null)
		{
			throw new NullPointerException("List.append: " +
					"параметр text равен нулевой ссылке.");
		}
		synchronized(lock)
		{
			if((len = elementsCount) == (list = elements).length)
			{
				Array.copy(list, 0, list = elements = new Element[(len << 1) + 1], 0, len);
			}
			list[result = len] = elem = new Element((t = type) == EXCLUSIVE && len == 0,
					Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM),
					text, icon);
			correctTruncated(elem);
			elementsCount = ++len;
			checkboxPressed = -1;
			checkboxFocused = -1;
			if(len == 1)
			{
				setDefaultCommand(t == IMPLICIT ? selectCommand : CHECK_COMMAND);
			}
			getVertScrollBar().setRange(ELEMENT_HEIGHT * len);
		}
		return result;
	}

	public int size()
	{
		return elementsCount;
	}

	public int getFitPolicy()
	{
		return fitPolicy;
	}

	public int getSelectedIndex()
	{
		if(elementsCount == 0)
		{
			return -1;
		}
		switch(type)
		{
		default:
			return -1;
		case EXCLUSIVE:
			return checkedElementIndex;
		case IMPLICIT:
			return focusedElementIndex;
		}
	}

	public int getSelectedFlags(boolean[] selected)
	{
		int f;
		int i;
		int len;
		int error;
		int result;
		Element[] list;
		if(selected == null)
		{
			throw new NullPointerException("List.getSelectedFlags: " +
					"параметр selected равен нулевой ссылке.");
		}
		result = 0;
		error = 0;
		synchronized(lock)
		{
			label0:
			{
				if(selected.length < (len = elementsCount))
				{
					error = 1;
					break label0;
				}
				switch(type)
				{
				case EXCLUSIVE:
					result = len > 0 ? 1 : 0;
					f = checkedElementIndex;
					for(i = len; i-- > 0; selected[i] = i == f);
					break;
				case MULTIPLE:
					result = 0;
					list = elements;
					for(i = len; i-- > 0; )
					{
						if(selected[i] = list[i].selected)
						{
							result++;
						}
					}
					break;
				case IMPLICIT:
					result = len > 0 ? 1 : 0;
					f = focusedElementIndex;
					for(i = len; i-- > 0; selected[i] = i == f);
					break;
				}
			}
		}
		if(error == 1)
		{
			throw new IllegalArgumentException("List.getSelectedFlags: " +
					"длина параметра selected меньше количества элементов списка.");
		}
		return result;
	}

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

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

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

	public void setSelectCommand(Command selectCommand)
	{
		boolean f;
		if(type != IMPLICIT)
		{
			return;
		}
		synchronized(lock)
		{
			f = false;
			if(this.selectCommand != selectCommand)
			{
				this.selectCommand = selectCommand;
				f = elementsCount > 0;
			}
		}
		if(!f)
		{
			return;
		}
		setDefaultCommand(selectCommand);
	}

	void paint(Graphics render, ScrollBar horzScrollBar, ScrollBar vertScrollBar)
	{
		int t;
		int i;
		int len;
		int top;
		int bottom;
		int pressed;
		int focused;
		int clientWidth;
		int leftMarginCheckbox;
		int leftMarginIcon;
		int leftMarginText;
		int topMarginCheckbox;
		int topMarginElement;
		Element[] list;
		Element elem;
		Image icon;
		Font font;
		clientWidth = getClientWidth();
		render.setColor(RasterCanvas.getSystemColor(0x28));
		synchronized(lock)
		{
			if((len = elementsCount) == 0)
			{
				font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_SMALL);
				render.setFont(font);
				render.drawString(truncate(emptyLabel, font, clientWidth),
						clientWidth / 2, 0, Graphics.HCENTER | Graphics.TOP);
			} else
			{
				bottom = (top = vertScrollBar.getPosition()) + vertScrollBar.getPage();
				pressed = checkboxPressed;
				focused = focusedElementIndex;
				leftMarginText = (leftMarginIcon = (leftMarginCheckbox = 3) +
						((t = type) == IMPLICIT ? 0 : CHECKBOX_WIDTH + 3)) + 35;
				topMarginCheckbox = (ELEMENT_HEIGHT - CHECKBOX_HEIGHT) >> 1;
				topMarginElement = 0;
				list = elements;
				for(i = 0; i < len; render.translate(0, ELEMENT_HEIGHT),
						topMarginElement += ELEMENT_HEIGHT, i++)
				{
					if(!((elem = list[i]).visible = topMarginElement < bottom &&
							topMarginElement + ELEMENT_HEIGHT > top))
					{
						continue;
					}
					if(i == focused)
					{
						render.drawElementOfGUI(1, 0, 0, 0, 0, clientWidth, ELEMENT_HEIGHT);
					}
					switch(t)
					{
					case EXCLUSIVE:
						render.drawElementOfGUI(12,
								elem.selected ? 1 : 0, i == pressed ? 1 : 0,
								leftMarginCheckbox, topMarginCheckbox,
								CHECKBOX_WIDTH, CHECKBOX_HEIGHT);
						break;
					case MULTIPLE:
						render.drawElementOfGUI(15,
								elem.selected ? 1 : 0, i == pressed ? 1 : 0,
								leftMarginCheckbox, topMarginCheckbox,
								CHECKBOX_WIDTH, CHECKBOX_HEIGHT);
						break;
					}
					if((icon = elem.icon) != null)
					{
						render.drawStretchRegion(icon, 0, 0,
								icon.getWidth(), icon.getHeight(), 0,
								leftMarginIcon, 3, 32, 32, 0);
					}
					render.setFont(font = elem.font);
					render.drawString(elem.truncated,
							icon != null ? leftMarginText : leftMarginIcon,
							(ELEMENT_HEIGHT - font.getHeight()) >> 1, 0);
				}
			}
		}
	}

	void onSizeChanged(int width, int height)
	{
		int i;
		Element[] list;
		synchronized(lock)
		{
			for(list = elements, i = elementsCount; i-- > 0; correctTruncated(list[i]));
		}
		super.onSizeChanged(width, height);
	}

	void onKeyPressed(int key, int charCode)
	{
		int i;
		MIDletProxy proxy;
		if(key == (proxy = MIDletProxy.getInstance()).getKeyUsedAs(MIDletProxy.DEVICE_KEY_POUND))
		{
			synchronized(lock)
			{
				if((i = focusedElementIndex) >= 0 && i < elementsCount)
				{
					ChoiceElementDetailsScreen.show(proxy.getEmulatorScreen(),
							super.getTitle(), elements[i], this);
				}
			}
			return;
		}
		if(!super.keyHandling(key))
		{
			moveCursor(key);
			return;
		}
		super.onKeyPressed(key, charCode);
	}

	void onKeyRepeated(int key, int charCode)
	{
		if(!super.keyHandling(key))
		{
			moveCursor(key);
			return;
		}
		super.onKeyRepeated(key, charCode);
	}

	void onClientPointerPressed(int x, int y, int button)
	{
		int i;
		synchronized(lock)
		{
			if(button == MIDletProxy.BUTTON_MAIN && (i = y / ELEMENT_HEIGHT) < elementsCount)
			{
				if(type != IMPLICIT && x < CHECKBOX_WIDTH + 6)
				{
					checkboxFocused = i;
					checkboxPressed = i;
				}
				focusedElementIndex = i;
				correctScrollBarPositionToItem();
			}
		}
	}

	void onClientPointerDragged(int x, int y)
	{
		int i;
		int f;
		synchronized(lock)
		{
			if((f = checkboxFocused) >= 0)
			{
				if(checkboxPressed != (checkboxPressed =
						f == (x < CHECKBOX_WIDTH + 6 ? y / ELEMENT_HEIGHT : -1) ? f : -1))
				{
					callSeriallyPaintScreen(CLIENT);
				}
			}
			else if((i = y / ELEMENT_HEIGHT) < elementsCount)
			{
				focusedElementIndex = i;
				correctScrollBarPositionToItem();
			}
		}
	}

	void onClientPointerReleased(int x, int y, int button)
	{
		int f;
		Element[] list;
		Element elem;
		synchronized(lock)
		{
			if(button == MIDletProxy.BUTTON_MAIN)
			{
				f = checkboxPressed;
				checkboxFocused = -1;
				checkboxPressed = -1;
				if(f >= 0 && f < elementsCount)
				{
					switch(type)
					{
					case EXCLUSIVE:
						(list = elements)[checkedElementIndex].selected = false;
						list[checkedElementIndex = f].selected = true;
						break;
					case MULTIPLE:
						(elem = elements[f]).selected = !elem.selected;
						break;
					}
					callSeriallyPaintScreen(CLIENT);
				}
			}
		}
	}

	void onCommandAction(Command command)
	{
		int i;
		int f;
		Element[] list;
		Element elem;
		if(command == SELECT[0])
		{
			synchronized(lock)
			{
				list = elements;
				for(i = elementsCount; i-- > 0; list[i].selected = true);
				callSeriallyPaintScreen(CLIENT);
			}
			return;
		}
		if(command == SELECT[1])
		{
			synchronized(lock)
			{
				list = elements;
				for(i = elementsCount; i-- > 0; list[i].selected = false);
				callSeriallyPaintScreen(CLIENT);
			}
			return;
		}
		if(command == SELECT[2])
		{
			synchronized(lock)
			{
				list = elements;
				for(i = elementsCount; i-- > 0; (elem = list[i]).selected = !elem.selected);
				callSeriallyPaintScreen(CLIENT);
			}
			return;
		}
		if(command == CHECK_COMMAND)
		{
			synchronized(lock)
			{
				if((f = focusedElementIndex) >= 0 && f < elementsCount)
				{
					switch(type)
					{
					case EXCLUSIVE:
						(list = elements)[checkedElementIndex].selected = false;
						list[checkedElementIndex = f].selected = true;
						break;
					case MULTIPLE:
						(elem = elements[f]).selected = !elem.selected;
						break;
					}
					callSeriallyPaintScreen(CLIENT);
				}
			}
			return;
		}
		super.onCommandAction(command);
	}

	private void moveCursor(int key)
	{
		int len;
		MIDletProxy proxy;
		if(key == (proxy = MIDletProxy.getInstance()).getKeyUsedAs(MIDletProxy.DEVICE_KEY_UP))
		{
			synchronized(lock)
			{
				if((len = elementsCount) > 0)
				{
					focusedElementIndex = (focusedElementIndex + len - 1) % len;
					correctScrollBarPositionToItem();
				}
			}
			return;
		}
		if(key == proxy.getKeyUsedAs(MIDletProxy.DEVICE_KEY_DOWN))
		{
			synchronized(lock)
			{
				if((len = elementsCount) > 0)
				{
					focusedElementIndex = (focusedElementIndex + 1) % len;
					correctScrollBarPositionToItem();
				}
			}
		}
	}

	private void correctTruncated(Element elem)
	{
		elem.truncated = Displayable.truncate(elem.text, elem.font, getClientWidth() -
				(type != IMPLICIT ? List.CHECKBOX_WIDTH + 3 : 0) -
				(elem.icon != null ? 35 : 0) - 6);
	}

	private void correctScrollBarPositionToItem()
	{
		int pos;
		int need;
		ScrollBar scrollbar = getVertScrollBar();
		if((pos = scrollbar.getPosition()) > (need = focusedElementIndex * ELEMENT_HEIGHT))
		{
			scrollbar.setPosition(need);
		}
		else if(pos < (need -= scrollbar.getPage() - ELEMENT_HEIGHT))
		{
			scrollbar.setPosition(need);
		}
		callSeriallyPaintScreen(CLIENT);
	}
}
