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

public final class TextInput extends TextOutputMultilined
		implements CharDestination, Input
{
	private static final class InputCommandHandler extends WeakReference
			implements CommandListener
	{
		InputCommandHandler(CommandListener thisListener)
		{
			super(thisListener);
		}

		public void commandAction(Command command, Displayable screen)
		{
			CommandListener thisListener;
			if((thisListener = (CommandListener) get()) == null)
			{
				return;
			}
			thisListener.commandAction(command, screen);
		}
	}

	public static final char DEFAULT_PASSWORD_CHAR = 0x2022;
	public static final int DEFAULT_CHAR_ARRAY_LENGTH = 1024;
	private static String CLIP_BOARD;
	private static final char[][] CHARACTER_SUBSETS_CHARS;
	private static final String[] CHARACTER_SUBSETS_NAMES;
	private static final Command[] ADDITIONAL_CAPABILITIES_COMMANDS;

	static
	{
		CHARACTER_SUBSETS_CHARS = new char[][] {
				"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray(), (
				"АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ" +
				"абвгдеёжзийклмнопрстуфхцчшщъыьэюя").toCharArray(), (
				"\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407" +
				"\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f" +
				"\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417" +
				"\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f" +
				"\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427" +
				"\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f" +
				"\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437" +
				"\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f" +
				"\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447" +
				"\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f" +
				"\u0450\u0451\u0452\u0453\u0454\u0455\u0456\u0457" +
				"\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f").toCharArray(), (
				"\u0020\u0021\"\u0023\u0024\u0025\u0026\u0027" +
				"\u0028\u0029\u002a\u002b\u002c\u002d\u002e\u002f" +
				"\u003a\u003b\u003c\u003d\u003e\u003f\u0040\u005b" +
				"\\\u005d\u005e\u005f\u0060\u007b\u007c\u007d" +
				"\u007e\u00a1\u00a3\u00a4\u00a5\u00a7\u00bf\u20ac").toCharArray()
		};
		CHARACTER_SUBSETS_NAMES = new String[] {
				"Базовая латиница",
				"Кириллица (русский язык)",
				"Кириллица (вся)",
				"Прочие символы"
		};
		ADDITIONAL_CAPABILITIES_COMMANDS = new Command[] {
				new Command("Вставить символ", Command.SCREEN, 0),
				new Command("Перенос строки", Command.SCREEN, 0),
				new Command("Язык ввода", Command.SCREEN, 0),
				new Command("Скопировать", Command.SCREEN, 0),
				new Command("Вставить", Command.SCREEN, 0)
		};
	}

	public static boolean isConstraintsValid(int constraints)
	{
		return (constraints & CONSTRAINT_MASK) <= 5 && (constraints >>> 16) < 0x40;
	}

	public static boolean isConstraintsMatch(int constraints, String src)
	{
		boolean f;
		int i;
		int length;
		if(!isConstraintsValid(constraints))
		{
			throw new IllegalArgumentException("TextInput.isConstraintsMatch: " +
					"недопустимое значение параметра constraints.");
		}
		if(src == null)
		{
			return true;
		}
		length = src.length();
		switch(constraints & CONSTRAINT_MASK)
		{
		case ANY:
		case EMAILADDR:
		case URL:
			return true;
		case NUMERIC:
			for(i = 0; i < length; i++)
			{
				switch(src.charAt(i))
				{
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					break;
				case '-':
					if(i > 0)
					{
						return false;
					}
					break;
				default:
					return false;
				}
			}
			return true;
		case PHONENUMBER:
			for(i = 0; i < length; i++)
			{
				switch(src.charAt(i))
				{
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
				case '*':
				case '#':
					break;
				case '+':
					if(i > 0)
					{
						return false;
					}
					break;
				default:
					return false;
				}
			}
			return true;
		case DECIMAL:
			f = false;
			for(i = 0; i < length; i++)
			{
				switch(src.charAt(i))
				{
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					break;
				case '.':
					if(f)
					{
						return false;
					}
					f = true;
					break;
				case '-':
					if(i > 0)
					{
						return false;
					}
					break;
				default:
					return false;
				}
			}
			return true;
		}
		return false;
	}

	public static boolean isConstraintsMatch(int constraints, char[] src, int offset, int length)
	{
		boolean f;
		int i;
		int j;
		int lim;
		int len;
		if(!isConstraintsValid(constraints))
		{
			throw new IllegalArgumentException("TextInput.isConstraintsMatch: " +
					"недопустимое значение параметра constraints.");
		}
		if(src == null)
		{
			return true;
		}
		if((lim = offset + length) > (len = src.length) ||
				lim < offset || offset > len || offset < 0)
		{
			throw new IndexOutOfBoundsException("TextInput.isConstraintsMatch: " +
					"индекс выходит из дапазона.");
		}
		switch(constraints & CONSTRAINT_MASK)
		{
		case ANY:
		case EMAILADDR:
		case URL:
			return true;
		case NUMERIC:
			for(j = offset, i = 0; i < length; j++, i++)
			{
				switch(src[j])
				{
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					break;
				case '-':
					if(i > 0)
					{
						return false;
					}
					break;
				default:
					return false;
				}
			}
			return true;
		case PHONENUMBER:
			for(j = offset, i = 0; i < length; j++, i++)
			{
				switch(src[j])
				{
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
				case '*':
				case '#':
					break;
				case '+':
					if(i > 0)
					{
						return false;
					}
					break;
				default:
					return false;
				}
			}
			return true;
		case DECIMAL:
			f = false;
			for(j = offset, i = 0; i < length; j++, i++)
			{
				switch(src[j])
				{
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					break;
				case '.':
					if(f)
					{
						return false;
					}
					f = true;
					break;
				case '-':
					if(i > 0)
					{
						return false;
					}
					break;
				default:
					return false;
				}
			}
			return true;
		}
		return false;
	}

	public static boolean isAdditionalCapabilitiesCommand(Command command)
	{
		int i;
		for(i = ADDITIONAL_CAPABILITIES_COMMANDS.length; i-- > 0; )
		{
			if(ADDITIONAL_CAPABILITIES_COMMANDS[i] == command)
			{
				return true;
			}
		}
		return false;
	}

	public static Command[] getAdditionalCapabilitiesCommands(int constraints,
			boolean lineSeparatorAllowed)
	{
		boolean any;
		int len;
		Command[] result;
		any = (len = constraints & CONSTRAINT_MASK) == ANY || len == EMAILADDR || len == URL;
		switch((constraints >> 16) & 0x03)
		{
		default:
			result = new Command[0];
			break;
		case UNEDITABLE >> 16:
			result = new Command[] {
					ADDITIONAL_CAPABILITIES_COMMANDS[3]
			};
			break;
		case PASSWORD >> 16:
			if(!any)
			{
				result = new Command[] {
						ADDITIONAL_CAPABILITIES_COMMANDS[4]
				};
			}
			else if(lineSeparatorAllowed)
			{
				result = new Command[] {
						ADDITIONAL_CAPABILITIES_COMMANDS[0],
						ADDITIONAL_CAPABILITIES_COMMANDS[1],
						ADDITIONAL_CAPABILITIES_COMMANDS[2],
						ADDITIONAL_CAPABILITIES_COMMANDS[4]
				};
			}
			else
			{
				result = new Command[] {
						ADDITIONAL_CAPABILITIES_COMMANDS[0],
						ADDITIONAL_CAPABILITIES_COMMANDS[2],
						ADDITIONAL_CAPABILITIES_COMMANDS[4]
				};
			}
			break;
		case 0:
			if(!any)
			{
				result = new Command[] {
						ADDITIONAL_CAPABILITIES_COMMANDS[3],
						ADDITIONAL_CAPABILITIES_COMMANDS[4]
				};
			}
			else if(lineSeparatorAllowed)
			{
				Array.copy(ADDITIONAL_CAPABILITIES_COMMANDS, 0, result = new Command[len =
						ADDITIONAL_CAPABILITIES_COMMANDS.length], 0, len);
			}
			else
			{
				result = new Command[] {
						ADDITIONAL_CAPABILITIES_COMMANDS[0],
						ADDITIONAL_CAPABILITIES_COMMANDS[2],
						ADDITIONAL_CAPABILITIES_COMMANDS[3],
						ADDITIONAL_CAPABILITIES_COMMANDS[4]
				};
			}
			break;
		}
		return result;
	}

	private static void fillPasswordChars(char[] dst)
	{
		int i;
		for(i = dst.length; i-- > 0; dst[i] = DEFAULT_PASSWORD_CHAR);
	}

	private static boolean isCapsLockPressed()
	{
		return (((int) MalikSystem.syscall(0L, 0x0051)) & 0x02) != 0;
	}


	public class AdditionalCapabilities extends Object
			implements CommandListener
	{
		private CharList[] screensWithChars;
		private List screenSelectCharset;
		private List screenSelectLanguage;
		private Displayable sourceScreen;
		private CharDestination destination;
		private CommandListener listener;
		private Command back;
		private Command insert;

		public AdditionalCapabilities(CharDestination destination)
		{
			this.screensWithChars = new CharList[CHARACTER_SUBSETS_CHARS.length];
			this.destination = destination;
			this.listener = new InputCommandHandler(this);
		}

		public void commandAction(Command command, Displayable screen)
		{
			List list;
			String content;
			CharList charList;
			Displayable source;
			Display display = MIDletProxy.getInstance().getEmulatorScreen();
			Command[] additional = ADDITIONAL_CAPABILITIES_COMMANDS;
			if(command == additional[0])
			{
				sourceScreen = screen;
				display.setCurrent(getScreenSelectCharset());
				return;
			}
			if(command == additional[1])
			{
				destination.insert(LINE_SEPARATOR);
				return;
			}
			if(command == additional[2])
			{
				sourceScreen = screen;
				display.setCurrent(getScreenSelectLanguage());
				return;
			}
			if(command == additional[3])
			{
				if((content = (TextInput.this).getString()).length() > 0)
				{
					CLIP_BOARD = content;
				}
				return;
			}
			if(command == additional[4])
			{
				destination.insert(CLIP_BOARD);
				return;
			}
			if(screen == (list = screenSelectCharset))
			{
				if(command == List.SELECT_COMMAND)
				{
					display.setCurrent(getScreenWithChars(list.getSelectedIndex()));
					return;
				}
				if(command == back)
				{
					if((source = sourceScreen) != null && source.isSystem())
					{
						display.setCurrent(source);
					} else
					{
						display.hideSystemScreen();
					}
					sourceScreen = null;
				}
				return;
			}
			if(screen == (list = screenSelectLanguage))
			{
				if(command == List.SELECT_COMMAND)
				{
					(TextInput.this).setInputMode(AvailableInputModes.getInputModeAt(
							list.getSelectedIndex()));
					if((source = sourceScreen) != null && source.isSystem())
					{
						display.setCurrent(source);
					} else
					{
						display.hideSystemScreen();
					}
					sourceScreen = null;
					return;
				}
				if(command == back)
				{
					if((source = sourceScreen) != null && source.isSystem())
					{
						display.setCurrent(source);
					} else
					{
						display.hideSystemScreen();
					}
					sourceScreen = null;
				}
				return;
			}
			if(screen instanceof CharList)
			{
				if(command == insert)
				{
					destination.insert((charList = (CharList) screen).getChar(
							charList.getSelectedIndex()));
					return;
				}
				if(command == back)
				{
					display.setCurrent(getScreenSelectCharset());
				}
			}
		}

		private Command getBack()
		{
			Command result;
			if((result = back) == null)
			{
				result = back = new Command("Назад", Command.BACK, 0);
			}
			return result;
		}

		private Command getInsert()
		{
			Command result;
			if((result = insert) == null)
			{
				result = insert = new Command("Вставить", Command.OK, 0);
			}
			return result;
		}

		private List getScreenSelectCharset()
		{
			Command[] commands;
			String title;
			List result;
			if((result = screenSelectCharset) == null)
			{
				title = "Выберите набор символов:";
				commands = new Command[] {
						getBack()
				};
				result = screenSelectCharset = new List(false, title, null,
						commands, List.SELECT_COMMAND, listener,
						Choice.IMPLICIT, CHARACTER_SUBSETS_NAMES, null);
			}
			return result;
		}

		private List getScreenSelectLanguage()
		{
			int i;
			Command[] commands;
			String title;
			List result;
			if((result = screenSelectLanguage) == null)
			{
				title = "Язык ввода";
				commands = new Command[] {
						getBack()
				};
				result = screenSelectLanguage = new List(false, title, null,
						commands, List.SELECT_COMMAND, listener,
						Choice.IMPLICIT, new String[0], null);
				for(i = 0; i < AvailableInputModes.getCount(); i++)
				{
					result.append(AvailableInputModes.getInputModeAt(i).getLocalizedName(), null);
				}
			}
			return result;
		}

		private CharList getScreenWithChars(int index)
		{
			Command[] commands;
			CharList[] screens;
			CharList result;
			String title;
			if((result = (screens = screensWithChars)[index]) == null)
			{
				title = CHARACTER_SUBSETS_NAMES[index];
				commands = new Command[] {
						getBack()
				};
				result = screens[index] = new CharList(false, title, null,
						commands, getInsert(), listener,
						CHARACTER_SUBSETS_CHARS[index]);
			}
			return result;
		}
	}

	private boolean shiftPressed;
	private boolean changed;
	private int caretPosition;
	private int maxLength;
	private int constraints;
	private char[] content;
	private InputMode mode;

	public TextInput(String text, int maxLength, int constraints)
	{
		this(text, maxLength, constraints, DEFAULT_CHAR_ARRAY_LENGTH);
	}

	public TextInput(String text, int maxLength, int constraints, int charArrayLength)
	{
		int len;
		char[] chars;
		char[] content;
		if(text == null)
		{
			text = "";
		}
		if(charArrayLength < DEFAULT_CHAR_ARRAY_LENGTH)
		{
			charArrayLength = DEFAULT_CHAR_ARRAY_LENGTH;
		}
		if(maxLength <= 0)
		{
			throw new IllegalArgumentException("TextInput: " +
					"параметр maxLength может быть только положительным.");
		}
		if(!isConstraintsValid(constraints))
		{
			throw new IllegalArgumentException("TextInput: " +
					"недопустимое значение параметра constraints.");
		}
		if(!isConstraintsMatch(constraints, text))
		{
			throw new IllegalArgumentException("TextInput: " +
					"заданный текст не соответствует заданным ограничениям.");
		}
		if((len = text.length()) > maxLength)
		{
			throw new IllegalArgumentException("TextInput: " +
					"длина текста больше заданной максимальной длины.");
		}
		if(maxLength > charArrayLength)
		{
			maxLength = charArrayLength;
		}
		len = Math.min(len, maxLength);
		chars = new char[charArrayLength];
		content = new char[charArrayLength];
		if((constraints & PASSWORD) != 0)
		{
			fillPasswordChars(chars);
		} else
		{
			text.getChars(0, len, chars, 0);
		}
		text.getChars(0, len, content, 0);
		this.length = len;
		this.chars = chars;
		this.maxLength = maxLength;
		this.constraints = constraints;
		this.content = content;
		this.mode = AvailableInputModes.getSystemInputMode();
	}

	public void clear()
	{
		length = 0;
		caretPosition = 0;
	}

	public void setText(String text)
	{
		setString(text);
	}

	public void append(String data)
	{
		if(data == null)
		{
			return;
		}
		insert(data, this.length);
	}

	public void append(char[] data, int offset, int length)
	{
		if(data == null)
		{
			return;
		}
		insert(data, offset, length, this.length);
	}

	public void append(int data, int radix, boolean upperCase)
	{
		boolean negative;
		int value;
		int digit;
		int len;
		int i;
		char[] c = new char[len = i = 33];
		value = (negative = data < 0) ? data : -data;
		if(radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
		{
			radix = 10;
		}
		do
		{
			c[--i] = (char) ((digit = -(value % radix)) < 10 ?
					digit + '0' : digit + (upperCase ? 'A' - 10 : 'a' - 10));
		} while((value /= radix) != 0);
		if(negative)
		{
			c[--i] = '-';
		}
		insert(c, i, len - i, this.length);
	}

	public void insert(char src)
	{
		int apos;
		int bpos;
		int curLength;
		int newLength;
		int remLength;
		int constraints;
		char[] content;
		if(((constraints = this.constraints) & UNEDITABLE) != 0 ||
				(newLength = (curLength = this.length) + 1) > maxLength)
		{
			return;
		}
		content = this.content;
		bpos = (apos = caretPosition) + 1;
		if((remLength = curLength - apos) > 0)
		{
			Array.copy(content, apos, content, bpos, remLength);
		}
		content[apos] = src;
		if(!isConstraintsMatch(constraints, content, 0, newLength))
		{
			if(remLength > 0)
			{
				Array.copy(content, bpos, content, apos, remLength);
			}
			return;
		}
		if((constraints & PASSWORD) == 0 && newLength > 0)
		{
			Array.copy(content, 0, chars, 0, newLength);
		}
		this.length = newLength;
		this.changed = true;
		this.caretPosition = bpos;
	}

	public void insert(String src)
	{
		int apos;
		int bpos;
		int length;
		int curLength;
		int newLength;
		int remLength;
		int constraints;
		char[] content;
		if((length = src == null ? 0 : src.length()) == 0)
		{
			this.changed = true;
			return;
		}
		if(((constraints = this.constraints) & UNEDITABLE) != 0 ||
				(newLength = (curLength = this.length) + length) > maxLength)
		{
			return;
		}
		content = this.content;
		bpos = (apos = caretPosition) + length;
		if((remLength = curLength - apos) > 0)
		{
			Array.copy(content, apos, content, bpos, remLength);
		}
		src.getChars(0, length, content, apos);
		if(!isConstraintsMatch(constraints, content, 0, newLength))
		{
			if(remLength > 0)
			{
				Array.copy(content, bpos, content, apos, remLength);
			}
			return;
		}
		if((constraints & PASSWORD) == 0 && newLength > 0)
		{
			Array.copy(content, 0, chars, 0, newLength);
		}
		this.length = newLength;
		this.changed = true;
		this.caretPosition = bpos;
	}

	public void insert(String src, int position)
	{
		int apos;
		int bpos;
		int length;
		int curLength;
		int newLength;
		int remLength;
		int constraints;
		char[] content;
		if(src == null)
		{
			throw new NullPointerException("TextInput.insert: " +
					"параметр src равен нулевой ссылке.");
		}
		if((newLength = (curLength = this.length) + (length = src.length())) > maxLength)
		{
			throw new IllegalArgumentException("TextInput.insert: " +
					"длина текста больше заданной максимальной длины.");
		}
		content = this.content;
		bpos = (apos = position < 0 ? 0 : (position > curLength ? curLength : position)) + length;
		if((remLength = curLength - apos) > 0)
		{
			Array.copy(content, apos, content, bpos, remLength);
		}
		if(length > 0)
		{
			src.getChars(0, length, content, apos);
		}
		if(!isConstraintsMatch(constraints = this.constraints, content, 0, newLength))
		{
			if(remLength > 0)
			{
				Array.copy(content, bpos, content, apos, remLength);
			}
			throw new IllegalArgumentException("TextInput.insert: " +
					"заданный текст не соответствует заданным ограничениям.");
		}
		if((constraints & PASSWORD) == 0 && newLength > 0)
		{
			Array.copy(content, 0, chars, 0, newLength);
		}
		this.length = newLength;
	}

	public void insert(char[] src, int offset, int length, int position)
	{
		int lim;
		int len;
		int apos;
		int bpos;
		int curLength;
		int newLength;
		int remLength;
		int constraints;
		char[] content;
		if(src == null)
		{
			throw new NullPointerException("TextInput.insert: " +
					"параметр src равен нулевой ссылке.");
		}
		if((lim = offset + length) > (len = src.length) ||
				lim < offset || offset > len || offset < 0)
		{
			throw new ArrayIndexOutOfBoundsException("TextInput.insert: " +
					"индекс выходит из диапазона.");
		}
		if((newLength = (curLength = this.length) + length) > maxLength)
		{
			throw new IllegalArgumentException("TextInput.insert: " +
					"длина текста больше заданной максимальной длины.");
		}
		content = this.content;
		bpos = (apos = position < 0 ? 0 : (position > curLength ? curLength : position)) + length;
		if((remLength = curLength - apos) > 0)
		{
			Array.copy(content, apos, content, bpos, remLength);
		}
		if(length > 0)
		{
			Array.copy(src, offset, content, apos, length);
		}
		if(!isConstraintsMatch(constraints = this.constraints, content, 0, newLength))
		{
			if(remLength > 0)
			{
				Array.copy(content, bpos, content, apos, remLength);
			}
			throw new IllegalArgumentException("TextInput.insert: " +
					"заданный текст не соответствует заданным ограничениям.");
		}
		if((constraints & PASSWORD) == 0 && newLength > 0)
		{
			Array.copy(content, 0, chars, 0, newLength);
		}
		this.length = newLength;
	}

	public void delete(int offset, int length)
	{
		int lim;
		int len;
		int apos;
		int bpos;
		int curLength;
		int newLength;
		int remLength;
		char[] content;
		if((lim = offset + length) > (len = this.length) ||
				lim < offset || offset > len || offset < 0)
		{
			throw new StringIndexOutOfBoundsException("TextInput.delete: " +
					"индекс выходит из диапазона.");
		}
		curLength = len;
		content = this.content;
		bpos = (apos = offset) + length;
		if((remLength = len - bpos) > 0)
		{
			Array.copy(content, bpos, content, apos, remLength);
		}
		if(caretPosition > (newLength = curLength - length))
		{
			caretPosition = newLength;
		}
		if((constraints & PASSWORD) == 0 && newLength > 0)
		{
			Array.copy(content, 0, chars, 0, newLength);
		}
		this.length = newLength;
	}

	public void setString(String src)
	{
		int newLength;
		int constraints;
		if(src == null)
		{
			src = "";
		}
		if((newLength = src.length()) > maxLength)
		{
			throw new IllegalArgumentException("TextInput.setString: " +
					"длина текста больше заданной максимальной длины.");
		}
		if(!isConstraintsMatch(constraints = this.constraints, src))
		{
			throw new IllegalArgumentException("TextInput.setString: " +
					"заданный текст не соответствует заданным ограничениям.");
		}
		if(newLength > 0)
		{
			src.getChars(0, newLength, content, 0);
		}
		if(caretPosition > newLength)
		{
			caretPosition = newLength;
		}
		if((constraints & PASSWORD) == 0 && newLength > 0)
		{
			src.getChars(0, newLength, chars, 0);
		}
		this.length = newLength;
	}

	public void setChars(char[] src, int offset, int length)
	{
		int lim;
		int len;
		int newLength;
		int constraints;
		char[] tmp;
		if(src == null)
		{
			setString(null);
			return;
		}
		if((lim = offset + length) > (len = src.length) ||
				lim < offset || offset > len || offset < 0)
		{
			throw new ArrayIndexOutOfBoundsException("TextInput.setChars: " +
					"индекс выходит из диапазона.");
		}
		Array.copy(src, offset, tmp = new char[length], 0, length);
		if((newLength = length) > maxLength)
		{
			throw new IllegalArgumentException("TextInput.setChars: " +
					"длина текста больше заданной максимальной длины.");
		}
		if(!isConstraintsMatch(constraints = this.constraints, tmp, 0, newLength))
		{
			throw new IllegalArgumentException("TextInput.setChars: " +
					"заданный текст не соответствует заданным ограничениям.");
		}
		if(newLength > 0)
		{
			Array.copy(tmp, 0, content, 0, newLength);
		}
		if(caretPosition > newLength)
		{
			caretPosition = newLength;
		}
		if((constraints & PASSWORD) == 0 && newLength > 0)
		{
			Array.copy(tmp, 0, chars, 0, newLength);
		}
		this.length = newLength;
	}

	public void setConstraints(int constraints)
	{
		int len;
		int oldConstraints;
		char[] content;
		if((oldConstraints = this.constraints) == constraints)
		{
			return;
		}
		if(!isConstraintsValid(constraints))
		{
			throw new IllegalArgumentException("TextInput.setConstraints: " +
					"недопустимое значение параметра constraints.");
		}
		if(!isConstraintsMatch(constraints, content = this.content, 0, len = length))
		{
			length = len = 0;
			caretPosition = 0;
		}
		if((oldConstraints & PASSWORD) == 0 && (constraints & PASSWORD) != 0)
		{
			fillPasswordChars(chars);
		}
		if((oldConstraints & PASSWORD) != 0 && (constraints & PASSWORD) == 0 && len > 0)
		{
			Array.copy(content, 0, chars, 0, len);
		}
		this.constraints = constraints;
	}

	public void setInitialInputMode(String characterSubset)
	{
		mode = AvailableInputModes.getInputMode(characterSubset);
	}

	public int setMaxSize(int maxLength)
	{
		int tmp;
		if(maxLength <= 0)
		{
			throw new IllegalArgumentException("TextInput.setMaxSize: " +
					"недопустимое значение параметра maxLength.");
		}
		if(maxLength > (tmp = content.length))
		{
			maxLength = tmp;
		}
		if(caretPosition > maxLength)
		{
			caretPosition = maxLength;
		}
		if(length > maxLength)
		{
			length = maxLength;
		}
		return this.maxLength = maxLength;
	}

	public int size()
	{
		return length;
	}

	public int getChars(char[] dst)
	{
		int result;
		if(dst == null)
		{
			throw new NullPointerException("TextInput.getChars: " +
					"параметр dst равен нулевой ссылке.");
		}
		if(dst.length < (result = length))
		{
			throw new ArrayIndexOutOfBoundsException("TextInput.getChars: " +
					"параметр dst имеет маленькую длину, не позволяющую уместить все символы.");
		}
		Array.copy(content, 0, dst, 0, result);
		return result;
	}

	public int getConstraints()
	{
		return constraints;
	}

	public int getCaretPosition()
	{
		return caretPosition;
	}

	public int getMaxSize()
	{
		return maxLength;
	}

	public String getString()
	{
		return (new String(content, 0, length)).intern();
	}

	public void keyPressed(int key, int charCode)
	{
		boolean shift;
		int tmp;
		InputMode mode;
		changed = false;
		switch(key)
		{
		case MIDletProxy.KEY_TAB:
			/* Нет реакции на клавишу Tab. */
			return;
		case MIDletProxy.KEY_SHIFT:
		case MIDletProxy.KEY_LSHIFT:
		case MIDletProxy.KEY_RSHIFT:
			/* Нажатие любого Shift. */
			shiftPressed = true;
			return;
		case MIDletProxy.KEY_BACKSPACE:
			/* Стереть символ перед кареткой (если он есть). */
			if((constraints & UNEDITABLE) == 0 && (tmp = caretPosition) > 0)
			{
				changed = true;
				caretPosition = --tmp;
				delete(tmp, 1);
			}
			return;
		case MIDletProxy.KEY_ENTER:
			/* Вставка символа перевода строки '\n' и сдвиг каретки. */
			insert(LINE_SEPARATOR);
			return;
		}
		/* Обработка нажатия алфавитно-цифровых клавиш */
		if((constraints & UNEDITABLE) == 0 && charCode > 0 && (mode = this.mode) != null)
		{
			shift = shiftPressed;
			mode.keyPressedHandle(shift, shift ^ isCapsLockPressed(), key, charCode, this);
		}
	}

	public void keyReleased(int key)
	{
		switch(key)
		{
		case MIDletProxy.KEY_SHIFT:
		case MIDletProxy.KEY_LSHIFT:
		case MIDletProxy.KEY_RSHIFT:
			shiftPressed = false;
			break;
		}
	}

	public void setCaretPosition(int caretPosition)
	{
		int tmp;
		this.caretPosition = caretPosition < 0 ? 0 :
				(caretPosition > (tmp = length) ? tmp : caretPosition);
	}

	public void setInputMode(InputMode mode)
	{
		this.mode = mode == null ? AvailableInputModes.getSystemInputMode() : mode;
	}

	public boolean isChanged()
	{
		return changed;
	}

	public int getCaretLine()
	{
		return getLineAt(caretPosition);
	}

	public InputMode getInputMode()
	{
		return mode;
	}
}
