/*
Реализация спецификаций 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.lcdui.*;
import malik.emulator.util.*;
public abstract class Item extends Object
{
private static final class CommandList extends Object implements Command.Owner
{
private int commandSize;
private Command[] commandList;
private Command commandDefault;
public CommandList() {
this.commandList = new Command[7];
}
public boolean isMyOwnedCommand(Command command) {
boolean result;
synchronized(Command.MONITOR)
{
result = command != null && (command == commandDefault || Array.findf(commandList, 0, command) < commandSize);
}
return result;
}
public int size() {
return commandSize;
}
public Command commandAt(int index) {
return index >= 0 && index < commandSize ? commandList[index] : null;
}
public Command defaultCommand() {
return commandDefault;
}
public void addCommand(Command command) {
int len;
Command[] commands;
if(command == null || command == commandDefault || Array.findf(commands = commandList, 0, command) < (len = commandSize)) return;
if(len == commands.length) Array.copy(commands, 0, commands = commandList = new Command[(len << 1) + 1], 0, len);
commands[len++] = command;
commandSize = len;
}
public void removeCommand(Command command) {
int len;
int index;
Command[] commands;
if(command == null) return;
if(command == commandDefault)
{
commandDefault = null;
return;
}
if((index = Array.findf(commands = commandList, 0, command)) > (len = commandSize - 1)) return;
if(index < len) Array.copy(commands, index + 1, commands, index, len - index);
commands[len] = null;
commandSize = len;
}
public void setDefaultCommand(Command command) {
Command previous = commandDefault;
commandDefault = null;
addCommand(previous);
removeCommand(command);
commandDefault = command;
}
}
private static final char LABEL_END = ':';
private static final int LAYOUT_MASK = ~0x7f33;
public static final int LAYOUT_DEFAULT = 0x0000;
public static final int LAYOUT_LEFT = 0x0001;
public static final int LAYOUT_RIGHT = 0x0002;
public static final int LAYOUT_CENTER = 0x0003;
public static final int LAYOUT_TOP = 0x0010;
public static final int LAYOUT_BOTTOM = 0x0020;
public static final int LAYOUT_VCENTER = 0x0030;
public static final int LAYOUT_NEWLINE_BEFORE = 0x0100;
public static final int LAYOUT_NEWLINE_AFTER = 0x0200;
public static final int LAYOUT_SHRINK = 0x0400;
public static final int LAYOUT_EXPAND = 0x0800;
public static final int LAYOUT_VSHRINK = 0x1000;
public static final int LAYOUT_VEXPAND = 0x2000;
public static final int LAYOUT_2 = 0x4000;
public static final int PLAIN = 0;
public static final int HYPERLINK = 1;
public static final int BUTTON = 2;
static final int LEFT = 0;
static final int TOP = 1;
static final int WIDTH = 2;
static final int HEIGHT = 3;
static final int DIR_NONE = 0;
static final int DIR_UP = Canvas.UP;
static final int DIR_DOWN = Canvas.DOWN;
static final int DIR_LEFT = Canvas.LEFT;
static final int DIR_RIGHT = Canvas.RIGHT;
private static final long WIDTH_MASK = (1L << 32) - 1L;
private static final long HEIGHT_MASK = -1L << 32;
static final Font LABEL_FONT;
static final Object OWNER_MONITOR;
static {
LABEL_FONT = Font.getFont(Font.FONT_STATIC_TEXT);
OWNER_MONITOR = new Object();
}
private static void writeLabel(StringBuilder builder, String label) {
int len;
builder.clear();
if(label != null && (len = label.length()) > 0)
{
builder.append(label);
if(label.charAt(len - 1) != LABEL_END) builder.append(LABEL_END);
}
}
private static long packSize(int width, int height) {
return (long) width & WIDTH_MASK | (long) height << 32;
}
boolean newLine;
boolean visible;
int left;
int top;
int width;
int height;
int minimumWidth;
int minimumHeight;
private int layout;
private long preferredSize;
private long copyPreferredSize;
private long minimumContentSize;
private long currentContentSize;
private long previousContentSize;
private char[] buffer;
Screen owner;
private ItemCommandListener listener;
private String labelAsString;
private final MultilinedStringBuilder labelAsBuilder;
private final CommandList commands;
Item() {
this(null, LAYOUT_DEFAULT);
}
Item(String label) {
this(label, LAYOUT_DEFAULT);
}
Item(String label, int layout) {
MultilinedStringBuilder builder;
if((layout & LAYOUT_MASK) != 0)
{
throw new IllegalArgumentException("Item: аргумент layout имеет недопустимое значение.");
}
this.layout = layout;
this.preferredSize = -1L;
this.previousContentSize = -1L;
this.labelAsString = label;
this.labelAsBuilder = builder = new MultilinedStringBuilder();
this.commands = new CommandList();
writeLabel(builder, label);
}
public void notifyStateChanged() {
Screen owner;
if((owner = this.owner) == null)
{
throw new IllegalStateException("Item.notifyStateChanged: элемент не принадлежит какому-либо экрану.");
}
owner.requestStateChanged(this);
}
public void addCommand(Command command) {
Screen owner;
if((owner = this.owner) != null && !owner.focusSupported())
{
throw new IllegalStateException("Item.addCommand: экран-владелец не поддерживает установку фокуса ввода на элемент.");
}
if(command == null)
{
throw new NullPointerException("Item.addCommand: аргумент command равен нулевой ссылке.");
}
synchronized(Command.MONITOR)
{
commands.addCommand(command);
}
if(owner != null && owner.isFocused(this))
{
owner.placeCommands();
owner.requestPaintAll();
}
}
public void removeCommand(Command command) {
Screen owner;
if(command == null) return;
synchronized(Command.MONITOR)
{
commands.removeCommand(command);
}
if((owner = this.owner) != null && owner.isFocused(this))
{
owner.placeCommands();
owner.requestPaintAll();
}
}
public void setDefaultCommand(Command command) {
Screen owner;
if((owner = this.owner) != null && !owner.focusSupported())
{
throw new IllegalStateException("Item.setDefaultCommand: экран-владелец не поддерживает установку фокуса ввода на элемент.");
}
synchronized(Command.MONITOR)
{
commands.setDefaultCommand(command);
}
if(owner != null && owner.isFocused(this))
{
owner.placeCommands();
owner.requestPaintAll();
}
}
public void setItemCommandListener(ItemCommandListener listener) {
Screen owner;
if((owner = this.owner) != null && !owner.focusSupported())
{
throw new IllegalStateException("Item.setItemCommandListener: экран-владелец не поддерживает установку фокуса ввода на элемент.");
}
this.listener = listener;
}
public void setPreferredSize(int width, int height) {
Screen owner;
if((owner = this.owner) != null && !owner.focusSupported())
{
throw new IllegalStateException("Item.setPreferredSize: экран-владелец не поддерживает установку фокуса ввода на элемент.");
}
if(width < -1)
{
throw new IllegalArgumentException("Item.setPreferredSize: аргумент width не может быть меньше (-1).");
}
if(height < -1)
{
throw new IllegalArgumentException("Item.setPreferredSize: аргумент height не может быть меньше (-1).");
}
this.preferredSize = packSize(width, height);
if(owner != null) owner.requestInvalidate();
}
public void setLayout(int layout) {
Screen owner;
if((owner = this.owner) != null && !owner.focusSupported())
{
throw new IllegalStateException("Item.setLayout: экран-владелец не поддерживает установку фокуса ввода на элемент.");
}
if((layout & LAYOUT_MASK) != 0)
{
throw new IllegalArgumentException("Item.setLayout: аргумент layout имеет недопустимое значение.");
}
this.layout = layout;
if(owner != null) owner.requestInvalidate();
}
public void setLabel(String label) {
Screen owner;
StringBuilder builder;
if((owner = this.owner) != null && !owner.focusSupported())
{
throw new IllegalStateException("Item.setLabel: экран-владелец не поддерживает установку фокуса ввода на элемент.");
}
synchronized(builder = labelAsBuilder)
{
labelAsString = label;
writeLabel(builder, label);
}
if(owner != null) owner.requestInvalidate();
}
public int getMinimumWidth() {
return minimumWidth;
}
public int getMinimumHeight() {
return minimumHeight;
}
public int getPreferredWidth() {
int result;
return (result = (int) preferredSize) < 0 ? width : result;
}
public int getPreferredHeight() {
int result;
return (result = (int) (preferredSize >> 32)) < 0 ? height : result;
}
public int getLayout() {
return layout;
}
public String getLabel() {
return labelAsString;
}
abstract void paint(Graphics render, int contentWidth, int contentHeight);
abstract int getMinimumContentWidth();
abstract int getMinimumContentHeight();
abstract int getPreferredContentWidth(int contentHeight);
abstract int getPreferredContentHeight(int contentWidth);
void onShow() {
}
void onHide() {
}
void onSizeChanged(int contentWidth, int contentHeight) {
}
void onCommandAction(Command command) {
ItemCommandListener listener;
if((listener = this.listener) != null) listener.commandAction(command, this);
}
void onKeyboardEvent(KeyboardEvent event) {
}
void onPointerEvent(PointerEvent event) {
}
void onFocusLost() {
}
boolean onFocusMove(int direction, int viewportWidth, int viewportHeight, int[] visibleRectangle) {
return false;
}
boolean hasRowBreakAfter(int layout, int alignment) {
int actual;
return (layout & LAYOUT_NEWLINE_AFTER) != 0 || (actual = layout & LAYOUT_CENTER) != 0 && actual != alignment;
}
boolean hasRowBreakBefore(int layout, int alignment) {
int actual;
return (layout & LAYOUT_NEWLINE_BEFORE) != 0 || (actual = layout & LAYOUT_CENTER) != 0 && actual != alignment;
}
int getDefaultLayout() {
return LAYOUT_DEFAULT;
}
final void onPaint(ScreenGraphics render) {
try
{
int clipLeft = render.getClipX();
int clipTop = render.getClipY();
int clipWidth = render.getClipWidth();
int clipHeight = render.getClipHeight();
int translateX = render.getTranslateX();
int translateY = render.getTranslateY();
int labelHeight;
int contentWidth;
int contentHeight;
long contentSize;
contentWidth = (int) (contentSize = currentContentSize);
contentHeight = (int) (contentSize >> 32);
labelHeight = height - contentHeight;
if(clipTop < labelHeight) paintLabel(render, width, labelHeight, clipTop, clipHeight);
if(contentWidth > 0 && contentHeight > 0 && clipTop + clipHeight > labelHeight && clipLeft < contentWidth)
{
int diff;
if((diff = labelHeight - clipTop) > 0)
{
clipTop += diff;
clipHeight -= diff;
}
if(clipLeft + clipWidth > contentWidth) clipWidth = contentWidth - clipLeft;
clipLeft += translateX;
clipTop += translateY;
translateY += labelHeight;
render.reset();
render.restricts(clipLeft, clipTop, clipWidth, clipHeight);
render.setStartPoint(translateX, translateY);
render.setClip(clipLeft, clipTop, clipWidth, clipHeight);
render.translate(translateX, translateY);
paint(render, contentWidth, contentHeight);
}
}
catch(RuntimeException e)
{
e.printRealStackTrace();
}
}
final void onSizeChanged() {
long contentSize;
if(previousContentSize != (contentSize = getContentSize()))
{
previousContentSize = contentSize;
onSizeChanged((int) contentSize, (int) (contentSize >> 32));
}
}
final void requestPaint() {
Screen owner;
if((owner = this.owner) != null && visible) owner.requestPaint(Displayable.CLIENT);
}
final void requestInvalidate() {
Screen owner;
if((owner = this.owner) != null) owner.requestInvalidate();
}
final void requestDefaultCommandAction() {
Screen owner;
final Command command;
if((owner = this.owner) != null && (command = commands.defaultCommand()) != null)
{
owner.requestAction(new Runnable() {
public void run() {
(Item.this).onCommandAction(command);
}
});
}
}
final void resetContentSize() {
currentContentSize = -1L;
}
final void writeContentRectangleTo(int[] rect) {
int itemHeight;
long contentSize = currentContentSize;
rect[0] = 0;
rect[1] = (itemHeight = height) - (int) (contentSize >> 32);
rect[2] = (int) contentSize;
rect[3] = itemHeight;
}
final boolean hasLabel() {
return labelAsString != null;
}
final int suppliedLayout() {
return layout;
}
final int suppliedPreferredWidth() {
long copy = copyPreferredSize = preferredSize;
return (int) copy;
}
final int suppliedPreferredHeight() {
return (int) (copyPreferredSize >> 32);
}
final int computeMinimumWidth() {
int minLabelWidth = getMinimumLabelWidth();
int minContentWidth = getMinimumContentWidth();
minimumContentSize = minimumContentSize & HEIGHT_MASK | (long) minContentWidth;
return minLabelWidth < minContentWidth ? minContentWidth : minLabelWidth;
}
final int computeMinimumHeight() {
int minLabelHeight = getMinimumLabelHeight();
int minContentHeight = getMinimumContentHeight();
minimumContentSize = minimumContentSize & WIDTH_MASK | (long) minContentHeight << 32;
return minLabelHeight + minContentHeight;
}
final int computePreferredWidth(int height) {
int minSize;
int maxWidth = width;
int prefLabelWidth;
int prefLabelHeight;
int prefContentWidth;
long prefLabelSize;
prefLabelWidth = (int) (prefLabelSize = getPreferredLabelSize(height < 0 ? 100 : maxWidth));
prefLabelHeight = (int) (prefLabelSize >> 32);
if(height >= 0) height = height > prefLabelHeight ? height - prefLabelHeight : 0;
if((prefContentWidth = getPreferredContentWidth(height)) < (minSize = (int) minimumContentSize)) prefContentWidth = minSize;
if(height >= 0 && prefContentWidth > maxWidth) prefContentWidth = maxWidth;
currentContentSize = packSize(prefContentWidth, height);
return prefLabelWidth < prefContentWidth ? prefContentWidth : prefLabelWidth;
}
final int computePreferredHeight(int width) {
int minSize;
int prefLabelHeight = getPreferredLabelHeight(width);
int prefContentHeight;
if((prefContentHeight = getPreferredContentHeight(width)) < (minSize = (int) (minimumContentSize >> 32))) prefContentHeight = minSize;
currentContentSize = packSize(width, prefContentHeight);
return prefLabelHeight + prefContentHeight;
}
final Command.Owner getCommands() {
return commands;
}
final ItemCommandListener getListener() {
return listener;
}
private void paintLabel(Graphics render, int labelWidth, int labelHeight, int clipTop, int clipHeight) {
int line;
int srow;
int frow;
Font font = LABEL_FONT;
MultilinedStringBuilder builder;
srow = clipTop / (line = font.getHeight());
frow = (clipTop + clipHeight - 1) / line;
render.setFont(font);
render.setColor(RasterCanvas.getSystemColor(0x28));
synchronized(builder = labelAsBuilder)
{
char[] lbuf = buffer;
builder.split(font, labelWidth);
for(int len = builder.lines(), top = srow * line, index = srow; index <= frow && index < len; top += line, index++)
{
int lofs = builder.lineOffset(index);
int llen = builder.lineLength(index);
if(lbuf == null || lbuf.length < llen) lbuf = buffer = new char[StringBuilder.optimalCapacity(llen)];
builder.copy(lofs, lofs + llen, lbuf, 0);
render.drawChars(lbuf, 0, llen, 0, top, Graphics.LEFT | Graphics.TOP);
}
}
}
private int getMinimumLabelWidth() {
int result = 0;
StringBuilder builder;
synchronized(builder = labelAsBuilder)
{
if(!builder.isEmpty()) result = 100;
}
return result;
}
private int getMinimumLabelHeight() {
int result = 0;
MultilinedStringBuilder builder;
synchronized(builder = labelAsBuilder)
{
if(!builder.isEmpty())
{
Font font;
builder.split(font = LABEL_FONT, Integer.MAX_VALUE);
result = font.getHeight() * builder.lines();
}
}
return result;
}
private int getPreferredLabelHeight(int labelWidth) {
int result = 0;
MultilinedStringBuilder builder;
synchronized(builder = labelAsBuilder)
{
if(!builder.isEmpty())
{
Font font;
builder.split(font = LABEL_FONT, labelWidth >= 0 ? labelWidth : Integer.MAX_VALUE);
result = font.getHeight() * builder.lines();
}
}
return result;
}
private long getPreferredLabelSize(int labelWidth) {
int width = 0;
int height = 0;
MultilinedStringBuilder builder;
synchronized(builder = labelAsBuilder)
{
if(!builder.isEmpty())
{
int index;
Font font;
builder.split(font = LABEL_FONT, labelWidth);
width = 100;
height = font.getHeight() * (index = builder.lines());
for(char[] lbuf = buffer; index-- > 0; )
{
int lwid;
int lofs = builder.lineOffset(index);
int llen = builder.lineLength(index);
if(lbuf == null || lbuf.length < llen) lbuf = buffer = new char[StringBuilder.optimalCapacity(llen)];
builder.copy(lofs, lofs + llen, lbuf, 0);
if(width < (lwid = font.charsWidth(lbuf, 0, llen))) width = lwid;
}
}
}
return packSize(width, height);
}
private long getContentSize() {
int contentWidth;
int contentHeight;
long contentSize;
contentWidth = (int) (contentSize = currentContentSize);
contentHeight = (int) (contentSize >> 32);
if(contentWidth < 0)
{
int minSize;
int itemWidth = width;
int itemHeight = height;
int labelWidth;
int labelHeight;
long labelSize;
labelWidth = (int) (labelSize = getPreferredLabelSize(itemWidth));
labelHeight = (int) (labelSize >> 32);
if(contentHeight < 0) contentHeight = labelHeight < itemHeight ? itemHeight - labelHeight : 0;
if(labelWidth < itemWidth)
{
contentWidth = itemWidth;
}
else if((contentWidth = (int) preferredSize) < (minSize = (int) minimumContentSize))
{
if((contentWidth = getPreferredContentWidth(contentHeight)) < minSize) contentWidth = minSize;
}
if(contentWidth > itemWidth) contentWidth = itemWidth;
}
else if(contentHeight < 0)
{
int itemWidth = width;
int itemHeight = height;
int labelHeight = getPreferredLabelHeight(itemWidth);
contentHeight = labelHeight < itemHeight ? itemHeight - labelHeight : 0;
}
else
{
return contentSize;
}
return currentContentSize = packSize(contentWidth, contentHeight);
}
}