package modules.text;

import com.one.AbstractFont;
import com.one.BufferedInputStream;
import com.one.ErrScreen;
import com.one.ExtGraphics;
import com.one.FileInputStream;
import com.one.ModuleRegistry;
import com.one.PaintableObject;
import com.one.RandomAccessInputStream;
import com.one.Renderer;
import com.one.ReplaceableInputStream;
import com.one.TextProcessor;
import com.one.file.Connector;
import com.one.file.FileConnection;
import com.vmx.AuxClass;
import com.vmx.BufDataInputStream;
import com.vmx.InputStreamDecoder;
import com.vmx.IntVector;
import com.vmx.Locale;
import com.vmx.OutputStreamEncoder;
import com.vmx.StringEncoder;
import com.vmx.gkcCanvas;
import filemanager.ColorScheme;
import filemanager.GraphicAlert;
import filemanager.MenuListener;
import filemanager.TemplateManager;
import filemanager.cvsContextMenu;
import filemanager.cvsWait;
import filemanager.filesystem;
import filemanager.images;
import filemanager.main;
import filemanager.options;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Vector;
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Choice;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.List;
import javax.microedition.lcdui.TextBox;
import javax.microedition.lcdui.TextField;
import javax.microedition.lcdui.Ticker;
import org.apache.regexp.RE;

/**
 * Класс для просмотра и редактирования текста
 */
public class cvsTextView extends gkcCanvas implements CommandListener, MenuListener
{
	// Чтение и запись текста
	private RandomAccessInputStream rais = null;
	private BufDataInputStream bdis = null;
	private InputStreamDecoder isd = null;
	
	/**
	 * История изменений, вносимых в текст.
	 * Вектор хранит потоки, из которых производится чтение текста.
	 * Первый поток - оригинальный, следующие - потоки с подстановкой,
	 * где каждая подстановка соответствует изменениям в тексте.
	 * Каждый следующий поток в векторе создается на основе предыдущего,
	 * таким образом хранится история изменений с возможностью перемещения по ней.
	 */
	private Vector editHistory = null;
	private int editIndex = -1;
	
	private int offset;
	
	private int enc;
	
	private int scrStart, scrEnd;
	
	// Отрисовка текста и заголовок
	private String caption = "";
	private int clockw, captionw, captionx;
	private Image icon;
	
	private int linesPerScreen;
	
	private int w, h;
	
	private AbstractFont fntText;
	private int fntTextHeight;
	
	private String lines[];
	private int lineposes[];
	
	private int maxStrWidth;
	
	// Полноэкранный режим
	private boolean fsmode;
	private int header, footer;
	
	// Правка текста
	private int selStart;
	private int editStart, editEnd;
	
	private TextBox tb;
	private String edittext;
	private int esclevel;

	private List lstTextCommands;
	
	//private FileConnection fc;
	private boolean fileUsed, newFile;
	private boolean rescanAfterExit;
	private boolean closing;
	private String fname;
	
	/* показывать самую нижнюю, возможно
	   не полностью видимую строку */
	public static final boolean showLastLine = false;
	
	private final Command cmdYes = new Command(Locale.getString(this, Locale.YES_CMD), Command.OK, 1);
	private final Command cmdNo = new Command(Locale.getString(this, Locale.NO_CMD), Command.BACK, 2);

	private final Command cmdOK = new Command(Locale.getString(this, Locale.OK_CMD), Command.OK, 1);
	private final Command cmdOptions = new Command(Locale.getString(this, Locale.OPTIONS_CMD), Command.OK, 2);
	
	private final Command cmdTemplate = new Command(Locale.getString(this, Locale.TEMPLATES_CMD), Command.OK, 2);
	private final Command cmdClear = new Command(Locale.getString(this, Locale.CLEAR_CMD), Command.OK, 3);
	private final Command cmdEscape = new Command(Locale.getString(this, Locale.ESCAPE_CMD), Command.OK, 4);
	private final Command cmdUnescape = new Command(Locale.getString(this, Locale.UNESCAPE_CMD), Command.OK, 5);
	private final Command cmdRegExp = new Command(Locale.getString(this, Locale.REG_EXP_CMD), Command.OK, 6);
	private final Command cmdBack = new Command(Locale.getString(this, Locale.BACK_CMD), Command.BACK, 7);
	private final Command cmdCancel = new Command(Locale.getString(this, Locale.CANCEL_CMD), Command.CANCEL, 8);
	
	private int runpanel;
	
	private int keyHeld;
	private boolean keyReleaseAllowed = false;
	
	private frmSeek seekForm;
	private frmFont fontForm;
	private frmEnc encForm;
	private frmSave saveForm;
	private frmFind findForm;
	private frmRegExp regExpForm;
	
	private GraphicAlert alConfirmSave,
						 alSearchEOF;
	
	private String searchText = "";
	private boolean searchIgnoreCase = true;
	
	private String searchReplaceText = "";
	private boolean searchReplace = false;

	private cvsContextMenu mn;
	
	private List lstEncodings;

	private static cvsTextView instance;

	public static void loadInstance()
	{
		if(instance == null)
		{
			instance = new cvsTextView();
		}
	}

	public static cvsTextView getInstance()
	{
		loadInstance();
		return instance;
	}
	
	/**
	 * Конструктор
	 */
	public cvsTextView()
	{
		rescanAfterExit = false;
		closing = false;
		
		setFullScreenMode(true);
		w = getWidth();
		h = getHeight();
		
		setFont(AbstractFont.getFont(TextOptions.textFontFace, TextOptions.textFontStyle, TextOptions.textFontSize));
				
		selStart = -1;
		
		// инициализация переменных i/o
		rais = null;
		bdis = null;
		isd = null;
		
		seekForm = new frmSeek(this);
		fontForm = new frmFont(this);
		encForm = new frmEnc(this);
		saveForm = new frmSave(this);
		findForm = new frmFind(this);
		regExpForm = new frmRegExp(this);
		
		alConfirmSave = new GraphicAlert(Locale.getString(this, Locale.CONFIRMATION), Locale.getString(this, Locale.CONFIRM_SAVE), images.getAlertIcon(AlertType.CONFIRMATION), AlertType.CONFIRMATION);
		alConfirmSave.setTimeout(Alert.FOREVER);
		alConfirmSave.addCommand(cmdYes);
		alConfirmSave.addCommand(cmdNo);
		alConfirmSave.addCommand(cmdBack);
		alConfirmSave.setCommandListener(this);
		
		alSearchEOF = new GraphicAlert(Locale.getString(this, Locale.MENU_SEARCH), Locale.getString(this, Locale.SEARCH_EOF_QUESTION), images.getAlertIcon(AlertType.CONFIRMATION), AlertType.CONFIRMATION);
		alSearchEOF.setTimeout(Alert.FOREVER);
		alSearchEOF.addCommand(cmdYes);
		alSearchEOF.addCommand(cmdNo);
		alSearchEOF.setCommandListener(this);

		mn = new cvsContextMenu(menuData, menuKeyConfig, menuKeyAllowed);
		mn.setListener(this);
		
		runpanel = -1;
	}
	
	/**
	 * Установить шрифт для текста
	 */
	public void setFont(AbstractFont font)
	{
		TextOptions.textFontFace = font.getFace();
		TextOptions.textFontSize = font.getSize();
		TextOptions.textFontStyle = font.getStyle();
		
		fntText = font;
		fntTextHeight = fntText.getHeight() + 1;
		
		clockw = fntText.stringWidth(AuxClass.getCurrentTime());
		captionw = w - images.iconWidth - clockw - 10;
		
		setFSMode(fsmode);
	}
	
	/**
	 * Установить полноэкранный / не полноэкранный режим
	 */
	public void setFSMode(boolean nm)
	{
		fsmode = nm;
		
		if(fsmode)
		{
			header = footer = 0;
			linesPerScreen = h / fntTextHeight;
			
			maxStrWidth = w - 4;
		}
		else
		{
			header = Math.max(images.iconHeight + 4, fntTextHeight + 1);
			footer = 8;
			
			linesPerScreen = (h - header - footer - 2) / fntTextHeight;
			
			maxStrWidth = w - 7;
		}
		
		//linesPerScreen = (h - header - footer) / fntTextHeight;
		
		/* высота полосы прокрутки */
		slall = h - header - footer;
		
		if(showLastLine)
		{
			linesPerScreen += 1;
		}
		
		lines = new String[linesPerScreen];
		lineposes = new int[linesPerScreen + 1];
		
		if(isd != null)
		{
			try
			{
				bdis.setPosition(scrStart);
				readScreen(true);
			}
			catch(IOException ioe)
			{
				ErrScreen.showErrMsg(18, ioe);
			}
		}
		
		repaint();
	}
		
	protected void initPanels()
	{
		if(runpanel < 0)
		{
			runpanel = main.manager.currentPanel();
		}
		
		main.manager.setCurrent(this, runpanel);
		main.manager.updateAssociation(runpanel, fname);
		main.manager.changePanel(runpanel);
	}
	
	/**
	 * Открыть входной поток
	 */
	public boolean openStream(InputStream is, String caption)
	{
		closeStream();
		
		fileUsed = false;
		newFile = false;
		rescanAfterExit = false;
		
		selStart = -1;
		clearScreen();

		fname = null;
		
		if(caption != null)
		{
			this.caption = caption;
		}
		else
		{
			this.caption = "";
		}

		icon = ModuleRegistry.getIcon(AuxClass.getFileExtension(caption));

		if(icon == null)
		{
			icon = ModuleRegistry.getIcon("txt");
		}

		initPanels();
		
		int bmpos = options.getBookMark(this.caption);
		
		try
		{
			rais = new BufferedInputStream(is);
			
			enc = InputStreamDecoder.detectEncoding(rais, options.textEnc);
			offset = rais.getPosition();
			rais.reset(); // сигнатуру потом пропускаем отдельно (если она есть)
			
			editHistory = new Vector();
			editHistory.addElement(rais);
			editIndex = 0;
			
			bdis = new BufDataInputStream(rais);
			isd = new InputStreamDecoder(bdis, enc, offset);
			
			if(bmpos < offset)
			{
				bmpos = offset;
			}
			
			bdis.setPosition(bmpos);
			readScreen(true);
			repaint();
		}
		catch(Exception e)
		{
			ErrScreen.showErrMsg(19, e);
			return false;
		}
		
		return true;
	}
	
	/**
	 * Осознать, в какой кодировке is, записать кодировку в enc,
	 * позиционировать is на начало текста и вернуть это смещение
	 */
	/*
	public int detectEncoding(InputStream is) throws IOException
	{
		enc = "windows-1251";
		
		int off = 0;
		
		is.mark(10);
		
		if(is.available() >= 3 && is.read() == 0xEF && is.read() == 0xBB && is.read() == 0xBF) // UTF-8 BOM signature
		{
			enc = "UTF-8";
			off = 3;
		}
		else
		{
			is.reset();
		}
		
		return off;
	}
	*/
	
	/**
	 * Открыть файл через FileConnection
	 */	
	public boolean openFile(String filename)
	{
		closeStream();
		
		fileUsed = true;
		newFile = false;
		rescanAfterExit = false;
		
		selStart = -1;
		clearScreen();
		
		this.fname = filename;
		caption = fname.substring(fname.lastIndexOf('/') + 1);

		icon = ModuleRegistry.getIcon(AuxClass.getFileExtension(caption));

		if(icon == null)
		{
			icon = images.getIcon(images.iFile);
		}

		initPanels();
		
		int bmpos = options.getBookMark(caption);
		
		try
		{
			FileConnection fc = (FileConnection)Connector.open("file:///" + fname, Connector.READ);
			
			rais = new FileInputStream(fc); // BufferedInputStream(fc.openInputStream());
			//RandomAccessInputStream is = new BufferedInputStream(fc.openInputStream());
			
			enc = InputStreamDecoder.detectEncoding(rais, options.textEnc);
			offset = rais.getPosition();
			rais.reset(); // сигнатуру потом пропускаем отдельно (если она есть)
			
			editHistory = new Vector();
			editHistory.addElement(rais);
			editIndex = 0;
			
			bdis = new BufDataInputStream(rais);
			isd = new InputStreamDecoder(bdis, enc, offset);
			
			if(bmpos < offset)
			{
				bmpos = offset;
			}
			
			bdis.setPosition(bmpos);
			readScreen(true);
			repaint();
		}
		catch(Exception e)
		{
			ErrScreen.showErrMsg(116, e);
			fileUsed = false;
			
			return false;
		}
		
		return true;
	}
	
	/**
	 * Редактировать новый файл.
	 * Открывается входной поток из массива, на основе которого
	 * впоследствие могут создаваться потоки с заменой.
	 * Массив может содержать BOM сигнатуру (для Юникода),
	 * а также некоторый начальный текст в заданной кодировке.
	 * В этом режиме пункты "Сохранить" и "Сохранить как" эквивалентны.
	 */	
	public boolean openNewFile(String filename, String text, int newEnc)
	{
		closeStream();
		
		fileUsed = true;
		newFile = true;
		rescanAfterExit = true;
		
		selStart = -1;
		clearScreen();
		
		this.fname = filename;
		caption = fname.substring(fname.lastIndexOf('/') + 1);

		icon = ModuleRegistry.getIcon(AuxClass.getFileExtension(caption));

		if(icon == null)
		{
			icon = images.getIcon(images.iFile);
		}

		initPanels();
		
		try
		{
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			OutputStreamEncoder ose = new OutputStreamEncoder(baos, newEnc);
			
			if(text != null)
			{
				for(int i = 0; i < text.length(); i++)
				{
					ose.writeChar(text.charAt(i));
				}
			}
			
			baos.flush();
			
			rais = new BufferedInputStream(baos.toByteArray());
			
			ose.close();
			ose = null;
			baos = null;
			
			enc = newEnc;
			offset = 0;
						
			editHistory = new Vector();
			editHistory.addElement(rais);
			editIndex = 0;
			
			bdis = new BufDataInputStream(rais);
			isd = new InputStreamDecoder(bdis, enc, offset);
			
			bdis.setPosition(offset);
			readScreen(true);
			
			editText();
		}
		catch(Exception e)
		{
			ErrScreen.showErrMsg(117, e);
			fileUsed = false;
			
			return false;
		}
		
		return true;
	}
	
	/**
	 * Закрыть входной поток
	 */
	public void closeStream()
	{
		try
		{
			if(isd != null)
			{
				options.setBookMark(caption, scrStart);

				rais = (RandomAccessInputStream)editHistory.elementAt(0);
				FileConnection fc = null;

				if(rais instanceof FileInputStream)
				{
					fc = ((FileInputStream)rais).getBaseConnection();
				}
				
				isd.close();
				isd = null;
				bdis = null;
				rais = null;

				if(fc != null)
				{
					fc.close();
				}
				
				editHistory = null;
				editIndex = -1;
			}
		}
		catch(Exception e)
		{
			ErrScreen.showErrMsg(20, e);
		}
	}
	
	/**
	 * Прочитать строку из потока
	 */
	public String readString(boolean wordwrap)
	{
		try
		{
			return fntText.readString(isd, maxStrWidth, wordwrap);
		}
		catch(Exception e)
		{
			ErrScreen.showErrMsg(21, e);
			return null;
		}
	}
	
	/**
	 * Прочитать строку из потока НАЗАД
	 */
	public String readStringBack(boolean wordwrap)
	{
		try
		{
			return fntText.readStringBack(isd, maxStrWidth, wordwrap);
		}
		catch(Exception e)
		{
			ErrScreen.showErrMsg(22, e);
			return null;
		}
	}
	
	/*
	// Отладочная функция
	void printposes ()
	{
		System.out.print ("lineposes:");
		for (int i = 0; i < linesPerScreen+1; i++)
			System.out.print (" " + lineposes [i]);
		System.out.println ();
	}*/
	
	/**
	 * Прочитать экран из потока
	 */
	public void readScreen(boolean wordWrap)
	{
		try
		{
			for(int i = 0; i < linesPerScreen; i++)
			{
				lineposes[i] = bdis.getPosition();
				lines[i] = readString(wordWrap);
			}
			
			lineposes[linesPerScreen] = bdis.getPosition();
			
			scrStart = lineposes[0];
			scrEnd = lineposes[linesPerScreen];
		}
		catch(Exception ioe)
		{
			ErrScreen.showErrMsg(23, ioe);
		}
	}
	
	/**
	 * Очистить экран
	 */
	public void clearScreen()
	{
		for(int i = 0; i < linesPerScreen; i++)
		{
			lineposes[i] = 0;
			lines[i] = null;
		}
		
		lineposes[linesPerScreen] = 0;
	}
	
	/**
	 * Перейти строчкой выше
	 */
	public void lineUp(boolean repaints)
	{
		try
		{
			if(scrStart > offset)
			{
				bdis.setPosition(scrStart);
				
				scrEnd = lineposes[linesPerScreen] = lineposes[linesPerScreen - 1];
				
				for(int i = linesPerScreen - 1; i > 0; i--)
				{
					lines[i] = lines[i - 1];
					lineposes[i] = lineposes[i - 1];
				}
				
				lines[0] = readStringBack(true);
				
				scrStart = lineposes[0] = bdis.getPosition();
				
				bdis.setPosition(scrEnd);
			}
		}
		catch(IOException ioe)
		{
			ErrScreen.showErrMsg(24, ioe);
		}

		if(repaints)
		{
			repaint();
		}
	}
	
	/**
	 * Перейти строчкой ниже
	 */
	public void lineDown(boolean repaints)
	{
		if(scrEnd < bdis.getCapacity())
		{
			scrStart = lineposes[1];
			
			for(int i = 0; i < linesPerScreen - 1; i++)
			{
				lines[i] = lines[i + 1];
				lineposes[i] = lineposes[i + 1];
			}
			
			lineposes[linesPerScreen - 1] = lineposes[linesPerScreen];
			
			lines[linesPerScreen - 1] = readString(true);
			
			lineposes[linesPerScreen] = scrEnd = bdis.getPosition();
		}

		if(repaints)
		{
			repaint();
		}
	}
	
	/**
	 * Перейти экраном выше
	 */
	public void screenUp()
	{
		try
		{
			bdis.setPosition(scrStart);
			
			for(int i = 0; i < linesPerScreen; i++)
			{
				readStringBack(true);
			}
			
			readScreen(true);
		}
		catch(Exception e)
		{
			ErrScreen.showErrMsg(26, e);
		}
		
		repaint();
	}
	
	/**
	 * Перейти экраном ниже
	 */
	public void screenDown()
	{
		try
		{
			if(scrEnd < bdis.getCapacity())
			{
				readScreen(true);
			}
		}
		catch(Exception e)
		{
			ErrScreen.showErrMsg(27, e);
		}
		
		repaint();
	}
	
	/**
	 * Проверка возможности редактирования текста.
	 * Редактировать можно только текст, открытый из файла.
	 */
	public boolean editPossible()
	{
		return fileUsed;
	}
	
	/**
	 * Проверка возможности непосредственного сохранения текста.
	 * Это возможно только если в текст внесены изменения.
	 * Что, в свою очередь, возможно только если текст открыт из файла.
	 */
	public boolean savePossible()
	{
		return editIndex > 0;
	}
	
	/**
	 * Начать редактирование выделенного текста или текущего экрана
	 */
	public void editText()
	{
		if(!editPossible())
		{
			return;
		}
		
		if(selStart < 0)
		{
			editText(scrStart, scrEnd);
		}
		else
		{
			start = selStart;
			end = scrEnd;
			
			selStart = -1;
			
			if(end < start)
			{
				start ^= end;
				end ^= start;
				start ^= end;
			}
			
			if(end > start)
			{
				editText(start, end);
			}
			else
			{
				repaint();
			}
		}
	}
	
	/**
	 * Редактировать кусок текста с start до end
	 */
	public void editText(int start, int end)
	{
		editStart = start;
		editEnd = end;
		
		edittext = "";
		
		// Читаем то, что будем редактировать
		try
		{
			bdis.setPosition(editStart);
			
			byte[] bs = new byte[editEnd - editStart];
			
			int rl = bdis.read(bs);

			if(rl > 0)
			{
				edittext = StringEncoder.decodeString(bs, 0, rl, enc);
			}

//			if(TextViewOptions.escapedStrings)
//			{
//				edittext = TextProcessor.escapeString(edittext);
//			}
		}
		catch(Exception e)
		{
			ErrScreen.showErrMsg(29, e);
			edittext = null;
			return;
		}
		
		// Запихиваем это в TextBox

		tb = new TextBox(TextOptions.textBoxTitle == 0 ? caption : null, "", edittext.length() + TextOptions.textBufSize, TextField.ANY);
		int maxsize = tb.getMaxSize();

		if(edittext.length() > maxsize)
		{
			edittext = edittext.substring(0, maxsize);
			editEnd = editStart + StringEncoder.getEncodedLength(edittext, enc);
		}

		tb.setString(edittext);

		tb.addCommand(cmdOK);
		tb.addCommand(cmdOptions);

//		tb.addCommand(cmdTemplate);
//		tb.addCommand(cmdClear);
//		tb.addCommand(cmdEscape);
//		tb.addCommand(cmdUnescape);
//		tb.addCommand(cmdRegExp);
//		tb.addCommand(cmdCancel);

		tb.setCommandListener(this);
		
		if(TextOptions.textBoxTitle == 1)
		{
			tb.setTicker(new Ticker(caption));
		}

		lstTextCommands = new List(cmdOptions.getLabel(), Choice.IMPLICIT);

		lstTextCommands.append(cmdTemplate.getLabel(), null);
		lstTextCommands.append(cmdClear.getLabel(), null);
		lstTextCommands.append(cmdEscape.getLabel(), null);
		lstTextCommands.append(cmdUnescape.getLabel(), null);
		lstTextCommands.append(cmdRegExp.getLabel(), null);
		//lstTextCommands.append(cmdBack.getLabel(), null);

		lstTextCommands.addCommand(cmdBack);
		lstTextCommands.addCommand(cmdCancel);
		lstTextCommands.setCommandListener(this);
		
		// А TextBox - на экран
		main.dsp.setCurrent(tb);
	}
	
	/*
	 * Завершение редактирования текста, пока без сохранения
	 */
	public void returnFromEditing()
	{
		if(tb == null)
		{
			return;
		}
		
		main.dsp.setCurrent(this);

		edittext = tb.getString();

		if(esclevel > 0)
		{
			edittext = TextProcessor.unescapeString(edittext);
		}

		replaceText(edittext, editStart, editEnd);

		readScreen(true);
		repaint();
	}
	
	public void replaceText(String newtext, int start, int end)
	{
		try
		{
			ReplaceableInputStream ris = new ReplaceableInputStream(rais);
			ris.setReplace(StringEncoder.encodeString(newtext, enc), start, end);
			
			if(editHistory.size() <= ++editIndex)
			{
				// мы в конце списка изменений - добавляем новый элемент
				editHistory.addElement(ris);
			}
			else
			{
				// мы в середине списка - обновляем текущий элемент
				// и удаляем все, что после него
				editHistory.setSize(editIndex + 1);
				editHistory.setElementAt(ris, editIndex);
			}
			
			rais = ris;
			bdis.setInputStream(rais);
			bdis.setPosition(scrStart);
		}
		catch(Exception e)
		{
			ErrScreen.showErrMsg(79, e);
		}
	}
	
	public boolean undoPossible()
	{
		return editIndex > 0;
	}
	
	public boolean redoPossible()
	{
		return editIndex < editHistory.size() - 1;
	}
	
	public void undo()
	{
		if(undoPossible())
		{
			try
			{
				rais = (RandomAccessInputStream)editHistory.elementAt(--editIndex);
				
				bdis.setInputStream(rais);
				bdis.setPosition(scrStart);
			}
			catch(Exception e)
			{
				ErrScreen.showErrMsg(80, e);
			}
			
			readScreen(true);
			
			repaint();
		}
	}
	
	public void redo()
	{
		if(redoPossible())
		{
			try
			{
				rais = (RandomAccessInputStream)editHistory.elementAt(++editIndex);
				
				bdis.setInputStream(rais);
				bdis.setPosition(scrStart);
			}
			catch(Exception e)
			{
				ErrScreen.showErrMsg(81, e);
			}
			
			readScreen(true);
			
			repaint();
		}
	}
	
	public void showSave()
	{
		if(fileUsed)
		{
			saveForm.setFileName(fname);
		}
		else
		{
			saveForm.setFileName(main.currentPath + caption);
		}
		
		saveForm.setEncoding(enc);
		saveForm.setUseBOM(TextOptions.useUTFBOM);
		
		main.dsp.setCurrent(saveForm);
	}
	
	public void saveFile(String newFileName, int newEnc, boolean useBOM)
	{
		if(!newFile && newFileName.equals(fname)) // сохраняем в старый файл
		{	
			saveEditText();
			
			if(newEnc != enc)
			{
				options.textEnc = newEnc;

				try
				{
					rais = (RandomAccessInputStream)editHistory.elementAt(0);

					FileConnection src;

					if(rais instanceof FileInputStream)
					{
						src = ((FileInputStream)rais).getBaseConnection();
					}
					else
					{
						src = (FileConnection)Connector.open("file:///" + fname);
					}

					closeStream();

					// если меняется что-то, связанное с юникодом...
					if(enc < 0 || newEnc < 0 || (offset != 0) != useBOM)
					{
						String tempname = Connector.createTempFile(fname, false);

						FileConnection dst = (FileConnection)Connector.open("file:///" + tempname);
						dst.create();

						StringEncoder.transcode(src, dst, enc, newEnc, useBOM);

						String name = src.getName();
						src.delete();
						src.close();

						dst.rename(name);
						dst.close();

						Connector.releaseTempFile(tempname);
					}
					else
					{
						StringEncoder.transcode(src, src, enc, newEnc, useBOM);

						src.close();
					}

					// открываем заново
					openFile(fname);
				}
				catch(Exception e)
				{
					ErrScreen.showErrMsg(66, e);
					return;
				}
			}
		}
		else // сохраняем в новый файл
		{
			try
			{
				int index = newFileName.lastIndexOf('/') + 1;
				
				if(index > 0)
				{
					String path = newFileName.substring(0, index);
					
					if(!filesystem.isFileExist(path))
					{
						filesystem.makeNewDir(path);
					}
				}
				
				FileConnection fc = (FileConnection)Connector.open("file:///" + newFileName);
				
				if(fc.exists())
				{
					fc.truncate(0);
				}
				else
				{
					fc.create();
				}
				
				OutputStream os = fc.openOutputStream();
				OutputStreamEncoder ose = new OutputStreamEncoder(os, newEnc);
				
				TextOptions.useUTFBOM = useBOM;
				
				if(useBOM)
				{
					ose.writeBOM();
				}
				
				if(true) // newEnc >= 0 || !useBOM)
				{
					options.textEnc = newEnc;
				}
				
				bdis.setPosition(offset);
				
				while(isd.available() > 0)
				{
					ose.writeChar(isd.readChar());
				}
				
				os.flush();
				ose.close();
				fc.close();
				
				// возвращаемся на offset назад, чтобы не пропустить потом
				// первые offset байт из-за сохраненной закладки
				if(scrStart >= offset)
				{
					scrStart -= offset;
				}
				
				// если сохранили в UTF-16, то устанавливаем
				// поток на четную позицию
				if((newEnc == StringEncoder.ENC_UTF16N || newEnc == StringEncoder.ENC_UTF16L) && scrStart % 2 != 0)
				{
					scrStart--;
				}
				
				openFile(newFileName);
				
				rescanAfterExit = true;
			}
			catch(Exception e)
			{
				ErrScreen.showErrMsg(82, e);
				return;
			}
		}

		if(closing)
		{
			close();
		}
		else
		{
			main.dsp.setCurrent(this);
		}
	}
	
	/**
	 * Сохранить изменения, внесенные в текст.
	 * Текст сохраняется в том виде, в котором он отображается.
	 * Отмененные изменения не записываются в файл,
	 * и после сохранения информация о них теряется.
	 */
	public void saveEditText()
	{
		if(editIndex <= 0) // нечего сохранять
		{
			return;
		}

		// если редактировали новый файл, то
		// в файловой системе он еще не существует
		if(newFile)
		{
			showSave();
			return;
		}
		
		// устанавливаем закладку,
		// чтобы потом текст открылся на том же месте,
		// где он был открыт до редактирования
		options.setBookMark(caption, scrStart);
		
		try
		{
			rais = (RandomAccessInputStream)editHistory.elementAt(0);

			FileConnection fc;

			if(rais instanceof FileInputStream)
			{
				fc = ((FileInputStream)rais).getBaseConnection();
			}
			else
			{
				fc = (FileConnection)Connector.open("file:///" + fname);
			}

			// берем последний поток с подстановкой
			ReplaceableInputStream ris = (ReplaceableInputStream)editHistory.elementAt(editIndex);

			// записываем подстановки в файл, так или иначе
			// исходный поток закрывается там же автоматически
			fc = AuxClass.updateFileData(fc, ris, null);
			
//			// последовательно записываем в файл данные замены
//			for(int i = 1; i <= editIndex; i++)
//			{
//				ris = (ReplaceableInputStream)editHistory.elementAt(i);
//				AuxClass.updateFileData(fc, ris.getReplace(), ris.getReplaceStart(), ris.getReplaceEnd());
//			}
			
			fc.close();
			
			// открываем заново
			int oldstart = editStart;
			
			openFile(fname);
			
			bdis.setPosition(oldstart);
		}
		catch(Exception e)
		{
			ErrScreen.showErrMsg(30, e);
		}
		
		if(!closing)
		{
			main.showMessage(Locale.getString(this, Locale.DONE), Locale.getString(this, Locale.SAVED), AlertType.INFO, 1500, this);
		}
		
		readScreen(true);
		repaint();
	}
	
	/**
	 * Прервать редактирование, если оно идёт
	 */
	public void breakEditing()
	{
		if(tb == null)
		{
			return;
		}
		
		main.dsp.setCurrent(this);
		
		repaint();
		
		tb = null;
		lstTextCommands = null;
	}
	
	/**
	 * Функция отрисовки
	 */
	 
	private int slall, slpos, slend;
	private int start, end;
	
	public void paint(ExtGraphics g)
	{
		images.drawVGradient(g, ColorScheme.colors[ColorScheme.back1], ColorScheme.colors[ColorScheme.back2], 0, 0, w, h, options.ditherGradients);

		// Рисуем сам текст

		//g.setColor(Colors.colors[Colors.hlfore]);
		//g.drawLine(maxStrWidth + 2, header, maxStrWidth + 2, h - footer);

		g.setColor(ColorScheme.colors[ColorScheme.fore]);
		g.setFont(fntText);

		for(int i = 0; i < linesPerScreen; i++)
		{
			if(lines[i] != null)
			{
				g.drawString(lines[i], 2, header + 2 + i * fntTextHeight, Graphics.LEFT | Graphics.TOP);
				//g.drawLine(2 + fntText.stringWidth(lines[i]), header + 2 + i * fntTextHeight, 2 + fntText.stringWidth(lines[i]), header + 2 + (i + 1) * fntTextHeight);
			}
		}
		
		if(!fsmode)
		{
			// Рисуем заголовок
			//g.setColor (Colors.back);
			//g.fillRect (0, 0, w, header);
			
			/* значок текста */
			//images.drawIcon(g, images.iText, 2, (header - images.iconHeight) / 2);

			if(icon != null)
			{
				g.drawImage(icon, 2, (header - images.iconHeight) / 2, Graphics.LEFT | Graphics.TOP);
			}
			
			/* заголовок */
			captionx = options.alterClockPos ? (images.iconWidth + clockw + 8) : (images.iconWidth + 4);
			
			g.setColor(ColorScheme.colors[ColorScheme.fore]);
			g.setClip(captionx, 0, captionw, header);
			g.drawString(caption, captionx, (header - fntTextHeight) / 2, Graphics.LEFT | Graphics.TOP);
			
			/* часы */
			g.setColor(ColorScheme.colors[ColorScheme.altfore]);
			g.setClip(0, 0, w, h);
			
			if(options.alterClockPos)
			{
				g.drawString(AuxClass.getCurrentTime(), images.iconWidth + 4, (header - fntTextHeight) / 2, Graphics.LEFT | Graphics.TOP);
			}
			else
			{
				g.drawString(AuxClass.getCurrentTime(), w - 2, (header - fntTextHeight) / 2, Graphics.RIGHT | Graphics.TOP);
			}
			
			/* линии */
			g.setColor(ColorScheme.colors[ColorScheme.mdborder]);
			g.drawLine(0, header - 1, w, header - 1); // header = 20; footer = 8;
			g.drawLine(0, h - footer, w, h - footer);
			
			// Рисуем "подписи" к софт-кнопкам (крестик и "...")
			g.setColor(ColorScheme.colors[ColorScheme.fore]);
			
			g.drawLine(w - footer + 2, h - footer + 2, w - footer + 6, h - footer + 6);
			g.drawLine(w - footer + 6, h - footer + 2, w - footer + 2, h - footer + 6);
			
			//g.drawString("...", 3, h - fntTextHeight - 1, Graphics.LEFT | Graphics.TOP);
			g.drawLine(3, h - footer / 2 - 1, 3, h - footer / 2 - 1);
			g.drawLine(5, h - footer / 2 - 1, 5, h - footer / 2 - 1);
			g.drawLine(7, h - footer / 2 - 1, 7, h - footer / 2 - 1);

			g.setClip(w - 3, header, 3, h - header - footer);
			g.setColor(ColorScheme.colors[ColorScheme.sbline]);
		
			// Рисуем полосу прокрутки
			g.drawLine(w - 2, header, w - 2, h - footer);
			
			if(selStart >= 0)
			{
				start = selStart;
				end = scrEnd;
				
				if(end < start)
				{				
					start ^= end;
					end ^= start;
					start ^= end;
				}
				
				try
				{
					slpos = header + slall * start / bdis.getCapacity();
					slend = header + slall * end / bdis.getCapacity();
				}
				catch(Exception e)
				{
					slpos = header;
					slend = header + slall;
				}
				
				if(slend > h - footer)
				{
					slend = h - footer;
				}
				
				g.setColor(ColorScheme.colors[ColorScheme.hlfore]);
			}
			else
			{
				try
				{
					slpos = header + slall * scrStart / bdis.getCapacity();
					slend = header + slall * scrEnd / bdis.getCapacity();
				}
				catch(Exception e)
				{
					slpos = header;
					slend = header + slall;
				}
				
				if(slend > h - footer)
				{
					slend = h - footer;
				}

				g.setColor(ColorScheme.colors[ColorScheme.sbfore]);
			}
			
			/* ползунок на полосе прокрутки */
			g.drawLine(w - 1, slpos, w - 1, slend);
			g.drawLine(w - 3, slpos, w - 3, slend);
			g.drawLine(w - 2, slpos - 1, w - 2, slend + 1);
		}
	}
	
	/**
	 * Функция обработки команд
	 */
	public void commandAction(Command c, Displayable d)
	{
		if(d == tb)
		{
			if(c == cmdOK) // учет изменений и выход из редактора
			{
				returnFromEditing();
			}
			else if(c == cmdBack) // выход из редактора без сохранения
			{
				breakEditing();
			}
			else if(c == cmdOptions)
			{
				main.dsp.setCurrent(lstTextCommands);
			}
			else if(c == cmdClear) // очистка
			{
				tb.setString("");
			}
			else if(c == cmdTemplate)
			{
				TemplateManager.showTemplateList(tb, tb);
			}
			else if(c == cmdEscape)
			{
				//tb.setString(TextProcessor.escapeString(tb.getString()));

				lstEncodings = new List(Locale.getString(this, Locale.ENCODING_CMD), List.IMPLICIT, StringEncoder.encnames, null);
				lstEncodings.insert(0, StringEncoder.ENC_NAME_UTF8, null);
				lstEncodings.setSelectedIndex(enc >= 0 ? enc + 1 : 0, true);

				lstEncodings.addCommand(cmdBack);
				lstEncodings.setCommandListener(this);

				main.dsp.setCurrent(lstEncodings);
			}
			else if(c == cmdUnescape)
			{
				edittext = TextProcessor.unescapeString(tb.getString());
				tb.setString(edittext);

				if(esclevel > 0)
				{
					esclevel--;
				}
			}
			else if(c == cmdRegExp)
			{
				main.dsp.setCurrent(regExpForm);
			}
		}
		else if(d == lstTextCommands)
		{
			main.dsp.setCurrent(tb);

			if(c == List.SELECT_COMMAND)
			{
				switch(lstTextCommands.getSelectedIndex())
				{
					case 0:
						commandAction(cmdTemplate, tb);
						break;

					case 1:
						commandAction(cmdClear, tb);
						break;

					case 2:
						commandAction(cmdEscape, tb);
						break;

					case 3:
						commandAction(cmdUnescape, tb);
						break;

					case 4:
						commandAction(cmdRegExp, tb);
						break;

					case 5:
						commandAction(cmdCancel, tb);
						break;
				}
			}
			else if(c == cmdCancel)
			{
				commandAction(cmdBack, tb);
			}
		}
		else if(d == seekForm)
		{
			if(c.getCommandType() == Command.OK)
			{
				try
				{
					int n = seekForm.getLineNum();
					
					if(n > 0)
					{
						isd.gotoLineNum(n);
					}
					else
					{
						n = seekForm.getValue() * bdis.getCapacity() / 100;
						
						if(n < offset)
						{
							n = offset;
						}
						
						bdis.setPosition(n);
					}
					
					readScreen(true);
				}
				catch(IOException ioe)
				{
					ErrScreen.showErrMsg(33, ioe);
				}
			}
			
			main.dsp.setCurrent(this);
		}
		else if(d == fontForm)
		{
			if(c.getCommandType() == Command.OK)
			{
				setFont(fontForm.getFont());
			}
			
			main.dsp.setCurrent(this);
		}
		else if(d == encForm)
		{
			if(c.getCommandType() == Command.OK)
			{
				int newEnc = encForm.getEncoding();
				
				if(true) // newEnc >= 0 || !TextOptions.useUTFBOM)
				{
					options.textEnc = newEnc;
				}
				
				setEncoding(newEnc);
			}
			
			main.dsp.setCurrent(this);
		}
		else if(d == saveForm)
		{
			switch(c.getCommandType())
			{
				case Command.OK:
					saveFile(saveForm.getFileName(), saveForm.getEncoding(), saveForm.getUseBOM());
					break;
				
				case Command.BACK:
					main.dsp.setCurrent(this);
			}
		}
		else if(d == findForm)
		{
			if(c.getCommandType() == Command.OK)
			{
				searchText = findForm.getText();
				searchReplaceText = findForm.getReplace();
				searchIgnoreCase = findForm.getIgnoreCase();
				
				switch(c.getPriority())
				{
					case 1:
						main.dsp.setCurrent(this);
						searchReplace = false;
						findNext(false);
						break;
					
					case 2:
						main.dsp.setCurrent(this);
						searchReplace = true;
						findNext(false);
						break;
					
					case 3:
						searchReplace = true;
						findReplaceAll();
						break;
				}
			}
			else
			{
				main.dsp.setCurrent(this);
			}
		}
		else if(d == regExpForm)
		{
			if(c.getCommandType() == Command.OK)
			{
				int flags = RE.MATCH_NORMAL;

				if(regExpForm.getFlags(0))
				{
					flags |= RE.MATCH_CASEINDEPENDENT;
				}

				if(regExpForm.getFlags(1))
				{
					flags |= RE.MATCH_MULTILINE;
				}

				RE re = new RE(regExpForm.getText(), flags);

				flags = RE.REPLACE_ALL;

				if(c.getPriority() == 1)
				{
					flags |= RE.REPLACE_FIRSTONLY;
				}

				if(regExpForm.getFlags(2))
				{
					flags |= RE.REPLACE_BACKREFERENCES;
				}

				tb.setString(re.subst(tb.getString(), regExpForm.getReplace(), flags));
			}

			main.dsp.setCurrent(tb);
		}
//		else if(d == lstTemplates)
//		{
//			if(tb == null)
//			{
//				return;
//			}
//
//			if(c == List.SELECT_COMMAND)
//			{
//				try
//				{
//					InputStreamDecoder tempDecoder = InputStreamDecoder.getResourceDecoder("/temp/" + TemplateManager.templates[lstTemplates.getSelectedIndex()] + ".tpl");
//					String temp = "";
//
//					while(tempDecoder.available() > 0)
//					{
//						temp += tempDecoder.readChar();
//					}
//
//					tb.setString(tb.getString() + temp);
//				}
//				catch(Exception e)
//				{
//					ErrScreen.showErrMsg(90, e);
//				}
//			}
//
//			main.dsp.setCurrent(tb);
//		}
		else if(d == lstEncodings)
		{
			if(tb == null)
			{
				return;
			}

			if(c == List.SELECT_COMMAND)
			{
				edittext = TextProcessor.escapeString(esclevel++ == 0 ? edittext : tb.getString(), lstEncodings.getSelectedIndex() - 1);
				tb.setString(edittext);
			}

			main.dsp.setCurrent(tb);
		}
		else if(d instanceof Renderer)
		{
			PaintableObject po = ((Renderer)d).getCurrent();
			
			if(po == alConfirmSave)
			{
				if(c == cmdYes)
				{
					saveEditText();
					
					if(!newFile)
					{
						close();
					}
				}
				else if(c == cmdNo)
				{
					close();
				}
				else
				{
					main.dsp.setCurrent(this);
				}
			}
			else if(po == alSearchEOF)
			{
				if(c == cmdYes)
				{
					findNext(true);
				}
				
				main.dsp.setCurrent(this);
			}
		}
	}

	public void menuAction(int string)
	{
		mn.ret();

		switch(string)
		{
			case Locale.SAVE_CMD:
				if(savePossible())
				{
					saveEditText();
				}
				break;

			case Locale.SAVE_AS_CMD:
				showSave();
				break;

			case Locale.EDIT_CMD:
				if(editPossible())
				{
					editText();
				}
				break;

			case Locale.UNDO_CMD:
				if(undoPossible())
				{
					undo();
				}
				break;

			case Locale.REDO_CMD:
				if(redoPossible())
				{
					redo();
				}
				break;

			case Locale.GOTO_CMD:
				showGoto();
				break;

			case Locale.GOTO_START_CMD:
				gotoStart();
				break;

			case Locale.GOTO_END_CMD:
				gotoEnd();
				break;

			case Locale.FIND_CMD:
				showFind();
				break;

			case Locale.FIND_NEXT_CMD:
				findNext(false);
				break;

			case Locale.FONT_CMD:
				showFont();
				break;

			case Locale.ENCODING_CMD:
				if(encPossible())
				{
					showEncoding();
				}
				break;

			case Locale.FULLSCREEN_CMD:
				setFSMode(!fsmode);
				break;

			case Locale.MINIMIZE_CMD:
				try
				{
					main.dsp.setCurrent(null);
				}
				catch(Exception e)
				{
				}
				break;
		}
	}
	
	public void showNotify()
	{
		keyReleaseAllowed = false;
		main.startLightControl(true);
	}

	public void hideNotify()
	{
		main.startLightControl(false);
	}
	
	/**
	 * Обработчик нажатия клавиши
	 */
	public void keyPressed(int keyCode)
	{
		keyCode = remapKey(keyCode);

		keyReleaseAllowed = true;
		
		if(keyCode == KEY_UP || keyCode == KEY_NUM2)
		{
			if(TextOptions.textPageScroll)
			{
				screenUp();
			}
			else
			{
				lineUp(true);
			}
		}
		else if(keyCode == KEY_DOWN || keyCode == KEY_NUM8)
		{
			if(TextOptions.textPageScroll)
			{
				screenDown();
			}
			else
			{
				lineDown(true);
			}
		}
		else if(keyCode == KEY_RIGHT || keyCode == KEY_NUM6)
		{
			if(TextOptions.textPageScroll)
			{
				lineDown(true);
			}
			else
			{
				screenDown();
			}
		}
		else if(keyCode == KEY_LEFT || keyCode == KEY_NUM4)
		{
			if(TextOptions.textPageScroll)
			{
				lineUp(true);
			}
			else
			{
				screenUp();
			}
		}
		else if(keyCode == KEY_DIAL)
		{
			if(selStart < 0)
			{
				selStart = scrStart;
			}
			else
			{
				selStart = -1;
			}
			
			repaint();
		}
		else if(keyCode == KEY_LSK) // редактирование
		{
			showMenu();
		}
		else if(keyCode == KEY_RSK) // выход
		{
			closing = true;
			
			if(savePossible())
			{
				main.dsp.setCurrent(alConfirmSave, this);
			}
			else
			{
				close();
			}
		}
		else if(keyCode == KEY_CANCEL)
		{
			main.manager.minimizePanel();
		}
		else if(keyCode == KEY_FIRE)
		{
			editText();
		}
	}
	
	public void keyRepeated(int keyCode)
	{
		keyCode = remapKey(keyCode);

		if(mn.isKeyAllowed(keyCode))
		{
			if(++keyHeld == 3)
			{
				keyHeld = 0;
				keyReleaseAllowed = false;

				mn.keyAction(keyCode | KEY_HELD);
			}
		}
		else
		{
			keyPressed(translateKey(keyCode));
		}
	}
	
	public void keyReleased(int keyCode)
	{
		keyCode = remapKey(keyCode);

		if(!keyReleaseAllowed)
		{
			return;
		}

		if(mn.isKeyAllowed(keyCode))
		{
			if(keyHeld < 3 && isKeyNum(keyCode))
			{
				mn.keyAction(keyCode);
			}
		}
		
		keyHeld = 0;
	}
	
	public void close()
	{
		clearScreen();
		closeStream();
		
		main.manager.ret();
		
		if(rescanAfterExit) // если новый файл правили
		{
			cvsWait.start(this, runpanel, caption);
			rescanAfterExit = false;
		}
		
		runpanel = -1;
		closing = false;

		if(options.unloadModules)
		{
			instance = null;
		}
	}
	
	/**
	 * Перейти в начало файла.
	 */
	public void gotoStart()
	{
		try
		{
			bdis.setPosition(offset);
			readScreen(true);
		}
		catch(Exception e)
		{
			ErrScreen.showErrMsg(91, e);
		}
		
		repaint();
	}
	
	/**
	 * Перейти в конец файла.
	 */
	public void gotoEnd()
	{
		try
		{
			bdis.setPosition(bdis.getCapacity());
			
			for(int i = 0; i < linesPerScreen; i++)
			{
				readStringBack(false);
			}
			
			readScreen(true);

			while(scrEnd < bdis.getCapacity())
			{
				lineDown(false);
			}
		}
		catch(Exception e)
		{
			ErrScreen.showErrMsg(92, e);
		}
		
		repaint();
	}
	
	/**
	 * Показать форму перехода на произвольную позицию
	 */
	public void showGoto()
	{
		seekForm.setValue(scrStart * 100 / bdis.getCapacity());
		main.dsp.setCurrent(seekForm);
	}
	
	/**
	 * Показать форму поиска
	 */
	public void showFind()
	{
		findForm.setText(searchText);
		findForm.setReplace(searchReplaceText);
		findForm.setIgnoreCase(searchIgnoreCase);
		findForm.setMatchesCount(-1);
		
		main.dsp.setCurrent(findForm);
	}
	
	public void findNext(boolean fromStart)
	{
		if(searchText.length() == 0)
		{
			showFind();
			return;
		}
		
		try
		{
			int index = -1;
			int searchTextLen = StringEncoder.getEncodedLength(searchText, enc);
			
			if(fromStart)
			{
				index = isd.indexOf(searchText, offset, scrStart, searchIgnoreCase);
			}
			else
			{
				index = isd.indexOf(searchText, scrStart + searchTextLen, -1, searchIgnoreCase);
				
				if(index < 0 && scrStart >= offset + searchTextLen)
				{
					main.dsp.setCurrent(alSearchEOF, this);
					return;
				}
			}
			
			if(index >= offset)
			{
				if(searchReplace)
				{
					replaceText(searchReplaceText, index, index + searchTextLen);
				}
				
				bdis.setPosition(index);
				readScreen(true);
				repaint();
			}
		}
		catch(Exception e)
		{
			ErrScreen.showErrMsg(88, e);
		}
	}
	
	public void findReplaceAll()
	{
		if(searchText.length() == 0)
		{
			showFind();
			return;
		}
		
		try
		{
			int index = -1;
			int searchStart = offset;
			int searchTextLen = StringEncoder.getEncodedLength(searchText, enc);
			int replaceTextLen = StringEncoder.getEncodedLength(searchReplaceText, enc);
			int matchesCount = 0;
			
			IntVector indices = new IntVector();
			
			while(searchStart + searchTextLen < bdis.getCapacity())
			{
				index = isd.indexOf(searchText, searchStart, -1, searchIgnoreCase);
				
				if(index < offset)
				{
					break;
				}
				
				//replaceText(searchReplaceText, index, index + searchTextLen);
				//searchStart = index + replaceTextLen + 1;
				
				indices.add(index);
				searchStart = index + searchTextLen + 1;
				
				findForm.setMatchesCount(matchesCount++);
			}
			
			if(indices.size() > 0)
			{
				StringBuffer repbuf = new StringBuffer();
				int replen = 0;
				int repstart = -1;
				int nextindex;
				int i = 0;
				
				while(true)
				{
					index = indices.get(i);
					
					if(repstart < 0)
					{
						repstart = index;
					}
					
					repbuf.append(searchReplaceText);
					replen += replaceTextLen;
					
					if(++i < indices.size())
					{
						nextindex = indices.get(i);
						
						if(replen + nextindex - index - searchTextLen + replaceTextLen <= TextOptions.textBufSize)
						{
							bdis.setPosition(index + searchTextLen);
							
							while(bdis.getPosition() < nextindex)
							{
								repbuf.append(isd.readChar());
							}
							
							replen += nextindex - index - searchTextLen;
						}
						else
						{
							replaceText(repbuf.toString(), repstart, index + searchTextLen);
							
							repbuf.delete(0, repbuf.length());
							replen = 0;
							repstart = -1;
						}
					}
					else
					{
						replaceText(repbuf.toString(), repstart, index + searchTextLen);
						break;
					}
				}
			}
			
			bdis.setPosition(scrStart);
			readScreen(true);
			main.dsp.setCurrent(this);
		}
		catch(Exception e)
		{
			ErrScreen.showErrMsg(95, e);
		}
	}
	
	/**
	 * Показать форму смены шрифта
	 */
	public void showFont()
	{
		fontForm.setFont(fntText);
		main.dsp.setCurrent(fontForm);
	}
	
	/**
	 * Показать форму смены кодировки
	 */
	public void showEncoding()
	{
		encForm.setEncoding(enc);
		main.dsp.setCurrent(encForm);
	}
	
	/**
	 * Переоткрыть поток в новой кодировке
	 */
	public void setEncoding(int encoding)
	{
		// UTF открывается только как UTF,
		// поэтому здесь проверка
		
		if(encPossible())
		{
			try
			{
				enc = encoding;
				
				if(enc < 0)
				{
					rais.setPosition(0);
					
					InputStreamDecoder.detectEncoding(rais, enc);
					offset = rais.getPosition();
				}
				else
				{
					offset = 0;
				}
				
				isd = new InputStreamDecoder(bdis, enc, offset);
				setFSMode(fsmode);
			}
			catch(Exception e)
			{
				ErrScreen.showErrMsg(36, e);
			}
		}
	}
	
	public boolean encPossible()
	{
		return true; // enc >= 0;
	}
	
	public void showMenu()
	{
		menuEnabled[0][1] = savePossible();

		menuEnabled[1][1] = editPossible();
		menuEnabled[1][2] = editPossible();
		menuEnabled[1][3] = undoPossible();
		menuEnabled[1][4] = redoPossible();

		menuEnabled[2][2] = encPossible();

		mn.setEnabledFlags(menuEnabled);
		mn.show(this);
	}

	/**
	 * Данные меню.
	 */
	public static final int[][] menuData =
	{
		{
			Locale.MENU_FILE,
			Locale.SAVE_CMD,
			Locale.SAVE_AS_CMD,
			Locale.MINIMIZE_CMD
		},
		{
			Locale.MENU_EDIT,
			Locale.EDIT_CMD,
			Locale.MARK_CMD,
			Locale.UNDO_CMD,
			Locale.REDO_CMD
		},
		{
			Locale.MENU_VIEW,
			Locale.FONT_CMD,
			Locale.ENCODING_CMD,
			Locale.FULLSCREEN_CMD
		},
		{
			Locale.MENU_SEARCH,
			Locale.FIND_CMD,
			Locale.FIND_NEXT_CMD,
			Locale.GOTO_CMD,
			Locale.GOTO_START_CMD,
			Locale.GOTO_END_CMD
		}
	};

	/**
	 * Данные enabled режимов меню.
	 */
	public static final boolean[][] menuEnabled =
	{
		{ false, false, true, true },
		{ false, false, false, false, false },
		{ false, true, false, true },
		{ false, true, true, true, true, true }
	};

	/**
	 * Кнопки, ассоциированные с пунктами меню.
	 * Может быть цифровая кнопка, *, # или KEY_INVALID.
	 */
	public static int[][] menuKeyConfig =
	{
		{
			KEY_STAR,
			KEY_STAR | KEY_HELD,
			KEY_NUM0 | KEY_HELD
		},
		{
			KEY_NUM5,
			KEY_NUM5 | KEY_HELD,
			KEY_NUM3 | KEY_HELD,
			KEY_NUM9 | KEY_HELD
		},
		{
			KEY_NUM1,
			KEY_NUM7,
			KEY_POUND
		},
		{
			KEY_NUM3,
			KEY_NUM9,
			KEY_NUM0,
			KEY_NUM1 | KEY_HELD,
			KEY_NUM7 | KEY_HELD
		}
	};

	/**
	 * Какие горячие клавиши разрешено назначать для данного типа меню.
	 * Идет *, #, потом цифровые.
	 */
	public static final boolean[] menuKeyAllowed =
	{
		true, true, true, true, false, true, false, true, false, true, false, true
	};
	
//	private static void out(String s)
//	{
//		System.out.println("[cvsTextView] " + s);
//	}
}