/*
    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 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.*;

/**
 *
 */
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);

  //midp 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 gradient; //java

  {
    setFont(new Font("SansSerif", Font.PLAIN, 12)); //java
    //midp setFont(Font.getDefaultFont());
    setColors(0xe6e6e6, 0x000000, 0xffffff,
              0x909090, 0xb0b0b0, 0xededed, 0xb9b9b9, 0x89899a, 0xc5c5dd); // f99237 eac16a // e68b2c ffc73c
  }

  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;

  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 */ }
  }

  {
    if (MOUSE_WHEEL != 0) { // disable global focus-manager for this component in 1.4
      try {
        getClass().getMethod("setFocusTraversalKeysEnabled", new Class[]{Boolean.TYPE}).
          invoke(this, new Object[]{Boolean.FALSE});
      }
      catch (Exception exc) { /* never */ }
    }
    enableEvents(AWTEvent.COMPONENT_EVENT_MASK |
                 AWTEvent.FOCUS_EVENT_MASK | AWTEvent.KEY_EVENT_MASK |
                 AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | WHEEL_MASK);
  }
  //<java

  /**
   *
   */
  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;
    //java>
    int[] pix = new int[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 r = r1 - (r1 - r2) * i / block;
      int g = g1 - (g1 - g2) * i / block;
      int b = b1 - (b1 - b2) * i / block;
      int color = (255 << 24) | (r << 16) | (g << 8) | b;
      for (int j = 0; j < block; j++) {
        pix[i * block + j] = color;
        //pix[j * block + i] = color;
      }
    }
    gradient = createImage(new MemoryImageSource(block, block, pix, 0, block));
    //<java
  }

  /**
   *
   */
  public void setFont(Font font) {
    block = getFontMetrics(font).getHeight(); //java
    super.setFont(font); //java
    //midp block = font.getHeight();
    //midp this.font = font;
  }

  /**
   *
   */
  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);
      }
      int caretx = 0;
      int carety = 0;
      FontMetrics fm = getFontMetrics(getFont()); //java
      int width = 0, height = 0;
      for (int i = 0, j = 0; j != -1; i = j + 1) {
        j = text.indexOf('\n', i);
        if (i != j) { // && i != text.length()
          String line = (j != -1) ? text.substring(i, j) : text.substring(i); //java
          width = Math.max(width, fm.stringWidth(line)); //java
          //midp width = font.substringWidth(text, i, ((j != -1) ? j : text.length()) - i);
        }
        if ((end >= i) && ((j == -1) || (end <= j))) {
          caretx = fm.stringWidth(text.substring(i, end)); //java
          //midp caretx = font.substringWidth(text, i, end - i);
          carety = height;
        }
        height += fm.getHeight();
      }
      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 == "popupmenu")) {
          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) || ("popupmenu" == classname)) {
      boolean combo = ("combolist" == classname);
      int pw = 0;
      int ph = 0;
      int pxy = combo ? 0 : 1;
      for (Object item = get(get(component, combo ? "combobox" : "menu"),
                             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) {
        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 Object popup(Object component, Object classname) {
    Object popup = null;
    int px = 0;
    int py = 0;
    if (("menubar" == classname) || ("popupmenu" == classname)) {
      Object popupmenu = get(component, "popupmenu");
      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, "popupmenu", null);
        if (mouseinside == popupmenu) {
          findComponent(content, mousex, mousey);
          handleMouseEvent(mousex, mousex, 1, false, false, false, //java
                           MouseEvent.MOUSE_ENTERED, mouseinside, insidepart); //java
        }
        popup(popupmenu, "popupmenu");
      }
      if ((selected == null) || (getClass(selected) != "menu")) {
        return null;
      }
      popup = createImpl("popupmenu");
      set(popup, "menu", selected);
      set(component, "popupmenu", 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, "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 == "popupmenu")) {
      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(getFont());
      int width = fm.stringWidth(text) + 4;
      int height = fm.getAscent() + fm.getDescent() + 4;
      if (tooltipowner == null) {
        tooltipowner = mouseinside;
      }
      setRectangle(tooltipowner, "tooltipbounds", mousex + 10, mousey + 10, width, height);
      repaint(mousex + 10, mousey + 10, 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(getFont());
    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;
    }
  }

  /**
   *
   */
  public Dimension getPreferredSize() {
    return getPreferredSize(content);
  }

  /**
   *
   */
  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) {
      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(getFont()); //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 != "popupmenu") &&
            (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);
    }
