/*
Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
и других спецификаций для функционирования компактных приложений на языке
Java (мидлетов) в среде программного обеспечения Малик Эмулятор.
Copyright © 2016, 2019 Малик Разработчик
Это свободная программа: вы можете перераспространять ее и/или изменять
ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
общественной лицензии GNU.
Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<https://www.gnu.org/licenses/>.
*/
package malik.emulator.midp;
import java.io.*;
import java.lang.ref.*;
import javax.microedition.lcdui.*;
import malik.emulator.i18n.*;
public class ConsoleScreen extends CustomDisplayable
implements PrintListener
{
private static final class HelpCommand extends ConsoleCommand
{
HelpCommand()
{
super("?",
"Использование:\n" +
" ?\n" +
" ? <команда>\n" +
"Без аргументов – получение списка доступных команд, " +
"с аргументом – получение справки по команде, указанной в аргументе.");
}
public void run(String[] arguments)
{
int i;
int len;
ConsoleCommand[] commands;
ConsoleCommand command;
ConsoleScreen console;
String name;
if((console = getConsoleScreen()) == null)
{
return;
}
if(arguments.length <= 0)
{
/* Список доступных команд */
console.print("Список доступных команд:\n");
commands = console.consoleCommands;
len = console.consoleCommandsCount;
for(i = 0; i < len; i++)
{
if((command = commands[i]).isHide())
{
continue;
}
console.print(" " + command.getName() + '\n');
}
console.print("Попробуйте так же\n" +
" ? <команда>\n" +
"для получения справки по конкретной команде.\n");
console.printHelp();
} else
{
/* Справка по определённой команде */
if((command = console.getConsoleCommand(name = arguments[0])) == null ||
command.isHide())
{
console.print("Неизвестная команда " + name + '\n');
return;
}
console.print(command.getHelp());
console.println();
}
}
}
public static final int DEFAULT_MAXIMUM_LINES = 200;
public static final int DEFAULT_BACKGROUND_COLOR = 0x000000;
public static final int DEFAULT_TITLE_COLOR = 0xc0d0c0;
public static final int DEFAULT_OUTPUT_COLOR = 0xc0c0c0;
public static final int DEFAULT_INPUT_COLOR = 0xffffff;
public static final Font DEFAULT_OUTPUT_FONT;
public static final Font DEFAULT_INPUT_FONT;
public static final Command BACK_COMMAND;
private static final char SPACE = 32;
private static final Command[] COMMANDS;
private static final Command RUN_COMMAND;
private static final String GREETING = "Команда: ";
static
{
Command[] commands;
Command run;
Command back;
Font font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
commands = new Command[] {
run = new Command("Выполнить", Command.OK, 0),
back = new Command("Назад", Command.BACK, 0)
};
DEFAULT_OUTPUT_FONT = font;
DEFAULT_INPUT_FONT = font;
BACK_COMMAND = back;
COMMANDS = commands;
RUN_COMMAND = run;
}
private static String toString(char[] src, int offset, int length)
{
char next;
int i;
int len;
char[] str;
Array.copy(src, offset, str = new char[len = length], 0, len);
for(i = len - 1; i-- > 0; )
{
if(str[i] == '\\' && ((next = str[i + 1]) == '"' || next == 'n' || next == '\\'))
{
Array.copy(str, i + 1, str, i, len-- - i - 1);
if(next == 'n')
{
str[i] = '\n';
}
}
}
return new String(str, 0, len);
}
private int backgroundColor;
private int titleColor;
private int inputColor;
private int inputTop;
private int outputColor;
private int outputLinesCount;
private int outputFirstLineIndex;
private int lastCommandIndex;
private int lastCommandsCount;
private int consoleCommandsCount;
private String[] lastCommands;
private ConsoleCommand[] consoleCommands;
private TextOutputMultilinedColor output;
private TextInput input;
private Font inputFont;
private Font outputFont;
private Reference reference;
public ConsoleScreen(String title, CommandListener listener)
{
this(false, title, listener, DEFAULT_MAXIMUM_LINES, DEFAULT_BACKGROUND_COLOR,
DEFAULT_TITLE_COLOR, DEFAULT_OUTPUT_COLOR, DEFAULT_OUTPUT_FONT,
DEFAULT_INPUT_COLOR, DEFAULT_INPUT_FONT);
}
public ConsoleScreen(boolean fullScreenMode, String title, CommandListener listener,
int maximumLines, int backgroundColor, int titleColor,
int outputColor, Font outputFont, int inputColor, Font inputFont)
{
super(fullScreenMode, title, null, COMMANDS, null, listener);
int t;
int sh;
int infh;
int outfh;
ConsoleCommand[] list;
Reference ref;
Font inf;
Font outf;
sh = super.getHeight();
infh = (inf = inputFont == null ? DEFAULT_INPUT_FONT : inputFont).getHeight();
outfh = (outf = outputFont == null ? DEFAULT_OUTPUT_FONT : outputFont).getHeight();
maximumLines = maximumLines < 10 ? 10 : (maximumLines > 1000 ? 1000 : maximumLines);
this.backgroundColor = backgroundColor;
this.titleColor = titleColor;
this.inputColor = inputColor;
this.inputTop = t = sh - (infh + 1);
this.outputColor = outputColor;
this.outputLinesCount = t / outfh;
this.consoleCommandsCount = 1;
this.lastCommands = new String[50];
this.consoleCommands = list = new ConsoleCommand[9];
this.output = new TextOutputMultilinedColor(0x0400, maximumLines, outputColor);
this.input = new TextInput(null, 1000, Input.ANY, 1000);
this.inputFont = inf;
this.outputFont = outf;
this.reference = ref = new WeakReference(this);
(list[0] = new HelpCommand()).setConsoleScreen(ref);
}
public void addCommand(Command command)
{
throw new IllegalStateException("ConsoleScreen.addCommand: " +
"консольный экран не поддерживает дополнительные команды.");
}
public void setDefaultCommand(Command command)
{
throw new IllegalStateException("ConsoleScreen.setDefaultCommand: " +
"консольный экран не поддерживает дополнительные команды.");
}
public void setTicker(Ticker ticker)
{
throw new IllegalStateException("ConsoleScreen.setTicker: " +
"консольный экран не поддерживает бегущие строки.");
}
public void write(byte[] src, int offset, int length)
{
char[] ac;
TextOutputMultilinedColor output;
synchronized(output = this.output)
{
output.append(ac = Helper.byteToCharArray(src, offset, length), 0, ac.length);
checkLength(output);
}
repaint(CLIENT);
}
public void print(char src)
{
TextOutputMultilinedColor output;
synchronized(output = this.output)
{
output.append(String.valueOf(src));
checkLength(output);
}
repaint(CLIENT);
}
public void print(char[] src)
{
TextOutputMultilinedColor output;
synchronized(output = this.output)
{
output.append(src, 0, src != null ? src.length : 0);
checkLength(output);
}
repaint(CLIENT);
}
public void print(String src)
{
TextOutputMultilinedColor output;
synchronized(output = this.output)
{
output.append(src);
checkLength(output);
}
repaint(CLIENT);
}
public void println()
{
TextOutputMultilinedColor output;
synchronized(output = this.output)
{
output.append(String.valueOf('\n'));
checkLength(output);
}
repaint(CLIENT);
}
public void printHelp()
{
print("Регистр команд и аргументов учитывается. Аргументы пишутся через пробел, " +
"в них допустимы так называемые escape-последовательности \\\\, \\\", \\n; аргу" +
"менты можно заключать в кавычки (\"…\"), если внутри них содержатся пробелы.\n" +
"Стрелками \u2191 и \u2193 можно прокручивать текст консоли, " +
"стрелками \u2190 и \u2192 можно прокручивать последние набранные команды,\n" +
"Enter – выполнить набранную команду,\nEsc – выход из консоли.\n");
}
public void clear()
{
TextOutputMultilinedColor output;
synchronized(output = this.output)
{
output.setColor(outputColor);
output.clear();
output.split(outputFont, super.getWidth());
scrollToLastLine();
repaint(CLIENT);
}
}
public void setColor(int color)
{
TextOutputMultilinedColor output;
synchronized(output = this.output)
{
output.setColor(color);
}
}
public void setBackgroundColor(int backgroundColor)
{
this.backgroundColor = backgroundColor;
repaint();
}
public void setTitleColor(int titleColor)
{
this.titleColor = titleColor;
repaint(TITLE);
}
public void setOutputColor(int outputColor)
{
this.outputColor = outputColor;
}
public void setInputColor(int inputColor)
{
this.inputColor = inputColor;
repaint(CLIENT);
}
public void setOutputFont(Font outputFont)
{
this.outputFont = outputFont == null ? DEFAULT_OUTPUT_FONT : outputFont;
super.setEventSizeChanged();
repaint(CLIENT);
}
public void setInputFont(Font inputFont)
{
this.inputFont = inputFont == null ? DEFAULT_INPUT_FONT : inputFont;
super.setEventSizeChanged();
repaint(CLIENT);
}
public void addConsoleCommand(ConsoleCommand command)
{
int len;
ConsoleCommand[] c;
if(command == null)
{
throw new NullPointerException("ConsoleScreen.addConsoleCommand: " +
"параметр command равен нулевой ссылке.");
}
if(command.getConsoleScreen() != null)
{
throw new IllegalArgumentException("ConsoleScreen.addConsoleCommand: " +
"параметр command уже принадлежит консоли.");
}
synchronized(super.getMonitor())
{
if((len = consoleCommandsCount) == (c = consoleCommands).length)
{
Array.copy(c, 0, c = consoleCommands = new ConsoleCommand[(len << 1) - 1], 0, len);
}
c[len++] = command;
consoleCommandsCount = len;
command.setConsoleScreen(reference);
}
}
public int getBackgroundColor()
{
return backgroundColor;
}
public int getTitleColor()
{
return titleColor;
}
public int getOutputColor()
{
return outputColor;
}
public int getInputColor()
{
return inputColor;
}
public Font getOutputFont()
{
return outputFont;
}
public Font getInputFont()
{
return inputFont;
}
protected void paint(Graphics render)
{
int width;
int inputLeft;
int inputTop;
int outputFirstLineIndex;
int outputLinesCount;
int inputLength;
char[] inputChars;
Font inputFont = this.inputFont;
Font outputFont = this.outputFont;
TextOutputMultilinedColor output;
TextInput input = this.input;
render.setFont(outputFont);
synchronized(output = this.output)
{
if(output.getLineStart(outputLinesCount = output.getLinesCount()) < output.getLength())
{
output.split(outputFont, super.getWidth());
output.deleteStartLines();
scrollToLastLine();
}
if((outputLinesCount = output.getLinesCount()) > 0 &&
outputLinesCount > (outputFirstLineIndex = this.outputFirstLineIndex))
{
output.paintLines(render, 0, 0, outputFirstLineIndex,
Math.min(outputLinesCount - outputFirstLineIndex, this.outputLinesCount));
}
}
render.setFont(inputFont);
render.setColor(inputColor);
render.drawLine(0, inputTop = this.inputTop, (width = super.getWidth()) - 1, inputTop++);
render.drawString(GREETING, 0, inputTop, Graphics.LEFT | Graphics.TOP);
inputLeft = inputFont.stringWidth(GREETING);
inputLength = input.getLength();
inputChars = input.getChars();
if(inputLength < inputChars.length)
{
inputChars[inputLength++] = '_';
}
render.clipRect(inputLeft, inputTop, width - inputLeft, super.getHeight() - inputTop);
render.drawChars(inputChars, 0, inputLength,
Math.min(inputLeft, width - inputFont.charsWidth(inputChars, 0, inputLength)),
inputTop, Graphics.LEFT | Graphics.TOP);
}
protected void paintTitle(Graphics render, int width, int height, String title)
{
render.setColor(backgroundColor);
render.fillRect(0, 0, width--, --height);
render.setColor(titleColor);
render.drawLine(0, height, width, height);
render.drawString(title, 1, 2, Graphics.LEFT | Graphics.TOP);
}
protected void paintClient(Graphics render, int width, int height,
int clipLeft, int clipTop, int clipWidth, int clipHeight)
{
Graphics clientRender;
render.setColor(backgroundColor);
render.fillRect(0, 0, width, height);
(clientRender = getClientGraphics()).reset();
clientRender.setClip(clipLeft, clipTop, clipWidth, clipHeight);
paint(clientRender);
}
protected void paintCommands(Graphics render, int width, int height,
Command[] commands, int pressedIndex, Font defaultCommandFont)
{
int w;
int a;
int b;
b = (w = width) - (a = w / 3);
render.setColor(backgroundColor);
render.fillRect(0, 0, width, height);
render.setColor(inputColor);
paintCommand(render, 0, 0, a, height, commands[0], false, pressedIndex == 0);
paintCommand(render, b, 0, w - b, height, commands[2], false, pressedIndex == 2);
render.setFont(defaultCommandFont);
paintCommand(render, a, 0, b - a, height, commands[1], true, pressedIndex == 1);
}
protected void paintCommand(Graphics render, int left, int top, int width, int height,
Command command, boolean asDefault, boolean asPressed)
{
if(command != null)
{
render.drawRoundRect(left, top, width - 1, height - 1, 2, 2);
render.drawString(
command.getTruncatedLabel(render.getFont(), width - 4),
left + (width / 2) + (asPressed ? 1 : 0),
top + (asPressed ? 3 : 2) + (asDefault ? -1 : 1),
Graphics.HCENTER | Graphics.TOP);
}
}
protected void onSizeChanged(int width, int height)
{
int t;
int infh = inputFont.getHeight();
inputTop = t = height - (infh + 1);
outputLinesCount = t / outputFont.getHeight();
super.onSizeChanged(width, height);
}
protected void onKeyPressed(int key, int charCode)
{
if(!super.keyHandling(key))
{
switch(key)
{
case MIDletProxy.KEY_UP:
{
int firstLineIndex;
if((firstLineIndex = outputFirstLineIndex) > 0)
{
outputFirstLineIndex = firstLineIndex - 1;
repaint(CLIENT);
}
}
return;
case MIDletProxy.KEY_DOWN:
{
int firstLineIndex;
if((firstLineIndex = outputFirstLineIndex) <
output.getLinesCount() - outputLinesCount)
{
outputFirstLineIndex = firstLineIndex + 1;
repaint(CLIENT);
}
}
return;
case MIDletProxy.KEY_LEFT:
{
int count = lastCommandsCount + 1;
int index = lastCommandIndex = (lastCommandIndex + count - 1) % count;
TextInput input;
(input = this.input).setString(lastCommands[index]);
input.setCaretPosition(input.getLength());
repaint(CLIENT);
}
return;
case MIDletProxy.KEY_RIGHT:
{
int count = lastCommandsCount + 1;
int index = lastCommandIndex = (lastCommandIndex + 1) % count;
TextInput input;
(input = this.input).setString(lastCommands[index]);
input.setCaretPosition(input.getLength());
repaint(CLIENT);
}
return;
default:
{
TextInput input;
(input = this.input).keyPressed(key, charCode);
if(input.isChanged())
{
repaint(CLIENT);
}
}
return;
}
}
super.onKeyPressed(key, charCode);
}
protected void onKeyRepeated(int key, int charCode)
{
if(!super.keyHandling(key))
{
switch(key)
{
case MIDletProxy.KEY_UP:
{
int firstLineIndex;
if((firstLineIndex = outputFirstLineIndex) > 0)
{
outputFirstLineIndex = firstLineIndex - 1;
repaint(CLIENT);
}
}
return;
case MIDletProxy.KEY_DOWN:
{
int firstLineIndex;
if((firstLineIndex = outputFirstLineIndex) <
output.getLinesCount() - outputLinesCount)
{
outputFirstLineIndex = firstLineIndex + 1;
repaint(CLIENT);
}
}
return;
default:
{
TextInput input;
(input = this.input).keyPressed(key, charCode);
if(input.isChanged())
{
repaint(CLIENT);
}
}
return;
}
}
super.onKeyRepeated(key, charCode);
}
protected void onKeyReleased(int key)
{
switch(key)
{
case MIDletProxy.KEY_ESCAPE:
if(super.keyHandling(key))
{
break;
}
super.setEventCommandAction(BACK_COMMAND);
return;
case MIDletProxy.KEY_ENTER:
if(super.keyHandling(key))
{
break;
}
super.setEventCommandAction(RUN_COMMAND);
return;
case MIDletProxy.KEY_SHIFT:
case MIDletProxy.KEY_LSHIFT:
case MIDletProxy.KEY_RSHIFT:
input.keyReleased(key);
return;
}
super.onKeyReleased(key);
}
protected void onCommandAction(Command command)
{
if(command == RUN_COMMAND)
{
runCommand();
return;
}
super.onCommandAction(command);
}
private void runCommand()
{
boolean q;
boolean cq;
char p;
char c;
int i;
int i0;
int len;
int argsLen;
int argsIndex;
int argsStart;
char[] line;
String[] args;
String name;
TextInput input;
ConsoleCommand command;
/* инициализация */
len = (input = this.input).getLength();
line = input.getChars();
i0 = -1;
argsStart = -1;
/* считывание имени команды */
for(i = 0; i <= len; i++)
{
if((c = i < len ? line[i] : (char) 0) > 32 && (i <= 0 || line[i - 1] <= 32))
{
i0 = i;
}
if(i > 0 && c <= 32 && line[i - 1] > 32)
{
argsStart = i + 1;
break;
}
}
if(i0 < 0 || argsStart < 0)
{
return;
}
name = new String(line, i0, argsStart - i0 - 1);
/* считывание аргументов команды */
argsLen = 0;
for(q = false, p = line[argsStart - 1], i = argsStart; i <= len; p = c, i++)
{
c = i < len ? line[i] : (char) 0;
if(cq = c == '"' && p != '\\')
{
q = !q;
c = SPACE;
}
if(cq ? !q : (q ? i == len : c <= 32 && p > 32))
{
argsLen++;
}
}
args = new String[argsLen];
argsIndex = 0;
for(q = false, p = line[argsStart - 1], i = argsStart; i <= len; p = c, i++)
{
c = i < len ? line[i] : (char) 0;
if(cq = c == '"' && p != '\\')
{
q = !q;
c = SPACE;
}
if(cq ? q : (!q) && c > 32 && p <= 32)
{
i0 = cq ? i + 1 : i;
}
if(cq ? !q : (q ? i == len : c <= 32 && p > 32))
{
args[argsIndex++] = toString(line, i0, i - i0);
}
}
if(argsIndex < argsLen)
{
return;
}
/* добавление команды в список недавно набранных */
addLastCommand(String.valueOf(line, 0, len).intern());
input.clear();
/* выполнение команды */
setColor(inputColor);
print((new StringBuffer(GREETING)).append(line, 0, len).append('\n').toString());
serviceRepaints();
setColor(outputColor);
if((command = getConsoleCommand(name)) == null)
{
print("Неизвестная команда " + name + '\n');
return;
}
repaint(CLIENT);
command.run(args);
}
private void addLastCommand(String lastCommand)
{
int len;
String[] list;
if((len = lastCommandsCount) == (list = lastCommands).length - 1)
{
Array.copy(list, 1, list, 0, len-- - 2);
}
list[len++] = lastCommand;
lastCommandIndex = len;
lastCommandsCount = len;
}
private void scrollToLastLine()
{
outputFirstLineIndex = Math.max(output.getLinesCount() - outputLinesCount, 0);
}
private void checkLength(TextOutputMultilinedColor output)
{
if(output.getLength() >= 25000)
{
output.split(outputFont, super.getWidth());
output.deleteStartLines();
scrollToLastLine();
}
}
private ConsoleCommand getConsoleCommand(String name)
{
int i;
ConsoleCommand[] commands;
ConsoleCommand command;
for(commands = consoleCommands, i = consoleCommandsCount; i-- > 0; )
{
if(name.equals((command = commands[i]).getName()))
{
return command;
}
}
return null;
}
}