/* áÁÕôÛûÍí
	Thinlet GUI toolkit - www.thinlet.com
	Copyright (C) 2002 Robert Bajzat (robert.bajzat@thinlet.com)
	
	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 2.1 of the License, or (at your option) any later version.
	
	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.
	
	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

package alice.util.thinlet; //java
//midp package thinlet.midp;

import java.applet.*; //java
import java.awt.*; //java
import java.awt.datatransfer.*; //java
import java.awt.image.*; //java
import java.awt.event.*; //java
import java.lang.reflect.*; //java
import java.io.*;
import java.net.*; //java
import java.util.*;
//midp import javax.microedition.lcdui.*;
//midp import javax.microedition.midlet.*;

/**
 *
 */
public class Thinlet extends Container //java
	implements Runnable, Serializable { //java
//midp public class Thinlet extends Canvas implements CommandListener {

	//midp private static final Boolean TRUE = new Boolean(true);
	//midp private static final Boolean FALSE = new Boolean(false);

	private transient Font font;
	private transient Color c_bg;
	private transient Color c_text;
	private transient Color c_textbg;
	private transient Color c_border;
	private transient Color c_disable;
	private transient Color c_hover;
	private transient Color c_press;
	private transient Color c_focus;
	private transient Color c_select;
	private transient Color c_ctrl = null; //java
	//midp private transient Color c_ctrl;
	private transient int block;
	private transient Image hgradient, vgradient; //java
	{
		setFont(new Font("SansSerif", Font.PLAIN, 12)); //java
		//setFont((Font) getToolkit().getDesktopProperty("win.messagebox.font"));
		//midp setFont(Font.getDefaultFont());
		setColors(0xe6e6e6, 0x000000, 0xffffff,
			0x909090, 0xb0b0b0, 0xededed, 0xb9b9b9, 0x89899a, 0xc5c5dd);
	}

	private transient Thread timer;
	private transient long watchdelay;
	private transient long watch;
	private transient String clipboard;

	private Object content = createImpl("desktop");
	private transient Object mouseinside;
	private transient Object insidepart;
	private transient Object mousepressed;
	private transient Object pressedpart;
	private transient int referencex, referencey;
	private transient int mousex, mousey;
	private transient Object focusowner;
	private transient boolean focusinside; //midp { focusinside = true; }
	private transient Object popupowner;
	private transient Object tooltipowner;
	//private transient int pressedkey;

	//java>	
	private static final int DRAG_ENTERED = AWTEvent.RESERVED_ID_MAX + 1;
	private static final int DRAG_EXITED = AWTEvent.RESERVED_ID_MAX + 2;
	
	private static long WHEEL_MASK = 0;
	private static int MOUSE_WHEEL = 0;
	private static Method wheelrotation = null;
	//<java
	private static int evm = 0;
	//java>
	static {
		try {
			WHEEL_MASK = AWTEvent.class.getField("MOUSE_WHEEL_EVENT_MASK").getLong(null);
			MOUSE_WHEEL = MouseEvent.class.getField("MOUSE_WHEEL").getInt(null);
		} catch (Exception exc) { /* not 1.4 */ }
	}
	{
		// disable global focus-manager for this component in 1.4
		if (MOUSE_WHEEL != 0) {
			try {
				getClass().getMethod("setFocusTraversalKeysEnabled", new Class[] { Boolean.TYPE }).
					invoke(this, new Object[] { Boolean.FALSE });
			} catch (Exception exc) { /* never */ }
		}
		// set listeners flags
		enableEvents(AWTEvent.COMPONENT_EVENT_MASK |
			AWTEvent.FOCUS_EVENT_MASK | AWTEvent.KEY_EVENT_MASK |
			AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | WHEEL_MASK);
			// EVM has larger fillRect, fillOval, and drawImage(part), others are correct
			// contributed by Ibsen Ramos-Bonilla
			try {
				if (System.getProperty("java.vendor").indexOf("Insignia") != -1) { evm = -1; }
			} catch (Exception exc) { /* never */ }
	}
	//<java

	/**
	 * Sets the 9 colors used for components, and repaints the whole UI
	 *
	 * @param background the backround of panels (dialogs, desktops),
	 * and disabled controls, not editable texts, lines between list items
	 * (the default value if <i>#e6e6e6</i>)
	 * @param text for text, arrow foreground (<i>black</i> by default)
	 * @param textbackground the background of text components, and lists
	 * (<i>white</i> by default)
	 * @param border for outer in inner borders of enabled components
	 * (<i>#909090</i> by default)
	 * @param disable for text, border, arrow color in disabled components
	 * (<i>#b0b0b0</i> by default)
	 * @param hover indicates that the mouse is inside a button area
	 * (<i>#ededed</i> by default)
	 * @param press for pressed buttons,
	 * gradient image is calculated using the background and this press color
	 * (<i>#b9b9b9</i> by default)
	 * @param focus for text caret and rectagle color marking the focus owner
	 * (<i>#89899a</i> by default)
	 * @param select used as the background of selected text, and list items,
	 * and in slider (<i>#c5c5dd</i> by default)
	 */
	public void setColors(int background, int text, int textbackground,
			int border, int disable, int hover, int press,
			int focus, int select) {
		c_bg = new Color(background); c_text = new Color(text);
		c_textbg = new Color(textbackground); c_border = new Color(border);
		c_disable = new Color(disable); c_hover = new Color(hover);
		c_press = new Color(press); c_focus = new Color(focus);
		c_select = new Color(select);
		//midp c_ctrl = c_hover;
		hgradient = vgradient = null; //java
		repaint();
	}

	/**
	 * Sets the only one font used everywhere, and revalidates the whole UI.
	 * Scrollbar width/height, spinbox, and combobox button width,
	 * and slider size is the same as the font height
	 *
	 * @param font the default font is <i>SansSerif</i>, <i>plain</i>, and <i>12pt</i>
	 */
	public void setFont(Font font) {
		block = getFontMetrics(font).getHeight(); //java
		super.setFont(font); //java
		//midp block = font.getHeight();
		this.font = font;
		hgradient = vgradient = null; //java
		if (content != null) validate(content);
	}
	
	/**
	 *
	 */
	private void doLayout(Object component) {
		String classname = getClass(component);
		if ("combobox" == classname) {
			if (getBoolean(component, "editable", true)) {
				Image icon = getIcon(component, "icon", null);
				layoutField(component, block, false,
					(icon != null) ? icon.getWidth(this) : 0);
			} // set editable -> validate (overwrite textfield repaint)
			else {
				int selected = getInteger(component, "selected", -1);
				if (selected != -1) {
					Object choice = getItem(component, "choice", selected);
					set(component, "text", get(choice, "text"));
					set(component, "icon", get(choice, "icon"));
				}
			}
		}
		else if (("textfield" == classname) || ("passwordfield" == classname)) {
			layoutField(component, 0, ("passwordfield" == classname), 0);
		}
		else if ("textarea" == classname) {
			String text = getString(component, "text", "");
			int start = getInteger(component, "start", 0);
			if (start > text.length()) { setInteger(component, "start", start = text.length(), 0); }
			int end = getInteger(component, "end", 0);
			if (end > text.length()) { setInteger(component, "end", end = text.length(), 0); }
			
			boolean wrap = getBoolean(component, "wrap", false);
			char[] chars = null;
			if (wrap) {
				Rectangle bounds = getRectangle(component, "bounds");
				chars = getChars(component, text, true, bounds.width - 4, bounds.height);
				if (chars == null) { // need scrollbars
					chars = getChars(component, text, true, bounds.width - block - 4, 0);
				}
			}
			else {
				chars = getChars(component, text, false, 0, 0);
			}
			
			FontMetrics fm = getFontMetrics(font); //java
			int width = 0, height = 0;
			int caretx = 0; int carety = 0;
			for (int i = 0, j = 0; j <= chars.length; j++) {
				if ((j == chars.length) || (chars[j] == '\n')) {
					width = Math.max(width, fm.charsWidth(chars, i, j - i));
					if ((end >= i) && (end <= j)) {
						caretx = fm.charsWidth(chars, i, end - i);
						carety = height;
					}
					height += fm.getHeight();
					i = j + 1;
				}
			}
			layoutScrollPane(component, width + 2,
				height - fm.getLeading() + 2, 0, 0);
			scrollToVisible(component, caretx, carety,
				2, fm.getAscent() + fm.getDescent() + 2); //?
		} 
		else if ("tabbedpane" == classname) {
			Rectangle bounds = getRectangle(component, "bounds");
			String placement = getString(component, "placement", "top");
			boolean horizontal = ((placement == "top") || (placement == "bottom"));
			int tabd = 0;
			int tabsize = 0;
			for (Object comp = get(component, "tab");
					comp != null; comp = get(comp, ":next")) {
				Dimension d = getSize(comp, 8, 4, "left");
				setRectangle(comp, "bounds",
					horizontal ? tabd : 0, horizontal ? 0 : tabd, d.width, d.height);
				tabd += horizontal ? d.width : d.height;
				tabsize = Math.max(tabsize, horizontal ? d.height : d.width);
			}
			for (Object comp = get(component, "tab");
					comp != null; comp = get(comp, ":next")) {
				Rectangle r = getRectangle(comp, "bounds");
				if (horizontal) {
					if (placement == "bottom") { r.y = bounds.height - tabsize; }
					r.height = tabsize;
				} else {
					if (placement == "right") { r.x = bounds.width - tabsize; }
					r.width = tabsize;
				}
			}
			int cx = (placement == "left") ? (tabsize + 1) : 2;
			int cy = (placement == "top") ? (tabsize + 1) : 2;
			int cwidth = bounds.width - (horizontal ? 4 : (tabsize + 3));
			int cheight = bounds.height - (horizontal ? (tabsize + 3) : 4);
			for (Object comp = get(component, "component");
					comp != null; comp = get(comp, ":next")) {
				if (!getBoolean(comp, "visible", true)) { continue; }
				setRectangle(comp, "bounds", cx, cy, cwidth, cheight);
				doLayout(comp);
			}
		}
		else if (("panel" == classname) || (classname == "dialog")) {
			int gap = getInteger(component, "gap", 0);
			int[][] grid = getGrid(component, gap);
			if (grid != null) {
				int top = getInteger(component, "top", 0);
				int left = getInteger(component, "left", 0);
				int bottom = getInteger(component, "bottom", 0);
				int right = getInteger(component, "right", 0);
				if (classname == "dialog") {
					int titleheight = getInteger(component, ":titleheight", 0);
					top += 4 + titleheight; left += 4; bottom += 4; right += 4;
				}

				Rectangle bounds = getRectangle(component, "bounds");
				for (int i = 0; i < 2; i++) {
					int d = ((i == 0) ? (bounds.width - left - right) :
						(bounds.height - top - bottom)) -
						getSum(grid[i], 0, grid[i].length, gap, false);
					if (d != 0) {
						int w = getSum(grid[2 + i], 0, grid[2 + i].length, 0, false);
						if (w > 0) {
							for (int j = 0; j < grid[i].length; j++) {
								if (grid[2 + i][j] != 0) {
									grid[i][j] += d * grid[2 + i][j] / w;
								}
							}
						}
					}
				}

				int i = 0;
				for (Object comp = get(component, "component");
						comp != null; comp = get(comp, ":next")) {
					if (!getBoolean(comp, "visible", true)) { continue; }
					int ix = left + getSum(grid[0], 0, grid[4][i], gap, true);
					int iy = top + getSum(grid[1], 0, grid[5][i], gap, true);
					int iwidth = getSum(grid[0], grid[4][i], grid[6][i], gap, false);
					int iheight = getSum(grid[1], grid[5][i], grid[7][i], gap, false);
					String halign = getString(comp, "halign", "fill");
					String valign = getString(comp, "valign", "fill");
					if ((halign != "fill") || (valign != "fill")) {
						Dimension d = getPreferredSize(comp);
						if (halign != "fill") {
							int dw = Math.max(0, iwidth - d.width);
							if (halign == "center") { ix += dw / 2; }
								else if (halign == "right") { ix += dw; }
							iwidth -= dw;
						}
						if (valign != "fill") {
							int dh = Math.max(0, iheight - d.height);
							if (valign == "center") { iy += dh / 2; }
								else if (valign == "bottom") { iy += dh; }
							iheight -= dh;
						}
					}
					setRectangle(comp, "bounds", ix, iy, iwidth, iheight);
					doLayout(comp);
					i++;
				}
			}
		}
		else if ("desktop" == classname) {
			Rectangle bounds = getRectangle(component, "bounds");
			for (Object comp = get(component, "component");
					comp != null; comp = get(comp, ":next")) {
				String iclass = getClass(comp);
				if (iclass == "dialog") {
					Dimension d = getPreferredSize(comp);
					if (get(comp, "bounds") == null)
					setRectangle(comp, "bounds",
						Math.max(0, (bounds.width - d.width) / 2),
						Math.max(0, (bounds.height - d.height) / 2),
						Math.min(d.width, bounds.width), Math.min(d.height, bounds.height));
				} else if ((iclass == ":combolist") || (iclass == ":popup")) {
						//iclass = iclass; //compiler bug
				} else {
					setRectangle(comp, "bounds", 0, 0, bounds.width, bounds.height);
				}
				doLayout(comp);
			}
		}
		else if ("spinbox" == classname) {
			layoutField(component, block, false, 0);
		}
		else if ("splitpane" == classname) {
			Rectangle bounds = getRectangle(component, "bounds");
			boolean horizontal = ("vertical" != get(component, "orientation"));
			int divider = getInteger(component, "divider", -1);
			int maxdiv = (horizontal ? bounds.width : bounds.height) - 5;

			Object comp1 = get(component, "component");
			boolean visible1 = (comp1 != null) && getBoolean(comp1, "visible", true);
			if (divider == -1) {
				int d1 = 0;
				if (visible1) {
					Dimension d = getPreferredSize(comp1);
					d1 = horizontal ? d.width : d.height;
				}
				divider = Math.min(d1, maxdiv);
				setInteger(component, "divider", divider, -1);
			}
			else if (divider > maxdiv) {
				setInteger(component, "divider", divider = maxdiv, -1);
			}

			if (visible1) {
				setRectangle(comp1, "bounds", 0, 0, horizontal ? divider : bounds.width,
					horizontal ? bounds.height : divider);
				doLayout(comp1);
			}
			Object comp2 = (comp1 != null) ? get(comp1, ":next") : null;
			if ((comp2 != null) && getBoolean(comp2, "visible", true)) {
				setRectangle(comp2, "bounds", horizontal ? (divider + 5) : 0,
					horizontal ? 0 : (divider + 5),
					horizontal ? (bounds.width - 5 - divider) : bounds.width,
					horizontal ? bounds.height : (bounds.height - 5 - divider));
				doLayout(comp2);
			}
		} 
		else if (("list" == classname) ||
				("table" == classname) || ("tree" == classname)) {
			int width = 0;
			int columnheight = 0;
			if ("table" == classname) {
				for (Object column = get(component, "column");
						column != null; column = get(column, ":next")) {
					width += getInteger(column, "width", 80);
					Dimension d = getSize(column, 2, 2, "left");
					columnheight = Math.max(columnheight, d.height);
				}
			}
			String itemname = ("list" == classname) ? "item" :
				(("table" == classname) ? "row" : "node");
			int y = 0;
			int level = 0;
			for (Object item = get(component, itemname); item != null;) {
				int x = 0;
				int iwidth = 0; int iheight = 0;
				if ("table" == classname) {
					iwidth = width;
					for (Object cell = get(item, "cell");
							cell != null; cell = get(cell, ":next")) {
						Dimension d = getSize(cell, 2, 3, "left");
						iheight = Math.max(iheight, d.height);
					}
				}
				else {
					if ("tree" == classname) {
						x = (level + 1) * block;
					}
					Dimension d = getSize(item, 2, 3, "left");
					iwidth = d.width; iheight = d.height;
					width = Math.max(width, x + d.width);
				}
				setRectangle(item, "bounds", x, y, iwidth, iheight);
				y += iheight;
				if ("tree" == classname) {
					Object next = get(item, "node");
					if ((next != null) && getBoolean(item, "expanded", true)) {
						level++;
					} else {
						while (((next = get(item, ":next")) == null) && (level > 0)) {
							item = getParent(item);
							level--;
						}
					}
					item = next;
				} else {
					item = get(item, ":next");
				}
			}
			layoutScrollPane(component, width, y - 1, 0, columnheight);
		}
		else if ("menubar" == classname) { 
			Rectangle bounds = getRectangle(component, "bounds");
			int x = 0;
			for (Object menu = get(component, "menu");
					menu != null; menu = get(menu, ":next")) {
				Dimension d = getSize(menu, 8, 4, "left");
				setRectangle(menu, "bounds", x, 0, d.width, bounds.height);
				x += d.width;
			}
		}
		else if ((":combolist" == classname) || (":popup" == classname)) {
			boolean combo = (":combolist" == classname);
			int pw = 0; int ph = 0; int pxy = combo ? 0 : 1;
			Object pr = get(component, combo ? "combobox" : "menu");
			for (Object item = get(pr, combo ? "choice" : "menu");
					item != null; item = get(item, ":next")) {
				String itemclass = combo ? null : getClass(item);
				Dimension d = (itemclass == "separator") ? new Dimension(1, 1) :
					getSize(item, 8 , 4, "left");
				if (itemclass == "checkboxmenuitem") {
					d.width = d.width + block + 3;
					d.height = Math.max(block, d.height);
				}
				else if (itemclass == "menu") {
					d.width += block;
				}
				setRectangle(item, "bounds", pxy, pxy + ph, d.width, d.height);
				pw = Math.max(pw, d.width);
				ph += d.height;
			}
			Rectangle r = getRectangle(component, "bounds");
			r.width = pw + 2; r.height = ph + 2;
			if (combo) { r.width = Math.max(r.width, getRectangle(pr, "bounds").width); }
			if (combo) {
				Rectangle db = getRectangle(content, "bounds");
				if (r.y + ph + 2 > db.height) {
					r.width = pw + 2 + block;
					r.height = db.height - r.y;
				}
				else {
					r.height = Math.min(r.height, db.height - r.y);
				}
				r.width = Math.min(r.width, db.width - r.x);
				layoutScrollPane(component, pw, ph, 0, 0);//~
			}
		}
		//java>
		else if ("bean" == classname) {
				Rectangle r = getRectangle(component, "bounds");
				((Component) get(component, "bean")).setBounds(r);
		}
		//<java
	}

	/**
	 *
	 */
	private char[] getChars(Object component,
			String text, boolean wrap, int width, int height) {
		char[] chars = (char[]) get(component, ":text");
		if ((chars == null) || (chars.length != text.length())) {
			chars = text.toCharArray();
			set(component, ":text", chars);
		}
		else text.getChars(0, chars.length, chars, 0);
		
		if (wrap) {
			FontMetrics fm = getFontMetrics(font); //java
			int lines = (height - 4 + fm.getLeading()) / fm.getHeight();
			boolean prevletter = false; int n = chars.length; int linecount = 0;
			for (int i = 0, j = -1, k = 0; k <= n; k++) { // j is the last space index (before k)
				if (((k == n) || (chars[k] == '\n') || (chars[k] == ' ')) &&
						(j  > i) && (fm.charsWidth(chars, i, k - i) > width)) {
					chars[j] = '\n';
					k--; // draw line to the begin of the current word (+ spaces) if it is out of width
				}
				else if ((k == n) || (chars[k] == '\n')) { // draw line to the text/line end
					j = k; prevletter = false;
				}
				else {
					if ((chars[k] == ' ') && (prevletter || (j > i))) { j = k; } // keep spaces starting the line
					prevletter = (chars[k] != ' ');
					continue;
				}
				linecount++;
				if ((lines != 0) && (linecount == lines)) { return null; }
				i = j + 1;
			}
		}
		return chars;
	}
	
	/**
	 *
	 */
	/*private boolean wrap(char[] chars, int width, int lines) {
		FontMetrics fm = getFontMetrics(font);
		boolean prevletter = false; int n = chars.length; int linecount = 0;
		for (int i = 0, j = -1, k = 0; k <= n; k++) { // j is the last space index (before k)
			if (((k == n) || (chars[k] == '\n') || (chars[k] == ' ')) &&
					(j  > i) && (fm.charsWidth(chars, i, k - i) > width)) {
				chars[j] = '\t';
				k--; // draw line to the begin of the current word (+ spaces) if it is out of width
			}
			else if ((k == n) || (chars[k] == '\n')) { // draw line to the text/line end
				j = k; prevletter = false;
			}
			else {
				if (chars[k] == '\t') { chars[k] = ' '; }
				if ((chars[k] == ' ') && (prevletter || (j > i))) { j = k; } // keep spaces starting the line
				prevletter = (chars[k] != ' ');
				continue;
			}
			linecount++;
			if ((lines != 0) && (linecount == lines)) { return false; }
			i = j + 1;
		}
		return true;
	}*/

	/**
	 *
	 */
	private Object popup(Object component, Object classname) {
		Object popup = null;
		int px = 0; int py = 0;
		if (("menubar" == classname) || (":popup" == classname)) {
			Object popupmenu = get(component, ":popup");
			Object selected = get(component, "selected");
			if (popupmenu != null) {
				if (get(popupmenu, "menu") == selected) { return null; }
				set(popupmenu, "selected", null);
				set(popupmenu, "menu", null);
				removeItemImpl(content, "component", popupmenu);
				repaint(popupmenu);
				set(popupmenu, ":parent", null);
				set(component, ":popup", null);
				if (mouseinside == popupmenu) {
					findComponent(content, mousex, mousey);
					handleMouseEvent(mousex, mousex, 1, false, false, false, //java
						MouseEvent.MOUSE_ENTERED, mouseinside, insidepart); //java
				}
				popup(popupmenu, ":popup");
			}
			if ((selected == null) || (getClass(selected) != "menu")) { return null; }
			popup = createImpl(":popup");
			set(popup, "menu", selected);
			set(component, ":popup", popup);

			Rectangle bounds = getRectangle(selected, "bounds");
			if ("menubar" == classname) {
				px = bounds.x; py = bounds.y + bounds.height - 1;
			} else {
				px = bounds.x + getRectangle(component, "bounds").width - 4;
				py = bounds.y;
			}
		}
		else { //if ("combobox" == classname) {
			popup = createImpl(":combolist");
			set(popup, "combobox", component);
			set(component, ":combolist", popup);

			py = getRectangle(component, "bounds").height + 1;
		}
		if (("menubar" == classname) || ("combobox" == classname)) {
			popupowner = component;
		}
		insertItem(content, "component", popup, 0);
		set(popup, ":parent", content);
		while (component != content) {
			Rectangle r = getRectangle(component, "bounds");
			px += r.x; py += r.y;
			component = getParent(component);
		}
		setRectangle(popup, "bounds", px, py, 0, 0);
		doLayout(popup); repaint(popup);
		return popup;
	}

	/**
	 *
	 */
	private void closeup(Object combobox, Object combolist, Object item) {
		if ((item != null) && getBoolean(item, "enabled", true)) {
			String text = getString(item, "text", "");
			set(combobox, "text", text); // if editable
			setInteger(combobox, "start", text.length(), 0);
			setInteger(combobox, "end", 0, 0);
			set(combobox, "icon", get(item, "icon"));
			validate(combobox);
			setInteger(combobox, "selected", getIndex(combobox, "choice", item), -1);
			invoke(combobox, item, "action");
		}
		set(combolist, "combobox", null);
		set(combobox, ":combolist", null);
		removeItemImpl(content, "component", combolist);
		repaint(combolist);
		set(combolist, ":parent", null);
		popupowner = null;
		if (mouseinside == combolist) {
			findComponent(content, mousex, mousey);
			handleMouseEvent(mousex, mousex, 1, false, false, false, //java
				MouseEvent.MOUSE_ENTERED, mouseinside, insidepart); //java
		}
	}

	/**
	 *
	 */
	private void closeup(Object menubar) {
		set(menubar, "selected", null);
		popup(menubar, "menubar");
		repaint(menubar); // , selected
		popupowner = null;
	}

	/**
	 *
	 */
	private void closeup() {
		if (popupowner != null) {
			String classname = getClass(popupowner);
			if ("menubar" == classname) {
				closeup(popupowner);
			}
			else if ("combobox" == classname) {
				closeup(popupowner, get(popupowner, ":combolist"), null);
			}
		}
	}

	/**
	 *
	 */
	private void showTip() {
		String text = null;
		tooltipowner = null;
		String classname = getClass(mouseinside);
		if ((classname == "tabbedpane") || (classname == "menubar") || (classname == ":popup")) {
			if (insidepart != null) {
				text = getString(insidepart, "tooltip", null);
			}
		}
		else if (classname == ":combolist") {
			if (insidepart instanceof Object[]) {
				text = getString(insidepart, "tooltip", null);
			}
		}
		//list table tree
		if (text == null) { text = getString(mouseinside, "tooltip", null); }
			else { tooltipowner = insidepart; }
		if (text != null) {
			FontMetrics fm = getFontMetrics(font);
			int width = fm.stringWidth(text) + 4;
			int height = fm.getAscent() + fm.getDescent() + 4;
			if (tooltipowner == null) { tooltipowner = mouseinside; }
			Rectangle bounds = getRectangle(content, "bounds");
			int tx = Math.max(0, Math.min(mousex + 10, bounds.width - width));
			int ty = Math.max(0, Math.min(mousey + 10, bounds.height - height));
			setRectangle(tooltipowner, ":tooltipbounds", tx, ty, width, height);
			repaint(tx, ty, width, height);
		}
	}

	/**
	 *
	 */
	private void hideTip() {
		if (tooltipowner != null) {
			Rectangle bounds = getRectangle(tooltipowner, ":tooltipbounds");
			set(tooltipowner, ":tooltipbounds", null);
			tooltipowner = null;
			repaint(bounds.x, bounds.y, bounds.width, bounds.height);
		}
	}

	/**
	 *
	 */
	private void layoutField(Object component, int dw, boolean hidden, int left) {
		int width = getRectangle(component, "bounds").width - left -dw;
		String text = getString(component, "text", "");
		int start = getInteger(component, "start", 0);
		if (start > text.length()) { setInteger(component, "start", start = text.length(), 0); }
		int end = getInteger(component, "end", 0);
		if (end > text.length()) { setInteger(component, "end", end = text.length(), 0); }
		int offset = getInteger(component, "offset", 0);
		int off = offset;
		FontMetrics fm = getFontMetrics(font);
		int caret = hidden ? (fm.charWidth('*') * end) :
			fm.stringWidth(text.substring(0, end)); //java
			//midp font.substringWidth(text, 0, end);
		if (off > caret) {
			off = caret;
		}
		else if (off < caret - width + 4) {
			off = caret - width + 4;
		}
		off = Math.max(0, Math.min(off, (hidden ? (fm.charWidth('*') *
			text.length()) : fm.stringWidth(text)) - width + 4)); 
		if (off != offset) {
			setInteger(component, "offset", off, 0);
		}
	}

	/**
	 *
	 */
	private void layoutScrollPane(Object component,
			int contentwidth, int contentheight, int rowwidth, int columnheight) {
		Rectangle bounds = getRectangle(component, "bounds");
		boolean hneed = false; boolean vneed = false;
		if (contentwidth > bounds.width - rowwidth - 2) {
			hneed = true;
			vneed = (contentheight > bounds.height - columnheight - 2 - block);
		}
		if (vneed || (contentheight > bounds.height - columnheight - 2)) {
			vneed = true;
			hneed = hneed || (contentwidth > bounds.width - rowwidth - 2 - block);
		}
		int viewportwidth = bounds.width - rowwidth - (vneed ? block : 0);
		int viewportheight = bounds.height - columnheight - (hneed ? block : 0);
		setRectangle(component, ":port",
			rowwidth, columnheight, viewportwidth, viewportheight); //?rowwidth

		Rectangle view = getRectangle(component, ":view");
		setRectangle(component, ":view",
			(view != null) ? Math.max(0,
				Math.min(view.x, contentwidth - viewportwidth + 2)) : 0,
			(view != null) ? Math.max(0,
				Math.min(view.y, contentheight - viewportheight + 2)) : 0,
			Math.max(viewportwidth - 2, contentwidth),
			Math.max(viewportheight - 2, contentheight));
	}

	/**
	 *
	 */
	private void scrollToVisible(Object component,
			int x, int y, int width, int height) {
		Rectangle view = getRectangle(component, ":view");
		Rectangle port = getRectangle(component, ":port");
		int vx = Math.max(x + width - port.width + 2, Math.min(view.x, x));
		int vy = Math.max(y + height - port.height + 2, Math.min(view.y, y));
		if ((view.x != vx) || (view.y != vy)) {
			repaint(component); // horizontal | vertical
			view.x = vx; view.y = vy;
		}
	}
	
	/**
	 * Gets the preferred size of the root component
	 *
	 * @return a dimension object indicating the root component's preferred size 
	 */
	public Dimension getPreferredSize() {
		return getPreferredSize(content);
	}

	/**
	 *
	 * @throws java.lang.IllegalArgumentException
	 */
	private Dimension getPreferredSize(Object component) {
		int width = getInteger(component, "width", 0);
		int height = getInteger(component, "height", 0);
		if ((width > 0) && (height > 0)) {
			return new Dimension(width, height);
		}
		String classname = getClass(component);
		//System.out.println("classname: " + classname);
		if ("label" == classname) {
			return getSize(component, 0, 0, "left");
		} 
		if (("button" == classname) || ("togglebutton" == classname)) {
			return getSize(component, 12, 6, "center");
		} 
		if ("checkbox" == classname) {
			Dimension d = getSize(component, 0, 0, "left");
			d.width = d.width + block + 3;
			d.height = Math.max(block, d.height);
			return d;
		}
		if ("combobox" == classname) {
			if (getBoolean(component, "editable", true)) {
				Dimension size = getFieldSize(component);
				Image icon = getIcon(component, "icon", null);
				if (icon != null) {
					size.width += icon.getWidth(this);
					size.height = Math.max(size.height, icon.getHeight(this) + 2);
				}
				size.width += block;
				return size;
			} else {
				int selected = getInteger(component, "selected", -1);
				return getSize((selected != -1) ?
					getItemImpl(component, "choice", selected) :
					get(component, "choice"), 4 + block, 4, "left");
			}
		}
		if (("textfield" == classname) || ("passwordfield" == classname)) {
			return getFieldSize(component);
		}
		if ("textarea" == classname) {
			int columns = getInteger(component, "columns", 0);
			int rows = getInteger(component, "rows", 0); // 'e' -> 'm' ?
			FontMetrics fm = getFontMetrics(font); //java
			return new Dimension(
				((columns > 0) ? (columns * fm.charWidth('e') + 2) : 76) + 2 + block,
				((rows > 0) ? (rows * fm.getHeight() - fm.getLeading() + 2) : 76) + 2 + block);
		}
		if ("tabbedpane" == classname) {
			String placement = getString(component, "placement", "top");
			boolean horizontal = ((placement == "top") || (placement == "bottom"));
			int tabsize = 0;	
			int contentwidth = 0; int contentheight = 0;
			for (Object comp = get(component, "tab");
					comp != null; comp = get(comp, ":next")) {
				Dimension d = getSize(comp, 8, 4, "left");
				tabsize = Math.max(tabsize, horizontal ? d.height : d.width);
			}
			for (Object comp = get(component, "component");
					comp != null; comp = get(comp, ":next")) {
				if (!getBoolean(comp, "visible", true)) { continue; }
				Dimension d = getPreferredSize(comp);
				contentwidth = Math.max(contentwidth, d.width);
				contentheight = Math.max(contentheight, d.height);
			}
			return new Dimension(contentwidth + (horizontal ? 4 : (tabsize + 3)),
				contentheight + (horizontal ? (tabsize + 3) : 4));
		}
		if (("panel" == classname) || (classname == "dialog")) {
			Dimension size = new Dimension(
				getInteger(component, "left", 0) + getInteger(component, "right", 0),
				getInteger(component, "top", 0) + getInteger(component, "bottom", 0));
			if (classname == "dialog") {
				int titleheight = getSize(component, 0, 0, "left").height;
				setInteger(component, ":titleheight", titleheight, 0);
				size.width += 8; size.height += 8 + titleheight;
			}
			int gap = getInteger(component, "gap", 0);
			int[][] grid = getGrid(component, gap);
			if (grid != null) {
				size.width += getSum(grid[0], 0, grid[0].length, gap, false);
				size.height += getSum(grid[1], 0, grid[1].length, gap, false);
			}
			return size;
		}
		else if ("desktop" == classname) {
			Dimension size = new Dimension();
			for (Object comp = get(component, "component");
					comp != null; comp = get(comp, ":next")) {
				String iclass = getClass(comp);
				if ((iclass != "dialog") && (iclass != ":popup") &&
						(iclass != ":combolist")) {
					Dimension d = getPreferredSize(comp);
					size.width = Math.max(d.width, size.width);
					size.height = Math.max(d.height, size.height);
				}
			}
			return size;
		}
		if ("spinbox" == classname) {
			Dimension size = getFieldSize(component);
			size.width += block;
			return size;
		}
		if ("progressbar" == classname) {
			boolean horizontal = ("vertical" != get(component, "orientation"));
			return new Dimension(horizontal ? 76 : 6, horizontal ? 6 : 76);
		}
		if ("slider" == classname) {
			boolean horizontal = ("vertical" != get(component, "orientation"));
			return new Dimension(horizontal ? 76 : 10, horizontal ? 10 : 76);
		}
		if ("splitpane" == classname) {
			boolean horizontal = ("vertical" != get(component, "orientation"));
			Object comp1 = get(component, "component");
			Dimension size = ((comp1 == null) || !getBoolean(comp1, "visible", true)) ?
				new Dimension() : getPreferredSize(comp1);
			Object comp2 = get(comp1, ":next");
			if ((comp2 != null) && getBoolean(comp2, "visible", true)) {
				Dimension d = getPreferredSize(comp2);
				size.width = horizontal ? (size.width + d.width) :
					Math.max(size.width, d.width);
				size.height = horizontal ? Math.max(size.height, d.height) :
					(size.height + d.height);
			}
			if (horizontal) { size.width += 5; } else { size.height += 5; }
			return size;
		}
		if (("list" == classname) ||
				("table" == classname) || ("tree" == classname)) {
			return new Dimension(76 + 2 + block, 76 + 2 + block);
		}
		if ("separator" == classname) {
			return new Dimension(1, 1);
		}
		if ("menubar" == classname) { 
			Dimension size = new Dimension(0, 0);
			for (Object menu = get(component, "menu");
					menu != null; menu = get(menu, ":next")) {
				Dimension d = getSize(menu, 8, 4, "left");
				size.width += d.width;
				size.height = Math.max(size.height, d.height);
			}
			return size;
		}
		//java>
		if ("bean" == classname) {
				return ((Component) get(component, "bean")).getPreferredSize();
		}
		//<java
		throw new IllegalArgumentException((String) classname);
	}

	/**
	 *
	 */
	private int[][] getGrid(Object component, int gap) {
		int count = 0;
		for (Object comp = get(component, "component"); comp != null;
				comp = get(comp, ":next")) {
			if (getBoolean(comp, "visible", true)) { count++; }
		}
		if (count == 0) { return null; }
		int columns = getInteger(component, "columns", 0);
		int icols = (columns != 0) ? columns : count;
		int irows = (columns != 0) ? ((count + columns - 1) / columns) : 1;
		int[][] grid = {
			new int[icols], new int[irows], // columnwidths, rowheights
			new int[icols], new int[irows], // columnweights, rowweights
			new int[count], new int[count], // gridx, gridy
			new int[count], new int[count] }; // gridwidth, gridheight
		int[] columnheight = new int[icols];
		int[][] cache = null; // preferredwidth, height, columnweight, rowweight

		int i = 0; int x = 0; int y = 0;
		int nextsize = 0;
		for (Object comp = get(component, "component");
				comp != null; comp = get(comp, ":next")) {
			if (!getBoolean(comp, "visible", true)) { continue; }
			int colspan = ((columns != 0) && (columns < count)) ?
				Math.min(getInteger(comp, "colspan", 1), columns) : 1;
			int rowspan = (columns != 1) ? getInteger(comp, "rowspan", 1) : 1;
			
			for (int j = 0; j < colspan; j++) {
				if ((columns != 0) && (x + colspan > columns)) {
					x = 0; y++; j = -1;
				}
				else if (columnheight[x + j] > y) {
					x += (j + 1); j = -1;
				}
			}
			if (y + rowspan > grid[1].length) {
				int[] rowheights = new int[y + rowspan];
				System.arraycopy(grid[1], 0, rowheights, 0, grid[1].length);
				grid[1] = rowheights;
				int[] rowweights = new int[y + rowspan];
				System.arraycopy(grid[3], 0, rowweights, 0, grid[3].length);
				grid[3] = rowweights;
			}
			for (int j = 0; j < colspan; j++) {
				columnheight[x + j] = y + rowspan;
			}

			int weightx = getInteger(comp, "weightx", 0);
			int weighty = getInteger(comp, "weighty", 0);
			Dimension d = getPreferredSize(comp);

			if (colspan == 1) {
				grid[0][x] = Math.max(grid[0][x], d.width); // columnwidths
				grid[2][x] = Math.max(grid[2][x], weightx); // columnweights
			}
			else {
				if (cache == null) { cache = new int[4][count]; }
				cache[0][i] = d.width;
				cache[2][i] = weightx;
				if ((nextsize == 0) || (colspan < nextsize)) { nextsize = colspan; }
			}
			if (rowspan == 1) {
				grid[1][y] = Math.max(grid[1][y], d.height); // rowheights 
				grid[3][y] = Math.max(grid[3][y], weighty); // rowweights
			}
			else {
				if (cache == null) { cache = new int[4][count]; }
				cache[1][i] = d.height;
				cache[3][i] = weighty;
				if ((nextsize == 0) || (rowspan < nextsize)) { nextsize = rowspan; }
			}
			grid[4][i] = x; //gridx
			grid[5][i] = y; //gridy
			grid[6][i] = colspan; //gridwidth
			grid[7][i] = rowspan; //gridheight
			
			x += colspan;
			i++;
		}

		while (nextsize != 0) {
			int size = nextsize; nextsize = 0;
			for (int j = 0; j < 2; j++) { // horizontal, vertical
				for (int k = 0; k < count; k++) {
					if (grid[6 + j][k] == size) { // gridwidth, gridheight
						int gridpoint = grid[4 + j][k]; // gridx, gridy

						int weightdiff = cache[2 + j][k];
						for (int m = 0; (weightdiff > 0) && (m < size); m++) {
							weightdiff -= grid[2 + j][gridpoint + m];
						}
						if (weightdiff > 0) {
							int weightsum = cache[2 + j][k] - weightdiff;
							for (int m = 0; (weightsum > 0) && (m < size); m++) {
								int weight = grid[2 + j][gridpoint + m];
								if (weight > 0) {
									int weightinc = weight * weightdiff / weightsum;
									grid[2 + j][gridpoint + m] += weightinc;
									weightdiff -= weightinc;
									weightsum -= weightinc;
								}
							}
							grid[2 + j][gridpoint + size - 1] += weightdiff;
						}

						int sizediff = cache[j][k];
						int weightsum = 0;
						for (int m = 0; (sizediff > 0) && (m < size); m++) {
							sizediff -= grid[j][gridpoint + m];
							weightsum += grid[2 + j][gridpoint + m];
						}
						if (sizediff > 0) {
							for (int m = 0; (weightsum > 0) && (m < size); m++) {
								int weight = grid[2 + j][gridpoint + m];
								if (weight > 0) {
									int sizeinc = weight * sizediff / weightsum;
									grid[j][gridpoint + m] += sizeinc;
									sizediff -= sizeinc;
									weightsum -= weight;
								}
							}
							grid[j][gridpoint + size - 1] += sizediff;
						}
					}
					else if ((grid[6 + j][k] > size) &&
							((nextsize == 0) || (grid[6 + j][k] < nextsize))) {
						nextsize = grid[6 + j][k];
					}
				}
			}
		}
		return grid;
	}

	/**
	 *
	 */
	private int getSum(int[] values,
			int from, int length, int gap, boolean last) {
		if (length <= 0) { return 0; }
		int value = 0;
		for (int i = 0; i < length; i++) {
			value += values[from + i];
		}
		return value + (length - (last ? 0 : 1)) * gap;
	}

	/**
	 *
	 */
	private Dimension getFieldSize(Object component) {
		String text = getString(component, "text", "");
		int columns = getInteger(component, "columns", 0);
		FontMetrics fm = getFontMetrics(font);
		return new Dimension(((columns > 0) ?
			(columns * fm.charWidth('e')) : 76) + 4,
			fm.getAscent() + fm.getDescent() + 4); // fm.stringWidth(text)
	}

	/**
	 *
	 */
	private Dimension getSize(Object component,
			int dx, int dy, String defaultalignment) {
		String text = getString(component, "text", null);
		int tw = 0; int th = 0;
		if (text != null) {
			FontMetrics fm = getFontMetrics(font);
			tw = fm.stringWidth(text);
			th = fm.getAscent() + fm.getDescent();
		}
		Image icon = getIcon(component, "icon", null);
		int iw = 0; int ih = 0;
		if (icon != null) {
			iw = icon.getWidth(this);
			ih = icon.getHeight(this);
		}
		return new Dimension(tw + iw + dx, Math.max(th, ih) + dy);
	}
	//java>

	/**
	 * Invokes the paint method
	 */
	public void update(Graphics g) {
		paint(g);
	}

	/**
	 *
	 */
	/*public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
		if (infoflags == ImageObserver.ALLBITS) {
			validate(content);
		}
		System.out.println("imageUpdate " + x + ", " + y + ", " + width + ", " + height + " " +
			(((infoflags & ABORT) > 0) ? "ABORT " : "") + (((infoflags & ALLBITS) > 0) ? "ALLBITS " : "") +
			(((infoflags & ERROR) > 0) ? "ERROR " : "") + (((infoflags & FRAMEBITS) > 0) ? "FRAMEBITS " : "") +
			(((infoflags & HEIGHT) > 0) ? "HEIGHT " : "") + (((infoflags & PROPERTIES) > 0) ? "PROPERTIES " : "") +
			(((infoflags & SOMEBITS) > 0) ? "SOMEBITS " : "") + (((infoflags & WIDTH) > 0) ? "WIDTH " : ""));
		return super.imageUpdate(img, infoflags, x, y, width, height);
	}*/

	/**
	 * Paints the components inside the graphics clip area
	 */
	public void paint(Graphics g) {
		g.setFont(font);
		if (hgradient == null) {
			int[][] pix = new int[2][block * block];
			int r1 = c_bg.getRed(); int r2 = c_press.getRed();
			int g1 = c_bg.getGreen(); int g2 = c_press.getGreen();
			int b1 = c_bg.getBlue(); int b2 = c_press.getBlue();
			for (int i = 0; i < block; i++) {
				int cr = r1 - (r1 - r2) * i / block;
				int cg = g1 - (g1 - g2) * i / block;
				int cb = b1 - (b1 - b2) * i / block;
				int color = (255 << 24) | (cr << 16) | (cg << 8) | cb;
				for (int j = 0; j < block; j++) {
					pix[0][i * block + j] = color;
					pix[1][j * block + i] = color;
				}
			}
			hgradient = createImage(new MemoryImageSource(block, block, pix[0], 0, block));
			vgradient = createImage(new MemoryImageSource(block, block, pix[1], 0, block));
		}
	
		//((Graphics2D) g).setRenderingHint(
			//RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		//g.setColor(Color.orange);
		//g.fillRect(0, 0, getSize().width, getSize().height);
		//long time = System.currentTimeMillis();
		Rectangle clip = g.getClipBounds();
		///dg.setClip(r.x, r.y, r.width, r.height);
		paint(g, clip.x, clip.y, clip.width, clip.height, content, isEnabled());
		//System.out.println(System.currentTimeMillis() - time);
		///g.setClip(0, 0, getSize().width, getSize().height);
		//g.setColor(Color.red); g.drawRect(clip.x, clip.y, clip.width - 1, clip.height - 1);
	}

	//<java
	/*midp
	protected void paint(Graphics g) {
		paint(g, g.getClipX(), g.getClipY(),
			g.getClipWidth(), g.getClipHeight(), content, true);
	}

	protected void showNotify() {
		setRectangle(content, "bounds", 0, 0, getWidth(), getHeight());
		doLayout(content);
	}
	midp*/

	/**
	 *
	 * @throws java.lang.IllegalArgumentException
	 */
	private void paint(Graphics g,
			int clipx, int clipy, int clipwidth, int clipheight,
			Object component, boolean enabled) {
		if (!getBoolean(component, "visible", true)) { return; }
		Rectangle bounds = getRectangle(component, "bounds");
		if (bounds == null) { return; }
		if (bounds.width < 0) {
			bounds.width = Math.abs(bounds.width);
			doLayout(component);
		}
		if ((clipx + clipwidth < bounds.x) ||
				(clipx > bounds.x + bounds.width) ||
				(clipy + clipheight < bounds.y) ||
				(clipy > bounds.y + bounds.height)) {
			return;
		}
		clipx -= bounds.x; clipy -= bounds.y;
		String classname = getClass(component);
		boolean pressed = (mousepressed == component);
		boolean inside = (mouseinside == component) &&
			((mousepressed == null) || pressed);
		boolean focus = focusinside && (focusowner == component);
		enabled = getBoolean(component, "enabled", true); //enabled &&
		g.translate(bounds.x, bounds.y); 
		//g.setClip(0, 0, bounds.width, bounds.height);

		if ("label" == classname) {
			paintContent(component, g, clipx, clipy, clipwidth, clipheight,
				0, 0, bounds.width, bounds.height,
				enabled ? c_text : c_disable, "left", true);
		}
		else if (("button" == classname) || ("togglebutton" == classname)) {
			boolean toggled = ("togglebutton" == classname) && getBoolean(component, "selected", false);
			paintRect(g, 0, 0, bounds.width, bounds.height,
				enabled ? c_border : c_disable,
				enabled ? ((inside != pressed) ? c_hover : ((pressed || toggled) ? c_press : c_ctrl)) :
					(toggled ? c_press : c_bg), true, true, true, true, true);
			if (focus) {
				g.setColor(c_focus);
				g.drawRect(2, 2, bounds.width - 5, bounds.height - 5);
			}
			paintContent(component, g, clipx, clipy, clipwidth, clipheight,
				6, 3, bounds.width - 12, bounds.height - 6,
				enabled ? c_text : c_disable, "center", true);
		}
		else if ("checkbox" == classname) {
			boolean selected = getBoolean(component, "selected", false);
			String group = getString(component, "group", null);
			Color border = enabled ? c_border : c_disable;
			Color foreground = enabled ? ((inside != pressed) ? c_hover :
				(pressed ? c_press : c_ctrl)) : c_bg;
			int dy = (bounds.height - block + 2) / 2;
			if (group == null) {
				paintRect(g, 1, dy + 1, block - 2, block - 2,
					border, foreground, true, true, true, true, true);
			} else {
				g.setColor((foreground != c_ctrl) ? foreground : c_bg);
				g.fillOval(1, dy + 1, block - 3 + evm, block - 3 + evm); //java
				g.setColor(border);
				g.drawOval(1, dy + 1, block - 3, block - 3); //java
			}
			if (focus) {
				g.setColor(c_focus);
				if (group == null) {
					g.drawRect(3, dy + 3, block - 7, block - 7);
				} else {
					g.drawOval(3, dy + 3, block - 7, block - 7); //java
				}
			}
			if((!selected && inside && pressed) ||
					(selected && (!inside || !pressed))) {
				g.setColor(enabled ? c_text : c_disable);
				if (group == null) {
					g.fillRect(3, dy + block - 9, 2 + evm, 6 + evm);
					g.drawLine(3, dy + block - 4, block - 4, dy + 3);
					g.drawLine(4, dy + block - 4, block - 4, dy + 4);
				} else {
					g.fillOval(5, dy + 5, block - 10 + evm, block - 10 + evm); //java
					g.drawOval(4, dy + 4, block - 9, block - 9); //java
				}
			}
			paintContent(component, g, clipx, clipy, clipwidth, clipheight,
				block + 3, 0, bounds.width - block - 3, bounds.height,
				enabled ? c_text : c_disable, "left", true);
		}
		else if ("combobox" == classname) {
			if (getBoolean(component, "editable", true)) {
				Image icon = getIcon(component, "icon", null);
				int left = (icon != null) ? icon.getWidth(this) : 0;
				paintField(g, clipx, clipy, clipwidth, clipheight, component,
					bounds.width - block, bounds.height,
					inside, pressed, focus, enabled, false, left);
				if (icon != null) {
					g.drawImage(icon, 2, (bounds.height - icon.getHeight(this)) / 2, this); //java
					//midp g.drawImage(icon, 2, bounds.height / 2, Graphics.LEFT | Graphics.VCENTER);
				}
				paintArrow(g, bounds.width - block, 0, block, bounds.height,
					'S', enabled, inside, pressed, "down", true, false, true, true, true);
			} else {
				paintRect(g, 0, 0, bounds.width, bounds.height,
					enabled ? c_border : c_disable,
					enabled ? ((inside != pressed) ? c_hover :
						(pressed ? c_press : c_ctrl)) : c_bg, true, true, true, true, true);
				if (focus) {
					g.setColor(c_focus);
					g.drawRect(2, 2, bounds.width - block - 5, bounds.height - 5);
				}
				paintContent(component, g, clipx, clipy, clipwidth, clipheight,
					2, 2, bounds.width - block - 4, bounds.height - 4,
					enabled ? c_text : c_disable, "left", false);
				g.setColor(enabled ? c_text : c_disable);
				paintArrow(g, bounds.width - block, 0, block, bounds.height, 'S');
			}
		}
		else if (":combolist" == classname) {
			Rectangle view = getRectangle(component, ":view");
			Rectangle viewport = getRectangle(component, ":port");
			g.setColor(c_border);
			g.drawRect(viewport.x, viewport.y, viewport.width - 1, viewport.height - 1);
			if (paintScrollPane(g, clipx, clipy, clipwidth, clipheight,
					bounds, view, viewport, enabled, inside, pressed)) {
				Object selected = get(component, ":inside");
				int ly = clipy - viewport.y - 1;
				int yfrom = view.y + Math.max(0, ly);
				int yto = view.y + Math.min(viewport.height - 2, ly + clipheight);
				for (Object choice = get(get(component, "combobox"), "choice");
						choice != null; choice = get(choice, ":next")) {
					Rectangle r = getRectangle(choice, "bounds");
					if (yto <= r.y) { break; }
					if (yfrom >= r.y + r.height) { continue; }
					boolean armed = (selected == choice);
					paintRect(g, r.x, r.y, bounds.width - 2, r.height, c_border,
						armed ? c_select : c_bg, false, false, false, false, true);
					paintContent(choice, g, clipx, yfrom, clipwidth, yto - yfrom,
						r.x + 4, r.y + 2, bounds.width - 10, r.height - 4,
						getBoolean(choice, "enabled", true) ? c_text : c_disable, "left", false);
				}
				resetScrollPane(g, clipx, clipy, clipwidth, clipheight, view, viewport);
			}
		}
		else if (("textfield" == classname) || ("passwordfield" == classname)) {
			paintField(g, clipx, clipy, clipwidth, clipheight, component,
				bounds.width, bounds.height,
				inside, pressed, focus, enabled, ("passwordfield" == classname), 0);
		}
		else if ("textarea" == classname) {
			Rectangle view = getRectangle(component, ":view");
			Rectangle viewport = getRectangle(component, ":port");
			boolean editable = getBoolean(component, "editable", true);
			paintRect(g, viewport.x, viewport.y, viewport.width, viewport.height,
				enabled ? c_border : c_disable, editable ? c_textbg : c_bg,
				true, true, true, true, true);
			if (paintScrollPane(g, clipx, clipy, clipwidth, clipheight,
					bounds, view, viewport, enabled, inside, pressed)) {
				char[] chars = (char[]) get(component, ":text");
				int start = focus ? getInteger(component, "start", 0) : 0;
				int end = focus ? getInteger(component, "end", 0) : 0;
				int is = Math.min(start, end); int ie = Math.max(start, end);
				FontMetrics fm = g.getFontMetrics(); //java
				int fontascent = fm.getAscent(); int fontheight = fm.getHeight(); //java
				//midp int fontheight = fm.getHeight();
				int ascent = 1;
				int ly = clipy - viewport.y - 1;
				int yfrom = view.y + Math.max(0, ly);
				int yto = view.y + Math.min(viewport.height - 2, ly + clipheight);
				//g.setColor(Color.pink); g.fillRect(0, yfrom - 1, 75, 2); g.fillRect(0, yto - 1, 75, 2);
				
				for (int i = 0, j = 0; j <= chars.length; j++) {
					if ((j == chars.length) || (chars[j] == '\n')) {
						if (yto <= ascent) { break; } // the next lines are bellow paint rectangle
						if (yfrom < ascent + fontheight) { // this line is not above painting area
							if (focus && (is != ie) && (ie >= i) && (is <= j)) {
								int xs = (is < i) ? -1 : ((is > j) ? (view.width - 1) :
									fm.charsWidth(chars, i, is - i));
								int xe = ((j != -1) && (ie > j)) ? (view.width - 1) :
									fm.charsWidth(chars, i, ie - i);
								g.setColor(c_select);
								g.fillRect(1 + xs, ascent, xe - xs + evm, fontheight + evm);
							}
							g.setColor(enabled ? c_text : c_disable);
							g.drawChars(chars, i, j - i, 1, ascent + fontascent); //java
							//midp g.drawChars(chars, i, j - i, 1, ascent, Graphics.LEFT | Graphics.TOP);
							if (focus && (end >= i) && (end <= j)) {
								int caret = fm.charsWidth(chars, i, end - i);
								g.setColor(c_focus);
								g.fillRect(caret, ascent, 1 + evm, fontheight + evm);
							}
						}
						ascent += fontheight;
						i = j + 1;
					}
				}
				resetScrollPane(g, clipx, clipy, clipwidth, clipheight, view, viewport);
			}
		}
		else if ("tabbedpane" == classname) {
			int i = 0; Rectangle last = null;
			int selected = getInteger(component, "selected", 0);
			String placement = getString(component, "placement", "top");
			for (Object comp = get(component, "tab");
					comp != null; comp = get(comp, ":next")) {
				Rectangle r = getRectangle(comp, "bounds");
				boolean hover = !(selected == i) && inside &&
					(mousepressed == null) && (insidepart == comp);
				boolean sel = (selected == i);
				boolean tabenabled = enabled && getBoolean(comp, "enabled", true);
				paintRect(g, r.x, r.y, r.width, r.height,
					enabled ? c_border : c_disable,
					tabenabled ? (sel ? c_bg : (hover ? c_hover : c_ctrl)) : c_ctrl,
					(placement != "bottom") || !sel, (placement != "right") || !sel,
					(placement == "bottom") || ((placement == "top") && !sel),
					(placement == "right") || ((placement == "left") && !sel), true);
				if (focus && sel) {
					g.setColor(c_focus);
					g.drawRect(r.x + 2, r.y + 2, r.width - 4, r.height - 4);
				}
				paintContent(comp, g, clipx, clipy, clipwidth, clipheight,
					r.x + 4, r.y + 2, r.width - 8, r.height - 4,
					tabenabled ? c_text : c_disable, "left", true);
				i++; last = r;
			}
			if (last != null) {
				boolean horizontal = ((placement == "top") || (placement == "bottom"));
				paintRect(g, horizontal ? (last.x + last.width) : last.x,
					horizontal ? last.y : (last.y + last.height),
					horizontal ? (bounds.width - last.x - last.width) : last.width,
					horizontal ? last.height : (bounds.height - last.y - last.height),
					enabled ? c_border : c_disable, c_bg,
					(placement != "top"), (placement != "left"),
					(placement == "top"), (placement == "left"), true);
				paintRect(g, (placement == "left") ? last.width : 0,
					(placement == "top") ? last.height : 0,
					horizontal ? bounds.width : (bounds.width - last.width),
					horizontal ? (bounds.height - last.height) : bounds.height,
					enabled ? c_border : c_disable, c_bg,
					(placement != "top"), (placement != "left"),
					(placement != "bottom"), (placement != "right"), true);					
			}
			Object tabcontent = getItemImpl(component, "component", selected);
			if (tabcontent != null) {
				paint(g, clipx, clipy, clipwidth, clipheight, tabcontent, enabled);
			}
		}
		else if (("panel" == classname) || ("dialog" == classname)) {
			if ("dialog" == classname) {
				int titleheight = getInteger(component, ":titleheight", 0);
				paintRect(g, 0, 0, bounds.width, 3 + titleheight,
					c_border, c_ctrl, true, true, false, true, true);
				paintRect(g, 0, 3 + titleheight, bounds.width, bounds.height - 3 - titleheight,
					c_border, c_press, false, true, true, true, true);
				paintContent(component, g, clipx, clipy, clipwidth, clipheight,
					3, 2, bounds.width - 6, titleheight, c_text, "left", false);
				paintRect(g, 3, 3 + titleheight, bounds.width - 6, bounds.height - 6 - titleheight,
					c_border, c_bg, true, true, true, true, true);
			} else {
				paintRect(g, 0, 0, bounds.width, bounds.height,
					c_border, c_bg, false, false, false, false, true);
				boolean border = getBoolean(component, "border", false);
				if (border) {
					g.setColor(enabled ? c_border : c_disable);
					g.drawRect(0, 0, bounds.width - 1, bounds.height - 1);
				}
			}
			for (Object comp = get(component, "component");
					comp != null; comp = get(comp, ":next")) {
				paint(g, clipx, clipy, clipwidth, clipheight, comp, enabled);
			}
		}
		else if ("desktop" == classname) {
			paintReverse(g, clipx, clipy, clipwidth, clipheight,
				get(component, "component"), enabled);
			//g.setColor(Color.red); if (clip != null) g.drawRect(clipx, clipy, clipwidth, clipheight);
			if ((tooltipowner != null) && (component == content)) {
				Rectangle r = getRectangle(tooltipowner, ":tooltipbounds");
				paintRect(g, r.x, r.y, r.width, r.height,
					c_border, c_bg, true, true, true, true, true);
				String text = getString(tooltipowner, "tooltip", null);
				g.setColor(c_text);
				g.drawString(text, r.x + 2, r.y + g.getFontMetrics().getAscent() + 2); //java
				//midp g.drawString(text, r.x + 2, r.y + (r.height - font.getHeight()) / 2, Graphics.LEFT | Graphics.TOP);
			}			
		}
		else if ("spinbox" == classname) {
			paintField(g, clipx, clipy, clipwidth, clipheight, component,
				bounds.width - block, bounds.height,
				inside, pressed, focus, enabled, false, 0);
			paintArrow(g, bounds.width - block, 0, block, bounds.height / 2,
					'N', enabled, inside, pressed, "up", true, false, false, true, true);
			paintArrow(g, bounds.width - block, bounds.height / 2,
				block, bounds.height - (bounds.height / 2),
				'S', enabled, inside, pressed, "down", true, false, true, true, true);
		}
		else if ("progressbar" == classname) {
			int minimum = getInteger(component, "minimum", 0);
			int maximum = getInteger(component, "maximum", 100);
			int value = getInteger(component, "value", 0);
			boolean horizontal = ("vertical" != get(component, "orientation"));
			int length = (value - minimum) *
				((horizontal ? bounds.width : bounds.height) - 1) / (maximum - minimum);
			paintRect(g, 0, 0, horizontal ? length : bounds.width,
				horizontal ? bounds.height : length, enabled ? c_border : c_disable,
				c_select, true, true, horizontal, !horizontal, true);
			paintRect(g, horizontal ? length : 0, horizontal ? 0 : length,
				horizontal ? (bounds.width - length) : bounds.width	,
				horizontal ? bounds.height : (bounds.height - length),
				enabled ? c_border : c_disable, c_bg, true, true, true, true, true);
		}
		else if ("slider" == classname) {
			int minimum = getInteger(component, "minimum", 0);
			int maximum = getInteger(component, "maximum", 100);
			int value = getInteger(component, "value", 0);
			boolean horizontal = ("vertical" != get(component, "orientation"));
			int length = (value - minimum) *
				((horizontal ? bounds.width : bounds.height) - block) /
				(maximum - minimum);
			paintRect(g, horizontal ? 0 : 3, horizontal ? 3 : 0,
				horizontal ? length : (bounds.width - 6),
				horizontal ? (bounds.height - 6) : length,
				enabled ? c_border : c_disable,
				c_bg, true, true, horizontal, !horizontal, true);
			paintRect(g, horizontal ? length : 0, horizontal ? 0 : length,
				horizontal ? block : bounds.width, horizontal ? bounds.height : block,
				enabled ? c_border : c_disable,
				enabled ? c_ctrl : c_bg, true, true, true, true, true);
			if (focus) {
				g.setColor(c_focus);
				g.drawRect(horizontal ? (length + 2) : 2, horizontal ? 2 : (length + 2),
					(horizontal ? block : bounds.width) - 5,
					(horizontal ? bounds.height : block) - 5);
				//g.drawRect(length + 1, 1, block - 3, bounds.height - 3);
			}
			paintRect(g, horizontal ? (block + length) : 3,
				horizontal ? 3 : (block + length),
				bounds.width - (horizontal ? (block + length) : 6),
				bounds.height - (horizontal ? 6 : (block + length)),
				enabled ? c_border : c_disable,
				c_bg, horizontal, !horizontal, true, true, true);
		}
		else if ("splitpane" == classname) {
			boolean horizontal = ("vertical" != get(component, "orientation"));
			int divider = getInteger(component, "divider", -1);
			paintRect(g, horizontal ? divider : 0, horizontal ? 0 : divider,
				horizontal ? 5 : bounds.width, horizontal ? bounds.height : 5,
				c_border, c_bg, false, false, false, false, true);
			g.setColor(enabled ? (focus ? c_focus : c_border) : c_disable);
			int xy = horizontal ? bounds.height : bounds.width;
			int xy1 = Math.max(0, xy / 2 - 12);
			int xy2 = Math.min(xy / 2 + 12, xy - 1);
			for (int i = divider + 1; i < divider + 4; i += 2) {
				if (horizontal) { g.drawLine(i, xy1, i, xy2); }
					else { g.drawLine(xy1, i, xy2, i); }
			}
			Object comp1 = get(component, "component");
			if (comp1 != null) {
				paint(g, clipx, clipy, clipwidth, clipheight, comp1, enabled);
				Object comp2 = get(comp1, ":next");
				if (comp2 != null) {
					paint(g, clipx, clipy, clipwidth, clipheight, comp2, enabled);
				}
			}
		}
		else if (("list" == classname) ||
				("table" == classname) || ("tree" == classname)) {
			Rectangle view = getRectangle(component, ":view");
			Rectangle viewport = getRectangle(component, ":port");
			int[] columnwidths = null;
			int lx = clipx - viewport.x - 1;
			int xfrom = view.x + Math.max(0, lx);
			int xto = view.x + Math.min(viewport.width - 2, lx + clipwidth);
			if ("table" == classname) {
				columnwidths = new int[getItemCountImpl(component, "column")];
				int i = 0; int x = 0; boolean drawheader = (clipy < viewport.y);
				if (drawheader) { g.setClip(viewport.x, 0, viewport.width, viewport.y); }
				for (Object column = get(component, "column");
						column != null; column = get(column, ":next")) {
					boolean lastcolumn = (i == columnwidths.length - 1);
					int width = getInteger(column, "width", 80);
					if (lastcolumn) { width = Math.max(width, viewport.width - x); }
					columnwidths[i] = width;
					if (drawheader && (xfrom < x + width) && (xto > x)) {
						paintRect(g, x - view.x, 0, width, viewport.y,
							enabled ? c_border : c_disable, enabled ? c_ctrl : c_bg,
							true, true, false, lastcolumn, true);
						paintContent(column, g, clipx, clipy, clipwidth, clipheight,
							x + 2 - view.x, 1, width - 2,
							viewport.y - 2, enabled ? c_text : c_disable, "left", false);
					}
					i++; x += width;
				}
				if (drawheader) { g.setClip(clipx, clipy, clipwidth, clipheight); }
			}
			paintRect(g, viewport.x, viewport.y, viewport.width, viewport.height,
				enabled ? c_border : c_disable, c_textbg, true, true, true, true, true);
			if (paintScrollPane(g, clipx, clipy, clipwidth, clipheight, bounds,
					view, viewport, enabled, inside, pressed)) {
				Object lead = get(component, ":lead");
				int ly = clipy - viewport.y - 1;
				int yfrom = view.y + Math.max(0, ly);
				int yto = view.y + Math.min(viewport.height - 2, ly + clipheight);
				for (Object item = get(component, ("list" == classname) ? "item" :
						(("table" == classname) ? "row" : "node")); item != null;) {
					Rectangle r = getRectangle(item, "bounds");
					if (lead == null) {
						set(component, ":lead", lead = item); // draw first item focused when lead is null
					}
					if (yto <= r.y) { break; } // the clip bounds are above

					Object next = ("tree" == classname) ? get(item, "node") : null;
					boolean expanded = (next != null) &&
							getBoolean(item, "expanded", true);
					if (yfrom < r.y + r.height) { // the clip rectangle is not bellow the current item
						boolean selected = getBoolean(item, "selected", false);
						paintRect(g, 0, r.y, view.width, r.height,
							c_bg, selected ? c_select : c_textbg, false, false, true, false, true);
						if (focus && (lead == item)) {
							g.setColor(c_focus);
							g.drawRect(0, r.y, view.width - 1, r.height - 2);
						}
						if ("table" == classname) {
							int x = 0; int i = 0;
							for (Object cell = get(item, "cell");
									cell != null; cell = get(cell, ":next")) {
								if (xto <= x) { break; }
								int iwidth = (i < columnwidths.length) ? columnwidths[i] : 80;
								if (xfrom < x + iwidth) {
									boolean cellenabled = enabled && getBoolean(cell, "enabled", true);
									paintContent(cell, g, xfrom, yfrom, xto - xfrom, yto - yfrom,
										r.x + x + 1, r.y + 1, iwidth - 2, r.height - 3,
										cellenabled ? c_text : c_disable, "left", false);
								}
								x += iwidth; i++;
							}
						} else {
							boolean itemenabled = enabled && getBoolean(item, "enabled", true);
							paintContent(item, g, xfrom, yfrom, xto - xfrom, yto - yfrom,
								r.x + 1, r.y + 1, view.width - r.x - 2,
								r.height - 3, itemenabled ? c_text : c_disable, "left", false);

							if (next != null) {
								int x = r.x - block / 2;
								int y = r.y + (r.height - 1) / 2;
								//g.drawRect(x - 4, y - 4, 8, 8);
								paintRect(g, x - 4, y - 4, 9, 9, itemenabled ? c_border : c_disable,
									itemenabled ? c_ctrl : c_bg, true, true, true, true, true);
								g.setColor(itemenabled ? c_text : c_disable);
								g.drawLine(x - 2, y, x + 2, y);
								if (!expanded) {
									g.drawLine(x, y - 2, x, y + 2);
								}
							}
						}
					}
					if ("tree" == classname) {
						if ((next == null) || !expanded) {
							while ((item != component) && ((next = get(item, ":next")) == null)) {
								item = getParent(item);
							}
						}
						item = next;
					} else {
						item = get(item, ":next");
					}
				}
				/*if (columnwidths != null) {
					g.setColor(c_bg);
					for (int i = 0, cx = -1; i < columnwidths.length - 1; i++) {
						cx += columnwidths[i];
						g.drawLine(cx, 0, cx, view.height);
					}
				}*/
				resetScrollPane(g, clipx, clipy, clipwidth, clipheight, view, viewport);
			}
		}
		else if ("separator" == classname) {
			g.setColor(enabled ? c_border : c_disable);
			g.fillRect(0, 0, bounds.width + evm, bounds.height + evm);
		}
		else if ("menubar" == classname) {
			Object selected = get(component, "selected");
			int lastx = 0;
			for (Object menu = get(component, "menu");
					menu != null; menu = get(menu, ":next")) {
				Rectangle mb = getRectangle(menu, "bounds");
				if (clipx + clipwidth <= mb.x) { break; }
				if (clipx >= mb.x + mb.width) { continue; }
				boolean armed = (selected == menu);
				boolean hoover = (selected == null) && (insidepart == menu);
				paintRect(g, mb.x, 0, mb.width, bounds.height, enabled ? c_border : c_disable,
					enabled ? (armed ? c_select : (hoover ? c_hover : c_ctrl)) : c_bg,
					armed, armed, true, armed, true);
				paintContent(menu, g, clipx, clipy, clipwidth, clipheight,
					mb.x + 4, 1, mb.width, bounds.height,
					(enabled && getBoolean(menu, "enabled", true)) ? c_text : c_disable,
					"left", true);
				lastx = mb.x + mb.width;
			}
			paintRect(g, lastx, 0, bounds.width - lastx, bounds.height,
				enabled ? c_border : c_disable, enabled ? c_ctrl : c_bg,
				false, false, true, false, true);
		}
		else if (":popup" == classname) {
			paintRect(g, 0, 0, bounds.width, bounds.height,
				c_border, c_bg, true, true, true, true, true);
			Object selected = get(component, "selected");
			for (Object menu = get(get(component, "menu"), "menu");
					menu != null; menu = get(menu, ":next")) {
				Rectangle r = getRectangle(menu, "bounds");
				if (clipy + clipheight <= r.y) { break; }
				if (clipy >= r.y + r.height) { continue; }
				String itemclass = getClass(menu);
				if (itemclass == "separator") {
					g.setColor(c_border);
					g.fillRect(r.x, r.y, bounds.width - 2 + evm, r.height + evm);
				} else {
					boolean armed = (selected == menu);
					boolean menuenabled = getBoolean(menu, "enabled", true);
					paintRect(g, r.x, r.y, bounds.width - 2, r.height, c_border,
						armed ? c_select : c_bg, false, false, false, false, true);
					int tx = r.x;
					if (itemclass == "checkboxmenuitem") {
						tx += block + 3;
						boolean checked = getBoolean(menu, "selected", false);
						String group = getString(menu, "group", null);
						g.translate(r.x + 4, r.y + 2);
						g.setColor(menuenabled ? c_border : c_disable);
						if (group == null) {
							g.drawRect(1, 1, block - 3, block - 3);
						} else {
							g.drawOval(1, 1, block - 3, block - 3); //java
						}
						if (checked) {
							g.setColor(menuenabled ? c_text : c_disable);
							if (group == null) {
								g.fillRect(3, block - 9, 2 + evm, 6 + evm);
								g.drawLine(3, block - 4, block - 4, 3);
								g.drawLine(4, block - 4, block - 4, 4);
							} else {
								g.fillOval(5, 5, block - 10 + evm, block - 10 + evm); //java
								g.drawOval(4, 4, block - 9, block - 9); //java
							}
						}
						g.translate(-r.x - 4, -r.y - 2);
					}
					paintContent(menu, g, clipx, clipy, clipwidth, clipheight,
						tx + 4, r.y + 2, bounds.width - 10,
						r.height - 4, menuenabled ? c_text : c_disable, "left", true);
					if (itemclass == "menu") {
						paintArrow(g, r.x + bounds.width - block, r.y, block, r.height, 'E');
					}
				}
			}
		}
		//java>
		else if ("bean" == classname) {
				g.clipRect(0, 0, bounds.width, bounds.height);
				((Component) get(component, "bean")).paint(g);
				g.setClip(clipx, clipy, clipwidth, clipheight);
		}
		//<java
		else throw new IllegalArgumentException((String) classname);
		g.translate(-bounds.x, -bounds.y);
		clipx += bounds.x; clipy += bounds.y;
	}

	/**
	 *
	 */
	private void paintReverse(Graphics g,
			int clipx, int clipy, int clipwidth, int clipheight,
			Object component, boolean enabled) {
		if (component != null) {
			Rectangle bounds = getRectangle(component, "bounds");
			if ((clipx < bounds.x) ||
					(clipx + clipwidth > bounds.x + bounds.width) ||
					(clipy < bounds.y) ||
					(clipy + clipheight > bounds.y + bounds.height)) {
				paintReverse(g, clipx, clipy, clipwidth, clipheight,
					get(component, ":next"), enabled);
			}
			paint(g, clipx, clipy, clipwidth, clipheight, component, enabled);
		}
	}

	/**
	 *
	 */
	private void paintField(Graphics g,
			int clipx, int clipy, int clipwidth, int clipheight, Object component,
			int width, int height, boolean inside, boolean pressed,
			boolean focus, boolean enabled, boolean hidden, int left) {
		boolean editable = getBoolean(component, "editable", true);
		paintRect(g, 0, 0, width, height, enabled ? c_border : c_disable,
			editable ? c_textbg : c_bg, true, true, true, true, true);
		g.clipRect(1 + left, 1, width - left - 2, height - 2);

		String text = getString(component, "text", "");
		int offset = getInteger(component, "offset", 0);
		FontMetrics fm = g.getFontMetrics(); //java

		int caret = 0;
		if (focus) { 
			int start = getInteger(component, "start", 0); 
			int end = getInteger(component, "end", 0);
			caret = hidden ? (fm.charWidth('*') * end) :
				fm.stringWidth(text.substring(0, end)); //java
				//midp font.substringWidth(text, 0, end);
			if (start != end) {
				int is = hidden ? (fm.charWidth('*') * start) :
					fm.stringWidth(text.substring(0, start)); //java
					//midp font.substringWidth(text, 0, start);
				g.setColor(c_select);
				g.fillRect(2 + left - offset + Math.min(is, caret), 1,
					Math.abs(caret - is) + evm, height - 2 + evm);
			}
		}

		if (focus) {
			g.setColor(c_focus);
			g.fillRect(1 + left - offset + caret, 1, 1 + evm, height - 2 + evm);
		}

		g.setColor(enabled ? c_text : c_disable);
		int fx = 2 + left - offset;
		int fy = (height + fm.getAscent() - fm.getDescent()) / 2; //java
		//midp int fy = (height - font.getHeight()) / 2;
		if (hidden) {
			int fh = fm.charWidth('*');
			for (int i = text.length(); i > 0; i--) {
				g.drawString("*", fx, fy); //java
				//midp g.drawChar('*', fx, fy, Graphics.LEFT | Graphics.TOP);
				fx += fh;
			}
		} else {
			g.drawString(text, fx, fy); //java
			//midp g.drawString(text, fx, fy, Graphics.LEFT | Graphics.TOP);
		}
		g.setClip(clipx, clipy, clipwidth, clipheight);
	}

	/**
	 *
	 */
	private boolean paintScrollPane(Graphics g,
			int clipx, int clipy, int clipwidth, int clipheight, Rectangle bounds,
			Rectangle view, Rectangle viewport,
			boolean enabled, boolean inside, boolean pressed) {
		if ((viewport.y + viewport.height < bounds.height) &&
				(clipy + clipheight > viewport.y + viewport.height)) { // need horizontal
			int x = viewport.x;
			int y = viewport.y + viewport.height;
			int height = bounds.height - y;
			int button = Math.min(block, viewport.width / 2);
			int track = viewport.width - (2 * button); //max 10
			int knob = Math.min(track, Math.max(track * (viewport.width - 2) / view.width, 6));
			int decrease = view.x * (track - knob) /
				(view.width - viewport.width + 2);
			int increase = track - decrease - knob;
			paintArrow(g, x, y, button, height,
				'W', enabled, inside, pressed, "left", false, true, true, false, true);
			paintRect(g, x + button, y, decrease, height,
				enabled ? c_border : c_disable, c_bg, false, true, true, false, true);
			paintRect(g, x + button + decrease, y, knob, height,
				enabled ? c_border : c_disable, enabled ? c_ctrl : c_bg, false, true, true, true, true);
			int n = Math.min(5, (knob - 4) / 3);
			g.setColor(enabled ? c_border : c_disable);
			int cx = (x + button + decrease) + (knob + 2 - n * 3) / 2;
			for (int i = 0; i < n; i++ ) {
				g.drawLine(cx + i * 3, y + 2, cx + i * 3, y + height - 4);
			}
			paintRect(g, x + button + decrease + knob, y, increase, height,
				enabled ? c_border : c_disable, c_bg, false, false, true, true, true);
			paintArrow(g, x + button + track, y, button, height,
				'E', enabled, inside, pressed, "right", false, false, true, true, true);
		}
		if ((viewport.x + viewport.width < bounds.width) &&
				(clipx + clipwidth > viewport.x + viewport.width)) { // need vertical
			int x = viewport.x + viewport.width;
			int y = viewport.y;
			int width = bounds.width - x;
			//if (y > 0) {
			//	paintRect(g, x - 1, 0, width + 1, y,
			//		enabled ? c_border : c_disable, c_bg, false, true, false, false);
			//}
			int track = viewport.height - (2 * block);
			int knob = track * (viewport.height - 2) / view.height;
			int decrease = view.y * (track - knob) /
				(view.height - viewport.height + 2);
			int increase = track - decrease - knob;
			paintArrow(g, x, y, width, block,
				'N', enabled, inside, pressed, "up", true, false, false, true, false);
			paintRect(g, x, y + block, width, decrease,
				enabled ? c_border : c_disable, c_bg, true, false, false, true, false);
			paintRect(g, x, y + block + decrease, width, knob,
				enabled ? c_border : c_disable, enabled ? c_ctrl : c_bg, true, false, true, true, false);
			int n = Math.min(5, (knob - 4) / 3);
			g.setColor(enabled ? c_border : c_disable);
			int cy = (y + block + decrease) + (knob + 2 - n * 3) / 2;
			for (int i = 0; i < n; i++ ) {
				g.drawLine(x + 2, cy + i * 3, x + width - 4, cy + i * 3);
			}
			paintRect(g, x, y + block + decrease + knob, width, increase,
				enabled ? c_border : c_disable, c_bg, false, false, true, true, false);
			paintArrow(g, x, y + block + track, width, block,
			'S', enabled, inside, pressed, "down", false, false, true, true, false);
		}
		if ((clipx + clipwidth > viewport.x) && (clipy + clipheight > viewport.y) &&
				(clipx < viewport.x + viewport.width) && (clipy < viewport.y + viewport.height)) {
			g.clipRect(viewport.x + 1, viewport.y + 1, viewport.width - 2, viewport.height - 2);
			g.translate(viewport.x + 1 - view.x, viewport.y + 1 - view.y);
	 		return true;
		}
		return false;
	}
		
	/**
	 *
	 */
	private void resetScrollPane(Graphics g,
			int clipx, int clipy, int clipwidth, int clipheight,
			Rectangle view, Rectangle viewport) {
		g.translate(view.x - viewport.x - 1, view.y - viewport.y - 1);
		g.setClip(clipx, clipy, clipwidth, clipheight);
	}

	/**
	 *
	 */
	private void paintRect(Graphics g, int x, int y, int width, int height,
			Color border, Color bg,
			boolean top, boolean left, boolean bottom, boolean right, boolean horizontal) {
		if ((width <= 0) || (height <= 0)) return;
		g.setColor(border);
		if (top) {
			g.drawLine(x + width - 1, y, x, y);
			y++; height--; if (height <= 0) return;
		}
		if (left) {
			g.drawLine(x, y, x, y + height - 1);
			x++; width--; if (width <= 0) return;
		}
		if (bottom) {
			g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
			height--; if (height <= 0) return;
		}
		if (right) {
			g.drawLine(x + width - 1, y + height - 1, x + width - 1, y);
			width--; if (width <= 0) return;
		}

		//java>
		if (bg == c_ctrl) {
			if (horizontal) {
				if (height > block) {
					g.setColor(c_bg);
					g.fillRect(x, y, width + evm, height - block + evm);
				}
				for (int i = 0; i < width; i += block) {
					g.drawImage(hgradient, x + i, (height > block) ? (y + height - block) : y,
						x + Math.min(i + block, width) + evm, y + height + evm,
						0, 0, Math.min(block, width - i) + evm, Math.min(block, height) + evm, null);
				}
			}
			else {
				if (width > block) {
					g.setColor(c_bg);
					g.fillRect(x, y, width - block + evm, height + evm);
				}
				for (int i = 0; i < height; i += block) {
					g.drawImage(vgradient, (width > block) ? (x + width - block) : x, y + i,
						x + width + evm, y + Math.min(i + block, height) + evm,
						0, 0, Math.min(block, width) + evm, Math.min(block, height - i) + evm, null);
				}
			}
		}
		else {
			g.setColor(bg);
			g.fillRect(x, y, width + evm, height + evm);
		}
		//<java
		//midp g.setColor(bg);
		//midp g.fillRect(x, y, width, height);
	}

	/**
	 *
	 */
	private void paintArrow(Graphics g, int x, int y, int width, int height,
			char dir, boolean enabled, boolean inside, boolean pressed, String part,
			boolean top, boolean left, boolean bottom, boolean right, boolean horizontal) {
		inside = inside && (insidepart == part);
		pressed = pressed && (pressedpart == part);
		paintRect(g, x, y, width, height, enabled ? c_border : c_disable,
			enabled ? ((inside != pressed) ? c_hover :
				(pressed ? c_press : c_ctrl)) : c_bg,
			top, left, bottom, right, horizontal);
		g.setColor(enabled ? c_text : c_disable);
		paintArrow(g, x, y, width, height, dir);
	}

	/**
	 *
	 */
	private void paintArrow(Graphics g,
			int x, int y, int width, int height, char dir) {
		int cx = x + width / 2 - 2;
		int cy = y + height / 2 - 2;
		for (int i = 0; i < 4; i++) {
			if (dir == 'N') { // north
				g.drawLine(cx + 1 - i, cy + i, cx + 1/*2*/ + i, cy + i);
			}
			else if (dir == 'W') { // west
				g.drawLine(cx + i, cy + 1 - i, cx + i, cy + 1/*2*/ + i);
			}
			else if (dir == 'S') { // south
				g.drawLine(cx + 1 - i, cy + 4 - i, cx + 1/*2*/ + i, cy + 4 - i);
			}
			else { // east
				g.drawLine(cx + 4 - i, cy + 1 - i, cx + 4 - i, cy + 1/*2*/ + i);
			}
		}
	}

	/**
	 *
	 */
	private void paintContent(Object component, Graphics g,
			int clipx, int clipy, int clipwidth, int clipheight,
			int x, int y, int width, int height, Color fg, String defaultalignment,
			boolean checkmnemonic) {
		String text = getString(component, "text", null);
		Image icon = getIcon(component, "icon", null);
		if ((text == null) && (icon == null)) { return; }
		String alignment = getString(component, "alignment", defaultalignment);

		FontMetrics fm = null; //java
		int tw = 0, th = 0;
		int ta = 0; //java
		if (text != null) {
			fm = g.getFontMetrics(); //java
			tw = fm.stringWidth(text);
			ta = fm.getAscent(); //java
			th = fm.getDescent() + ta; //java
			//midp th = font.getHeight();
			g.setColor(fg);
		}
		int iw = 0, ih = 0;
		if (icon != null) {
			iw = icon.getWidth(this);
			ih = icon.getHeight(this); //java
		}

		boolean clipped = (tw + iw > width) || (th > height) || (ih > height);
		int cx = x;
		if ("center" == alignment) { cx += (width - tw - iw) / 2; }
			else if ("right" == alignment) { cx += width - tw - iw; }

		if (clipped) { g.clipRect(x, y, width, height); }
		if (icon != null) {
			g.drawImage(icon, cx, y + (height - ih) / 2, this); //java
			//midp g.drawImage(icon, cx, y + height / 2, Graphics.LEFT | Graphics.VCENTER);
			cx += iw;
		}
		if (text != null) {
			int ty = y + (height - th) / 2 + ta; //java
			g.drawString(text, cx, ty); //java
			//midp g.drawString(text, cx, y + (height - th) / 2, Graphics.LEFT | Graphics.TOP);
			if (checkmnemonic) {
				int mnemonic = getInteger(component, "mnemonic", -1);
				if ((mnemonic != -1) && (mnemonic < text.length())) {
					int mx = cx + fm.stringWidth(text.substring(0, mnemonic)); //java
					//midp int mx = cx + font.substringWidth(text, 0, mnemonic);
					//midp int ty = (height + th) / 2;
					g.drawLine(mx, ty + 1, mx + fm.charWidth(text.charAt(mnemonic)), ty + 1);
				}
			}
		}
		if (clipped) { g.setClip(clipx, clipy, clipwidth, clipheight); }
	}
	//midp private void setTimer(long delay) {}
	//java>

	/**
	 * A second thread is used to repeat value change events for scrollbar or spinbox
	 * during the mouse is pressed, or to pop up tooltip
	 */
	public synchronized void run() {
		while (timer == Thread.currentThread()) {
			try {
				if (watch == 0) {
					wait(0);
				} else {
					long current = System.currentTimeMillis();
					if (watch > current) {
						wait(watch - current);
					} else {
						watch = 0;
						if ((watchdelay == 300L) || (watchdelay == 60L)) {
							if (processScroll(mousepressed, pressedpart)) { setTimer(60L); }
						} else if ((watchdelay == 375L) || (watchdelay == 75L)) {
							if (processSpin(mousepressed, pressedpart)) { setTimer(75L); }
						} else if (watchdelay == 750L) {
							//System.out.println("> tip: " + getClass(mouseinside) + " : " + ((insidepart instanceof Object[]) ? getClass(insidepart) : insidepart));
							showTip();
						}
					}
				}
			} catch (InterruptedException ie) {} //ie.printStackTrace();
		}
	}

	/**
	 *
	 */
	private void setTimer(long delay) {
		watchdelay = delay;
		if (delay == 0) {
			watch = 0;
		} else {
			long prev = watch;
			watch = System.currentTimeMillis() + delay;
			if (timer == null) {
				timer = new Thread(this);
				timer.setPriority(Thread.MIN_PRIORITY);
				timer.setDaemon(true);
				timer.start();
			}
			if ((prev == 0) || (watch < prev)) {
				synchronized (this) { notify(); }
				//synchronized (this) { try { notify(); }catch (IllegalMonitorStateException imse) {} }
			}
		}
	}

	/**
	 * This component can be traversed using Tab or Shift-Tab keyboard focus traversal,
	 * although 1.4 replaced this method by <i>isFocusable</i>,
	 * so 1.4 compilers write deprecation warning
	 *
	 * @return true as focus-transverable component, overwrites the default false value
	 */
	public boolean isFocusTraversable() {
		return true;
	}

	//<java
	/*midp
	private static final int MOUSE_ENTERED = 1;
	private static final int MOUSE_EXITED = 2; //
	private static final int MOUSE_PRESSED = 3;
	private static final int MOUSE_DRAGGED = 4;
	private static final int MOUSE_RELEASED = 5;
	private static final int DRAG_ENTERED = 6;
	private static final int DRAG_EXITED = 7;

	protected void pointerPressed(int x, int y) {
		findComponent(content, x, y);
		if (popupowner != null) {
			String classname = getClass(mouseinside);
			if ((popupowner != mouseinside) &&
					(classname != ":popup") && (classname != ":combolist")) {
				closeup();
			}
		}
		handleMouseEvent(x, y, 1, false, false, false,
			MouseEvent.MOUSE_ENTERED, mouseinside, insidepart);
		mousepressed = mouseinside;
		pressedpart = insidepart;
		handleMouseEvent(x, y, 1, false, false, false,
			MouseEvent.MOUSE_PRESSED, mousepressed, pressedpart);
	}

	protected void pointerReleased(int x, int y) {
		Object mouserelease = mousepressed;
		Object releasepart = pressedpart;
		mousepressed = pressedpart = null;
		handleMouseEvent(x, y, 1, false, false, false,
			MouseEvent.MOUSE_RELEASED, mouserelease, releasepart);
	}

	protected void pointerDragged(int x, int y) {
		Object previnside = mouseinside;
		Object prevpart = insidepart;
		findComponent(content, x, y);
		boolean same = (previnside == mouseinside) && (prevpart == insidepart);
		boolean isin = (mousepressed == mouseinside) && (pressedpart == insidepart);
		boolean wasin = (mousepressed == previnside) && (pressedpart == prevpart);
		
		if (wasin && !isin) {
			handleMouseEvent(x, y, 1, false, false, false,
				MouseEvent.MOUSE_EXITED, mousepressed, pressedpart);
		}
		else if (!same && (popupowner != null) && !wasin) {
			handleMouseEvent(x, y, 1, false, false, false,
				DRAG_EXITED, previnside, prevpart);
		}
		if (isin && !wasin) {
			handleMouseEvent(x, y, 1, false, false, false,
				MouseEvent.MOUSE_ENTERED, mousepressed, pressedpart);
		}
		else if (!same && (popupowner != null) && !isin) {
			handleMouseEvent(x, y, 1, false, false, false,
				DRAG_ENTERED, mouseinside, insidepart);
		}
		if (isin == wasin) {
			handleMouseEvent(x, y, 1, false, false, false,
				MouseEvent.MOUSE_DRAGGED, mousepressed, pressedpart);
		}
	}

	protected void keyPressed(int keyCode) {
		if ((popupowner != null) || (focusowner != null)) {
			hideTip();
			if (keyCode > 0) {
				processKeyPress((popupowner != null) ? popupowner : focusowner,
					false, false, 1, keyCode, 0);
			}
			else {
				int keychar = 0, key = 0;
				switch (getGameAction(keyCode)) {
					case UP: key = KeyEvent.VK_UP; break;
					case LEFT: key = KeyEvent.VK_LEFT; break;
					case DOWN: key = KeyEvent.VK_DOWN; break;
					case RIGHT: key = KeyEvent.VK_RIGHT; break;
					case FIRE: key = KeyEvent.VK_ENTER; keychar = KeyEvent.VK_SPACE; break;
					case GAME_A: key = KeyEvent.VK_ESCAPE; break;
				}
				if (key != 0) {
					processKeyPress((popupowner != null) ? popupowner : focusowner,
						false, false, 1, keychar, key);
				}
				//if (keyCode == getKeyCode(LEFT)) {
			}
		}
	}

	protected void keyRepeated(int keyCode) {
		keyPressed(keyCode);
	}
	
	private static final Command nextcommand = new Command("Next", Command.SCREEN, 0);
	//private static final Command prevcommand = new Command("Previous", Command.SCREEN, 0);
	{
		addCommand(nextcommand);
		//addCommand(prevcommand);
		setCommandListener(this);
	}
	
	public void commandAction(Command command, Displayable displayable) {
		if (command == nextcommand) {
			setNextFocusable(focusowner, false);
			repaint(focusowner);
			closeup();
		}
		//else if (command == prevcommand) {
			//setPreviousFocusable(focusowner, null, true, true, false);
			//repaint(focusowner);
			//closeup();
		//}
	}
	midp*/
	//java>

	/**
	 * Dispatches mouse, key, focus, and component events occurring on the
	 * <i>Thinlet</i> component internally
	 */
	protected void processEvent(AWTEvent e) {
		// evm (touchscreen) events: entered/moved/pressed -> dragged -> dragged/released/exited
		int id = e.getID();
		if ((id == MouseEvent.MOUSE_ENTERED) || (id == MouseEvent.MOUSE_MOVED) ||
				(id == MouseEvent.MOUSE_EXITED) || (id == MouseEvent.MOUSE_PRESSED) ||
				(id == MouseEvent.MOUSE_DRAGGED) || (id == MouseEvent.MOUSE_RELEASED)) {
			MouseEvent me = (MouseEvent) e;
			int x = me.getX();
			int y = me.getY();
			int clickcount = me.getClickCount();
			boolean shiftdown = me.isShiftDown();
			boolean controldown = me.isControlDown();
			boolean popuptrigger = me.isPopupTrigger();
			if (id == MouseEvent.MOUSE_ENTERED) {
				if (mousepressed == null) {
					findComponent(content, x, y);
					handleMouseEvent(x, y, clickcount, shiftdown, controldown, popuptrigger,
						MouseEvent.MOUSE_ENTERED, mouseinside, insidepart);
				}
			}
			else if (id == MouseEvent.MOUSE_MOVED) {
				Object previnside = mouseinside;
				Object prevpart = insidepart;
				findComponent(content, x, y);
				if ((previnside == mouseinside) && (prevpart == insidepart)) {
					handleMouseEvent(x, y, clickcount, shiftdown, controldown, popuptrigger,
						MouseEvent.MOUSE_MOVED, mouseinside, insidepart);
				}
				else {
					handleMouseEvent(x, y, clickcount, shiftdown, controldown, popuptrigger,
						MouseEvent.MOUSE_EXITED, previnside, prevpart);
					handleMouseEvent(x, y, clickcount, shiftdown, controldown, popuptrigger,
						MouseEvent.MOUSE_ENTERED, mouseinside, insidepart);
				}
			}
			else if (id == MouseEvent.MOUSE_EXITED) {
				if (mousepressed == null) {
					Object mouseexit = mouseinside;
					Object exitpart = insidepart;
					mouseinside = insidepart = null;
					handleMouseEvent(x, y, clickcount, shiftdown, controldown, popuptrigger,
						MouseEvent.MOUSE_EXITED, mouseexit, exitpart);
				}
			}
			else if (id == MouseEvent.MOUSE_PRESSED) {
				if (popupowner != null) {
					String classname = getClass(mouseinside);
					if ((popupowner != mouseinside) &&
							(classname != ":popup") && (classname != ":combolist")) {
						closeup();
					}
				}
				mousepressed = mouseinside;
				pressedpart = insidepart;
				handleMouseEvent(x, y, clickcount, shiftdown, controldown, popuptrigger,
					MouseEvent.MOUSE_PRESSED, mousepressed, pressedpart);
			}
			else if (id == MouseEvent.MOUSE_DRAGGED) {
				Object previnside = mouseinside;
				Object prevpart = insidepart;
				findComponent(content, x, y);
				boolean same = (previnside == mouseinside) && (prevpart == insidepart);
				boolean isin = (mousepressed == mouseinside) && (pressedpart == insidepart);
				boolean wasin = (mousepressed == previnside) && (pressedpart == prevpart);
				
				if (wasin && !isin) {
					handleMouseEvent(x, y, clickcount, shiftdown, controldown, popuptrigger,
						MouseEvent.MOUSE_EXITED, mousepressed, pressedpart);
				}
				else if (!same && (popupowner != null) && !wasin) {
					handleMouseEvent(x, y, clickcount, shiftdown, controldown, popuptrigger,
						DRAG_EXITED, previnside, prevpart);
				}
				if (isin && !wasin) {
					handleMouseEvent(x, y, clickcount, shiftdown, controldown, popuptrigger,
						MouseEvent.MOUSE_ENTERED, mousepressed, pressedpart);
				}
				else if (!same && (popupowner != null) && !isin) {
					handleMouseEvent(x, y, clickcount, shiftdown, controldown, popuptrigger,
						DRAG_ENTERED, mouseinside, insidepart);
				}
				if (isin == wasin) {
					handleMouseEvent(x, y, clickcount, shiftdown, controldown, popuptrigger,
						MouseEvent.MOUSE_DRAGGED, mousepressed, pressedpart);
				}
			}
			else if (id == MouseEvent.MOUSE_RELEASED) {
				Object mouserelease = mousepressed;
				Object releasepart = pressedpart;
				mousepressed = pressedpart = null;
				handleMouseEvent(x, y, clickcount, shiftdown, controldown, popuptrigger,
					MouseEvent.MOUSE_RELEASED, mouserelease, releasepart);
				if ((mouseinside != null) &&
						((mouserelease != mouseinside) || (releasepart != insidepart))) {
					handleMouseEvent(x, y, clickcount, shiftdown, controldown, popuptrigger,
						MouseEvent.MOUSE_ENTERED, mouseinside, insidepart);
				}
			}
		}
		else if (id == MOUSE_WHEEL) {
			Rectangle port = getRectangle(mouseinside, ":port");
			if (port != null) { // is scrollable
				Rectangle bounds = getRectangle(mouseinside, "bounds");	
				try { // mouse wheel is supported since 1.4 thus it use reflection
					if (wheelrotation == null) {
						wheelrotation = e.getClass().getMethod("getWheelRotation", null);
					}
					int rotation = ((Integer) wheelrotation.invoke(e, null)).intValue();
					
					if (port.x + port.width < bounds.width) { // has vertical scrollbar
						processScroll(mouseinside, (rotation > 0) ? "down" : "up");
					}
					else if (port.y + port.height < bounds.height) { // has horizontal scrollbar
						processScroll(mouseinside, (rotation > 0) ? "right" : "left");
					}
				} catch (Exception exc) { /* never */ }
			}
		}
		else if ((id == KeyEvent.KEY_PRESSED) || (id == KeyEvent.KEY_TYPED)) {
			if (focusinside && ((popupowner != null) || (focusowner != null))) {
				hideTip();
				KeyEvent ke = (KeyEvent) e;
				int keychar = ke.getKeyChar();
				boolean control = (keychar <= 0x1f) ||
					((keychar >= 0x7f) && (keychar <= 0x9f)) ||
					(keychar >= 0xffff) || ke.isControlDown();
				if (control == (id == KeyEvent.KEY_PRESSED)) {
					int keycode = control ? ke.getKeyCode() : 0;
					if (!processKeyPress((popupowner != null) ? popupowner : focusowner,
							ke.isShiftDown(), ke.isControlDown(), ke.getModifiers(),
							control ? 0 : keychar, keycode)) {
						if ((keycode == KeyEvent.VK_TAB) ||
								((keycode == KeyEvent.VK_F6) && (ke.isAltDown() || ke.isControlDown()))) {
							boolean outgo = (keycode == KeyEvent.VK_F6);
							if (!ke.isShiftDown() ? setNextFocusable(focusowner, outgo) :
									setPreviousFocusable(focusowner, outgo)) {
								ke.consume();
							} else if (MOUSE_WHEEL != 0) { // 1.4
								if (!ke.isShiftDown()) {
									transferFocus();
								}
								else { try {
										getClass().getMethod("transferFocusBackward", null). invoke(this, null);
								} catch (Exception exc) { /* never */ } }
							}
							repaint(focusowner);
							closeup();
						}
						if (keycode == KeyEvent.VK_F8) {
							for (Object splitpane = focusowner;
									splitpane != null; splitpane = getParent(splitpane)) {
								if (getClass(splitpane) == "splitpane") {
									setFocus(splitpane); repaint(splitpane); break; //middle
								}
							}
						}
						/*else if (keycode == KeyEvent.VK_F10) {
							Object menubar = findMenubar(getParent(focusowner), focusowner);
							System.out.println("menubar=" + menubar);
							if (menubar != null) {
								Object firstmenu = get(menubar, "menu"); // only enabled
								if (firstmenu != null) {
									closeup();
									set(menubar, "selected", firstmenu);
									popup(menubar, "menubar");
									repaint(menubar, "menubar", firstmenu);
									ke.consume();
								}
							}
						}*/
					}
					else ke.consume();
				}
			}
		}
		/*else if (id == KeyEvent.KEY_RELEASED) {
			if (focusinside && (focusowner != null)) {
				KeyEvent ke = (KeyEvent) e;
				//pressedkey = 0;
				processKeyRelease(focusowner, ke, ke.getKeyCode());
			}
		}*/
		else if (id == FocusEvent.FOCUS_LOST) {
			focusinside = false;
			if (focusowner != null) { repaint(focusowner); }
			closeup();
		}
		else if (id == FocusEvent.FOCUS_GAINED) {
			focusinside = true;
			if (focusowner == null) { setFocus(content); }
				else { repaint(focusowner); }
		}
		else if ((id == ComponentEvent.COMPONENT_RESIZED) ||
				(id == ComponentEvent.COMPONENT_SHOWN)) {
			Dimension d = getSize();
			//System.out.println(id + ": " + d.width + ", " + d.height);
			setRectangle(content, "bounds", 0, 0, d.width, d.height);
			validate(content);
			closeup();
			if (!focusinside)  { requestFocus(); }
		}
	}
	
	/**
	 *
	 */
	/*private Object findMenubar(Object component, Object from) {
		if (component == null) { return null; }
		if (getClass(component) == "menubar") { return component; }
		for (Object comp = get(component, "component");
				comp != null; comp = get(comp, ":next")) {
			if (comp != from) {
				Object found = findMenubar(comp, null);
				if (found != null) { return found; }
			}
		}
		return (from != null) ? findMenubar(getParent(component), component) : null;
	}*/
	
	/**
	 *
	 */
	/*private boolean processKeyPress(Object component,
			KeyEvent e, int keycode, Object invoker) {
		if (processKeyPress(component, e, keycode)) { return true; }
		for (Object comp = get(component, "component");
				comp != null; comp = get(comp, ":next")) {
			if ((comp != invoker) && processKeyPress(comp, e, keycode, null)) {
				return true;
			}
		}
		if ((invoker != null) && (component != content)) {
			Object parent = getParent(component);
			if (parent != null) {
				return processKeyPress(parent, e, keycode, component);
			}
		}
		return false;
	}*/
	//<java

	/**
	 *
	 */
	private boolean processKeyPress(Object component,
			boolean shiftdown, boolean controldown, int modifiers, int keychar, int keycode) {
		String classname = getClass(component);
		if ("button" == classname) {
			if (keychar == KeyEvent.VK_SPACE ||
					((keycode == KeyEvent.VK_ENTER) &&
						(getString(component, "type", null) == "default")) ||
					((keycode == KeyEvent.VK_ESCAPE) && //...
						(getString(component, "type", null) == "cancel"))) {
				//pressedkey = keychar;
				invoke(component, null, "action");
				repaint(component);
				return true;
			}
		}
		else if (("checkbox" == classname) || ("togglebutton" == classname)) {
			if (keychar == KeyEvent.VK_SPACE) {
				changeCheck(component, true);
				repaint(component);
				return true;
			}
		}
		else if ("combobox" == classname) {
			Object combolist = get(component, ":combolist");
			if (combolist == null) {
				if (getBoolean(component, "editable", true) &&
						processField(component, shiftdown, controldown, modifiers,
							keychar, keycode, false, false)) {
					setInteger(component, "selected", -1, -1);
					return true;
				}
				if ((keychar == KeyEvent.VK_SPACE) || (keycode == KeyEvent.VK_DOWN)) {
					combolist = popup(component, classname);
					int selected = getInteger(component, "selected", -1);
					set(combolist, ":inside", (selected != -1) ?
						getItemImpl(component, "choice", selected) :
						get(component, "choice")); //scroll to it!
				}
				else return false;
			} else {
				if ((keycode == KeyEvent.VK_UP) ||
						(keycode == KeyEvent.VK_DOWN) || (keycode == KeyEvent.VK_PAGE_UP) ||
						(keycode == KeyEvent.VK_PAGE_DOWN) ||
						(keycode == KeyEvent.VK_HOME) || (keycode == KeyEvent.VK_END)) {
					Object selected = get(combolist, ":inside");
					Object next = getListItem(component, combolist,
						keycode, selected, "choice", null);
					if (next != null) {
						set(combolist, ":inside", next);
						Rectangle r = getRectangle(next, "bounds");
						scrollToVisible(combolist, r.x, r.y, 0, r.height);
						if (selected != null) { repaint(combolist, ":combolist", selected); } 
						repaint(combolist, ":combolist", next);
					}
				}
				else if ((keycode == KeyEvent.VK_ENTER) || (keychar == KeyEvent.VK_SPACE)) {
					closeup(component, combolist, get(combolist, ":inside")); //Alt+Up
				}
				else if (keycode == KeyEvent.VK_ESCAPE) {
					closeup(component, combolist, null);
				}
				else return processField(component, shiftdown, controldown, modifiers,
					keychar, keycode, false, false);
			}
			return true;
		}
		else if (("textfield" == classname) || ("passwordfield" == classname)) {
			return processField(component, shiftdown, controldown, modifiers,
				keychar, keycode, false, ("passwordfield" == classname));
		}
		else if ("textarea" == classname) {
			char[] chars = (char[]) get(component, ":text");
			int start = getInteger(component, "start", 0);
			int end = getInteger(component, "end", 0);

			int istart = start;
			int iend = end;
			String insert = null;
			if ((keycode == KeyEvent.VK_HOME) && !controldown) {
				while ((iend > 0) && (chars[iend - 1] != '\n')) { iend--; }
				if (!shiftdown) { istart = iend; }
			}
			else if ((keycode == KeyEvent.VK_END) && !controldown) {
				while ((iend < chars.length) && (chars[iend] != '\n')) { iend++; }
				if (!shiftdown) { istart = iend; }
			}
			else if ((keycode == KeyEvent.VK_UP) || (keycode == KeyEvent.VK_PAGE_UP) ||
					(keycode == KeyEvent.VK_DOWN) || (keycode == KeyEvent.VK_PAGE_DOWN)) {
				FontMetrics fm = getFontMetrics(font);
				int fh = fm.getHeight();
				int y = 0; int linestart = 0;
				for (int i = 0; i < iend; i++) {
					if ((chars[i] == '\n') || (chars[i] == '\t')) {
						linestart = i + 1; y += fh;
					}
				}
				if (keycode == KeyEvent.VK_UP) { y -= fh; }
				else if (keycode == KeyEvent.VK_DOWN) { y += fh; }
				else {
					int dy = getRectangle(component, ":port").height;
					y += (keycode == KeyEvent.VK_PAGE_UP) ? -dy : dy; // VK_PAGE_DOWN
				}
				int x = fm.charsWidth(chars, linestart, iend - linestart);
				iend = getCaretLocation(component, x, y, true, false);
				if (!shiftdown) { istart = iend; }
			}
			else return processField(component, shiftdown, controldown, modifiers,
					keychar, keycode, true, false);
			return changeField(component,
				getString(component, "text", ""), insert, istart, iend, start, end);
		}
		else if ("tabbedpane" == classname) {
			if ((keycode == KeyEvent.VK_RIGHT) || (keycode == KeyEvent.VK_DOWN) ||
					(keycode == KeyEvent.VK_LEFT) || (keycode == KeyEvent.VK_UP)) {
				int selected = getInteger(component, "selected", 0);
				boolean increase = (keycode == KeyEvent.VK_RIGHT) || (keycode == KeyEvent.VK_DOWN);
				int newvalue = selected;
				int n = increase ? getItemCountImpl(component, "tab") : 0;
				int d = (increase ? 1 : -1);						
				for (int i = selected + d; increase ? (i < n)  : (i >= 0); i += d) {
					if (getBoolean(getItemImpl(component, "tab", i), "enabled", true)) {
						newvalue = i; break;
					}	
				}
				if (newvalue != selected) {
					setInteger(component, "selected", newvalue, 0);
					repaint(component);
					invoke(component, getItemImpl(component, "component", newvalue), "action");
				}
			}
		}
		else if ("spinbox" == classname) {
			if ((keycode == KeyEvent.VK_UP) || (keycode == KeyEvent.VK_DOWN)) {
				processSpin(component, (keycode == KeyEvent.VK_UP)? "up" : "down");
				return true;
			}
			return processField(component, shiftdown, controldown, modifiers,
				keychar, keycode, false, false);
		}
		else if ("slider" == classname) {
			int value = getInteger(component, "value", 0);
			int d = 0;
			if ((keycode == KeyEvent.VK_HOME) || (keycode == KeyEvent.VK_LEFT) ||
					(keycode == KeyEvent.VK_UP) || (keycode == KeyEvent.VK_PAGE_UP)) {
				d = getInteger(component, "minimum", 0) - value;
				if ((keycode == KeyEvent.VK_LEFT) || (keycode == KeyEvent.VK_UP)) {
					d = Math.max(d, -getInteger(component, "unit", 5));
				}
				else if (keycode == KeyEvent.VK_PAGE_UP) {
					d = Math.max(d, -getInteger(component, "block", 25));
				}
			}
			else if ((keycode == KeyEvent.VK_END) || (keycode == KeyEvent.VK_RIGHT) ||
					(keycode == KeyEvent.VK_DOWN) || (keycode == KeyEvent.VK_PAGE_DOWN)) {
				d = getInteger(component, "maximum", 100) - value;
				if ((keycode == KeyEvent.VK_RIGHT) || (keycode == KeyEvent.VK_DOWN)) {
					d = Math.min(d, getInteger(component, "unit", 5));
				}
				else if (keycode == KeyEvent.VK_PAGE_DOWN) {
					d = Math.min(d, getInteger(component, "block", 25));
				}
			}
			if (d != 0) {
				setInteger(component, "value", value + d, 0);
				repaint(component);
				invoke(component, null, "action");
			}
		}
		else if ("splitpane" == classname) {
			int divider = getInteger(component, "divider", -1);
			int d = 0;
			if (keycode == KeyEvent.VK_HOME) {
				d = -divider;
			}
			else if ((keycode == KeyEvent.VK_LEFT) || (keycode == KeyEvent.VK_UP)) {
				d = Math.max(-10, -divider);
			}
			else if ((keycode == KeyEvent.VK_END) ||
					(keycode == KeyEvent.VK_RIGHT) || (keycode == KeyEvent.VK_DOWN)) {
				boolean horizontal = ("vertical" != get(component, "orientation"));
				Rectangle bounds = getRectangle(component, "bounds");
				int max = (horizontal ? bounds.width : bounds.height) - 5;				
				d = max - divider;
				if (keycode != KeyEvent.VK_END) {
					d = Math.min(d, 10);
				}
			}
			if (d != 0) {
				setInteger(component, "divider", divider + d, -1);
				validate(component);
			}
		}
		else if ("list" == classname) {
			return processList(component, shiftdown, controldown, keychar, keycode, "item", null);
		}
		else if ("table" == classname) {
			return processList(component, shiftdown, controldown, keychar, keycode, "row", null);
		}
		else if ("tree" == classname) {
			//? clear childs' selection, select this is its 	subnode was selected
			if (keycode == KeyEvent.VK_LEFT) {
				Object lead = get(component, ":lead");
				if ((get(lead, "node") != null) && getBoolean(lead, "expanded", true)) {
					setBoolean(lead, "expanded", false, true);
					selectItem(component, lead, "node", "node");
					validate(component);
					invoke(component, lead, "collapse"); //lead
					return true;
				}
				else {
					Object parent = getParent(lead);
					if (parent != component) {
						selectItem(component, parent, "node", "node");
						setLead(component, lead, parent);
						return true;
					}
				}
			}
			//? for interval mode select its all subnode or deselect all after
			else if (keycode == KeyEvent.VK_RIGHT) {
				Object lead = get(component, ":lead");
				Object node = get(lead, "node");
				if (node != null) {
					if (getBoolean(lead, "expanded", true)) {
						selectItem(component, node, "node", "node");
						setLead(component, lead, node);
					}
					else {
						setBoolean(lead, "expanded", true, true);
						selectItem(component, lead, "node", "node");
						validate(component);
						invoke(component, lead, "expand"); //lead
					}
					return true;
				}
			}
			return processList(component, shiftdown, controldown, keychar, keycode, "node", "node");
		}
		else if ("menubar" == classname) {
			Object previous = null; Object last = null;
			for (Object i = get(component, ":popup");
					i != null; i = get(i, ":popup")) {
				previous = last; last = i;
			}
			Object selected = get(last, "selected");
			Object hotpopup = ((selected != null) || (previous == null)) ?
				last : previous;
			if ((selected == null) && (previous != null)) {
				selected = get(previous, "selected");
			}

			if ((keycode == KeyEvent.VK_UP) || (keycode == KeyEvent.VK_DOWN)) {
				set(hotpopup, "selected", null);
				popup(hotpopup, ":popup");
				selected = getMenu(hotpopup,
					selected, keycode == KeyEvent.VK_DOWN, true);
				set(hotpopup, "selected", selected);
				repaint(hotpopup);
			}
			else if (keycode == KeyEvent.VK_LEFT) {
				if (previous != null) {
					selected = get(previous, "selected");
					set(previous, "selected", null);
					popup(previous, ":popup");
					set(previous, "selected", selected);
					repaint(previous); // , selected
				}
				else {
					selected = getMenu(component, get(component, "selected"), false, false);
					set(component, "selected", selected);
					Object popup = popup(component, "menubar");
					set(popup, "selected", getMenu(popup, null, true, true));
					repaint(component); // , selected
				}
			}
			else if (keycode == KeyEvent.VK_RIGHT) {
				if ((previous != null) && (selected == null)) {
					set(last, "selected", get(get(last, "menu"), "menu"));
					repaint(last); // , selected
				}
				else if ((selected != null) && (getClass(selected) == "menu")) {
					Object popup = popup(last, ":popup");
					set(popup, "selected", get(get(popup, "menu"), "menu"));
				}
				else {
					selected = getMenu(component, get(component, "selected"), true, false);
					set(component, "selected", selected);
					Object popup = popup(component, "menubar");
					set(popup, "selected", getMenu(popup, null, true, true));
					repaint(component); // , selected
				}
			}
			else if ((keycode == KeyEvent.VK_ENTER) ||
					(keychar == KeyEvent.VK_SPACE) || (keycode == KeyEvent.VK_ESCAPE)) {
				if ((keycode != KeyEvent.VK_ESCAPE) &&
						getBoolean(selected, "enabled", true)) {
					if ((selected != null) && (getClass(selected) == "checkboxmenuitem")) {
						changeCheck(selected, false);
					}
					else invoke(selected, null, "action");
				}
				closeup(component);
			}
			else return false;
			return true;
		}
		return false;
	}

	/**
	 *
	 */
	private boolean changeCheck(Object component, boolean box) {
		String group = getString(component, "group", null);
		if (group != null) {
			if (getBoolean(component, "selected", false)) { return false; }
			for (Object comp = get(getParent(component),
					box ? "component" : "menu"); comp != null; comp = get(comp, ":next")) {
				if (comp == component) {
					setBoolean(component, "selected", true);
				}
				else if (group.equals(get(comp, "group")) &&
						getBoolean(comp, "selected", false)) {
					setBoolean(comp, "selected", false);
					if (box) { repaint(comp); } //checkbox only
				}
			}
		}
		else {
			setBoolean(component, "selected",
				!getBoolean(component, "selected", false), false);
		}
		invoke(component, null, "action");
		return true;
	}

	/**
	 *
	 */
	private Object getMenu(Object component, Object part,
			boolean forward, boolean popup) {
		if (forward) {
			if (part != null) { part = get(part, ":next"); }
			if (part == null) {
				part = get(popup ? get(component, "menu") : component, "menu");
			}
		}
		else {
			Object prev = get(popup ? get(component, "menu") : component, "menu");
			for (Object next = get(prev, ":next");
					(next != null) && (next != part); next = get(next, ":next")) {
				prev = next;
			}
			part = prev;
		}
		return (getClass(part) == "separator") ?
			getMenu(component, part, forward, popup) : part;
	}

	/**
	 *
	 */
	/*private boolean processKeyRelease(Object component, KeyEvent e, int keycode) {
		return true;
	}*/

	/**
	 *
	 */
	private boolean processField(Object component,
			boolean shiftdown, boolean controldown, int modifiers,
			int keychar, int keycode, boolean multiline, boolean hidden) {
		String text = getString(component, "text", ""); 
		int start = getInteger(component, "start", 0);
		int end = getInteger(component, "end", 0);
		boolean editable = getBoolean(component, "editable", true);

		int istart = start;
		int iend = end;
		String insert = null;
		//midp if (editable && (keychar != 0)) {
		if (editable && (keychar != 0) && //java
			((modifiers == 0) || (modifiers == InputEvent.SHIFT_MASK))) { //java
			insert = String.valueOf((char) keychar);
		}
		else if (multiline && editable && (keycode == KeyEvent.VK_ENTER)) {
			insert = "\n";
		}
		else if (editable && (keycode == KeyEvent.VK_BACK_SPACE)) {
			insert = "";
			if (start == end) { istart -= 1; }
		}
		else if (keycode == KeyEvent.VK_END) {
			iend = text.length();
			if (!shiftdown) { istart = iend; }
		}
		else if (keycode == KeyEvent.VK_HOME) {
			iend = 0;
			if (!shiftdown) { istart = iend; }
		}
		else if (keycode == KeyEvent.VK_LEFT) {
			//java>
			if (controldown) {
				for (int i = 0; i < 2; i++) {
					while ((iend > 0) && ((i != 0) ==
						Character.isLetterOrDigit(text.charAt(iend - 1)))) { iend--; }	
				}
			} else {
				iend -= 1;
			}
			//<java
			//midp iend -= 1;
			if (!shiftdown) { istart = iend; }
		}
		else if (keycode == KeyEvent.VK_RIGHT) {
			//java>
			if (controldown) {
				for (int i = 0; i < 2; i++) {
					while ((iend < text.length()) && ((i == 0) ==
						Character.isLetterOrDigit(text.charAt(iend)))) { iend++; }
				}
			} else {
				iend += 1;
			}
			//<java
			//midp iend += 1;
			if (!shiftdown) { istart = iend; }
		}
		else if (editable && (keycode == KeyEvent.VK_DELETE)) {
			insert = "";
			if (start == end) { iend += 1; }
		}
		else if (controldown &&
				((keycode == KeyEvent.VK_A) || (keycode == 0xBF))) {
			istart = 0; // KeyEvent.VK_SLASH
			iend = text.length();
		}
		else if (controldown && (keycode == 0xDC)) {
			istart = iend = text.length(); // KeyEvent.VK_BACK_SLASH
		}
		else if ((editable && !hidden && controldown && (keycode == KeyEvent.VK_X)) ||
				(!hidden && controldown && (keycode == KeyEvent.VK_C))) {
			if (start != end) {
				clipboard = text.substring(
					Math.min(start, end), Math.max(start, end));
				//java>
				try {				
					getToolkit().getSystemClipboard().setContents(
						new StringSelection(clipboard), null);
				} catch (Exception exc) {}
				//<java
				if (keycode == KeyEvent.VK_X) { insert = ""; }
			}
		}
		else if (editable && controldown && (keycode == KeyEvent.VK_V)) {
			//java>
			try {
				insert = (String) getToolkit().getSystemClipboard().
					getContents(this).getTransferData(DataFlavor.stringFlavor);
			} catch (Exception exc) {
				insert = clipboard;
			}
			//<java
			//midp insert = clipboard;
			StringBuffer filtered = new StringBuffer(insert.length());
			for (int i = 0; i < insert.length(); i++) {
				char ckey = insert.charAt(i);
				if (((ckey > 0x1f) && (ckey < 0x7f)) ||
						((ckey > 0x9f) && (ckey < 0xffff)) ||
						(multiline && (ckey == '\n'))) {
					filtered.append(ckey);
				}
			}
			if (filtered.length() != insert.length()) {
				insert = filtered.toString();
			}
		}
		return changeField(component, text, insert, istart, iend, start, end);
	}

	/**
	 *
	 */
	private boolean changeField(Object component, String text, String insert,
			int movestart, int moveend, int start, int end) {
		if ((insert == null) && (start == movestart) && (end == moveend)) {
			return false;
		}
		movestart = Math.max(0, Math.min(movestart, text.length()));
		moveend = Math.max(0, Math.min(moveend, text.length()));
		if (insert != null) {
			int min = Math.min(movestart, moveend);
			set(component, "text", text.substring(0, min) + insert +
				text.substring(Math.max(movestart, moveend)));
			movestart = moveend = min + insert.length();
			invoke(component, null, "action");
		}
		if (start != movestart) { setInteger(component, "start", movestart, 0); }
		if (end != moveend) { setInteger(component, "end", moveend, 0); }
		if ((insert != null) || (start != movestart) || (end != moveend)) {
			validate(component);
		}
		return true;
	}

	/**
	 *
	 */
	private boolean processList(Object component, boolean shiftdown, boolean controldown,
			int keychar, int keycode, String itemname, String leafname) {
		if ((keycode == KeyEvent.VK_UP) ||
				(keycode == KeyEvent.VK_DOWN) || (keycode == KeyEvent.VK_PAGE_UP) ||
				(keycode == KeyEvent.VK_PAGE_DOWN) ||
				(keycode == KeyEvent.VK_HOME) || (keycode == KeyEvent.VK_END)) {
			Object lead = get(component, ":lead");
			Object row = getListItem(component, component,
				keycode, lead, itemname, leafname);
			if (row != null) {
				String selection = getString(component, "selection", "single");
				if (shiftdown && (selection != "single") && (lead != null)) {
					extend(component, lead, row, itemname, leafname);
				}
				else if (!controldown) {
					selectItem(component, row, itemname, leafname);
				}
				setLead(component, lead, row);
				Rectangle r = getRectangle(row, "bounds");
				scrollToVisible(component, r.x, r.y, 0, r.height - 1);
				return true;
			}
		}
		else if (keychar == KeyEvent.VK_SPACE) {
			select(component, get(component, ":lead"),
				itemname, leafname, shiftdown, controldown); //...
			return true;
		}
		else if (controldown) {
			if (((keycode == KeyEvent.VK_A) || (keycode == 0xBF)) && //KeyEvent.VK_SLASH
					(getString(component, "selection", "single") != "single")) {
				selectAll(component, true, itemname, leafname);
				return true;
			}
			else if (keycode == 0xDC) { //KeyEvent.VK_BACK_SLASH
				selectAll(component, false, itemname, leafname);
				return true;
			}
		}
		return false;
	}

	/**
	 *
	 */
	private Object getListItem(Object component, Object scrollpane,
			int keycode, Object lead, String itemname, String leafname) {
		Object row = null;
		if (keycode == KeyEvent.VK_UP) {
			for (Object prev = get(component, itemname); prev != lead;
					prev = getNextItem(component, prev, leafname)) {
				row = prev; // component -> getParent(lead)
			}
		}
		else if (keycode == KeyEvent.VK_DOWN) {
			row = (lead == null) ? get(component, itemname) :
				getNextItem(component, lead, leafname);
		}
		else if ((keycode == KeyEvent.VK_PAGE_UP) ||
				(keycode == KeyEvent.VK_PAGE_DOWN)) {
			Rectangle view = getRectangle(scrollpane, ":view");
			Rectangle port = getRectangle(scrollpane, ":port");
			Rectangle rl = (lead != null) ? getRectangle(lead, "bounds") : null;
			int vy = (keycode == KeyEvent.VK_PAGE_UP) ?
				view.y : (view.y + port.height - 2);
			if ((keycode == KeyEvent.VK_PAGE_UP) &&
					(rl != null) && (rl.y <= view.y)) {
				vy -= port.height - 2;
			}
			if ((keycode == KeyEvent.VK_PAGE_DOWN) &&
					(rl != null) && (rl.y + rl.height >= view.y + port.height - 2)) {
				vy += port.height - 2;
			}
			for (Object item = get(component, itemname); item != null;
					item = getNextItem(component, item, leafname)) {
				Rectangle r = getRectangle(item, "bounds");
				if (keycode == KeyEvent.VK_PAGE_UP) {
					row = item;
					if (r.y + r.height > vy) { break; }
				} else {
					if (r.y > vy) { break; }
					row = item;
				}
			}
		}
		else if (keycode == KeyEvent.VK_HOME) {
			row = get(component, itemname);
		}
		else if (keycode == KeyEvent.VK_END) {
			for (Object last = lead; last != null;
					last = getNextItem(component, last, leafname)) {
				row = last;
			}
		}
		return row;
	}

	/**
	 *
	 */
	private void selectAll(Object component,
			boolean selected, String itemname, String leafname) {
		boolean changed = false;
		for (Object item = get(component, itemname);
				item != null; item = getNextItem(component, item, leafname)) {
			if (setBoolean(item, "selected", selected, false)) {
				repaint(component, null, item); changed = true;
			}
		}
		set(component, ":anchor", null);
		if (changed) { invoke(component, null, "action"); }
	}

	/**
	 *
	 */
	private void selectItem(Object component,
			Object row, String itemname, String leafname) {
		boolean changed = false;
		for (Object item = get(component, itemname);
				item != null; item = getNextItem(component, item, leafname)) {
			if (setBoolean(item, "selected", (item == row), false)) {
				repaint(component, null, item); changed = true;
			}
		}
		set(component, ":anchor", null);
		if (changed) { invoke(component, row, "action"); }
	}

	/**
	 *
	 */
	private void extend(Object component, Object lead,
			Object row, String itemname, String leafname) {
		Object anchor = get(component, ":anchor");
		if (anchor == null) { set(component, ":anchor", anchor = lead); }
		char select = 'n'; boolean changed = false;
		for (Object item = get(component, itemname); // anchor - row
				item != null; item = getNextItem(component, item, leafname)) {
			if (item == anchor) select = (select == 'n') ? 'y' : 'r';
			if (item == row) select = (select == 'n') ? 'y' : 'r';
			if (setBoolean(item, "selected", (select != 'n'), false)) {
				repaint(component, null, item); changed = true;
			}
			if (select == 'r') select = 'n';
		}
		if (changed) { invoke(component, row, "action"); }
	}

	/**
	 *
	 */
	private void setLead(Object component, Object oldlead, Object lead) {
		if (oldlead != lead) { //?
			if (oldlead != null) { repaint(component, null, oldlead); }
			set(component, ":lead", lead);
			repaint(component, null, lead);
		}
	}

	/*public void repaint(int x, int y, int width, int height) {
		System.out.println("repaint(" + x + ", " + y + ", " + width + ", " + height + ")");
		super.repaint(x, y, width, height);
	}*/
			
	/**
	 * @param x mouse x position relative to thinlet component
	 * @param y mouse y position relative to the main desktop
	 */
	private void handleMouseEvent(int x, int y, int clickcount,
			boolean shiftdown, boolean controldown, boolean popuptrigger,
			int id, Object component, Object part) {
		if (id == MouseEvent.MOUSE_ENTERED) {
			setTimer(750L);
		}
		else if (id == MouseEvent.MOUSE_EXITED) {
			hideTip();
		}
		if (!getBoolean(component, "enabled", true)) { return; }
		String classname = getClass(component);
		if (("button" == classname) ||
				("checkbox" == classname) || ("togglebutton" == classname)) {
			if ((id == MouseEvent.MOUSE_ENTERED) ||
					(id == MouseEvent.MOUSE_EXITED) ||
					(id == MouseEvent.MOUSE_PRESSED) ||
					(id == MouseEvent.MOUSE_RELEASED)) {
				if (id == MouseEvent.MOUSE_PRESSED) {
					setFocus(component);
				}
				else if ((id == MouseEvent.MOUSE_RELEASED) &&
						(mouseinside == component)) {
					if ("button" != classname) {
						changeCheck(component, true);
					}
					else invoke(component, null, "action");
				}
				repaint(component);
			}
		}
		else if ("combobox" == classname) {
			boolean editable = getBoolean(component, "editable", true);
			if (editable && (part == null)) {
				Image icon = null;
				int left = ((id == MouseEvent.MOUSE_PRESSED) &&
					((icon = getIcon(component, "icon", null)) != null)) ?
						icon.getWidth(this) : 0;
				processField(x, y, clickcount, id, component, part, false, false, left);
			}
			else if (part != "icon") { // part = "down"
				if (((id == MouseEvent.MOUSE_ENTERED) ||
						(id == MouseEvent.MOUSE_EXITED)) && (mousepressed == null)) {
					if (editable) { repaint(component, "combobox", part); }
						else { repaint(component); }
				}
				else if (id == MouseEvent.MOUSE_PRESSED) {
					Object combolist = get(component, ":combolist");
					if (combolist == null) {
						setFocus(component);
						repaint(component);
						popup(component, classname);
					} else {
						closeup(component, combolist, null);
					}
				}
				else if (id == MouseEvent.MOUSE_RELEASED) {
					if (mouseinside != component) {
						Object combolist = get(component, ":combolist");
						closeup(component, combolist,
							(mouseinside == combolist) ? insidepart : null);
					} else {
						repaint(component);
					}
				}
			}
		}
		else if (":combolist" == classname) {
			if (!processScroll(x, y, id, component, part)) {
				if ((id == MouseEvent.MOUSE_ENTERED) || (id == DRAG_ENTERED)) {
					if (part != null) {
						// repaint previous inside
						set(component, ":inside", part);
						repaint(component, classname, part);
					}
				}
				else if ((id == MouseEvent.MOUSE_EXITED) || (id == DRAG_EXITED)) {
					if (part != null) {
						set(component, ":inside", null);
						repaint(component, classname, part);
					}
				}
				else if (id == MouseEvent.MOUSE_RELEASED) {
					closeup(get(component, "combobox"), component, part);
				}
			}
		}
		else if (("textfield" == classname) || ("passwordfield" == classname)) {
			processField(x, y, clickcount, id, component, part,
				false, ("passwordfield" == classname), 0);
		}
		else if ("textarea" == classname) {
			if (!processScroll(x, y, id, component, part)) {
				processField(x, y, clickcount, id, component, part, true, false, 0);
			}
		}
		//java>
		else if ("desktop" == classname) {
			if (id == MouseEvent.MOUSE_ENTERED) {
				setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
			}
			else if (id == MouseEvent.MOUSE_EXITED) {
				setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
			}
		}
		//<java
		else if ("spinbox" == classname) {
			if (part == null) {
				processField(x, y, clickcount, id, component, part, false, false, 0);
			}
			else { // part = "up" || "down"
				if ((id == MouseEvent.MOUSE_ENTERED) ||
						(id == MouseEvent.MOUSE_EXITED) ||
						(id == MouseEvent.MOUSE_PRESSED) ||
						(id == MouseEvent.MOUSE_RELEASED)) {
					if (id == MouseEvent.MOUSE_PRESSED) {
						setFocus(component);
						if (processSpin(component, part)) { setTimer(375L); }
						//settext: start end selection, parse exception...
					}
					else {
						if (id == MouseEvent.MOUSE_RELEASED) {
							setTimer(0L);
						}
					}
					repaint(component, classname, part);
				}
			}
		}
		else if ("tabbedpane" == classname) {
			if ((id == MouseEvent.MOUSE_ENTERED) ||
					(id == MouseEvent.MOUSE_EXITED)) {
				if ((part != null) && getBoolean(part, "enabled", true) &&
						(getInteger(component, "selected", 0) != getIndex(component, "tab", part))) {
					repaint(component, "tabbedpane", part);
				}
			}
			else if ((part != null) && (id == MouseEvent.MOUSE_PRESSED) &&
					getBoolean(part, "enabled", true)) {
				int selected = getInteger(component, "selected", 0);
				int current = getIndex(component, "tab", part);
				if (selected == current) {
					setFocus(component);
					repaint(component, "tabbedpane", part);
				}
				else {
					setInteger(component, "selected", current, 0);
					//Object tabcontent = getItemImpl(component, "component", current);
					//setFocus((tabcontent != null) ? tabcontent : component);
					setNextFocusable(component, false);
					repaint(component);
					invoke(component, part, "action");
				}
			}
		}
		else if ("slider" == classname) {
			if ((id == MouseEvent.MOUSE_PRESSED) ||
					(id == MouseEvent.MOUSE_DRAGGED)) {
				if (id == MouseEvent.MOUSE_PRESSED) {
					setReference(component, block / 2, block / 2);
					setFocus(component);
				}
				int minimum = getInteger(component, "minimum", 0);
				int maximum = getInteger(component, "maximum", 100);
				int value = getInteger(component, "value", 50);
				Rectangle bounds = getRectangle(component, "bounds");
				boolean horizontal = ("vertical" != get(component, "orientation"));
				int newvalue = minimum +
					(horizontal ? (x - referencex) : (y - referencey)) *
					(maximum - minimum) /
					((horizontal ? bounds.width : bounds.height) - block); //... +0.5
				newvalue = Math.max(minimum, Math.min(newvalue, maximum));
				if (value != newvalue) {
					setInteger(component, "value", newvalue, 50);
					invoke(component, null, "action");
				}
				if ((value != newvalue) || (id == MouseEvent.MOUSE_PRESSED)) {
					repaint(component);
				}
			}
		}
		else if ("splitpane" == classname) {
			if (id == MouseEvent.MOUSE_PRESSED) {
				setReference(component, 2, 2);
			}
			else if (id == MouseEvent.MOUSE_DRAGGED) {
				int divider = getInteger(component, "divider", -1);
				boolean horizontal = ("vertical" != get(component, "orientation"));
				int moveto = horizontal ? (x - referencex) : (y - referencey);
				Rectangle bounds = getRectangle(component, "bounds");
				moveto = Math.max(0, Math.min(moveto,
					Math.abs(horizontal ? bounds.width : bounds.height) - 5));
				if (divider != moveto) {
					setInteger(component, "divider", moveto, -1);
					validate(component);
				}
			}
			//java>
			else if ((id == MouseEvent.MOUSE_ENTERED) && (mousepressed == null)) {
				boolean horizontal = ("vertical" != get(component, "orientation"));
				setCursor(Cursor.getPredefinedCursor(horizontal ?
					Cursor.E_RESIZE_CURSOR : Cursor.S_RESIZE_CURSOR));
			}
			else if (((id == MouseEvent.MOUSE_EXITED) && (mousepressed == null)) ||
					((id == MouseEvent.MOUSE_RELEASED) && (mouseinside != component))) {
				setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
			}
			//<java
		}
		else if (("list" == classname) ||
				("table" == classname) || ("tree" == classname)) {
			if (!processScroll(x, y, id, component, part)) {
				if (((id == MouseEvent.MOUSE_PRESSED)||
						((id == MouseEvent.MOUSE_DRAGGED) &&
							!shiftdown && !controldown)) &&
						!popuptrigger) { // e.getClickCount() 
					Rectangle view = getRectangle(component, ":view");
					Rectangle viewport = getRectangle(component, ":port");
					int my = y + view.y - referencey;
					String itemname = ("list" == classname) ? "item" :
						(("table" == classname) ? "row" : "node");
					String subitem = ("tree" == classname) ? "node" : null;
					for (Object item = get(component, itemname); item != null;) {
						Rectangle r = getRectangle(item, "bounds");
						if (my < r.y + r.height) {
							if (id == MouseEvent.MOUSE_DRAGGED) { //!!!
								scrollToVisible(component, r.x, r.y, 0, r.height - 1);
							}
							else if ("tree" == classname) {
								int mx = x + view.x - referencex;
								if (mx < r.x) {
									if ((mx >= r.x - block) && (get(item, "node") != null)) {
										boolean expanded = getBoolean(item, "expanded", true);
										setBoolean(item, "expanded", !expanded, 	true);
										selectItem(component, item, "node", "node");
										setLead(component, get(component, ":lead"), item);
										setFocus(component);
										validate(component);
										invoke(component, item, expanded ? "collapse" : "expand"); //item
									}
									break;
								}
							}
							if ((id != MouseEvent.MOUSE_DRAGGED) ||
									!getBoolean(item, "selected", false)) {
								select(component, item, itemname, subitem, shiftdown, controldown);
								if (id != MouseEvent.MOUSE_DRAGGED) {
									if (setFocus(component)) { repaint(component, classname, item); } //?
								}
							}
							break;
						}
						item = getNextItem(component, item, subitem);
					}
				}
		 	}
		}
		else if ("menubar" == classname) {
			Object selected = get(component, "selected");
			if (((id == MouseEvent.MOUSE_ENTERED) || (id == MouseEvent.MOUSE_EXITED)) &&
					(part != null) && (selected == null)) {
				repaint(component, classname, part);
			}
			else if ((part != null) && ((selected == null) ?
				(id == MouseEvent.MOUSE_PRESSED) :
					((id == MouseEvent.MOUSE_ENTERED) || (id == DRAG_ENTERED)))) {
					// || ((id == MouseEvent.MOUSE_PRESSED) && (insidepart != part))
				set(component, "selected", part);
				popup(component, classname);
				repaint(component, classname, part);
			}
			else if ((id == MouseEvent.MOUSE_PRESSED) && (selected != null)) {
				closeup(component);
			}
			else if (id == MouseEvent.MOUSE_RELEASED) {
				if ((part != insidepart) &&
						((insidepart == null) || (getClass(insidepart) != "menu"))) {
					if ((insidepart != null) && getBoolean(insidepart, "enabled", true)) {
						if (getClass(insidepart) == "checkboxmenuitem") {
							changeCheck(insidepart, false);
						}
						else invoke(insidepart, null, "action");
					}
					closeup(component);
				}
			}
		}
		else if (":popup" == classname) {
			if (part != null) {
				if ((id == MouseEvent.MOUSE_ENTERED) || (id == DRAG_ENTERED)) {
					set(component, "selected", part);
					popup(component, classname);
					repaint(component, classname, part);
				}
				else if (id == MouseEvent.MOUSE_RELEASED) {
					if ((insidepart == null) || (getClass(insidepart) != "menu")) {
						Object menubar = part;
						do {
							menubar = getParent(menubar);
						} while (getClass(menubar) != "menubar");
						if ((insidepart != null) && getBoolean(insidepart, "enabled", true)) {
							if (getClass(insidepart) == "checkboxmenuitem") {
								changeCheck(insidepart, false);
							}
							else invoke(insidepart, null, "action");
						}
						closeup(menubar);
					}
				}
				else if ((id == MouseEvent.MOUSE_EXITED) || (id == DRAG_EXITED)) {
					if (getClass(part) != "menu") {
						set(component, "selected", null);
					}
					repaint(component, classname, part);
				}
			}
		}
		else if ("dialog" == classname) {
			if (part == "header") {
				if (id == MouseEvent.MOUSE_PRESSED) {
					Rectangle bounds = getRectangle(component, "bounds");
					referencex = x - bounds.x; referencey = y - bounds.y;
					Object parent = getParent(component);
					if (get(parent, "component") != component) { // to front
						removeItemImpl(parent, "component", component);
						insertItem(parent, "component", component, 0);
						set(component, ":parent", parent);
						repaint(component); // to front always...
						setNextFocusable(component, false);
					}
				}
				else if (id == MouseEvent.MOUSE_DRAGGED) {
					Rectangle bounds = getRectangle(component, "bounds");
					Rectangle parents = getRectangle(getParent(component), "bounds");
					//int mx = Math.max(5 - bounds.width, Math.min(x - referencex, parents.width - 5));
					//int my = Math.max(5 - bounds.height, Math.min(y - referencey, parents.height - 5));
					int mx = Math.max(0, Math.min(x - referencex, parents.width - bounds.width));
					int my = Math.max(0, Math.min(y - referencey, parents.height - bounds.height));
					if ((bounds.x != mx) || (bounds.y != my)) {
						// repaint the union of the previous and next bounds
						repaint(component, Math.min(bounds.x, mx), Math.min(bounds.y, my),
							bounds.width + Math.abs(mx - bounds.x), bounds.height + Math.abs(my - bounds.y));
						bounds.x = mx; bounds.y = my;
					}
				}
			}
		}
	}

	/**
	 * Calculate the given point in a component relative to the thinlet desktop and
	 * set as reference value. 
	 */
	private void setReference(Object component, int x, int y) {
		referencex = x; referencey = y;
		for (; component != null; component = getParent(component)) {
			Rectangle bounds = getRectangle(component, "bounds");
			referencex += bounds.x; referencey += bounds.y;
		}
	}

	/**
	 *
	 */
	private void select(Object component, Object row, String first,
			String child, boolean shiftdown, boolean controldown) {
		String selection = getString(component, "selection", "single");
		Object lead = null;
		if (shiftdown && (selection != "single") &&
				((lead = get(component, ":lead")) != null)) {
			extend(component, lead, row, first, child);
		}
		else {
			if (controldown && (selection == "multiple")) {
				setBoolean(row, "selected",
					!getBoolean(row, "selected", false), false);
				repaint(component, null, row);
				invoke(component, row, "action");
				set(component, ":anchor", null);
			}
			else if (controldown && getBoolean(row, "selected", false)) {
				for (Object item = row;
						item != null; item = getNextItem(component, item, child)) {
					if (setBoolean(item, "selected", false, false)) {
						repaint(component, null, item);
					}
				}
				invoke(component, row, "action");
				set(component, ":anchor", null);
			}
			else {
				selectItem(component, row, first, child);
			}
		}
		setLead(component, (lead != null) ? lead : get(component, ":lead"), row);
	}

	/**
	 *
	 */
	private Object getNextItem(Object component,
			Object item, String subitem) {
		if (subitem == null) { return get(item, ":next"); }
		Object next = get(item, subitem);
		if ((next == null) || !getBoolean(item, "expanded", true)) {
			while ((item != component) && ((next = get(item, ":next")) == null)) {
				item = getParent(item);
			}
		}
		return next;
	}
	
	/**
	 *
	 */
	private void processField(int x, int y, int clickcount,
			int id, Object component,
			Object part, boolean multiline, boolean hidden, int left) {
		if (id == MouseEvent.MOUSE_PRESSED) {
			setReference(component, 2 + left, 2);
			int mx = x - referencex;
			int my = 0;
			if (!multiline) {
				mx += getInteger(component, "offset", 0);
			} else {
				Rectangle view = getRectangle(component, ":view");
				mx += view.x - 1;
				my = y - referencey + view.y - 1;
			}
			int caretstart = getCaretLocation(component, mx, my, multiline, hidden);
			int caretend = caretstart;
			//java>
			if (clickcount > 1) {
				String text = getString(component, "text", "");
				while ((caretstart > 0) && ((clickcount == 2) ?
					Character.isLetterOrDigit(text.charAt(caretstart - 1)) :
						(text.charAt(caretstart - 1) != '\n'))) { caretstart--; }
				while ((caretend < text.length()) && ((clickcount == 2) ?
					Character.isLetterOrDigit(text.charAt(caretend)) :
						(text.charAt(caretend) != '\n'))) { caretend++; }
			}
			//<java
			setInteger(component, "start", caretstart, 0);
			setInteger(component, "end", caretend, 0);
			setFocus(component);
			validate(component); // caret check only
		}
		else if (id == MouseEvent.MOUSE_DRAGGED) {
			int mx = x - referencex;
			int my = 0;
			if (!multiline) {
				mx += getInteger(component, "offset", 0);
			} else {
				Rectangle view = getRectangle(component, ":view");
				mx += view.x - 1;
				my = y - referencey + view.y - 1;
			}
			int dragcaret = getCaretLocation(component, mx, my, multiline, hidden);
			if (dragcaret != getInteger(component, "end", 0)) {
				setInteger(component, "end", dragcaret, 0);
				validate(component); // caret check only
			}
		}
		//java>
		else if ((id == MouseEvent.MOUSE_ENTERED) && (mousepressed == null)) {
			setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
		}
		else if (((id == MouseEvent.MOUSE_EXITED) && (mousepressed == null)) ||
			((id == MouseEvent.MOUSE_RELEASED) &&
				((mouseinside != component) || (insidepart != null)))) {
			setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
		}
		//<java
	}

	/**
	 *
	 */
	private int getCaretLocation(Object component,
			int x, int y, boolean multiline, boolean hidden) {
		FontMetrics fm = getFontMetrics(font);
		char[] chars = multiline ? ((char[]) get(component, ":text")) :
			getString(component, "text", "").toCharArray(); // update it
		int linestart = 0;
		if (multiline) {
			int height = fm.getHeight(); // find the line start by y value
			for (int i = 0; (y >= height) && (i < chars.length); i++) {
				if ((chars[i] == '\n') || (chars[i] == '\t')) {
					linestart = i + 1; y -= height;
				}
			}
		}
		for (int i = linestart; i < chars.length; i++) {
			if ((chars[i] == '\n') || (chars[i] == '\t')) { return i; }
			int charwidth = fm.charWidth(hidden ? '*' : chars[i]);
			if (x <= (charwidth / 2)) { return i; }
			x -= charwidth;
		}
		return chars.length;
	}

	/**
	 *
	 */
	private boolean processScroll(int x, int y,
			int id, Object component, Object part) {
		if ((part == "up") || (part == "down") ||
				(part == "left") || (part == "right")) {
			if ((id == MouseEvent.MOUSE_ENTERED) ||
					(id == MouseEvent.MOUSE_EXITED) ||
					(id == MouseEvent.MOUSE_PRESSED) ||
					(id == MouseEvent.MOUSE_RELEASED)) {
				if (id == MouseEvent.MOUSE_PRESSED) {
					if (processScroll(component, part)) {
						setTimer(300L); return true;
					}
				}
				else {
					if (id == MouseEvent.MOUSE_RELEASED) { setTimer(0L); }
					repaint(component, null, part);
				}
			}
		}
		else if ((part == "uptrack") || (part == "downtrack") ||
				(part == "lefttrack") || (part == "righttrack")) {
			if (id == MouseEvent.MOUSE_PRESSED) {
				if (processScroll(component, part)) {
					setTimer(300L);
				}
			}
			else if (id == MouseEvent.MOUSE_RELEASED) {
				setTimer(0L);
			}
		}
		else if ((part == "vknob") || (part == "hknob")) {
			if (id == MouseEvent.MOUSE_PRESSED) {
				Rectangle port = getRectangle(component, ":port");
				Rectangle view = getRectangle(component, ":view");
				if (part == "hknob") {
					referencex = x -
						view.x * (port.width - 2 * block) / view.width;
				} else {
					referencey = y -
						view.y * (port.height - 2 * block) / view.height;
				}
			}
			else if (id == MouseEvent.MOUSE_DRAGGED) {
				Rectangle port = getRectangle(component, ":port");
				Rectangle view = getRectangle(component, ":view");
				if (part == "hknob") {
					int viewx = (x - referencex) *
						view.width / (port.width - 2 * block);
					viewx = Math.max(0, Math.min(viewx, view.width - port.width + 2));
					if (view.x != viewx) {
						view.x = viewx;
						repaint(component, null, "horizontal");
					}
				} else {
					int viewy = (y - referencey) *
						view.height / (port.height - 2 * block);
					viewy = Math.max(0, Math.min(viewy, view.height - port.height + 2));
					if (view.y != viewy) {
						view.y = viewy;
						repaint(component, null, "vertical");
					}
				}
			}
		}
		else if (part == "corner") {
				part = "corner"; // compiler bug
		}
		else {
			if (id == MouseEvent.MOUSE_PRESSED) {
				Rectangle port = getRectangle(component, ":port");
				setReference(component, port.x + 1, port.y + 1);
			}
			return false;
		}
		return true;
	}

	/**
	 *
	 */
	private boolean processScroll(Object component, Object part) {
		Rectangle view = getRectangle(component, ":view");
		Rectangle port = ((part == "left") || (part == "up")) ? null :
			getRectangle(component, ":port");
		int dx = 0; int dy = 0;
		if (part == "left") { dx = -10; }
		else if (part == "lefttrack") { dx = -port.width; }
		else if (part == "right") { dx = 10; }
		else if (part == "righttrack") { dx = port.width; }
		else if (part == "up") { dy = -10; }
		else if (part == "uptrack") { dy = -port.height; }
		else if (part == "down") { dy = 10; }
		else if (part == "downtrack") { dy = port.height; }
		if (dx != 0) {
			dx = (dx < 0) ? Math.max(-view.x, dx) :
				Math.min(dx, view.width - port.width + 2 - view.x);
		}
		else if (dy != 0) {
			dy = (dy < 0) ? Math.max(-view.y, dy) :
				Math.min(dy, view.height - port.height + 2 - view.y);
		}
		else return false;
		view.x += dx; view.y += dy;
		repaint(component, null, (dx != 0) ? "horizontal" : "vertical");
		return (((part == "left") || (part == "lefttrack")) && (view.x > 0)) ||
			(((part == "right") || (part == "righttrack")) &&
				(view.x < view.width - port.width + 2)) ||
			(((part == "up") || (part == "uptrack")) && (view.y > 0)) ||
			(((part == "down") || (part == "downtrack")) &&
				(view.y < view.height - port.height + 2));
	}

	/**
	 *
	 */
	private boolean processSpin(Object component, Object part) {
		String text = getString(component, "text", null);
		if (text != null) {
			try {
				String value = String.valueOf(
					Integer.parseInt(text) + ((part == "up") ? 1 : -1));
				setString(component, "text", value, null);
				setInteger(component, "start", value.length(), 0);
				setInteger(component, "end", 0, 0);
				repaint(component, "spinbox", null);
				invoke(component, null, "action");
				return true;
			} catch (NumberFormatException nfe) {}
		}
		return false;
	}

	//java>
	/*public void setEventHandler(Object component, Object eventhandler) {
		set(component, ":handler", eventhandler);
	}*/
	
	/**
	 *
	 */
	private void invoke(Object component, Object part, String event) {
		Object method = get(component, event);
		if (method != null) {
			Object[] data = (Object[]) method;
			Object[] args = (data.length > 2) ? new Object[(data.length - 2) / 3] : null;
			if (args != null) for (int i = 0; i < args.length; i++) {
				Object target = data[2 + 3 * i];
				Object parametername = data[2 + 3 * i + 1];
				if (parametername == null) {
					args[i] = target;
				}
				else {
					args[i] = get(target, parametername);
					if (args[i] == null) { args[i] = data[2 + 3 * i + 2]; }
				}
			}
			try {
				((Method) data[1]).invoke(data[0], args);
			} catch (InvocationTargetException ite) {
				ite.getTargetException().printStackTrace();
			} catch (Exception exc) {
				exc.printStackTrace();
			}
		}
	}
	//<java
	/*midp
	private void invoke(Object component, Object part, String event) {
			String action = (String) get(component, event);
			if (action != null) { handle(component, action); }
	}
	protected void handle(Object source, String action) {
	}
	midp*/

	/**
	 *
	 */
	private boolean findComponent(Object component, int x, int y) {
		if (component == content) {
			mouseinside = insidepart = null;
			mousex = x; mousey = y;
		}
		if (!getBoolean(component, "visible", true)) { return false; }
		Rectangle bounds = getRectangle(component, "bounds");
		if ((bounds == null) || !(bounds.contains(x, y))) { return false; } //java
		//midp if ((bounds == null) || (x < bounds.x) || (x - bounds.x >= bounds.width) ||
		//midp 	(y < bounds.y) || (y - bounds.y >= bounds.height)) { return false; }
		mouseinside = component;
		x -= bounds.x; y -= bounds.y;
		String classname = getClass(component);

		if ("combobox" == classname) {
			if (getBoolean(component, "editable", true) && (x <= bounds.width - block)) {
				Image icon = getIcon(component, "icon", null);
				insidepart = ((icon != null) && (x <= 2 + icon.getWidth(this))) ?
					"icon" : null;
			} else {
				insidepart = "down";
			}
		}
		else if (":combolist" == classname) {
			if (!findScrollPane(component, x, y, bounds)) {
				y += getRectangle(component, ":view").y;
				for (Object choice = get(get(component, "combobox"), "choice");
						choice != null; choice = get(choice, ":next")) {
					Rectangle r = getRectangle(choice, "bounds");
					if ((y >= r.y) && (y < r.y + r.height)) {
						insidepart = choice; break;
					}
				}
			}
		}
		else if ("textarea" == classname) {
			findScrollPane(component, x, y, bounds);
		}
		else if ("tabbedpane" == classname) {
			Object tabcontent = getItemImpl(component,
				"component", getInteger(component, "selected", 0));
			if ((tabcontent == null) || !findComponent(tabcontent, x, y)) {
				for (Object comp = get(component, "tab");
						comp != null; comp = get(comp, ":next")) {
					Rectangle r = getRectangle(comp, "bounds");
					if (r.contains(x, y)) { //java
					//midp if ((x >= r.x) && (x - r.x < r.width) && (y >= r.y) && (y - r.y < r.height)) {
						insidepart = comp; break;
					}
				}
			}
		}
		else if (("panel" == classname) || ("desktop" == classname) ||
				("dialog" == classname)) {
			if (("dialog" == classname) &&
					(y < 4 + getInteger(component, ":titleheight", 0))) {
				insidepart = "header";
			} else {
				for (Object comp = get(component, "component");
						comp != null; comp = get(comp, ":next")) {
					if (findComponent(comp, x, y)) { break; }
					if (("desktop" == classname) &&
							getBoolean(comp, "modal", false)) { break; } // && dialog
				}
			}
		}
		else if ("spinbox" == classname) {
			insidepart = (x <= bounds.width - block) ? null :
				((y <= bounds.height / 2) ? "up" : "down");
		}
		else if ("splitpane" == classname) { 
			Object comp1 = get(component, "component");
			if (comp1 != null) {
				if (!findComponent(comp1, x, y)) {
					Object comp2 = get(comp1, ":next");
					if (comp2 != null) {
						findComponent(comp2, x, y);
					}
				}
			}
		}
		else if ("list" == classname) {
			findScrollPane(component, x, y, bounds);
		}
		else if ("table" == classname) {
			findScrollPane(component, x, y, bounds);
		}
		else if ("tree" == classname) {
			findScrollPane(component, x, y, bounds);
		}
		else if ("menubar" == classname) {
			for (Object menu = get(component, "menu");
					menu != null; menu = get(menu, ":next")) {
				Rectangle r = getRectangle(menu, "bounds");
				if ((x >= r.x) && (x < r.x + r.width)) {
					insidepart = menu; break;
				}
			}
		}
		else if (":popup" == classname) {
			for (Object menu = get(get(component, "menu"), "menu");
					menu != null; menu = get(menu, ":next")) {
				Rectangle r = getRectangle(menu, "bounds");
				if ((y >= r.y) && (y < r.y + r.height)) {
					insidepart = menu; break;
				}
			}
		}
		return true;
	}

	/**
	 *
	 */
	private boolean findScrollPane(Object component,
			int x, int y, Rectangle bounds) {
		Rectangle port = getRectangle(component, ":port");
		if ((x < port.x) || (y < port.y) ||
				((x >= port.x + port.width) && (y >= port.y + port.height))) {
			insidepart = "corner";
		}
		else if ((x >= port.x + port.width) || (y >= port.y + port.height)) {
			boolean horizontal = (y >= port.y + port.height);
			int p = horizontal ? (x - port.x) : (y - port.y);
			int portsize = horizontal ? port.width : port.height;
			int button = Math.min(block, portsize / 2);
			if (p < button) {
				insidepart = horizontal ? "left" : "up";
			}
			else if (p >= portsize - button) {
				insidepart = horizontal ? "right" : "down";
			}
			else {
				Rectangle view = getRectangle(component, ":view");
				int viewp = horizontal ? view.x : view.y;
				int viewsize = horizontal ? view.width : view.height;
				int track = portsize - (2 * button);
				int knob = Math.min(track,
					Math.max(track * (portsize - 2) / viewsize, 6));
				int decrease = viewp * (track - knob) / (viewsize - portsize + 2);
				if (p < button + decrease) {
					insidepart = horizontal ? "lefttrack" : "uptrack";
				}
				else if (p < button + decrease + knob) {
					insidepart = horizontal ? "hknob" : "vknob";
				}
				else {
					insidepart = horizontal ? "righttrack" : "downtrack";
				}
			}
		}
		else { return false; }
		return true;
	}

	/**
	 *
	 */
	private void repaint(Object component, Object classname, Object part) {
		Rectangle b = getRectangle(component, "bounds");
		if ((classname == "combobox") || (classname == "spinbox")) {
			boolean down = (part == "up") || (part == "down"); // else text
			repaint(component, down ? (b.x + b.width - block) : b.x, b.y,
				down ? block : (b.width - block), b.height);
		}
		//else if (classname == "dialog") {}
			//int titleheight = getInteger(component, ":titleheight", 0);
		//else if (classname == "splitpane") {}
		else if ((classname == "tabbedpane") ||
				(classname == "menubar") || (classname == ":popup")) {
			Rectangle r = getRectangle(part, "bounds");
			repaint(component, b.x + r.x, b.y + r.y,
				(classname == ":popup") ? b.width : r.width, r.height);
		}
		else //if ((classname == ":combolist") || (classname == "textarea") ||
				{//(classname == "list") || (classname == "table") || (classname == "tree")) {
			Rectangle port = getRectangle(component, ":port");
			if (part == "left") {
				repaint(component, b.x + port.x, b.y + b.height - block, block, block);
			}
			else if (part == "right") {
				repaint(component, b.x + port.x + port.width - block, b.y + b.height - block, block, block);
			}
			else if (part == "up") {
				repaint(component, b.x + b.width - block, b.y + port.y, block, block);
			}
			else if (part == "down") {
				repaint(component, b.x + b.width - block, b.y + port.y + port.height - block, block, block);
			}
			else if (part == "horizontal") { // horizontaly center part
				repaint(component, b.x + port.x, b.y, port.width, b.height);
			}
			else if (part == "vertical") {
				repaint(component, b.x, b.y + port.y, b.width, port.height);
			}
			else if (part == "text") { //textarea
				repaint(component, b.x + port.x, b.y + port.y, port.width, port.height);
			}
			else {
				Rectangle view = getRectangle(component, ":view");
				Rectangle r = getRectangle(part, "bounds");
				if ((r.y + r.height >= view.y) && (r.y <= view.y + port.height)) {
					repaint(component, b.x + port.x, b.y + port.y - view.y + 1 + r.y,
						port.width, r.height);
					//? need cut item rectangle above/bellow viewport
				}
			}
		}
	}
	
	/**
	 *
	 */
	private void validate(Object component) {
		repaint(component);
		Rectangle bounds = getRectangle(component, "bounds");
		if (bounds != null) { bounds.width = -1 * Math.abs(bounds.width); }
	}
	
	/**
	 *
	 */
	private void repaint(Object component) {
		Rectangle bounds = getRectangle(component, "bounds");
		if (bounds != null) {
			repaint(component, bounds.x, bounds.y, bounds.width, bounds.height);
		}
	}

	/**
	 *
	 */
	private void repaint(Object component, int x, int y, int width, int height) {
		while ((component = getParent(component)) != null) {
			Rectangle bounds = getRectangle(component, "bounds");
			x += bounds.x;
			y += bounds.y;
		}
		repaint(x, y, width, height);
	}

	/*private void clip(Graphics g,
			Rectangle clip, int x, int y, int width, int height) {
		int x1 = Math.max(clip.x, x); 
		int y1 = Math.max(clip.y, y);
		int x2 = Math.min(clip.x + clip.width, x + width);
		int y2 = Math.min(clip.y + clip.height, y + height);
		g.setClip(x1, y1, x2 - x1, y2 - y1);
	}*/

	/**
	 * Requests that both the <i>Thinlet</i> component,
	 * and the given widget get the input focus
	 *
	 * @param component a focusable widget inside
	 * visible and enabled parents, and tabbedpane's selected tab
	 * @return true, if the given component was focusable
	 */
	public boolean requestFocus(Object component) {
		if (checkFocusable(component, true)) {
			setFocus(component); return true;
		}
		return false;
	}

	/**
	 *
	 */
	private boolean setFocus(Object component) {
		if (!focusinside) { //java
			requestFocus(); //java
		} //java
		if (focusowner != component) {
			Object focused = focusowner;
			focusowner = component;
			if (focused != null) {
				//mouseEvent(null, FocusEvent.FOCUS_LOST, focused, null, null);
				repaint(focused);
				//focusGained(component);
			}
			return true;
		}
		return false;
	}
	
	/**
	 * @return next focusable component is found (not the first of the desktop/dialog)
	 */
	private boolean setNextFocusable(Object current, boolean outgo) {
		boolean consumed = true;
		for (Object next = null, component = current; true; component = next) {
			next = get(component, "component"); // check first subcomponent
			if (next == null) { next = get(component, ":next"); } // check next component
			while (next == null) { // find the next of the parents, or the topmost
				component = getParent(component); // current is not on the desktop
				if (component == null) { return false; }
				if ((component == content) || ((getClass(component) == "dialog") &&
						(!outgo  || getBoolean(component, "modal", false)))) {
					consumed = false; // find next focusable but does not consume event
					next = component; // the topmost (desktop or modal dialog)
				}
				else {
					next = get(component, ":next");
				}
			}
			if (next == current) { return false; } // one fucusable, no loop
			if (checkFocusable(next, false)) {
				setFocus(next);
				return consumed;
			}
		}
	}

	//java>
	/**
	 * @return previous focusable component is found (not the last of the desktop/dialog)
	 */
	private boolean setPreviousFocusable(Object component, boolean outgo) {
		for (int i = 0; i < 2; i++) { // 0 is backward direction
			Object previous = getPreviousFocusable(component, null, true, false, (i == 0), outgo);
			if (previous != null) {
				setFocus(previous);
				return (i == 0);
			}
		}
		return false;
	}
	
	/**
	 * For the starting component search its parent direction for a focusable component, and then
	 * its next component (if not search backward from the component).<br>
	 * For its parent components check its first component, the current one, and its parent direction
	 * (backward search), or its parent, then next component (forward direction).<br>
	 * For the rest components check the next, then the first subcomponent direction, and finally
	 * check whether the component is focusable.
	 */
	private Object getPreviousFocusable(Object component,
			Object block, boolean start, boolean upward, boolean backward, boolean outgo) {
		Object previous = null;
		if ((component != null) && (component != block)) {
			boolean go = ((getClass(component) != "dialog") ||
				(outgo && !getBoolean(component, "modal", false)));
			if (!start && !upward && go) {
				previous = getPreviousFocusable(get(component, ":next"), block, false, false, backward, outgo);
			}
			if ((previous == null) && ((upward && backward) || (!start && !upward))) {
				previous = getPreviousFocusable(get(component, "component"), block, false, false, backward, outgo);
				if ((previous == null) && checkFocusable(component, false)) {
					previous = component;
				}
			}
			if ((previous == null) && (start || upward) && go) {
				previous = getPreviousFocusable(getParent(component), component, false, true, backward, outgo);
			}
			if ((previous == null) && (start || upward) && !backward && go) {
				previous = getPreviousFocusable(get(component, ":next"), block, false, false, backward, outgo);
			}
		}
		return previous;
	}
	//<java
	
	/**
	 *
	 */
	private boolean checkFocusable(Object component, boolean forced) {
		String classname = getClass(component);
		//midp forced=true;
		if ((classname == "button") || (classname == "checkbox") || ("togglebutton" == classname) ||
				(classname == "combobox") || (classname == "textfield") ||
				(classname == "passwordfield") || (classname == "textarea") ||
				(classname == "spinbox") || (classname == "slider") ||
				(classname == "list") || (classname == "table") || (classname == "tree") ||
				(classname == "tabbedpane") || (forced && (classname == "splitpane"))) {
			for (Object comp = component; comp != null;) {
				if (!getBoolean(comp, "enabled", true) || !getBoolean(comp, "visible", true)) {
					return false;
				}
				Object parent = getParent(comp);
				if ((getClass(parent) == "tabbedpane") && (getItemImpl(parent,
					"component", getInteger(parent, "selected", 0)) != comp)) { return false; }
				comp = parent;
			}
			return true;
		}
		return false;
	}

	/*if (cliparea == null) { add(cliparea = new TextArea()); }
	cliparea.setText(content);
	cliparea.selectAll();
	cliparea.dispatchEvent(new KeyEvent(this, KeyEvent.KEY_TYPED, 0L, KeyEvent.CTRL_MASK, 0, (char) 3));
	cliparea.selectAll();

	if (cliparea == null) { add(cliparea = new TextArea()); }
	cliparea.dispatchEvent(new KeyEvent(this, KeyEvent.KEY_TYPED, 0L, KeyEvent.CTRL_MASK, 0, (char) 22));
	cliparea.getText(); return cliparea.getText();*/

	// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

	/**
	 * Creates a new component
	 *
	 * @param classname the widget type (e.g. <i>button</i>)
	 * @return a new component, every component is simply an <i>Object</i>
	 * @throws java.lang.IllegalArgumentException for unknown widget type
	 */
	public Object create(String classname) {
		for (int i = 0; i < dtd.length; i += 3) {
			if (dtd[i].equals(classname)) {
				return createImpl((String) dtd[i]);
			}
		}
		throw new IllegalArgumentException("unknown " + classname);
	}
	
	/**
	 * Gets the type of the given component
	 *
	 * @param component a widget
	 * @return the class name of the component (e.g. <i>button</i>)
	 */
	public String getClass(Object component) {
		return (String) get(component, ":class");
	}

	/**
	 * Get the topmost component
	 *
	 * @return the root object (it is a <i>desktop</i>), never <i>null</i>
	 */
	public Object getDesktop() {
		return content;
	}

	/**
	 *
	 */
	private Object createImpl(String classname) {
		return new Object[] { ":class", classname, null };
	}
	
	/**
	 *
	 */
	private boolean set(Object component, Object key, Object value) {
		Object[] previous = (Object[]) component;
		for (Object[] entry = previous; entry != null;
				entry = (Object[]) entry[2]) {
			if (entry[0] == key) {
				if (value != null) { // set the row's value
					Object oldvalue = entry[1];
					entry[1] = value;
					return !value.equals(oldvalue);
				}
				else { // remove the row
					previous[2] = entry[2];
					entry[2] = null;
					return true;
				}
			}
			previous = entry;
		}
		if (value != null) { // append a new row
			previous[2] = new Object[] { key, value, null };
			return true;
		}
		return false;
	}

	/**
	 *
	 */
	private Object get(Object component, Object key) {
		for (Object[] entry = (Object[]) component; entry != null;
				entry = (Object[]) entry[2]) {
			if (entry[0] == key) {
				return entry[1];
			}
		}
		return null;
	}

	/**
	 * Gets the count of subcomponents in the list of the given component
	 *
	 * @param component a widget
	 * @return the number of components in this component
	 */
	public int getCount(Object component) {
		return getCount(component, null);
	}

	/**
	 * Gets the count of subcomponents in the list of the given component
	 *
	 * @param component a widget
	 * @param key the list is identified by the key name for <i>tabbed pane</i>
	 * (<i>tab</i>, or <i>component</i>) or <i>table</i>
	 * (<i>column</i>, or <i>row</i>) The <i>component</i> and the <i>row</i>
	 * is the default key
	 * @return the number of components in this component
	 */
	public int getCount(Object component, String key) {
		return getItemCountImpl(component, getComponentName(component, key));
	}
	
	/**
	 * Gets the parent of this component
	 *
	 * @param component a widget
	 * @return the parent container of this component or item
	 */
	public Object getParent(Object component) {
		return get(component, ":parent");
	}

	/**
	 * Gets the index of the first selected item in the given component (list, table, or tree)
	 *
	 * @param component a widget
	 * @return the first selected index or -1
	 */
	public int getSelectedIndex(Object component) {
		int i = 0;
		for (Object item = get(component, getComponentName(component, null));
				item != null; item = get(item, ":next")) {
			if (getBoolean(item, "selected", false)) { return i; }
			i++;
		}
		return -1;
	}
	
	/**
	 * Gets the first selected item of the given component (list, table, or tree)
	 *
	 * @param component a widget
	 * @return the first selected item or null
	 */
	public Object getSelectedItem(Object component) {
		String classname = getClass(component);
		for (Object item = findNextItem(component, classname, null); item != null;
				item = findNextItem(component, classname, item)) {
			if (getBoolean(item, "selected", false)) { return item; }
		}
		return null;
	}

	/**
	 * Gets the selected item of the given component (list, table, or tree)
	 * when multiple selection is allowed
	 *
	 * @param component a widget
	 * @return the array of selected items, or a 0 length array
	 */
	public Object[] getSelectedItems(Object component) {
		String classname = getClass(component);
		Object[] selecteds = new Object[0];
		for (Object item = findNextItem(component, classname, null); item != null;
				item = findNextItem(component, classname, item)) {
			if (getBoolean(item, "selected", false)) {
				Object[] temp = new Object[selecteds.length + 1];
				System.arraycopy(selecteds, 0, temp, 0, selecteds.length);
				temp[selecteds.length] = item;
				selecteds = temp;
			}
		}
		return selecteds;
	}

	/**
	 * @return the first or the next item of the (list, table, or tree) component
	 */
	private Object findNextItem(Object component, String classname, Object item) {
		if (item == null) { // first item
			String itemname = ("list" == classname) ? "item" : (("table" == classname) ? "row" : "node");
			return get(component, itemname);
		}
		else if ("tree" == classname) { // next tree node
			Object next = get(item, "node");
			if ((next == null) || !getBoolean(item, "expanded", true)) { // no subnode or collapsed
				while ((item != component) && ((next = get(item, ":next")) == null)) {
					item = getParent(item); //next node of in backward path
				}
			}
			return next;
		}
		else { //next list or tree item
			return get(item, ":next");
		}
	}

	/**
	 * Removes all the components from this container
	 *
	 * @param component the specified container
	 */
	public void removeAll(Object component) {
		removeAll(component, null);
	}

	/**
	 * Removes all the components from this container's specified list
	 *
	 * @param component the specified container
	 * @param key the list is identified by the key name for <i>tabbed pane</i>
	 * (<i>tab</i>, or <i>component</i>) or <i>table</i>
	 * (<i>column</i>, or <i>row</i>) The <i>component</i> and the <i>row</i>
	 * is the default key
	 */
	public void removeAll(Object component, String key) {
		String list = getComponentName(component, key);
		if (get(component, list) != null) {
			set(component, list, null);
			update(component, "validate");
		}
	}

	/**
	 *
	 */
	private int getItemCountImpl(Object component, Object key) {
		int i = 0;
		for (component = get(component, key); component != null;
				component = get(component, ":next")) {
			i++;
		}
		return i;
	}

	/**
	 * Returns the subcomponent of the given component at the specified index
	 *
	 * @param component a specified container
	 * @param index the index of the component to get
	 * @return the index<sup>th</sup> component in this container
	 */
	public Object getItem(Object component, int index) {
		return getItem(component, null, index);
	}
	
	/**
	 * Gets all the components in this container
	 *
	 * @param component a specified container
	 * @return an array of all the components in this container
	 */
	public Object[] getItems(Object component) {
		return getItems(component, null);
	}

	/**
	 * Returns the subcomponent of the given component's specified list at the given index
	 *
	 * @param component a specified container
	 * @param key the list is identified by the key name
	 * @param index the index of the component to get
	 * @return the index<sup>th</sup> component in this container
	 */
	public Object getItem(Object component, String key, int index) {
		return getItemImpl(component, getComponentName(component, key), index);
	}

	/**
	 * Gets all the components in this container's specified list
	 *
	 * @param component a specified container
	 * @param key the list is identified by the key name
	 * @return an array of all the components in this container
	 */
	public Object[] getItems(Object component, String key) {
		key = getComponentName(component, key);
		Object[] items = new Object[getItemCountImpl(component, key)];
		component = get(component, key);
		for (int i = 0; i < items.length; i++) {
			items[i] = component;
			component = get(component, ":next");
		}
		return items;
	}

	/**
	 *
	 */
	private Object getItemImpl(Object component, Object key, int index) {
		int i = 0;
		for (Object item = get(component, key);
				item != null; item = get(item, ":next")) {
			if (i == index) { return item; }
			i++;
		}
		return null;
	}

	/**
	 *
	 */
	private int getIndex(Object component, Object key, Object value) {
		int index = 0;
		for (Object item = get(component, key);
				item != null; item = get(item, ":next")) {
			if (value == item) { return index; }
			index++;
		}
		return -1;
	}

	/**
	 * Adds the specified component to the root desktop
	 *
	 * @param component a widget to be added
	 */
	public void add(Object component) {
		add(content, component, 0);
	}

	/**
	 * Adds the specified component to the end of the specified container
	 *
	 * @param parent a container
	 * @param component a component to be added
	 */
	public void add(Object parent, Object component) {
		add(parent, component, -1);
	}

	/**
	 * Adds the specified component to the container at the given position
	 *
	 * @param parent a container
	 * @param component a component to be inserted
	 * @param index the position at which to insert the component, 
	 * or -1 to insert the component at the end
	 */
	public void add(Object parent, Object component, int index) {
		addImpl(parent, component, index);
		update(component, "validate");
		if (parent == content) {
			setNextFocusable(component, false);
		}
	}

	/**
	 *
	 */
	private void addItem(Object parent, Object key, Object component) {
		insertItem(parent, key, component, -1);
	}

	/**
	 *
	 */
	private void insertItem(Object parent,
			Object key, Object component, int index) {
		Object target = get(parent, key);
		if (index == -1) {
			while (target != null) {
				target = get(parent = target, key = ":next");
			}
		}
		else {
			for (int i = 0; i < index; i++) {
				target = get(parent = target, key = ":next");
			}
			set(component, ":next", get(parent, key));
		}
		set(parent, key, component);
	}

	/**
	 * Removes the specified component from its parent
	 *
	 * @param component the component to be removed
	 * @throws java.lang.IllegalArgumentException
	 */
	public void remove(Object component) {
		Object parent = getParent(component);
		String parentclass = getClass(parent);
		String classname = getClass(component);
		String listkey = ("combobox" == parentclass) ? "choice" :
			(("tabbedpane" == parentclass) && ("tab" == classname)) ? "tab" :
			("list" == parentclass) ? "item" :
			(("table" == parentclass) && ("column" == classname)) ? "column" :
			(("table" == parentclass) && ("row" == classname)) ? "row" :
			("row" == parentclass) ? "cell" :
			(("tree" == parentclass) || ("node" == parentclass)) ? "node" :
			(("menubar" == parentclass) || ("menu" == parentclass)) ? "menu" :
			(("panel" == parentclass) || ("desktop" == parentclass) ||
				("splitpane" == parentclass) || ("dialog" == parentclass) ||
				("tabbedpane" == parentclass)) ? "component" : null;
		if (listkey == null) { throw new IllegalArgumentException("unknown " + classname); }
		update(component, "validate");
		removeItemImpl(parent, listkey, component);
		// reuest focus for its parent if the component (or subcomponent) is currently focused
		for (Object comp = focusowner; comp != null; comp = getParent(comp)) {
			if (comp == component) {
				setNextFocusable(parent, false); break;
			}
		}
	}

	/**
	 *
	 */
	private void removeItemImpl(Object parent, Object key, Object component) {
		Object target = get(parent, key);
		while (target != component) {
			target = get(parent = target, key = ":next"); // (target != null)
		}
		set(parent, key, get(target, ":next"));
		set(target, ":next", null);
		set(target, ":parent", null);
	}

	/*
	private void removeItemImpl(Object parent, String key, int index) {
		Object target = get(parent, key);
		for (int i = 0; i < index; i++) {
			target = get(parent = target, key = ":next");
		}
		set(parent, key, get(target, ":next"));
		set(target, ":next", null);
	}*/

	/**
	 *
	 * @throws java.lang.IllegalArgumentException
	 */
	private String getComponentName(Object parent, Object classname) {
		String parentclass = getClass(parent);
		String compname = ("combobox" == parentclass) ? "choice" :
			("list" == parentclass) ? "item" :
			("row" == parentclass) ? "cell" :
			(("tree" == parentclass) || ("node" == parentclass)) ? "node" :
			(("menubar" == parentclass) || ("menu" == parentclass)) ? "menu" :
			(("panel" == parentclass) || ("desktop" == parentclass) ||
				("splitpane" == parentclass) || ("dialog" == parentclass)) ?
				"component" : null;
		if ((compname != null) && ((classname == null) ||
				compname.equals(classname))) { return compname; }
		if ("tabbedpane" == parentclass) {
			if ("tab".equals(classname)) { return "tab"; }
			if ((classname == null) || "component".equals(classname)) { return "component"; }
		}
		else if ("table" == parentclass) {
			if ("column".equals(classname)) { return "column"; }
			if ((classname == null) || "row".equals(classname)) { return "row"; }
		}
		throw new IllegalArgumentException("unknown " + classname);
	}

	/**
	 * Finds the first component from the root desktop by a specified name value
	 *
	 * @param name parameter value identifies the widget
	 * @return the first suitable component, or null
	 */
	public Object find(String name) {
		return find(content, name);
	}

	/**
	 * Finds the first component from the specified component by a name
	 *
	 * @param component the widget is searched inside this component
	 * @param name parameter value identifies the widget
	 * @return the first suitable component, or null
	 */
	public Object find(Object component, String name) {
		if (name.equals(get(component, "name"))) { return component; }
		String classname = getClass(component);
		String childname = null; String childname2 = null;
		if (("panel" == classname) || ("desktop" == classname) ||
				("splitpane" == classname) || ("dialog" == classname)) { childname = "component"; }
		else if ("combobox" == classname) { childname = "choice"; }
		else if ("tabbedpane" == classname) { childname = "tab"; childname2 = "component"; }
		else if ("list" == classname) { childname = "item"; }
		else if ("table" == classname) { childname = "column"; childname2 = "row"; }
		else if ("row" == classname) { childname = "cell"; }
		else if (("tree" == classname) || ("node" == classname)) { childname = "node"; }
		else if (("menubar" == classname) || ("menu" == classname)) { childname = "menu"; }
		while (childname != null) {
			for (Object comp = get(component, childname);
					comp != null; comp = get(comp, ":next")) {
				Object found = find(comp, name);
				if (found != null) { return found; }
			}
			childname = childname2; childname2 = null;
		}
		return null;
	}
	
	/**
	 * Binds the specified key to the specified value, and stores in this component.
	 * <i>Null</i> value removes the property. Use the parameter tag in the xml
	 * resource to bind a string value, the format is: <i>parameter='key=value'</i>
	 *
	 * @param component the hashtable is binded to this component
	 * @param key the client property key
	 * @param value the new client property value
	 */
	public void putProperty(Object component, Object key, Object value) {
		Object table = get(component, ":bind");
		if (value != null) {
			if (table == null) {
				set(component, ":bind", table = new Hashtable());
			}
			((Hashtable) table).put(key, value);
		}
		else if (table != null) {
			((Hashtable) table).remove(key);
		}
	}
	
	/**
	 * Returns the value of the property with the specified key.
	 *
	 * @param component searches the hashtable of this component
	 * @param key the client property key
	 * @return the value to which the key is mapped or null if the key is not mapped to any value
	 */
	public Object getProperty(Object component, Object key) {
		Object table = get(component, ":bind");
		return (table != null) ? ((Hashtable) table).get(key) : null;
	}

	// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

	/*public String toXML(Object component) {
		StringBuffer xml = new StringBuffer();
		Object[] entry = (Object[]) component;
		String classname = (String) entry[1];
		while ((entry = (Object[]) entry[2]) != null) {
			try {
				Object[] definition = getDefinition(component, (String) entry[0], null);
				if (definition != null) {
					xml.append(" " + entry[0] + "=\"" + entry[1] + "\"");
				}
			} catch (IllegalArgumentException exc) {}
		}
		return xml.toString();
	}*/

	/**
	 * Creates a component (and its subcomponents, and properties)
	 * from the given xml resource
	 *
	 * @param path is relative to your thinlet instance or the classpath
	 * (if the path starts with an <i>/</i> character)
	 * @return the root component of the parsed resource
	 * @throws java.io.IOException
	 */
	public Object parse(String path) throws IOException {
		return parse(path, this);
	}
	
	

	/**
	 * Creates a component from the given xml resource using the
	 * specified event handler
	 *
	 * @param path is relative to your application package or the classpath
	 * @param handler bussiness methods are implemented in this object 
	 * @return the parsed components' root
	 * @throws java.io.IOException
	 */
	public Object parse(String path, Object handler) throws IOException {
		InputStream inputstream = null;
		try { //java
			//midp inputstream = getClass().getResourceAsStream(path);
			inputstream = getClass().getResourceAsStream(path); //java
			//System.out.println("> " + path + " " + inputstream);
		} catch (Throwable e) {} //java
		//if (inputstream == null) { // applet code
		//	inputstream = new URL(getCodeBase(), path).openStream();
		//}
		return parse(inputstream, handler);
	}

	/**
	 * Creates a component from the given stream
	 *
	 * @param inputstream e.g. <i>new URL("http://myserver/myservlet").openStream()</i>
	 * @return the root component of the parsed stream
	 * @throws java.io.IOException
	 */
	public Object parse(InputStream inputstream) throws IOException {
		return parse(inputstream, this);
	}

	/**
	 * Creates a component from the given stream and event handler
	 *
	 * @param inputstream read xml from this stream
	 * @param handler event handlers are implemented in this object
	 * @return the parsed components' root
	 * @throws java.io.IOException
	 */
	public Object parse(InputStream inputstream, Object handler) throws IOException {
		return parse(inputstream, true, false, handler);
	}

	/**
	 * You can use the internal xml parser as a simple SAX-like parser,
	 * during the process it calls the <i>startElement</i>, <i>characters</i>,
	 * and <i>endElement</i> methods
	 *
	 * @param inputstream e.g. <i>new URL("http://myserver/myservlet").openStream()</i>
	 * @throws java.io.IOException
	 */
	protected void parseXML(InputStream inputstream) throws IOException {
		parse(inputstream, false, false, null);
	}

	/**
	 * The SAX-like parser calls this method, you have to overwrite it
	 *
	 * @param name of the tag
	 * @param attributelist a list of attributes including keys and value pairs
	 */
	protected void startElement(String name, Hashtable attributelist) {}

	/**
	 * The SAX-like parser calls this method, you have to overwrite it
	 *
	 * @param text the content of a tag
	 */
	protected void characters(String text) {}

	/**
	 * The SAX-like parser calls this method, you have to overwrite it
	 */
	protected void endElement() {}
	
	/**
	 * You can use the internal xml parser as a simple DOM-like parser,
	 * use the <i>getDOMAttribute</i>, <i>getDOMText</i>,
	 * <i>getDOMCount</i>, <i>getDOMNode</i>, <i>getClass</i>,
	 * and <i>getParent</i> methods to analise the document
	 *
	 * @param inputstream e.g. <i>new URL("http://myserver/myservlet").openStream()</i>
	 * @return the root tag
	 * @throws java.io.IOException
	 */
	protected Object parseDOM(InputStream inputstream) throws IOException {
		return parse(inputstream, false, true, null);
	}
	
	/**
	 * Gets the attribute value by the specified key for a DOM tag
	 *
	 * @param node a specified tag
	 * @param key a string to identify the value pair
	 * @return the value, or null
	 */
	protected String getDOMAttribute(Object node, String key) {
		return (String) get(node, key.intern());
	}
	
	/**
	 * Gets the content string of a tag
	 *
	 * @param node a specified tag
	 * @return the value, or null
	 */
	protected String getDOMText(Object node) {
		return (String) get(node, ":text");
	}
	
	/**
	 * Gets the number of tags in a tag by a specified tagname 
	 *
	 * @param node a specified tag
	 * @param key the searched tagname
	 * @return the number of tags
	 */
	protected int getDOMCount(Object node, String key) {
		return getItemCountImpl(node, key.intern());
	}
	
	/**
	 * Gets the subtag of the specified tag by tagname and index 
	 *
	 * @param node a specified tag
	 * @param key the searched tagname
	 * @param index the index of the requested subtag
	 * @return the found tag, or null
	 */
	protected Object getDOMNode(Object node, String key, int index) {
		return getItemImpl(node, key.intern(), index);
	}

	/**
	 *
	 * @param inputstream
	 * @param validate parse GUI from xml if true
	 * @param dom parse an xml resoource
	 * @param handler
	 * @return
	 * @throws java.io.IOException
	 * @throws java.lang.IllegalArgumentException
	 */
	private Object parse(InputStream inputstream,
			boolean validate, boolean dom, Object handler) throws IOException {
		//UnsupportedEncodingException
		Reader reader = new BufferedReader(new InputStreamReader(inputstream)); //java
		//midp InputStreamReader reader = new InputStreamReader(inputstream);
		Object[] parentlist = null;
		Object current = null;
		Hashtable attributelist = null;
		Vector methods = (validate && !dom) ? new Vector() : null;
		StringBuffer text = new StringBuffer();
		for (int c = reader.read(); c != -1;) {
			if (c == '<') {
				if ((c = reader.read()) == '/') { //endtag
					if (text.length() > 0) {
						if (text.charAt(text.length() - 1) == ' ') {
							text.setLength(text.length() - 1);
						}
						if (!validate) {
							if (dom) {
								set(current, ":text", text.toString());
							} else {
								characters(text.toString());
							}
						}
						// else {
							//addContent(current, text.toString());
						//}
						text.setLength(0);
					}
					String tagname = (String) parentlist[2]; //getClass(current);
					for (int i = 0; i < tagname.length(); i++) { // current-tag
						if ((c = reader.read()) != tagname.charAt(i)) {
							throw new IllegalArgumentException(tagname);
						}
					}
					while (" \t\n\r".indexOf(c = reader.read()) != -1); // whitespace
					if (c != '>') throw new IllegalArgumentException(); // '>'
					c = reader.read();
					if (!validate && !dom) { endElement(); }
					if (parentlist[0] == null) {
						reader.close();
						finishParse(methods, current, handler);
						return current;
					}
					current = parentlist[0];
					parentlist = (Object[]) parentlist[1];
				}
				else { //start or standalone tag
					boolean instruction = (c == '?'); // Processing Instructions
					if (c == '!') { while ((c = reader.read()) != '>'); continue; } // DOCTYPE
					if (instruction) { c = reader.read(); }
					text.setLength(0);
					boolean iscomment = false;
					while (">/ \t\n\r".indexOf(c) == -1) {
						text.append((char) c);
						if ((text.length() == 3) && (text.charAt(0) == '!') &&
								(text.charAt(1) == '-') && (text.charAt(2) == '-')) {
							int m = 0;
							while (true) {
								c = reader.read();
								if (c == '-') { m++; }
								else if ((c == '>') && (m >= 2)) { break; }
								else { m = 0; }
							}
							iscomment = true;
						}
						c = reader.read();
					}
					if (iscomment) { continue; }
					if (!instruction) {
						String tagname = text.toString();
						parentlist = new Object[] { current, parentlist, tagname };
						if (validate) {
							current = (current != null) ?
								addElement(current, tagname) : create(tagname);
						} else {
							if (dom) {
								Object parent = current;
								current = createImpl(tagname = tagname.intern());
								if (parent != null) {
									insertItem(parent, tagname, current, -1);
									set(current, ":parent", parent);
								}
							} else {
								current = tagname;
							}
						}
					}
					text.setLength(0);
					while (true) {
						boolean whitespace = false;
						while (" \t\n\r".indexOf(c) != -1) {
							c = reader.read();
							whitespace = true;
						}
						if (c == '>') {
							if (instruction) throw new IllegalArgumentException(); // '?>'
							if (!validate && !dom) {
								startElement((String) current, attributelist); attributelist = null;
							}
							c = reader.read();
							break;
						}
						else if (c == '/') {
							if (instruction) throw new IllegalArgumentException(); // '?>'
							if ((c = reader.read()) != '>') {
								throw new IllegalArgumentException(); // '>'
							}
							if (!validate && !dom) {
								startElement((String) current, attributelist); attributelist = null;
								endElement();
							}
							if (parentlist[0] == null) {
								reader.close();
								finishParse(methods, current, handler);
								return current;
							}
							current = parentlist[0];
							parentlist = (Object[]) parentlist[1];
							c = reader.read();
							break;
						}
						else if (instruction && (c == '?')) {
							if ((c = reader.read()) != '>') {
								throw new IllegalArgumentException(); // '>'
							}
							c = reader.read();
							break;
						}
						else if (whitespace) {
							while ("= \t\n\r".indexOf(c) == -1) {
								text.append((char) c);
								c = reader.read();
							}
							String key = text.toString();
							text.setLength(0);
							while (" \t\n\r".indexOf(c) != -1) c = reader.read();
							if (c != '=') throw new IllegalArgumentException();
							while (" \t\n\r".indexOf(c = reader.read()) != -1);
							char quote = (char) c;
							if ((c != '\"') && (c != '\'')) throw new IllegalArgumentException();
							while (quote != (c = reader.read())) {
								if (c == '&') {
									StringBuffer eb = new StringBuffer();
									while (';' != (c = reader.read())) { eb.append((char) c); }
									String entity = eb.toString();
									if ("lt".equals(entity)) { text.append('<'); }
									else if ("gt".equals(entity)) { text.append('>'); }
									else if ("amp".equals(entity)) { text.append('&'); }
									else if ("quot".equals(entity)) { text.append('"'); }
									else if ("apos".equals(entity)) { text.append('\''); }
									else if (entity.startsWith("#")) {
										text.append((char) Integer.parseInt(entity.substring(1)));
									}
									else throw new IllegalArgumentException("unknown " + "entity " + entity);
								}
								else text.append((char) c);
							}
							if (!instruction) {
								if (validate) {
									addAttribute(current, key, text.toString(), methods);
								} else {
									if (dom) {
										set(current, key.intern(), text.toString());
									} else {
										if (attributelist == null) { attributelist = new Hashtable(); }
										attributelist.put(key, text.toString());
									}
								}
							}
							//else if ("encoding".equals(key)) {
								//reader = new InputStreamReader(inputstream, text.toString());
							//}
							text.setLength(0);
							c = reader.read();
						}
						else throw new IllegalArgumentException();
					}
				}
			}
			else {
				if (" \t\n\r".indexOf(c) != -1) {
					if ((text.length() > 0) && (text.charAt(text.length() - 1) != ' ')) {
						text.append(' ');
					}
				}
				else {
					text.append((char) c);
				}
				c = reader.read();
			} 
		}
		throw new IllegalArgumentException();
	}
	
	/**
	 *
	 */
	private void finishParse(Vector methods, Object root, Object handler) {
		if (methods != null) {
			for (int i = 0; i < methods.size(); i++) {
				Object[] args = (Object[]) methods.elementAt(i);
				setMethodImpl(args[0], (String) args[1], (String) args[2], root, handler); //java
			}
		}
	}
	
	/**
	 * Convert entities.
	 */
	private static String convert(StringBuffer text) {
		return null;
	}

	/*private InputStream inputreader;
	private byte[] data;
	private int inputfrom = 0, inputto = 0;
	private int read() throws Exception {
		if (data == null) { data = new byte[1024]; }
		if (inputfrom >= inputto) {
			inputfrom = 0;
			inputto = inputreader.read(data);
		}
		inputfrom++;
		return data[inputfrom - 1];
	}*/

	/**
	 *
	 * @throws java.lang.IllegalArgumentException
	 */
	private void addImpl(Object parent, Object component, int index) {
		String parentclass = getClass(parent);
		String classname = getClass(component);
		//System.out.println("add " + classname + " -> " + parentclass);
		if ((("combobox" == parentclass) && ("choice" == classname)) ||
					(("tabbedpane" == parentclass) && ("tab" == classname)) ||
					(("list" == parentclass) && ("item" == classname)) ||
					(("table" == parentclass) && (("column" == classname) || ("row" == classname))) ||
					(("row" == parentclass) && ("cell" == classname)) ||
					((("tree" == parentclass) || ("node" == parentclass)) && ("node" == classname)) ||
					(("menubar" == parentclass) && ("menu" == classname))) {
				//classname = classname; // compiler bug
		}
		else if (("menu" == parentclass) && (("menu" == classname) || ("menuitem" == classname) ||
				("checkboxmenuitem" == classname) || ("separator" == classname))) {
			classname = "menu";
		}
		else if (("panel" == parentclass) || ("desktop" == parentclass) ||
				("splitpane" == parentclass) || ("dialog" == parentclass) ||
				("tabbedpane" == parentclass)) {
			while ("component" != classname) {
				String extendclass = null;
				for (int i = 0; i < dtd.length; i += 3) {
					if (classname == dtd[i]) {
						extendclass = (String) dtd[i + 1]; break;
					}
				}
				if (extendclass == null) throw new IllegalArgumentException(classname + " not component");
				classname = extendclass;
			}
		}
		else throw new IllegalArgumentException(classname + " add " + parentclass);
		insertItem(parent, (String) classname, component, index);
		set(component, ":parent", parent);
		//if (parent == content) System.out.println(getClass(parent) + ".add(" + getClass(component) + ") : " + classname);
	}

	/**
	 *
	 */
	private Object addElement(Object parent, String name) {
		//System.out.println("create '" + name + "'");
		Object component = create(name);
		addImpl(parent, component, -1);
		return component;
	}

	/**
	 *
	 * @throws java.lang.IllegalArgumentException
	 */
	private void addAttribute(Object component, String key, String value, Vector methods) {
		//System.out.println("attribute '" + key + "'='" + value + "'");
		Object[] definition = getDefinition(component, key, null);
		key = (String) definition[1];
		if ("string" == definition[0]) {
			setString(component, key, value, (String) definition[3]);
		}
		else if ("choice" == definition[0]) {
			String[] values = (String[]) definition[3];
			setChoice(component, key, value, values, values[0]);
		}
		else if ("boolean" == definition[0]) {
			if ("true".equals(value)) {
				if (definition[3] == Boolean.FALSE) {
					set(component, key, Boolean.TRUE);
			 	}
			}
			else if ("false".equals(value)) {
				if (definition[3] == Boolean.TRUE) {
					set(component, key, Boolean.FALSE);
			 	}
			}
			else throw new IllegalArgumentException(value);
		}
		else if ("integer" == definition[0]) {
			set(component, key, Integer.valueOf(value));
		}
		else if ("icon" == definition[0]) {
			set(component, key, getIcon(value));
		}
		else if ("method" == definition[0]) {
			methods.addElement(new Object[] { component, key, value }); //java
			//midp set(component, key, value);
		}
		else if ("property" == definition[0]) {
			int equals = value.indexOf('=');
			if (equals == -1) { throw new IllegalArgumentException(value); }
			putProperty(component, value.substring(0, equals), value.substring(equals + 1));
		}
		//java>
		else if ("bean" == definition[0]) {
			try {
				set(component, key, (Component) Class.forName(value).newInstance());
			} catch (Exception exc) { throw new IllegalArgumentException(value); }
		}
		//<java
		else throw new IllegalArgumentException((String) definition[0]);
	}

	/**
	 *
	 * @throws java.lang.IllegalArgumentException
	 */
	private Object[] getDefinition(Object component, String key, String type) {
		Object classname = getClass(component);
		Object currentname = classname;
		while (classname != null) {
			for (int i = 0; i < dtd.length; i += 3) {
				if (dtd[i] == classname) {
					Object[][] attributes = (Object[][]) dtd[i + 2];
					for (int j = 0; j < attributes.length; j++) {
						if (attributes[j][1].equals(key)) {
							if ((type != null) && (type != attributes[j][0])) {
								throw new IllegalArgumentException(attributes[j][0].toString());
							}
							return attributes[j];
						}
					}
					classname = dtd[i + 1];
					break;
				}
			}
		}
		throw new IllegalArgumentException("unknown " + key + " " + type +
			" for " + currentname);
	}

	/**
	 *
	 */
	//private void addContent(Object component, String content) {
		//set(component, "content", content);
	//}

	// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

	/**
	 * Sets the given property pair (key and value) for the component
	 */
	public void setString(Object component, String key, String value) {
		Object[] definition = getDefinition(component, key, "string");
		if (setString(component, (String) definition[1],
				value, (String) definition[3])) {
			update(component, definition[2]);
		}
	}

	/**
	 * Gets the property value of the given component by the property key
	 */
	public String getString(Object component, String key) {
		Object[] definition = getDefinition(component, key, "string");
		return getString(component, (String) definition[1],
			(String) definition[3]);
	}

	/**
	 * Sets the given property pair (key and value) for the component
	 */
	public void setChoice(Object component, String key, String value) {
		Object[] definition = getDefinition(component, key, "choice");
		String[] values = (String[]) definition[3];
		if (setChoice(component, (String) definition[1],
				value, values, values[0])) {
			update(component, definition[2]);
		}
	}

	/**
	 * Gets the property value of the given component by the property key
	 */
	public String getChoice(Object component, String key) {
		Object[] definition = getDefinition(component, key, "choice");
		return getString(component, (String) definition[1],
			((String[]) definition[3])[0]);
	}

	/**
	 * Sets the given property pair (key and value) for the component
	 */
	public void setBoolean(Object component, String key, boolean value) {
		Object[] definition = getDefinition(component, key, "boolean");
		if (setBoolean(component, (String) definition[1],
				value, (definition[3] == Boolean.TRUE))) {
			update(component, definition[2]);
		}
	}

	/**
	 * Gets the property value of the given component by the property key
	 */
	public boolean getBoolean(Object component, String key) {
		Object[] definition = getDefinition(component, key, "boolean");
		return getBoolean(component, (String) definition[1],
			(definition[3] == Boolean.TRUE));
	}

	/**
	 * Sets the given property pair (key and value) for the component
	 */
	public void setInteger(Object component, String key, int value) {
		Object[] definition = getDefinition(component, key, "integer");
		if (setInteger(component, (String) definition[1],
				value, ((Integer) definition[3]).intValue())) {
			update(component, definition[2]);
		}
	}

	/**
	 * Gets the property value of the given component by the property key
	 */
	public int getInteger(Object component, String key) {
		Object[] definition = getDefinition(component, key, "integer");
		return getInteger(component, (String) definition[1],
			((Integer) definition[3]).intValue());
	}

	/**
	 * Sets the given property pair (key and value) for the component
	 */
	public void setIcon(Object component, String key, Image icon) {
		Object[] definition = getDefinition(component, key, "icon");
		if (set(component, (String) definition[1], icon)) {
			update(component, definition[2]);
		}
	}

	/**
	 * Gets the property value of the given component by the property key
	 */
	public Image getIcon(Object component, String key) {
		Object[] definition = getDefinition(component, key, "icon");
		return getIcon(component, (String) definition[1], (Image) definition[3]);
	}
	
	//java>
	/**
	 * Sets a new event handler method for a component
	 *
	 * @param component the target component
	 * @param key the key name of the parameter (e.g. <i>action</i>)
	 * @param value the method name and parameters
	 * (e.g. <i>foo(this, this.text, mybutton, mybutton.enabled)</i>
	 * for <i>public void foo(Object component, String text, Object mybutton, boolean enabled)</i>)
	 * @param root the search starting component for name components in the arguments
	 * @param handler the target event handler object including the method
	 * @throws java.lang.IllegalArgumentException
	 */
	public void setMethod(Object component, String key, String value, Object root, Object handler) {
		key = (String) getDefinition(component, key, "method")[1];
		setMethodImpl(component, key, value, root, handler);
	}
	
	/**
	 *
	 */
	private void setMethodImpl(Object component, String key, String value, Object root, Object handler) {
		//System.out.println(key + "=" + value);
		StringTokenizer st  = new StringTokenizer(value, "(, )");
		String methodname = st.nextToken();
		int n = st.countTokens();
		Object[] data = new Object[2 + 3 * n];
		Class[] parametertypes = (n > 0) ? new Class[n] : null;
		for (int i = 0; i < n; i++) {
			String arg = st.nextToken();
			int dot = arg.indexOf('.');
			String compname = (dot == -1) ? arg : arg.substring(0, dot);
			Object comp = null;
			if ("this".equals(compname)) {
				comp = component;
			}
			//else if ("item".equals(compname)) {
			//}
			else {
				comp = find(root, compname);
			}
			data[2 + 3 * i] = comp; // the target component
			if (dot == -1) {
				parametertypes[i] = Object.class;
			}
			else {
				Object[] definition = getDefinition(comp, arg.substring(dot + 1), null);
				data[2 + 3 * i + 1] = definition[1]; // parameter name, e.g. enabled
				data[2 + 3 * i + 2] = definition[3]; // default value, e.g. Boolean.TRUE
				Object fieldclass = definition[0];
				if ((fieldclass == "string") || (fieldclass == "choice")) {
					parametertypes[i] = String.class;
				}
				else if (fieldclass == "boolean") {
					parametertypes[i] = Boolean.TYPE;
				}
				else if (fieldclass == "integer") {
					parametertypes[i] = Integer.TYPE;
				}
				else if (fieldclass == "icon") {
					parametertypes[i] = Image.class;
				}
				else throw new IllegalArgumentException((String) fieldclass);
			}
		}
		data[0] = handler;
		try {
			data[1] = handler.getClass().getMethod(methodname, parametertypes);
			set(component, key, data);
		} catch (Exception exc) { throw new IllegalArgumentException(exc.getMessage()); }
	}
	//<java

	/**
	 *
	 */
	private void update(Object component, Object mode) {
		if ("parent" == mode) {
			component = getParent(component);
			mode = "validate";
		}
		boolean firstpaint = true;
		int x = 0; int y = 0; int width = 0; int height = 0;
		while (component != null) {
			if (!getBoolean(component, "visible", true)) { break; }
			if ("paint" == mode) {//|| (firstpaint && (component == content))
				Rectangle bounds = getRectangle(component, "bounds");
				if (bounds == null) { return; }
				if (firstpaint) {
					x = bounds.x; y = bounds.y;
					width = Math.abs(bounds.width); height = bounds.height;
					firstpaint = false;
				} else {
					x += bounds.x; y += bounds.y;
				}
				if (component == content) {
					repaint(x, y, width, height);
				}
			}
			Object parent = getParent(component);
			String classname = getClass(parent);
			if ("combobox" == classname) {
				parent = get(parent, ":combolist");
			}
			else if ("menu" == classname) {
				parent = get(parent, ":popup");
			}
			else if (("paint" == mode) && ("tabbedpane" == classname)) {
				if (getItemImpl(parent, "component",
						getInteger(parent, "selected", 0)) != component) { break; }
			}
			if (("layout" == mode) || (("validate" == mode) &&
					(("list" == classname) || ("table" == classname) ||
					("tree" == classname) || ("dialog" == classname) || (parent == content)))) {
				Rectangle bounds = getRectangle(parent, "bounds");
				if (bounds == null) { return; }
				bounds.width = -1 * Math.abs(bounds.width);
				mode = "paint";
			}
			component = parent;
		}
	}

	// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

	/**
	 *
	 */
	private boolean setString(Object component,
			String key, String value, String defaultvalue) {
		/*boolean changed = set(component, key, value);
		if (changed && ("name" == key)) {
			//for (Class cls = getClass(); cls != null; cls = cls.getSuperclass()) {
			Field[] fields = getClass().getDeclaredFields();
			for (int i = 0; i < fields.length; i++) {
				if ((fields[i].getType() == Object.class) &&
						fields[i].getName().equals(value)) {
					try {
						fields[i].set(this, component);
						return true;
					} catch (IllegalAccessException iae) {}
				}
			}
			//}
			try {
				getClass().getField(value).set(this, component);
			} catch (Exception exc) {}
		}
		return changed;*/
		return set(component, key, value);
	}

	/**
	 *
	 */
	private String getString(Object component,
			String key, String defaultvalue) {
		Object value = get(component, key);
		return (value == null) ? defaultvalue : (String) value;
	}

	/**
	 *
	 * @throws java.lang.IllegalArgumentException
	 */
	private boolean setChoice(Object component,
			String key, String value, String[] values, String defaultvalue) {
		if (value == null) {
			return set(component, key, defaultvalue);
		}
		for (int i = 0; i < values.length; i++) {
			if (value.equals(values[i])) {
				return set(component, key, values[i]);
			}
		}
		throw new IllegalArgumentException("unknown " + value + " for " + key);
	}

	/**
	 *
	 */
	private boolean setIcon(Object component,
			String key, String path, Image defaultvalue) {
		return set(component, key, (path != null) ? getIcon(path) : defaultvalue);
	}

	/**
	 *
	 */
	private Image getIcon(Object component, String key, Image defaultvalue) {
		Object value = get(component, key);
		return (value == null) ? defaultvalue : (Image) value;
	}

	/**
	 *
	 */
	private boolean setBoolean(Object component,
			String key, boolean value, boolean defaultvalue) {
		return set(component, key, (value == defaultvalue) ? null :
			(value ? Boolean.TRUE : Boolean.FALSE));
	}

	/**
	 *
	 */
	private boolean getBoolean(Object component, 
			String key, boolean defaultvalue) {
		Object value = get(component, key);
		return (value == null) ? defaultvalue : ((Boolean) value).booleanValue();
	}

	/**
	 *
	 */
	private boolean setInteger(Object component,
			String key, int value, int defaultvalue) {
		return set(component, key, (value == defaultvalue) ? null : new Integer(value));
	}

	/**
	 *
	 */
	private int getInteger(Object component, String key, int defaultvalue) {
		Object value = get(component, key);
		return (value == null) ? defaultvalue : ((Integer) value).intValue();
	}

	/**
	 *
	 */
	private void setRectangle(Object component,
			String key, int x, int y, int width, int height) {
		Rectangle rectangle = getRectangle(component, key);
		if (rectangle != null) {
			rectangle.x = x; rectangle.y = y;
			rectangle.width = width; rectangle.height = height;
		}
		else {
			set(component, key, new Rectangle(x, y, width, height)); //java
			//midp set(component, key, new int[] { width, height, x, y });
		}
	}

	/**
	 *
	 */
	private Rectangle getRectangle(Object component, String key) {
		return (Rectangle) get(component, key); //java
		//midp return (int[]) get(component, key);
	}

	// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

	/**
	 * Creates an image, and loads it immediately by default
	 *
	 * @param path is relative to your thinlet instance or the classpath
	 * (if the path starts with <i>'/'</i> character)
	 * @return the loaded image or null
	 */
	public Image getIcon(String path) {
		return getIcon(path, true);
	}

	/**
	 * Creates an image from the specified resource.
	 * To speed up loading the same images use a cache (a simple hashtable).
	 * And flush the resources being used by an image when you won't use it henceforward
	 *
	 * @param path is relative to your thinlet instance or the classpath
	 * @param preload waits for the whole image if true, starts loading
	 * (and repaints, and updates the layout) only when required (painted, or size requested) if false
	 * @return the loaded image or null
	 */
	public Image getIcon(String path, boolean preload) {
		if ((path == null) || (path.length() == 0)) {
			return null;
		}
		Image image = null; //(Image) imagepool.get(path);
		//midp try {
		//midp 	image = Image.createImage(path);
		//midp } catch (IOException ioe) {}
		//java>
		try {
			URL url = getClass().getResource(path); //ClassLoader.getSystemResource(path)
			if (url != null) { // contributed by Stefan Matthias Aust
				image = Toolkit.getDefaultToolkit().getImage(url);
			}
		} catch (Throwable e) {}
		if (image == null) {
			try {
				InputStream is = getClass().getResourceAsStream(path);
				//InputStream is = ClassLoader.getSystemResourceAsStream(path);
				if (is != null) {
					byte[] data = new byte[is.available()];
					is.read(data, 0, data.length);
					image = getToolkit().createImage(data);
					is.close();
				}
			} catch (Throwable e) {}
		}
		//if (image == null) { // applet code
		//	try {
		//		image = getImage(getCodeBase(), path);
		//	} catch (Throwable e) {}
		//}
		if (preload && (image != null)) {
			MediaTracker mediatracker = new MediaTracker(this);
			mediatracker.addImage(image, 1);
			try {
				mediatracker.waitForID(1, 50);
			} catch (InterruptedException ie) { }
			//imagepool.put(path, image);
		} 
		//<java
		return image;
	}

	/**
	 * This method is called by the FrameLauncher if the window was closing,
	 * or AppletLauncher's destroy method. Overwrite it to e.g. save the application changes.
	 *
	 * @return true to exit, and false to keep the frame and continue the application
	 */
	public boolean destroy() {
		return true;
	}

	private static Object[] dtd;
	static {
		Integer integer_1 = new Integer(-1);
		Integer integer0 = new Integer(0);
		Integer integer1 = new Integer(1);
		String[] orientation = { "horizontal", "vertical" };
		String[] leftcenterright = { "left", "center", "right" };
		String[] selections = { "single", "interval", "multiple" };
		dtd = new Object[] {
			"component", null, new Object[][] {
				{ "string", "name", "", null },
				{ "boolean", "enabled", "paint", Boolean.TRUE },
				{ "boolean", "visible", "parent", Boolean.TRUE },
				{ "string", "tooltip", "", null },
				{ "integer", "width", "validate", integer0 },
				{ "integer", "height", "validate", integer0 },
				{ "integer", "colspan", "validate", integer1 },
				{ "integer", "rowspan", "validate", integer1 },
				{ "integer", "weightx", "validate", integer0 },
				{ "integer", "weighty", "validate", integer0 },
				{ "choice", "halign", "validate",
					new String[] { "fill", "center", "left", "right" } },
				{ "choice", "valign", "validate",
				new String[] { "fill", "center", "top", "bottom" } },
				// component class String null*
				// parent Object null
				// (bounds) Rectangle 0 0 0 0
				{ "property", "property", "", null }
			},
			"label", "component", new Object[][] {
				{ "string", "text", "validate", null },
				{ "icon", "icon", "validate", null },
			 	{ "choice", "alignment", "validate", leftcenterright },
			 	{ "integer", "mnemonic", "paint", integer_1 } },
			"button", "label", new Object[][] {
			 	{ "choice", "alignment", "validate", new String[] { "center", "left", "right" } },
				{ "method", "action", "", null },
				//{ "choice", "type", "", new String[] { "normal", "default", "cancel" } }
				},//...
			"checkbox", "label", new Object[][] {
				{ "boolean", "selected", "paint", Boolean.FALSE }, //...group
				{ "string", "group", "paint", null }, //...group
				{ "method", "action", "", null } },
			"togglebutton", "checkbox", new Object[][] {},
			"combobox", "textfield", new Object[][] {
				{ "icon", "icon", "validate", null },
				{ "integer", "selected", "layout", integer_1 } },
			"choice", null, new Object[][] {
				{ "string", "name", "", null },
				{ "boolean", "enabled", "paint", Boolean.TRUE },
				{ "string", "text", "", null },
				{ "icon", "icon", "", null },
				{ "choice", "alignment", "", leftcenterright },
				{ "string", "tooltip", "", null },
				{ "property", "property", "", null } },
			"textfield", "component", new Object[][] {
				{ "string", "text", "layout", "" },
				{ "integer", "columns", "validate", integer0 },
				{ "boolean", "editable", "paint", Boolean.TRUE },
				{ "integer", "start", "layout", integer0 },
				{ "integer", "end", "layout", integer0 },
				{ "method", "action", "", null } },
			"passwordfield", "textfield", new Object[][] {},
			"textarea", "textfield", new Object[][] {
				{ "integer", "rows", "validate", integer0 },
				{ "boolean", "wrap", "layout", Boolean.FALSE } },
			"tabbedpane", "component", new Object[][] {
				{ "choice", "placement", "validate",
					new String[] { "top", "left", "bottom", "right" } },
				{ "integer", "selected", "paint", integer0 },
				{ "method", "action", "", null } }, //...focus
			"tab", "choice", new Object[][] {
				{ "integer", "mnemonic", "paint", integer_1 } },
			"panel", "component", new Object[][] {
				{ "integer", "columns", "validate", integer0 },
				{ "integer", "top", "validate", integer0 },
				{ "integer", "left", "validate", integer0 },
				{ "integer", "bottom", "validate", integer0 },
				{ "integer", "right", "validate", integer0 },
				{ "integer", "gap", "validate", integer0 },
				{ "string", "text", "validate", null },
				{ "icon", "icon", "validate", null },
				{ "boolean", "border", "validate", Boolean.FALSE } },
			"desktop", "component", new Object[][] {},
			"dialog", "panel", new Object[][] {
				{ "string", "text", "", null },
				{ "icon", "icon", "", null },
				{ "boolean", "modal", "", Boolean.FALSE } },
			"spinbox", "textfield", new Object[][] {}, //...
			"progressbar", "component", new Object[][] {
				{ "choice", "orientation", "validate", orientation },
				{ "integer", "minimum", "paint", integer0 }, //...checkvalue
				{ "integer", "maximum", "paint", new Integer(100) },
				{ "integer", "value", "paint", integer0 } },
				// change stringpainted
			"slider", "progressbar", new Object[][] {
				{ "integer", "unit", "", new Integer(5) },
				{ "integer", "block", "", new Integer(25) },
				{ "method", "action", "", null } },
				// minor/majortickspacing
				// inverted
				// labelincrement labelstart
			"splitpane", "component", new Object[][] {
				{ "choice", "orientation", "validate", orientation },
				{ "integer", "divider", "layout", integer_1 } },
			"list", "component", new Object[][] {
				{ "choice", "selection", "paint", selections },
				{ "method", "action", "", null } }, //...?
			"item", "choice", new Object[][] {
				{ "boolean", "selected", "", Boolean.FALSE } },
			"table", "component", new Object[][] {
				{ "choice", "selection", "paint", selections },
				{ "method", "action", "", null }
				/*{ "choice", "selection",
					new String[] { "singlerow", "rowinterval", "multiplerow",
						"cell", "cellinterval",
						"singlecolumn", "columninterval", "multiplecolumn" } }*/ },
			"column", "choice", new Object[][] {
				{ "integer", "width", "", new Integer(80) }},
			"row", null, new Object[][] {
				{ "boolean", "selected", "", Boolean.FALSE } },
			"cell", "choice", new Object[][] {},
			"tree", "component", new Object[][] {
				{ "choice", "selection", "paint", selections },
				{ "method", "action", "", null },
				{ "method", "expand", "", null },
				{ "method", "collapse", "", null } },
			"node", "choice", new Object[][] {
				{ "boolean", "selected", "", Boolean.FALSE },
				{ "boolean", "expanded", "", Boolean.TRUE } },
			"separator", "component", new Object[][] {},
			"menubar", "component", new Object[][] {},
			"menu", "choice", new Object[][] {
				{ "integer", "mnemonic", "paint", integer_1 } },
			"menuitem", "choice", new Object[][] {
				{ "string", "accelerator", "", null },
				{ "method", "action", "", null },
				{ "integer", "mnemonic", "paint", integer_1 }
				//... KeyStroke=keyCode+modifiers(SHIFT CTRL META ALT_MASK)
			},
			"checkboxmenuitem", "menuitem", new Object[][] {
				{ "boolean", "selected", "paint", Boolean.FALSE }, //...group
				{ "string", "group", "paint", null } }, //...group
			"bean", "component", new Object[][] {
				{ "bean", "bean", "", null }
			}
		};
	}	
}
