/*
Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
и других спецификаций для функционирования компактных приложений на языке
Java (мидлетов) в среде программного обеспечения Малик Эмулятор.
Copyright © 2016–2017, 2019–2023 Малик Разработчик
Это свободная программа: вы можете перераспространять ее и/или изменять
ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
общественной лицензии GNU.
Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<https://www.gnu.org/licenses/>.
*/
package malik.emulator.microedition.system;
import javax.microedition.lcdui.*;
import malik.emulator.application.*;
import malik.emulator.media.graphics.*;
import malik.emulator.microedition.*;
import malik.emulator.microedition.lcdui.*;
import malik.emulator.microedition.system.console.*;
public final class Console extends CustomBackgroundScreen
{
private static final char SPACE = 32;
private static final int COLS = 0x0080;
private static final int ROWS = 0x0200;
private static final int BACK_COLOR = 0x101010;
private static final int TITLE_COLOR = 0xc0d0c0;
private static final int PANEL_COLOR = 0xffffff;
private static final int TICKER_COLOR = 0x809080;
public static final int DEFAULT_COLOR = 0xc0c0c0;
static final Command BACK;
static final Command EXECUTE;
private static final ConsoleCommand HELP_COMMAND;
private static final ConsoleCommand CLEAR_COMMAND;
static {
BACK = new Command("Назад", Command.BACK, Integer.MIN_VALUE);
EXECUTE = new Command("Выполнить", Command.OK, Integer.MIN_VALUE);
HELP_COMMAND = new ConsoleCommand(
"?",
"Использование:\n ?\n ? <команда>\nВариант без аргумента выдаст общую справку по консоли.\nВариант с аргументом выдаст справку по команде, указанной в аргументе."
) {
protected void execute(String[] arguments, Console console) {
if(arguments.length > 0)
{
ConsoleCommand command;
String name = arguments[0];
if((command = console.getConsoleCommand(name)) == null || command.hidden)
{
console.println("Неизвестная команда: ".concat(name));
return;
}
console.println(command.help);
return;
}
console.printHelp();
}
};
CLEAR_COMMAND = new ConsoleCommand("очистить", "Использование:\n очистить\nОчищает консоль от текста.") {
protected void execute(String[] arguments, Console console) {
console.clear();
}
};
}
private static int getLength(short[] attrs, int offset) {
int len = attrs.length;
int attr = attrs[offset++];
int result = 1;
for(; offset < len && attrs[offset++] == attr; result++);
return result;
}
private static int getLineEndIndex(String string, int startFromIndex, int defaultValue) {
int result1 = string.indexOf('\r', startFromIndex);
int result2 = string.indexOf('\n', startFromIndex);
return result1 >= 0 ? result2 >= 0 ? (result1 >= result2 ? result2 : result1) : result1 : (result2 >= 0 ? result2 : defaultValue);
}
private static String toString(char[] src, int offset, int length) {
int len;
char[] str;
Array.copy(src, offset, str = new char[len = length], 0, len);
for(int i = len - 1; i-- > 0; )
{
char next;
if(str[i] == '\\' && ((next = str[i + 1]) == '\"' || next == '\\' || next == 'n'))
{
Array.copy(str, i + 1, str, i, len-- - i - 1);
if(next == 'n') str[i] = '\n';
}
}
return new String(str, 0, len);
}
private boolean shiftPressed;
private int size;
private int index;
private int count;
private int lines;
private int focused;
private int scrollHorz;
private int scrollVert;
private final int lineHeight;
private final char[] buffer;
private final char[] chars;
private final short[] attrs;
private final short[] sizes;
private final String[] history;
private ConsoleCommand[] commands;
private final InputStringBuilder entered;
private final Font font;
CommandListener listener;
final SystemManager manager;
private final Object monitor;
Console(String title, Ticker ticker, boolean fullScreen, SystemManager manager) {
super(title, ticker, fullScreen);
ConsoleCommand[] list;
Command[] commands;
Font font;
SystemFont sfont;
CommandOwner owner;
InputStringBuilder entered;
if((sfont = SystemFont.get("Console")) == SystemFont.getDefault())
{
font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
} else
{
font = Font.getFont(sfont, Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
}
this.count = 2;
this.focused = -1;
this.lineHeight = font.getHeight();
this.buffer = new char[InputStringBuilder.CAPACITY];
this.chars = new char[ROWS * COLS];
this.attrs = new short[ROWS * COLS];
this.sizes = new short[ROWS];
this.history = new String[100];
this.commands = list = new ConsoleCommand[7];
this.entered = entered = new InputStringBuilder(null, InputStringBuilder.CAPACITY, Input.ANY);
this.font = font;
this.manager = manager;
this.monitor = entered;
list[0] = HELP_COMMAND;
list[1] = CLEAR_COMMAND;
super.setCommandListener(owner = entered.new AdditionalCapabilities(false, entered) {
public void commandAction(Command command, Displayable screen) {
CommandListener listener;
Console parent = Console.this;
if(command == Console.BACK)
{
parent.manager.switchToApplicationDisplay();
return;
}
if(command == Console.EXECUTE)
{
parent.executeEnteredCommand();
return;
}
if(super.isMyOwnedCommand(command) || screen != parent)
{
super.commandAction(command, screen);
if(parentBuilder().isModified()) parent.requestPaint();
return;
}
if((listener = parent.listener) != null) listener.commandAction(command, screen);
}
public Command[] getMyOwnedCommands() {
int len;
Command[] result;
if((result = super.getMyOwnedCommands()) == null) return new Command[] { Console.BACK, Console.EXECUTE };
Array.copy(result, 0, result = new Command[(len = result.length) + 2], 2, len);
result[0] = Console.BACK;
result[1] = Console.EXECUTE;
return result;
}
});
for(int len = (commands = owner.getMyOwnedCommands()) == null ? 0 : commands.length, i = 0; i < len; i++)
{
Command command;
if((command = commands[i]) != null) super.addCommand(command);
}
}
public void setCommandListener(CommandListener listener) {
this.listener = listener;
}
public void addConsoleCommand(ConsoleCommand command) {
if(command == null)
{
throw new NullPointerException("Console.addConsoleCommand: аргумент command равен нулевой ссылке.");
}
synchronized(monitor)
{
int len;
ConsoleCommand[] list;
if((list = commands).length == (len = count))
{
Array.copy(list, 0, list = new ConsoleCommand[(len << 1) + 1], 0, len);
commands = list;
}
list[len++] = command;
count = len;
}
}
public void clear() {
synchronized(monitor)
{
lines = 0;
Array.fill(sizes, 0, ROWS, 0);
updateScrollVert();
}
repaint();
}
public void print(String string) {
write(string == null ? "null" : string, false, DEFAULT_COLOR);
}
public void print(String string, int color) {
write(string == null ? "null" : string, false, color);
}
public void println(String string) {
write(string == null ? "null" : string, true, DEFAULT_COLOR);
}
public void println(String string, int color) {
write(string == null ? "null" : string, true, color);
}
protected void paintBackground(Graphics render, int width, int height, byte visibleElements, byte drawingElements) {
int elementTop = 0;
int elementHeight;
render.setColor(BACK_COLOR);
if((visibleElements & TICKER) != 0)
{
elementHeight = getTickerHeight();
if((drawingElements & TICKER) != 0) render.fillRect(0, elementTop, width, elementHeight);
elementTop += elementHeight;
}
if((visibleElements & TITLE) != 0)
{
elementHeight = getTitleHeight();
if((drawingElements & TITLE) != 0) render.fillRect(0, elementTop, width, elementHeight);
elementTop += elementHeight;
}
if((visibleElements & PANEL) != 0)
{
elementHeight = getPanelHeight();
if((drawingElements & PANEL) != 0) render.fillRect(0, height - elementHeight, width, elementHeight);
elementHeight = height - elementTop - elementHeight;
} else
{
elementHeight = height - elementTop;
}
if((drawingElements & CLIENT) != 0) render.fillRect(0, elementTop, width, elementHeight);
}
protected void paintTicker(Graphics render, int width, int height, Ticker ticker) {
render.setColor(TICKER_COLOR);
render.drawString(ticker.getDisplayedString(), ticker.getPosition(), 0, 0);
}
protected void paintTitle(Graphics render, int width, int height, String title) {
int posX = width - 1;
int posY = height - 1;
render.setColor(TITLE_COLOR);
render.drawLine(0, 0, posX, 0);
render.drawLine(0, posY, posX, posY);
render.drawString(MultilinedStringBuilder.truncate(title, render.getFont(), width), 0, 2, 0);
}
protected void paintClient(Graphics render, int width, int height) {
int sh;
int sv;
int tx;
int cw;
int ow;
int pos;
int len;
int temp;
int srow;
int frow;
int line = lineHeight;
char[] buffer = this.buffer;
Font font = this.font;
InputStringBuilder entered = this.entered;
render.setClip(0, 0, width, height -= line + 1);
render.translate(-(sh = scrollHorz), -(sv = scrollVert));
srow = (temp = render.getClipY()) / line;
frow = (temp + render.getClipHeight() - 1) / line;
render.setFont(font);
synchronized(monitor)
{
int lines = this.lines;
char[] chars = this.chars;
short[] attrs = this.attrs;
short[] sizes = this.sizes;
for(int top = srow * line, index = srow, offset = srow * COLS; index <= frow && index <= lines; top += line, index++, offset += COLS)
{
for(int left = 0, length = sizes[index], lim = offset + length, begin = offset, end; begin < lim; begin = end)
{
int limit;
int strlen;
int c = attrs[begin];
if((end = begin + getLength(attrs, begin)) > (limit = offset + length)) end = limit;
render.setColor((c & 0xf800) << 8 | (c & 0xe000) << 3 | (c & 0x07e0) << 5 | (c & 0x0600) >> 1 | (c & 0x001f) << 3 | (c & 0x0001c) >> 2);
render.drawChars(chars, begin, strlen = end - begin, left, top, 0);
left += font.charsWidth(chars, begin, strlen);
}
}
}
render.translate(sh, sv + height);
render.setClip(0, 0, width, line + 1);
render.setColor(PANEL_COLOR);
render.drawLine(0, 0, width - 1, 0);
entered.copy(0, len = entered.length(), buffer, 0);
pos = entered.getCaretPosition();
ow = font.charsWidth(buffer, 0, len);
cw = font.charsWidth(buffer, 0, pos);
if((tx = (width >> 1) - cw - 1) + (ow + 1) < width) tx = width - ow - 1;
if(tx > 0) tx = 0;
render.translate(tx, 1);
render.drawChars(buffer, 0, len, 1, 0, 0);
render.drawLine(cw, 0, cw, line - 1);
}
protected void paintPanel(Graphics render, int width, int height, Command[] commands, int pressedCommandIndex) {
int p1 = width / 3;
int p2 = width - p1;
paintCommand(render, 0, 0, p1, height, commands[0], false, pressedCommandIndex == 0);
paintCommand(render, p1, 0, p2 - p1, height, commands[1], true, pressedCommandIndex == 1);
paintCommand(render, p2, 0, width - p2, height, commands[2], false, pressedCommandIndex == 2);
}
protected void showNotify() {
shiftPressed = false;
}
protected void keyboardNotify(KeyboardEvent event) {
int key = event.getKey();
int action = event.getAction();
InputStringBuilder entered = this.entered;
DeviceSettings settings = manager.getSettings();
shiftPressed = event.isKeyPressed(KeyboardEvent.KEY_SHIFT) || event.isKeyPressed(KeyboardEvent.KEY_LSHIFT) || event.isKeyPressed(KeyboardEvent.KEY_RSHIFT);
if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_UP))
{
if(action == KeyboardEvent.ACTION_KEY_PRESSED || action == KeyboardEvent.ACTION_KEY_REPEATED)
{
int index;
int size = this.size;
this.index = index = (index = this.index) <= 0 ? size : index - 1;
entered.clear();
if(index < size) entered.append(history[index]);
if(entered.isModified()) repaint();
}
return;
}
if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_DOWN))
{
if(action == KeyboardEvent.ACTION_KEY_PRESSED || action == KeyboardEvent.ACTION_KEY_REPEATED)
{
int index;
int size = this.size;
this.index = index = (index = this.index) >= size ? 0 : index + 1;
entered.clear();
if(index < size) entered.append(history[index]);
if(entered.isModified()) repaint();
}
return;
}
if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_LEFT))
{
if(action == KeyboardEvent.ACTION_KEY_PRESSED || action == KeyboardEvent.ACTION_KEY_REPEATED)
{
entered.setCaretPosition(entered.getCaretPosition() - 1);
if(entered.isModified()) repaint();
}
return;
}
if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_RIGHT))
{
if(action == KeyboardEvent.ACTION_KEY_PRESSED || action == KeyboardEvent.ACTION_KEY_REPEATED)
{
entered.setCaretPosition(entered.getCaretPosition() + 1);
if(entered.isModified()) repaint();
}
return;
}
entered.keyboardEvent(event);
if(entered.isModified()) repaint();
}
protected void pointerNotify(PointerEvent event) {
int y;
int line;
int action;
switch(action = event.getAction())
{
case PointerEvent.ACTION_POINTER_PRESSED:
case PointerEvent.ACTION_BUTTON_PRESSED:
switch(event.getButton())
{
case PointerEvent.BUTTON_WHEEL_UP:
if(shiftPressed)
{
scroll(-16, 0);
} else
{
scroll(0, -lineHeight);
}
repaint();
break;
case PointerEvent.BUTTON_WHEEL_DOWN:
if(shiftPressed)
{
scroll(+16, 0);
} else
{
scroll(0, +lineHeight);
}
repaint();
break;
default:
if(action == PointerEvent.ACTION_BUTTON_PRESSED) break;
y = event.getY();
line = super.getHeight() - lineHeight - 1;
if(y < line)
{
focused = 0;
break;
}
if(y > line)
{
int tx;
int cw;
int ow;
int pos;
int len;
int caretCol;
int charsWidth;
int width = super.getWidth();
char[] buffer = this.buffer;
Font font = this.font;
InputStringBuilder entered = this.entered;
entered.copy(0, len = entered.length(), buffer, 0);
pos = entered.getCaretPosition();
ow = font.charsWidth(buffer, 0, len);
cw = font.charsWidth(buffer, 0, pos);
if((tx = (width >> 1) - cw - 1) + (ow + 1) < width) tx = width - ow - 1;
if(tx > 0) tx = 0;
charsWidth = event.getX() - tx;
label0:
{
for(int w1 = 0, w2, i = 1; i <= len; w1 = w2, i++)
{
w2 = font.charsWidth(buffer, 0, i);
if(charsWidth >= w1 && charsWidth < w2)
{
caretCol = charsWidth - w1 <= w2 - charsWidth ? i - 1 : i;
break label0;
}
}
caretCol = len;
}
entered.setCaretPosition(caretCol);
if(entered.isModified()) repaint();
}
break;
}
break;
case PointerEvent.ACTION_POINTER_RELEASED:
if(focused == 0)
{
focused = -1;
scroll(event.historicalX(1) - event.getX(), event.historicalY(1) - event.getY());
repaint();
}
break;
default:
if(focused == 0)
{
scroll(event.historicalX(1) - event.getX(), event.historicalY(1) - event.getY());
repaint();
}
break;
}
}
protected int getPanelCommandIndex(int x, int y, int width, int height) {
if(x >= 0 && x < width && y >= 0 && y < height)
{
int p1 = width / 3;
int p2 = width - p1;
return x < p1 ? 0 : x < p2 ? 1 : 2;
}
return -1;
}
protected int getTickerHeight() {
return getTickerFont().getHeight() + 1;
}
protected int getTitleHeight() {
return getTitleFont().getHeight() + 4;
}
protected int getPanelHeight() {
return getDefaultCommandFont().getHeight() + 5;
}
void requestPaint() {
repaint();
}
void printHelp() {
int len = count;
ConsoleCommand[] list = commands;
write("Доступные команды:", true, DEFAULT_COLOR);
for(int i = 0; i < len; i++)
{
ConsoleCommand command;
if(!(command = list[i]).hidden) write(" ".concat(command.name), true, DEFAULT_COLOR);
}
write(
"Команда\n ? <команда>\nвыведет справку по заданной команде.\nКоманды вводятся с учётом регистра, аргументы разделяются пробелами.\n" +
"Аргументы с пробелами следует заключать в кавычки \"…\".\nПоддерживаются escape-последовательности: \\n, \\\" и \\\\.\n" +
"С помощью стрелок \u2191 и \u2193 можно прокрутить последние набранные команды.\n" +
"С помощью указывающего устройства можно прокрутить текст консоли.", true, DEFAULT_COLOR
);
write("Этим цветом ", false, StdConsoleOutputStream.COLOR);
write("выводится всё, что приложение выводит через System.out.", true, DEFAULT_COLOR);
write("Этим цветом ", false, ErrConsoleOutputStream.COLOR);
write("выводится всё, что приложение выводит через System.err.", true, DEFAULT_COLOR);
}
void executeEnteredCommand() {
boolean q;
int i0;
int len;
int index;
int argsLen;
int argsStart;
char[] buffer;
String[] args;
String name;
String text;
ConsoleCommand command;
InputStringBuilder entered;
/* инициализация */
(entered = this.entered).copy(0, len = entered.length(), buffer = this.buffer, 0);
argsStart = -1;
i0 = -1;
/* считывание названия команды */
for(int i = 0; i <= len; i++)
{
char c;
if((c = i < len ? buffer[i] : '\0') > SPACE && (i <= 0 || buffer[i - 1] <= SPACE)) i0 = i;
if(i > 0 && c <= SPACE && buffer[i - 1] > SPACE)
{
argsStart = i + 1;
break;
}
}
if(i0 < 0 || argsStart < 0) return;
name = new String(buffer, i0, argsStart - i0 - 1);
/* считывание аргументов команды */
q = false;
argsLen = 0;
for(int p = buffer[argsStart - 1], c, i = argsStart; i <= len; p = c, i++)
{
boolean cq;
c = i < len ? buffer[i] : '\0';
if(cq = c == '\"' && p != '\\')
{
q = !q;
c = SPACE;
}
if(cq ? !q : q ? i == len : c <= SPACE && p > SPACE) argsLen++;
}
q = false;
index = 0;
args = new String[argsLen];
for(int p = buffer[argsStart - 1], c, i = argsStart; i <= len; p = c, i++)
{
boolean cq;
c = i < len ? buffer[i] : '\0';
if(cq = c == '\"' && p != '\\')
{
q = !q;
c = SPACE;
}
if(cq ? q : !q && c > SPACE && p <= SPACE) i0 = cq ? i + 1 : i;
if(cq ? !q : q ? i == len : c <= SPACE && p > SPACE) args[index++] = toString(buffer, i0, i - i0);
}
if(index < argsLen) return;
/* добавление команды в список недавно введённых */
addToHistory(text = String.valueOf(buffer, 0, len).intern());
entered.clear();
/* выполнение команды */
write(text, true, PANEL_COLOR);
serviceRepaints();
if((command = getConsoleCommand(name)) == null)
{
write("Неизвестная команда: ".concat(name), true, DEFAULT_COLOR);
return;
}
command.execute(args, this);
}
ConsoleCommand getConsoleCommand(String name) {
ConsoleCommand[] list = commands;
for(int i = count; i-- > 0; )
{
ConsoleCommand command = list[i];
if(name.equals(command.name)) return command;
}
return null;
}
private void paintCommand(Graphics render, int left, int top, int width, int height, Command command, boolean asDefault, boolean asPressed) {
int x;
int y;
Font font;
if(command == null) return;
x = left + (width >> 1) + (asPressed ? 1 : 0);
y = top + (asPressed ? 3 : 2);
render.setFont(font = asDefault ? getDefaultCommandFont() : getNormalCommandFont());
render.setColor(PANEL_COLOR);
render.drawRoundRect(left, top, width - 1, height - 1, 2, 2);
render.drawString(command.getTruncatedLabel(width - 4, font), x, y, Graphics.HCENTER | Graphics.TOP);
}
private void scroll(int deltaX, int deltaY) {
int line;
int maxScrollVert = (lines + 1) * (line = lineHeight);
long newScrollHorz = (long) scrollHorz + (long) deltaX;
long newScrollVert = (long) scrollVert + (long) deltaY;
if((maxScrollVert -= super.getHeight() - line - 1) < 0) maxScrollVert = 0;
scrollHorz = newScrollHorz < 0L ? 0 : newScrollHorz > (long) Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) newScrollHorz;
scrollVert = newScrollVert < 0L ? 0 : newScrollVert > maxScrollVert ? maxScrollVert : (int) newScrollVert;
}
private void write(String string, boolean ending, int color) {
short consoleAttr = (short) ((color & 0xf80000) >> 8 | (color & 0x00fc00) >> 5 | (color & 0x0000f8) >> 3);
int stringLength = string.length();
synchronized(monitor)
{
int consoleCharOffset;
int consoleLineLength;
int consoleLineIndex = lines;
char[] consoleChars = chars;
short[] consoleAttrs = attrs;
short[] consoleSizes = sizes;
consoleLineLength = consoleSizes[consoleLineIndex];
consoleCharOffset = COLS * consoleLineIndex + consoleLineLength;
for(int stringBegin = 0, stringEnd; stringBegin < stringLength; stringBegin = stringEnd)
{
boolean newLine;
int substringLength;
int stringLengthLimit = COLS - consoleLineLength;
if((stringEnd = getLineEndIndex(string, stringBegin, stringLength)) < 0)
{
newLine = false;
if(stringEnd - stringBegin > stringLengthLimit)
{
stringEnd = stringBegin + stringLengthLimit;
}
} else
{
if(stringEnd - stringBegin > stringLengthLimit)
{
newLine = false;
stringEnd = stringBegin + stringLengthLimit;
} else
{
newLine = true;
}
}
substringLength = stringEnd - stringBegin;
consoleSizes[consoleLineIndex] = (short) (consoleLineLength + substringLength);
Array.fill(consoleAttrs, consoleCharOffset, substringLength, consoleAttr);
string.getChars(stringBegin, stringEnd, consoleChars, consoleCharOffset);
if(stringEnd < stringLength)
{
if(lift(consoleLineIndex, consoleChars, consoleAttrs, consoleSizes)) consoleLineIndex++;
consoleCharOffset = COLS * consoleLineIndex;
consoleLineLength = 0;
}
if(newLine)
{
stringEnd++;
if(stringEnd < stringLength && string.charAt(stringEnd - 1) == '\r' && string.charAt(stringEnd) == '\n') stringEnd++;
}
}
if(ending) lift(consoleLineIndex, consoleChars, consoleAttrs, consoleSizes);
updateScrollVert();
}
repaint();
}
private void updateScrollVert() {
if(focused < 0)
{
int line;
int scrollPos;
int scrollRange = (lines + 1) * (line = lineHeight);
if((scrollPos = scrollRange - (super.getHeight() - line - 1)) < 0) scrollPos = 0;
scrollHorz = 0;
scrollVert = scrollPos;
}
}
private void addToHistory(String command) {
int size = this.size;
String[] history = this.history;
if(size < history.length)
{
history[size++] = command;
this.index = size;
this.size = size;
return;
}
this.index = size;
Array.copy(history, 1, history, 0, --size);
history[size] = command;
}
private boolean lift(int lines, char[] chars, short[] attrs, short[] sizes) {
if(lines < ROWS - 1)
{
this.lines = lines + 1;
return true;
}
Array.copy(chars, COLS, chars, 0, COLS * (ROWS - 1));
Array.fill(chars, COLS * (ROWS - 1), COLS, 0);
Array.copy(attrs, COLS, attrs, 0, COLS * (ROWS - 1));
Array.fill(attrs, COLS * (ROWS - 1), COLS, 0);
Array.copy(sizes, 1, sizes, 0, ROWS - 1);
sizes[ROWS - 1] = (short) 0;
return false;
}
}