/*
Реализация спецификаций 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 javax.microedition.lcdui;
import malik.emulator.application.*;
import malik.emulator.media.graphics.*;
import malik.emulator.microedition.*;
import malik.emulator.microedition.lcdui.*;
public class TextBox extends ScrollingScreen implements Input
{
private static final Font EDITOR_FONT;
static {
EDITOR_FONT = Font.getFont(Font.FONT_INPUT_TEXT);
}
class Helper extends ScrollingScreen.Helper
{
public Helper() {
}
protected void execute() {
serviceSizeChanged();
serviceSplitText();
serviceScroll();
servicePaint();
}
protected final void serviceSplitText() {
TextBox owner;
synchronized((owner = TextBox.this).monitor)
{
if(owner.builder.isModified()) owner.correctScrollBar(true);
}
}
}
private char[] buffer;
private final CommandOwner box;
private final Input alternative;
final InputStringBuilder builder;
final Object monitor;
public TextBox(String title, String inputText, int maximumLength, int constraints) {
this(title, null, false, inputText, maximumLength, constraints);
}
public TextBox(String title, Ticker ticker, boolean fullScreen, String inputText, int maximumLength, int constraints) {
super(title, ticker, fullScreen, SCROLL_VERT, new SystemScrollBarStyle());
Command[] commands;
CommandOwner box;
InputStringBuilder builder;
Object monitor = builder = new InputStringBuilder(inputText, maximumLength, constraints);
this.box = box = builder.new AdditionalCapabilities(true, monitor) {
public void commandAction(Command command, Displayable screen) {
super.commandAction(command, screen);
synchronized(monitor)
{
if(parentBuilder().isModified()) (TextBox.this).correctScrollBar(true);
}
}
};
this.alternative = builder.newInput();
this.builder = builder;
this.monitor = monitor;
for(int len = (commands = box.getMyOwnedCommands()) == null ? 0 : commands.length, i = 0; i < len; i++)
{
Command command;
if((command = commands[i]) != null) super.addCommand(command);
}
}
public void insert(char[] src, int offset, int length, int position) {
int error;
if(src == null)
{
throw new NullPointerException("TextBox.insert: аргумент src равен нулевой ссылке.");
}
Array.checkBound("TextBox.insert", src.length, offset, length);
if(length <= 0) return;
error = 0;
synchronized(monitor)
{
label0:
{
int currentLength;
InputStringBuilder builder = this.builder;
if(length + (currentLength = builder.length()) > builder.getMaximumLength())
{
error = 1;
break label0;
}
alternative.insert(src, offset, length, position);
if(currentLength == builder.length()) error = 2;
}
}
switch(error)
{
case 1:
throw new IllegalArgumentException("TextBox.insert: попытка превысить максимальную длину.");
case 2:
throw new IllegalArgumentException("TextBox.insert: текст не соответствует ограничениям.");
}
requestPaint(CLIENT);
}
public void insert(String text, int position) {
int error;
int length;
if(text == null)
{
throw new NullPointerException("TextBox.insert: аргумент text равен нулевой ссылке.");
}
if((length = text.length()) <= 0) return;
error = 0;
synchronized(monitor)
{
label0:
{
int currentLength;
InputStringBuilder builder = this.builder;
if(length + (currentLength = builder.length()) > builder.getMaximumLength())
{
error = 1;
break label0;
}
alternative.insert(text, position);
if(currentLength == builder.length()) error = 2;
}
}
switch(error)
{
case 1:
throw new IllegalArgumentException("TextBox.insert: попытка превысить максимальную длину.");
case 2:
throw new IllegalArgumentException("TextBox.insert: текст не соответствует ограничениям.");
}
requestPaint(CLIENT);
}
public void delete(int offset, int length) {
int error = 0;
synchronized(monitor)
{
label0:
{
int currentLength;
InputStringBuilder builder;
if(!Array.isBoundValid(currentLength = (builder = this.builder).length(), offset, length))
{
error = 1;
break label0;
}
alternative.delete(offset, length);
if(length > 0 && currentLength == builder.length()) error = 2;
}
}
switch(error)
{
case 1:
throw new StringIndexOutOfBoundsException("TextBox.delete: индекс выходит из диапазона.");
case 2:
throw new IllegalArgumentException("TextBox.delete: текст не соответствует ограничениям.");
}
requestPaint(CLIENT);
}
public void setConstraints(int constraints) {
if(!InputStringBuilder.isConstraintsValid(constraints))
{
throw new IllegalArgumentException("TextBox.setConstraints: аргумент constraints имеет недопустимое значение.");
}
synchronized(monitor)
{
Command[] commands;
CommandOwner box;
for(int i = (commands = (box = this.box).getMyOwnedCommands()) == null ? 0 : commands.length; i-- > 0; ) super.removeCommand(commands[i]);
alternative.setConstraints(constraints);
for(int len = (commands = box.getMyOwnedCommands()) == null ? 0 : commands.length, i = 0; i < len; i++)
{
Command command;
if((command = commands[i]) != null) super.addCommand(command);
}
}
requestPaint(CLIENT);
}
public void setChars(char[] src, int offset, int length) {
int error;
if(src != null)
{
Array.checkBound("TextBox.setChars", src.length, offset, length);
Array.copy(src, offset, src = new char[length], offset = 0, length);
} else
{
offset = length = 0;
}
error = 0;
synchronized(monitor)
{
label0:
{
InputStringBuilder builder;
if(length > (builder = this.builder).getMaximumLength())
{
error = 1;
break label0;
}
if(!InputStringBuilder.isConstraintsMatch(builder.getConstraints(), src, offset, length))
{
error = 2;
break label0;
}
alternative.setChars(src, offset, length);
}
}
switch(error)
{
case 1:
throw new IllegalArgumentException("TextBox.setChars: попытка превысить максимальную длину.");
case 2:
throw new IllegalArgumentException("TextBox.setChars: текст не соответствует ограничениям.");
}
requestPaint(CLIENT);
}
public void setString(String inputText) {
int error = 0;
int length = inputText == null ? 0 : inputText.length();
synchronized(monitor)
{
label0:
{
InputStringBuilder builder;
if(length > (builder = this.builder).getMaximumLength())
{
error = 1;
break label0;
}
if(!InputStringBuilder.isConstraintsMatch(builder.getConstraints(), inputText))
{
error = 2;
break label0;
}
alternative.setString(inputText);
}
}
switch(error)
{
case 1:
throw new IllegalArgumentException("TextBox.setString: попытка превысить максимальную длину.");
case 2:
throw new IllegalArgumentException("TextBox.setString: текст не соответствует ограничениям.");
}
requestPaint(CLIENT);
}
public void setInitialInputMode(String characterSubset) {
alternative.setInitialInputMode(characterSubset);
}
public int setMaxSize(int maximumSize) {
int result;
if(maximumSize <= 0)
{
throw new IllegalArgumentException("TextBox.setMaxSize: аргумент maximumSize может быть только положительным.");
}
synchronized(monitor)
{
result = alternative.setMaxSize(maximumSize);
}
requestPaint(CLIENT);
return result;
}
public int size() {
return alternative.size();
}
public int getConstraints() {
return alternative.getConstraints();
}
public int getCaretPosition() {
return alternative.getCaretPosition();
}
public int getChars(char[] dst) {
int error;
int result;
if(dst == null)
{
throw new NullPointerException("TextBox.getChars: аргумент dst равен нулевой ссылке.");
}
error = 0;
synchronized(monitor)
{
label0:
{
if(dst.length < builder.length())
{
error = 1;
result = 0;
break label0;
}
result = alternative.getChars(dst);
}
}
if(error == 1)
{
throw new ArrayIndexOutOfBoundsException("TextBox.getChars: индекс массива выходит из диапазона.");
}
return result;
}
public int getMaxSize() {
return alternative.getMaxSize();
}
public String getString() {
String result;
synchronized(monitor)
{
result = alternative.getString();
}
return result;
}
void paint(ScreenGraphics render) {
int temp;
int line;
int srow;
int frow;
Font font = EDITOR_FONT;
srow = (temp = render.getClipY()) / (line = font.getHeight());
frow = (temp + render.getClipHeight() - 1) / line;
render.setFont(font);
synchronized(monitor)
{
int caretLine;
int caretPosition;
char[] buffer = this.buffer;
InputStringBuilder builder = this.builder;
render.setColor(RasterCanvas.getSystemColor((builder.getConstraints() & UNEDITABLE) == 0 ? 0x21 : 0x23));
caretLine = builder.lineAt(caretPosition = builder.getCaretPosition());
for(int length = builder.lines(), top = srow * line, index = srow; index <= frow && index < length; top += line, index++)
{
int lofs = builder.lineOffset(index);
int llen = builder.lineLength(index);
if(buffer == null || buffer.length < llen) buffer = this.buffer = new char[InputStringBuilder.optimalCapacity(llen)];
builder.copy(lofs, lofs + llen, buffer, 0);
render.drawChars(buffer, 0, llen, 2, top, Graphics.LEFT | Graphics.TOP);
if(caretLine == index)
{
int caretLeft = font.charsWidth(buffer, 0, caretPosition - lofs) + 1;
render.drawLine(caretLeft, top, caretLeft, top + line - 1);
}
}
}
}
void paintBackground(ScreenGraphics render, int width, int height, byte visibleElements, byte drawingElements) {
int elementTop = 0;
int elementHeight;
if((visibleElements & TICKER) != 0)
{
elementHeight = getTickerHeight();
if((drawingElements & TICKER) != 0) render.drawElement(13, 0, 0, 0, elementTop, width, elementHeight);
elementTop += elementHeight;
}
if((visibleElements & TITLE) != 0)
{
elementHeight = getTitleHeight();
if((drawingElements & TITLE) != 0) render.drawElement(6, 1, 0, 0, elementTop, width, elementHeight);
elementTop += elementHeight;
}
elementHeight = height - elementTop;
if((visibleElements & PANEL) != 0)
{
int panelHeight = getPanelHeight();
if((drawingElements & PANEL) != 0)
{
render.setClip(0, height - panelHeight, width, panelHeight);
render.drawElement(6, 5, 0, 0, elementTop, width, elementHeight);
render.setClip(0, 0, width, height);
}
elementHeight -= panelHeight;
}
if((visibleElements & drawingElements & CLIENT) != 0) render.drawElement(8, (builder.getConstraints() & UNEDITABLE) != 0 ? 3 : 1, 0, -2, elementTop, width + 4, elementHeight);
}
void onSizeChanged(int width, int height) {
builder.setModified(true);
super.onSizeChanged(width, height);
}
void onCommandAction(Command command) {
CommandOwner box;
if((box = this.box).isMyOwnedCommand(command))
{
box.commandAction(command, this);
return;
}
super.onCommandAction(command);
}
void onClientKeyboardEvent(KeyboardEvent event) {
int key = event.getKey();
int action = event.getAction();
DeviceSettings settings = DeviceManager.getInstance().getSettings();
if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_LEFT))
{
if(action == KeyboardEvent.ACTION_KEY_PRESSED || action == KeyboardEvent.ACTION_KEY_REPEATED) synchronized(monitor)
{
InputStringBuilder builder;
(builder = this.builder).setCaretPosition(builder.getCaretPosition() - 1);
if(builder.isModified()) correctScrollBar(false);
}
return;
}
if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_RIGHT))
{
if(action == KeyboardEvent.ACTION_KEY_PRESSED || action == KeyboardEvent.ACTION_KEY_REPEATED) synchronized(monitor)
{
InputStringBuilder builder;
(builder = this.builder).setCaretPosition(builder.getCaretPosition() + 1);
if(builder.isModified()) correctScrollBar(false);
}
return;
}
if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_UP))
{
if(action == KeyboardEvent.ACTION_KEY_PRESSED || action == KeyboardEvent.ACTION_KEY_REPEATED) synchronized(monitor)
{
int caretRow;
int caretPosition;
InputStringBuilder builder;
if((caretRow = (builder = this.builder).lineAt(caretPosition = builder.getCaretPosition())) > 0) moveCaret(builder, caretRow--, caretPosition, caretRow);
}
return;
}
if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_DOWN))
{
if(action == KeyboardEvent.ACTION_KEY_PRESSED || action == KeyboardEvent.ACTION_KEY_REPEATED) synchronized(monitor)
{
int caretRow;
int caretPosition;
InputStringBuilder builder;
if((caretRow = (builder = this.builder).lineAt(caretPosition = builder.getCaretPosition())) < builder.lines() - 1) moveCaret(builder, caretRow++, caretPosition, caretRow);
}
return;
}
synchronized(monitor)
{
InputStringBuilder builder;
(builder = this.builder).keyboardEvent(event);
if(builder.isModified()) correctScrollBar(true);
}
}
void onClientPointerEvent(PointerEvent event) {
int action = event.getAction();
if(
event.isButtonPressed(PointerEvent.BUTTON_MAIN) || (action == PointerEvent.ACTION_BUTTON_RELEASED ||
action == PointerEvent.ACTION_POINTER_RELEASED) && event.getButton() == PointerEvent.BUTTON_MAIN
) synchronized(monitor)
{
int limit;
int caretRow;
InputStringBuilder builder;
if((caretRow = event.getY() / EDITOR_FONT.getHeight()) > (limit = (builder = this.builder).lines() - 1)) caretRow = limit;
if(caretRow < 0) caretRow = 0;
moveCaret(builder, event.getX() - 2, caretRow);
}
}
Displayable.Helper createHelper() {
return this.new Helper();
}
final void correctScrollBar(boolean splitNeeded) {
int pos;
int page;
int top1;
int top2;
int step;
int line;
ScrollBar scroll;
Font font = EDITOR_FONT;
InputStringBuilder builder = this.builder;
if(splitNeeded) builder.split(font, getApplicationWidth() - 4);
line = builder.lineAt(builder.getCaretPosition());
step = font.getHeight();
top1 = step * line;
top2 = step + top1;
(scroll = super.vert).setRange(builder.lines() * step);
if(top1 < (pos = scroll.getPosition()))
{
scroll.setPosition(top1);
}
else if(top2 > pos + (page = scroll.getPage()))
{
scroll.setPosition(top2 - page);
}
else
{
requestPaint(CLIENT);
}
}
private void moveCaret(InputStringBuilder builder, int caretRow, int caretPosition, int newCaretRow) {
int lofs = builder.lineOffset(caretRow);
int llen;
int caretCol = caretPosition - lofs;
int charsWidth;
char[] buffer;
Font font;
if((buffer = this.buffer) == null || buffer.length < caretCol) buffer = this.buffer = new char[InputStringBuilder.optimalCapacity(caretCol)];
builder.copy(lofs, caretPosition, buffer, 0);
charsWidth = (font = EDITOR_FONT).charsWidth(buffer, 0, caretCol);
lofs = builder.lineOffset(newCaretRow);
llen = builder.lineLength(newCaretRow);
if(buffer.length < llen) buffer = this.buffer = new char[InputStringBuilder.optimalCapacity(llen)];
builder.copy(lofs, lofs + llen, buffer, 0);
label0:
{
for(int w1 = 0, w2, i = 1; i <= llen; 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 = llen;
}
builder.setCaretPosition(lofs + caretCol);
if(builder.isModified()) correctScrollBar(false);
}
private void moveCaret(InputStringBuilder builder, int charsWidth, int newCaretRow) {
int caretCol;
int lofs = builder.lineOffset(newCaretRow);
int llen = builder.lineLength(newCaretRow);
char[] lbuf;
Font font = EDITOR_FONT;
if(charsWidth < 0) charsWidth = 0;
if((lbuf = buffer) == null || lbuf.length < llen) lbuf = buffer = new char[InputStringBuilder.optimalCapacity(llen - 1) + 1];
builder.copy(lofs, lofs + llen, lbuf, 0);
label0:
{
for(int w1 = 0, w2, i = 1; i <= llen; w1 = w2, i++)
{
w2 = font.charsWidth(lbuf, 0, i);
if(charsWidth >= w1 && charsWidth < w2)
{
caretCol = charsWidth - w1 <= w2 - charsWidth ? i - 1 : i;
break label0;
}
}
caretCol = llen;
}
builder.setCaretPosition(lofs + caretCol);
if(builder.isModified()) correctScrollBar(false);
}
}