/*
Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
и других спецификаций для функционирования компактных приложений на языке
Java (мидлетов) в среде программного обеспечения Малик Эмулятор.
Copyright © 2016, 2019 Малик Разработчик
Это свободная программа: вы можете перераспространять ее и/или изменять
ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
общественной лицензии GNU.
Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<https://www.gnu.org/licenses/>.
*/
package javax.microedition.lcdui;
import java.lang.ref.*;
import malik.emulator.media.graphics.*;
public abstract class Item extends Object
{
private static final class Helper extends WeakReference
implements ItemCommandListener, CommandEnumerationWithCheck, CommandEnumeration
{
Helper(Item thisItem)
{
super(thisItem);
}
public void commandAction(Command command, Item item)
{
Object thisItem;
if(!((thisItem = get()) instanceof ItemCommandListener))
{
return;
}
((ItemCommandListener) thisItem).commandAction(command, item);
}
public boolean hasCommand(Command command)
{
int i;
Command[] c;
Item thisItem;
if((thisItem = (Item) get()) == null)
{
return false;
}
if(command == thisItem.defaultCommand)
{
return true;
}
for(c = thisItem.commandsInItem, i = thisItem.commandsCount; i-- > 0; )
{
if(c[i] == command)
{
return true;
}
}
return false;
}
public int getCommandsCount()
{
Item thisItem;
return (thisItem = (Item) get()) == null ? 0 : thisItem.commandsCount;
}
public Command getCommand(int index)
{
Item thisItem;
if((thisItem = (Item) get()) == null ||
index < 0 || index >= thisItem.commandsCount)
{
throw new IndexOutOfBoundsException("Item.getCommands: " +
"индекс выходит из диапазона.");
}
return thisItem.commandsInItem[index];
}
public Command getDefaultCommand()
{
Item thisItem;
return (thisItem = (Item) get()) == null ? null : thisItem.defaultCommand;
}
public Object getMonitor()
{
Item thisItem;
Container owner;
return (thisItem = (Item) get()) == null ? this :
((owner = thisItem.getOwner()) == null ? thisItem.getMonitor() :
owner.getCommandsMonitor());
}
}
private static final class ItemUpdateEventStream extends Object
implements Runnable
{
private int count;
private Item[] items;
ItemUpdateEventStream()
{
this.items = new Item[8];
}
public synchronized void run()
{
int i;
int count;
Item[] items;
Item item;
do
{
waitEvent();
count = this.count;
items = this.items;
for(i = count; i-- > 0; )
{
(item = items[i]).onUpdate();
if(i == 0)
{
item.notifyPaint();
}
}
item = null;
} while(true);
}
public synchronized void add(Item item)
{
int i;
int count = this.count;
Item[] items = this.items;
for(i = count; i-- > 0; )
{
if(items[i] == item)
{
return;
}
}
if(count == items.length)
{
Array.copy(items, 0, items = this.items = new Item[count << 1], 0, count);
}
items[count++] = item;
this.count = count;
notify();
}
public synchronized void remove(Item item)
{
int i;
int index;
int count = this.count;
Item[] items = this.items;
label0:
{
for(i = count; i-- > 0; )
{
if(items[i] == item)
{
index = i;
break label0;
}
}
return;
}
if((i = count - index - 1) > 0)
{
Array.copy(items, index + 1, items, index, i);
}
items[--count] = null;
this.count = count;
notify();
}
private void waitEvent()
{
do
{
try
{
wait(count > 0 ? 100L : 0L);
break;
}
catch(InterruptedException e)
{
e.printRealStackTrace();
}
} while(true);
}
}
public static final int PLAIN = 0;
public static final int HYPERLINK = 1;
public static final int BUTTON = 2;
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;
static final int LEFT = 0;
static final int TOP = 1;
static final int WIDTH = 2;
static final int HEIGHT = 3;
private static final int LAYOUT_MASK = ~0x7f33;
private static final ItemUpdateEventStream STREAM;
static
{
ItemUpdateEventStream stream;
STREAM = stream = new ItemUpdateEventStream();
(new Thread(stream, "Поток, периодически вызывающий метод Item.onUpdate().")).start();
}
private boolean needRecompute;
private boolean focusedClient;
private boolean visible;
private int layout;
private int left;
private int top;
private int width;
private int height;
private int minWidth;
private int minHeight;
private int lockedWidth;
private int lockedHeight;
private int contentLeft;
private int contentTop;
private int contentWidth;
private int contentHeight;
private int commandsCount;
private Command[] commandsInItem;
private Command defaultCommand;
private ItemCommandListener listener;
private String label;
private Reference owningReference;
private TextOutputMultilined lines;
private Helper helper;
private Object lock;
Item(int layout, int lockedWidth, int lockedHeight, String label,
Command[] commands, Command defaultCommand, ItemCommandListener listener)
{
int i;
int j;
int len;
Command[] c;
Command cmd;
if((layout & LAYOUT_MASK) != 0)
{
throw new IllegalArgumentException("Item: " +
"недопустимое значение параметра layout.");
}
if(lockedWidth < -1 || lockedHeight < -1)
{
throw new IllegalArgumentException("Item: " +
"предпочитаемые размеры не могут быть меньше -1.");
}
if((len = commands == (c = null) ? 0 : commands.length) > 0)
{
Array.copy(commands, 0, c = new Command[len], 0, len);
for(i = defaultCommand != null ? -1 : 0; i < len; i++)
{
if((cmd = i < 0 ? defaultCommand : c[i]) == null && i >= 0)
{
throw new NullPointerException("Item: " +
"один из элементов параметра commands равен нулевой ссылке.");
}
for(j = i + 1; j < len; j++)
{
if(c[j] == cmd)
{
throw new IllegalArgumentException("Item: " +
"параметр commands содержит равные элементы или элементы, " +
"равные параметру defaultCommand.");
}
}
}
}
this.needRecompute = true;
this.layout = layout;
this.lockedWidth = lockedWidth;
this.lockedHeight = lockedHeight;
this.commandsCount = len;
this.commandsInItem = c;
this.defaultCommand = defaultCommand;
this.listener = listener;
this.label = label;
this.lines = new TextOutputMultilined(0);
this.helper = new Helper(this);
this.lock = new Object();
updateLines(label);
}
public void notifyStateChanged()
{
Container owner;
if(!((owner = getOwner()) instanceof Form))
{
throw new IllegalStateException("Item.notifyStateChanged: " +
"невозможно оповестить об изменении содержимого, " +
"если элемент находится не на форме.");
}
((Form) owner).notifyItemStateChanged(this);
}
public void addCommand(Command command)
{
boolean commandsChanged;
int len;
Command[] c;
Container owner;
if((owner = getOwner()) instanceof Alert)
{
throw new IllegalStateException("Item.addCommand: " +
"невозможно добавить команду, " +
"если элемент находится на диалоговом экране.");
}
if(command == null)
{
throw new NullPointerException("Item.addCommand: " +
"параметр command равен нулевой ссылке.");
}
synchronized(getCommands().getMonitor())
{
commandsChanged = false;
if(command != defaultCommand && (c = Displayable.addCommand(commandsInItem,
len = commandsCount, command)) != null)
{
commandsCount = len + 1;
commandsInItem = c;
commandsChanged = true;
}
}
if((!commandsChanged) || owner == null || (!owner.isFocused(this)))
{
return;
}
owner.updateCommands();
owner.callSeriallyPaintScreen(Displayable.CLIENT);
}
public void removeCommand(Command command)
{
boolean commandsChanged;
int len;
Command[] c;
Container owner;
synchronized(getCommands().getMonitor())
{
label0:
{
commandsChanged = false;
if(command != null && command == defaultCommand)
{
defaultCommand = null;
commandsChanged = true;
break label0;
}
if((c = Displayable.removeCommand(commandsInItem,
len = commandsCount, command)) != null)
{
commandsCount = len - 1;
commandsInItem = c;
commandsChanged = true;
}
}
}
if((!commandsChanged) || (owner = getOwner()) == null || (!owner.isFocused(this)))
{
return;
}
owner.updateCommands();
owner.callSeriallyPaintScreen(Displayable.CLIENT);
}
public void setDefaultCommand(Command command)
{
boolean commandsChanged;
int len;
int count;
Command[] c;
Command[] inItem;
Command currentDefault;
Container owner;
if((owner = getOwner()) instanceof Alert)
{
throw new IllegalStateException("Item.setDefaultCommand: " +
"невозможно изменить основную команду, " +
"если элемент находится на диалоговом экране.");
}
synchronized(getCommands().getMonitor())
{
if(commandsChanged = (currentDefault = defaultCommand) != command)
{
count = len = commandsCount;
inItem = commandsInItem;
if(command != null && (c = Displayable.removeCommand(inItem,
len, command)) != null)
{
count = --len;
inItem = c;
}
if(currentDefault != null && (c = Displayable.addCommand(inItem,
len, currentDefault)) != null)
{
count = len + 1;
inItem = c;
}
commandsCount = count;
commandsInItem = inItem;
defaultCommand = command;
}
}
if((!commandsChanged) || owner == null || (!owner.isFocused(this)))
{
return;
}
owner.updateCommands();
owner.callSeriallyPaintScreen(Displayable.CLIENT);
}
public void setItemCommandListener(ItemCommandListener listener)
{
this.listener = listener == this ? helper : listener;
notifyPaint();
}
public void setPreferredSize(int lockedWidth, int lockedHeight)
{
if(getOwner() instanceof Alert)
{
throw new IllegalStateException("Item.setPreferredSize: " +
"невозможно изменить предпочитаемые размеры, " +
"если элемент находится на диалоговом экране.");
}
if(lockedWidth < -1 || lockedHeight < -1)
{
throw new IllegalArgumentException("Item.setPreferredSize: " +
"предпочитаемые размеры не могут быть меньше -1.");
}
synchronized(lock)
{
this.lockedWidth = lockedWidth;
this.lockedHeight = lockedHeight;
}
notifyUpdate();
}
public void setLayout(int layout)
{
boolean layoutChanged;
if(getOwner() instanceof Alert)
{
throw new IllegalStateException("Item.setLayout: " +
"невозможно изменить расположение элемента, " +
"если элемент находится на диалоговом экране.");
}
if((layout & LAYOUT_MASK) != 0)
{
throw new IllegalArgumentException("Item.setLayout: " +
"недопустимое значение параметра layout.");
}
synchronized(lock)
{
if(layoutChanged = this.layout != layout)
{
this.layout = layout;
}
}
if(!layoutChanged)
{
return;
}
notifyUpdate();
}
public void setLabel(String label)
{
boolean labelChanged;
String oldLabel;
if(getOwner() instanceof Alert)
{
throw new IllegalStateException("Item.setLabel: " +
"невозможно изменить метку элемента, " +
"если элемент находится на диалоговом экране.");
}
synchronized(lock)
{
if(labelChanged = (oldLabel = this.label) == null ?
label != null : (!oldLabel.equals(label)))
{
this.label = label;
updateLines(label);
}
}
if(!labelChanged)
{
return;
}
notifyUpdate();
}
public int getMinimumWidth()
{
return minWidth;
}
public int getMinimumHeight()
{
return minHeight;
}
public int getPreferredWidth()
{
return width;
}
public int getPreferredHeight()
{
return height;
}
public int getLayout()
{
return layout;
}
public String getLabel()
{
return label;
}
abstract void paintContent(Graphics render, int contentWidth, int contentHeight);
abstract int getPreferredContentWidth(int contentHeight, int containerClientWidth);
abstract int getPreferredContentHeight(int contentWidth);
void onUpdate()
{
}
void onShow()
{
}
void onHide()
{
}
void onSizeChanged(int contentWidth, int contentHeight)
{
}
void onKeyPressed(int key, int charCode)
{
}
void onKeyRepeated(int key, int charCode)
{
}
void onKeyReleased(int key)
{
}
void onContentPointerPressed(int x, int y, int button)
{
}
void onContentPointerDragged(int x, int y)
{
}
void onContentPointerReleased(int x, int y, int button)
{
}
void onCommandAction(Command command)
{
ItemCommandListener listener;
if((listener = this.listener) == null)
{
return;
}
listener.commandAction(command, this);
}
void onTraverseOut()
{
}
boolean onTraverseNeedStayOn(int direction, int viewportWidth, int viewportHeight,
int[] visibleRectangle)
{
return false;
}
boolean keyHandling(int key)
{
return false;
}
boolean isMatchForAlert()
{
CommandEnumeration e;
return getOwner() == null &&
(e = getCommands()).getCommandsCount() == 0 && e.getDefaultCommand() == null &&
lockedWidth == -1 && lockedHeight == -1 && layout == LAYOUT_DEFAULT &&
listener == null && label == null;
}
boolean isMatchForButton()
{
return getCommands().getDefaultCommand() != null && listener != null;
}
boolean isMatchForHyperlink()
{
return getCommands().getDefaultCommand() != null && listener != null;
}
int getMinimumContentWidth(int containerClientWidth)
{
return getPreferredContentWidth(-1, containerClientWidth);
}
int getMinimumContentHeight()
{
return getPreferredContentHeight(-1);
}
CommandEnumerationWithCheck getCommands()
{
return helper;
}
final void onPointerPressed(int x, int y, int button)
{
int l;
int t;
if(x >= (l = contentLeft) && y >= (t = contentTop) &&
x < l + contentWidth && y < t + contentHeight)
{
focusedClient = true;
onContentPointerPressed(x - l, y - t, button);
}
}
final void onPointerDragged(int x, int y)
{
if(focusedClient)
{
onContentPointerDragged(x - contentLeft, y - contentTop);
}
}
final void onPointerReleased(int x, int y, int button)
{
if(focusedClient)
{
focusedClient = false;
onContentPointerReleased(x - contentLeft, y - contentTop, button);
}
}
final void onPaint(GraphicsClipRestricted render)
{
int w;
int h;
int t;
int s;
int ct;
int cb;
int i;
int len;
char[] labelChars;
TextOutputMultilined labelLines;
Font labelFont;
String labelText;
/* рисуем метку */
labelFont = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_SMALL);
render.setFont(labelFont);
render.setColor(RasterCanvas.getSystemColor(0x28));
synchronized(lock)
{
if((labelText = label) != null && labelText.length() > 0)
{
s = labelFont.getHeight();
cb = (ct = render.getClipY()) + render.getClipHeight();
len = (labelLines = lines).getLinesCount();
labelChars = labelLines.getChars();
for(t = 0, i = 0; i < len; t += s, i++)
{
if(t < cb && t + s > ct)
{
render.drawChars(labelChars,
labelLines.getLineStart(i), labelLines.getLineLength(i),
0, t, 0);
}
}
}
}
/* рисуем содержимое */
if((w = contentWidth) <= 0 || (h = contentHeight) <= 0)
{
return;
}
render.setColor(0x000000);
render.setFont(Font.getDefaultFont());
render.translate(contentLeft, contentTop);
render.restrictClipRect(0, 0, w, h);
if(render.getClipWidth() <= 0 || render.getClipHeight() <= 0)
{
return;
}
paintContent(render, w, h);
}
final void notifyCommandAction(Command command)
{
Displayable screen;
if(command == null || (screen = getOwner()) == null)
{
return;
}
screen.callSeriallyCommandAction(command);
}
final void notifyPaint()
{
Displayable owner;
if((owner = getOwner()) == null)
{
return;
}
owner.callSeriallyPaintScreen(Displayable.CLIENT);
}
final void notifyUpdate()
{
Container owner;
if(needRecompute || (!(needRecompute = true)) || (owner = getOwner()) == null)
{
return;
}
owner.notifyPlaceItems();
}
final void computeSizes(int containerClientWidth)
{
boolean needInvokeSizeChanged;
int i;
int len;
int tmp;
int pch1;
int pch2;
int minContentWidth;
int minContentHeight;
int currContentWidth;
int currContentHeight;
int lockedWidth;
int lockedHeight;
int labelWidth;
int labelHeight;
char[] labelChars;
TextOutputMultilined labelLines;
Font labelFont;
String labelText;
/* проверка необходимости пересчёта размеров */
if(!needRecompute)
{
return;
}
needRecompute = false;
/* расчёт размера метки */
labelWidth = 0;
labelHeight = 0;
synchronized(lock)
{
labelFont = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_SMALL);
(labelLines = lines).split(labelFont, containerClientWidth);
if((labelText = label) != null && labelText.length() > 0)
{
labelHeight = labelFont.getHeight() * (len = labelLines.getLinesCount());
labelChars = labelLines.getChars();
for(i = 0; i < len; i++)
{
if(labelWidth < (tmp = labelFont.charsWidth(labelChars,
labelLines.getLineStart(i), labelLines.getLineLength(i))))
{
labelWidth = tmp;
}
}
}
lockedWidth = this.lockedWidth;
lockedHeight = this.lockedHeight;
}
try
{
/* расчёт размеров содержимого и самого элемента */
minContentWidth = Math.max(getMinimumContentWidth(containerClientWidth), 0);
minContentHeight = Math.max(getMinimumContentHeight(), 0);
if(lockedWidth < 0)
{
if(lockedHeight < 0)
{
if(containerClientWidth > minContentWidth)
{
pch1 = Math.max(getPreferredContentHeight(containerClientWidth), 0);
pch2 = Math.max(getPreferredContentHeight(minContentWidth), 0);
if(pch1 < pch2)
{
currContentWidth = containerClientWidth;
currContentHeight = Math.max(pch1, minContentHeight);
} else
{
currContentWidth = minContentWidth;
currContentHeight = Math.max(pch2, minContentHeight);
}
} else
{
currContentWidth = minContentWidth;
currContentHeight = Math.max(getPreferredContentHeight(currContentWidth),
minContentHeight);
}
} else
{
currContentHeight = Math.max(minContentHeight, lockedHeight - labelHeight);
currContentWidth = Math.max(getPreferredContentWidth(currContentHeight,
containerClientWidth), minContentWidth);
}
} else
{
currContentWidth = Math.max(minContentWidth, Math.max(lockedWidth, labelWidth));
currContentHeight = Math.max(getPreferredContentHeight(currContentWidth),
lockedHeight < 0 ? minContentHeight :
Math.max(minContentHeight, lockedHeight - labelHeight));
}
currContentWidth = Math.max(labelWidth, currContentWidth);
minContentWidth = Math.max(labelWidth, minContentWidth);
needInvokeSizeChanged =
contentWidth != currContentWidth ||
contentHeight != currContentHeight;
width = currContentWidth;
height = labelHeight + currContentHeight;
minWidth = minContentWidth;
minHeight = labelHeight + minContentHeight;
contentLeft = 0;
contentTop = labelHeight;
contentWidth = currContentWidth;
contentHeight = currContentHeight;
/* вызываем onSizeChanged если размеры изменились */
if(needInvokeSizeChanged)
{
onSizeChanged(currContentWidth, currContentHeight);
}
}
catch(RuntimeException e)
{
e.printRealStackTrace();
}
}
final void startUpdating()
{
STREAM.add(this);
}
final void stopUpdating()
{
STREAM.remove(this);
}
final void setVisible(boolean visible)
{
this.visible = visible;
}
final void setPosition(int left, int top)
{
this.left = left;
this.top = top;
}
final void setOwner(Container owner)
{
this.owningReference = owner == null ? null : owner.getOwningReference();
}
final boolean isVisible()
{
return visible;
}
final int getLeft()
{
return left;
}
final int getTop()
{
return top;
}
final int getWidth()
{
return width;
}
final int getHeight()
{
return height;
}
final int getRealLayout()
{
return layout;
}
final Object getMonitor()
{
return lock;
}
final Container getOwner()
{
Reference ref;
return (ref = owningReference) == null ? null : (Container) ref.get();
}
private void updateLines(String label)
{
int len;
TextOutputMultilined labelLines;
(labelLines = lines).setText(label);
if(label == null || (len = label.length()) <= 0 || label.charAt(len - 1) == ':')
{
return;
}
labelLines.append(":");
}
}