TextArea.java
上传用户:haobig99
上传日期:2022-06-15
资源大小:369k
文件大小:37k
源码类别:

J2ME

开发平台:

Java

  1. /*
  2.  * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
  3.  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4.  *
  5.  * This code is free software; you can redistribute it and/or modify it
  6.  * under the terms of the GNU General Public License version 2 only, as
  7.  * published by the Free Software Foundation.  Sun designates this
  8.  * particular file as subject to the "Classpath" exception as provided
  9.  * by Sun in the LICENSE file that accompanied this code.
  10.  *
  11.  * This code is distributed in the hope that it will be useful, but WITHOUT
  12.  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13.  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14.  * version 2 for more details (a copy is included in the LICENSE file that
  15.  * accompanied this code).
  16.  *
  17.  * You should have received a coy of the GNU General Public License version
  18.  * 2 along with this work; if not, write to the Free Software Foundation,
  19.  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20.  *
  21.  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22.  * CA 95054 USA or visit www.sun.com if you need additional information or
  23.  * have any questions.
  24.  */
  25. package com.sun.lwuit;
  26. import com.sun.lwuit.events.ActionEvent;
  27. import com.sun.lwuit.events.ActionListener;
  28. import com.sun.lwuit.geom.Dimension;
  29. import com.sun.lwuit.geom.Rectangle;
  30. import com.sun.lwuit.plaf.LookAndFeel;
  31. import com.sun.lwuit.plaf.Style;
  32. import com.sun.lwuit.plaf.UIManager;
  33. import java.util.Vector;
  34. /**
  35.  * An optionally multi-line editable region that can display text and allow a user to edit it.
  36.  * Depending on the platform editing might occur in a new screen. Notice that when creating
  37.  * a text area with one row it will act as a text field and never grow beyond that, however 
  38.  * when assigning a greater number of rows the text area becomes multi-line with a minimum
  39.  * number of visible rows, the text area will grow based on its content.
  40.  *
  41.  * @author Chen Fishbein
  42.  */
  43. public class TextArea extends Component {
  44.     private static int defaultMaxSize = 124;
  45.     private static boolean autoDegradeMaxSize = false;
  46.     private static boolean hadSuccessfulEdit = false;
  47.     private int linesToScroll = 1;
  48.     /**
  49.      * Indicates the enter key to be used for editing the text area and by the
  50.      * text field
  51.      */
  52.     private static final char ENTER_KEY = 'n';
  53.     /**
  54.      * Unsupported characters is a string that contains characters that cause issues 
  55.      * when rendering on some problematic fonts. The rendering engine can thus remove them
  56.      * when drawing.
  57.      */
  58.     private String unsupportedChars = "tr";
  59.     
  60.     /**
  61.      * Allows any type of input into a text field, if a constraint is not supported
  62.      * by an underlying implementation this will be the default.
  63.      */
  64.     public static final int ANY = 0;
  65.     /**
  66.      * The user is allowed to enter an e-mail address.
  67.      */
  68.     public static final int EMAILADDR = 1;
  69.     /**
  70.      * The user is allowed to enter only an integer value.
  71.      */
  72.     public static final int NUMERIC = 2;
  73.     /**
  74.      * The user is allowed to enter a phone number.
  75.      */
  76.     public static final int PHONENUMBER = 3;
  77.     /**
  78.      * The user is allowed to enter a URL.
  79.      */
  80.     public static final int URL = 4;
  81.     /**
  82.      * The user is allowed to enter numeric values with optional decimal 
  83.      * fractions, for example "-123", "0.123", or ".5".
  84.      */
  85.     public static final int DECIMAL = 5;
  86.     
  87.     /**
  88.      * Indicates that the text entered is confidential data that should be 
  89.      * obscured whenever possible.
  90.      */
  91.     public static final int PASSWORD = 0x10000;
  92.     /**
  93.      *  Indicates that editing is currently disallowed.
  94.      */
  95.     public static final int UNEDITABLE = 0x20000;
  96.     /**
  97.      * Indicates that the text entered is sensitive data that the 
  98.      * implementation must never store into a dictionary or table for use 
  99.      * in predictive, auto-completing, or other accelerated input schemes.
  100.      */
  101.     public static final int SENSITIVE = 0x40000;
  102.     /**
  103.      * Indicates that the text entered does not consist of words that are 
  104.      * likely to be found in dictionaries typically used by predictive input 
  105.      * schemes.
  106.      */
  107.     public static final int NON_PREDICTIVE= 0x80000;
  108.     /**
  109.      * This flag is a hint to the implementation that during text editing, 
  110.      * the initial letter of each word should be capitalized.
  111.      */
  112.     public static final int INITIAL_CAPS_WORD = 0x100000;
  113.     /**
  114.      * This flag is a hint to the implementation that during text editing, 
  115.      * the initial letter of each sentence should be capitalized.
  116.      */
  117.     public static final int INITIAL_CAPS_SENTENCE = 0x200000;
  118.     //private int modifierFlag = 0x00000;
  119.              
  120.     /**
  121.      * Input constraint which should be one of CONSTRAINT_ANY, CONSTRAINT_NUMERIC,
  122.      * CONSTRAINT_PHONENUMBER, CONSTRAINT_URL or CONSTRAINT_EMAIL
  123.      */
  124.     private int constraint = ANY;
  125.     
  126.     private  String text="";
  127.     
  128.     private  boolean editable = true;
  129.     
  130.     private int maxSize = defaultMaxSize ; //maximum size (number of characters) that can be stored in this TextField.
  131.     
  132.     private int rows = 1;
  133.     
  134.     private int columns = 3;
  135.     
  136.     // problematic  maxSize = 20; //maximum size (number of characters) that can be stored in this TextField.
  137.     
  138.     private Vector rowStrings;
  139.     private int widthForRowCalculations = -1;
  140.     private int rowsGap = 2;
  141.     private boolean triggerClose;
  142.     private Vector actionListeners = null;
  143.     
  144.     /**
  145.      * Indicates that the text area should "grow" in height based on the content beyond the
  146.      * limits indicate by the rows variable
  147.      */
  148.     private boolean growByContent = true;
  149.     /**
  150.      * Indicates the widest character in the alphabet, this is useful for detecting
  151.      * linebreaks internally. In CJK languages the widest char is different than W
  152.      * hence this functionality is exposed to developers.
  153.      */
  154.     private static char widestChar = 'W';
  155.     /**
  156.      * Indicates whether this is a single line text area, in which case "growing" won't
  157.      * work as expected.
  158.      */
  159.     private boolean singleLineTextArea;
  160.     private int align = LEFT;
  161.     private int absAlign = isRTL() ? RIGHT : LEFT;
  162.     /**
  163.      * Creates an area with the given rows and columns
  164.      * 
  165.      * @param rows the number of rows
  166.      * @param columns - the number of columns
  167.      * @throws IllegalArgumentException if rows <= 0 or columns <= 1
  168.      */
  169.     public TextArea(int rows, int columns){
  170.         this("", defaultMaxSize, rows, columns, ANY);
  171.     }
  172.     /**
  173.      * Creates an area with the given rows, columns and constraint 
  174.      * 
  175.      * @param rows the number of rows
  176.      * @param columns - the number of columns
  177.      * @param constraint one of ANY, EMAILADDR, NUMERIC, PHONENUMBER, URL, DECIMAL
  178.      * it can be bitwised or'd with one of PASSWORD, UNEDITABLE, SENSITIVE, NON_PREDICTIVE,
  179.      * INITIAL_CAPS_SENTENCE, INITIAL_CAPS_WORD. E.g. ANY | PASSWORD.
  180.      * @throws IllegalArgumentException if rows <= 0 or columns <= 1
  181.      */
  182.     public TextArea(int rows, int columns, int constraint){
  183.         this("", defaultMaxSize, rows, columns, constraint);
  184.     }
  185.     
  186.     /**
  187.      * Creates an area with the given text, rows and columns
  188.      * 
  189.      * @param text the text to be displayed; if text is null, the empty 
  190.      * string "" will be displayed
  191.      * @param rows the number of rows
  192.      * @param columns - the number of columns
  193.      * @throws IllegalArgumentException if rows <= 0 or columns <= 1
  194.      */
  195.     public TextArea(String text, int rows, int columns){
  196.         this(text,defaultMaxSize, rows, columns, ANY); //String , maxSize, constraints= 0 (ANY)
  197.     }
  198.     /**
  199.      * Creates an area with the given text, rows, columns and constraint 
  200.      * 
  201.      * @param text the text to be displayed; if text is null, the empty 
  202.      * string "" will be displayed
  203.      * @param rows the number of rows
  204.      * @param columns - the number of columns
  205.      * @param constraint one of ANY, EMAILADDR, NUMERIC, PHONENUMBER, URL, DECIMAL
  206.      * it can be bitwised or'd with one of PASSWORD, UNEDITABLE, SENSITIVE, NON_PREDICTIVE,
  207.      * INITIAL_CAPS_SENTENCE, INITIAL_CAPS_WORD. E.g. ANY | PASSWORD.
  208.      * @throws IllegalArgumentException if rows <= 0 or columns <= 1
  209.      */
  210.     public TextArea(String text, int rows, int columns, int constraint){
  211.         this(text,defaultMaxSize, rows, columns, constraint); 
  212.     }
  213.     /**
  214.      * Creates an area with the given text and maximum size, this constructor
  215.      * will create a single line text area similar to a text field! 
  216.      * 
  217.      * @param text the text to be displayed; if text is null, the empty 
  218.      * string "" will be displayed
  219.      * @param maxSize text area maximum size
  220.      */
  221.     public TextArea(String text, int maxSize){
  222.         this(text,maxSize, 1, 3, ANY);
  223.     }
  224.     
  225.     /**
  226.      * Creates an area with the given text, this constructor
  227.      * will create a single line text area similar to a text field! 
  228.      * 
  229.      * @param text the text to be displayed; if text is null, the empty 
  230.      * string "" will be displayed
  231.      */
  232.     public TextArea(String text) {
  233.         this(text, Math.max(defaultMaxSize, text.length()), 1, 3, ANY);
  234.     }
  235.     /**
  236.      * Creates an empty text area, this constructor
  237.      * will create a single line text area similar to a text field! 
  238.      */
  239.     public TextArea() {
  240.         this("");
  241.     }
  242.     
  243.     /**
  244.      * Creates an area with the given text, maximum size, rows, columns and constraint 
  245.      * 
  246.      * @param text the text to be displayed; if text is null, the empty 
  247.      * string "" will be displayed
  248.      * @param maxSize text area maximum size
  249.      * @param rows the number of rows
  250.      * @param columns - the number of columns
  251.      * @param constraint one of ANY, EMAILADDR, NUMERIC, PHONENUMBER, URL, DECIMAL
  252.      * it can be bitwised or'd with one of PASSWORD, UNEDITABLE, SENSITIVE, NON_PREDICTIVE,
  253.      * INITIAL_CAPS_SENTENCE, INITIAL_CAPS_WORD. E.g. ANY | PASSWORD.
  254.      * @throws IllegalArgumentException if rows <= 0 or columns <= 1
  255.      */
  256.     private TextArea(String text, int maxSize, int rows, int columns, int constraint){
  257.         setUIID("TextArea");
  258.         setSelectCommandText(UIManager.getInstance().localize("edit", "Edit"));
  259.         this.maxSize = maxSize;
  260.         setText(text);
  261.         setConstraint(constraint);
  262.         if(rows <= 0){
  263.             throw new IllegalArgumentException("rows must be positive");
  264.         }
  265.         if(columns <= 1 && rows != 1){
  266.             throw new IllegalArgumentException("columns must be larger than 1");
  267.         }
  268.         this.rows = rows;
  269.         this.columns = columns;
  270.         LookAndFeel laf = UIManager.getInstance().getLookAndFeel();
  271.         setSmoothScrolling(laf.isDefaultSmoothScrolling());
  272.     }
  273.     /**
  274.      * Sets the constraint 
  275.      * 
  276.      * @param constraint one of ANY, EMAILADDR, NUMERIC, PHONENUMBER, URL, DECIMAL
  277.      * it can be bitwised or'd with one of PASSWORD, UNEDITABLE, SENSITIVE, NON_PREDICTIVE,
  278.      * INITIAL_CAPS_SENTENCE, INITIAL_CAPS_WORD. E.g. ANY | PASSWORD.
  279.      */
  280.     public void setConstraint(int constraint) {
  281.         this.constraint = constraint;
  282.     }
  283.     /**
  284.      * Returns the editing constraint value
  285.      * 
  286.      * @return the editing constraint value
  287.      * @see #setConstraint
  288.      */
  289.     public int getConstraint() {
  290.         return constraint;
  291.     }
  292.     /**
  293.      * @inheritDoc
  294.      */
  295.     public void setWidth(int width) {
  296.         super.setWidth(width);
  297.         getRowStrings();
  298.     }
  299.     
  300.     /**
  301.      * Sets the text within this text area
  302.      * 
  303.      * @param t new value for the text area
  304.      */
  305.     public void setText(String t) {
  306.         this.text = (t != null) ? t : "";
  307.         setShouldCalcPreferredSize(true);
  308.         if(maxSize < text.length()) {
  309.             maxSize = text.length() + 1;
  310.         }
  311.         
  312.         // reset scrolling
  313.         setScrollX(0);
  314.         setScrollY(0);
  315.         
  316.         // special case to make the text field really fast...
  317.         rowStrings=null; //zero the vector inorder to initialize it on the next paint
  318.         repaint();
  319.     }
  320.     /**
  321.      * Returns the text in the text area
  322.      * 
  323.      * @return the text in the text area
  324.      */
  325.     public String getText() {
  326.         return text;
  327.     }
  328.     
  329.     /**
  330.      * Returns true if this area is editable
  331.      * 
  332.      * @return true if this area is editable
  333.      */
  334.     public boolean isEditable() {
  335.         return editable;
  336.     }
  337.     /**
  338.      * Sets this text area to be editable or readonly
  339.      * 
  340.      * @param b true is text are is editable; otherwise false
  341.      */
  342.     public void setEditable(boolean b) {
  343.         editable = b;
  344.     }
  345.     /**
  346.      * Returns the maximum size for the text area
  347.      * 
  348.      * @return the maximum size for the text area
  349.      */
  350.     public int getMaxSize() {
  351.         return maxSize;
  352.     }
  353.     /**
  354.      * Sets the maximum size of the text area
  355.      * 
  356.      * @param maxSize the maximum size of the text area
  357.      */
  358.     public void setMaxSize(int maxSize) {
  359.         this.maxSize = maxSize;
  360.     }
  361.     
  362.     /**
  363.      * @inheritDoc
  364.      */
  365.     public void keyPressed(int keyCode) {
  366.         super.keyPressed(keyCode);
  367.         
  368.         int action = com.sun.lwuit.Display.getInstance().getGameAction(keyCode);
  369.         // this works around a bug where fire is also a softkey on devices such as newer Nokia
  370.         // series 40's (e.g. the Nokia emulator). It closes its native text box on fire then
  371.         // as a result of a Nokia bug we get the key released of that closing and assume the
  372.         // users wants to edit the text... When means the only way to exit the native text box
  373.         // is via the cancel option (after pressing OK once).
  374.         triggerClose = action == Display.GAME_FIRE;
  375.         //scroll the TextArea
  376.         Rectangle rect = new Rectangle(getScrollX(), getScrollY(), getWidth(), getHeight());
  377.         Font textFont = getStyle().getFont();
  378.         if(action == Display.GAME_DOWN){
  379.             if((getScrollY() + getHeight()) <(rowsGap + getStyle().getFont().getHeight()) * getLines()) {
  380.                 rect.setY(rect.getY() + (textFont.getHeight() + rowsGap) * linesToScroll);
  381.                 scrollRectToVisible(rect, this);
  382.             } else {
  383.                 setHandlesInput(false);
  384.             }
  385.         } else {
  386.             if(action == Display.GAME_UP){
  387.                 if(getScrollY() > 0) {
  388.                     rect.setY(Math.max(0, rect.getY() - (textFont.getHeight() + rowsGap) * linesToScroll));
  389.                     scrollRectToVisible(rect, this);
  390.                 } else {
  391.                     setHandlesInput(false);
  392.                 }
  393.             }
  394.         }
  395.         if(action == Display.GAME_RIGHT || action == Display.GAME_LEFT){
  396.             setHandlesInput(false);
  397.         }
  398.     }
  399.     
  400.     
  401.     /**
  402.      * @inheritDoc
  403.      */
  404.     protected void fireClicked() {
  405.         onClick();
  406.     }
  407.     
  408.     /**
  409.      * @inheritDoc
  410.      */
  411.     protected boolean isSelectableInteraction() {
  412.         return editable;
  413.     }
  414.     /**
  415.      * @inheritDoc
  416.      */
  417.     public void keyReleased(int keyCode) {
  418.         int action = com.sun.lwuit.Display.getInstance().getGameAction(keyCode);
  419.         if(isEditable()){
  420.             // this works around a bug where fire is also a softkey on devices such as newer Nokia
  421.             // series 40's
  422.             if (triggerClose && (action == Display.GAME_FIRE || isEnterKey(keyCode))) {
  423.                 triggerClose = false;
  424.                 onClick();
  425.                 return;
  426.             }
  427.         }
  428.     }
  429.     
  430.     /**
  431.      * @inheritDoc
  432.      */
  433.     public boolean isScrollableY() {
  434.         return isFocusable() && getScrollDimension().getHeight() > getHeight();
  435.     }
  436.         
  437.     void onClick(){
  438.         if(isEditable()) {
  439.             editString();
  440.         }
  441.     }
  442.         
  443.     void editString() {
  444.         if(autoDegradeMaxSize && (!hadSuccessfulEdit) && (maxSize > 1024)) {
  445.             try {
  446.                 Display.getInstance().editString(this, getMaxSize(), getConstraint(), getText());
  447.             } catch(IllegalArgumentException err) {
  448.                 maxSize -= 1024;
  449.                 setDefaultMaxSize(maxSize);
  450.                 editString();
  451.             }
  452.         } else {
  453.             Display.getInstance().editString(this, getMaxSize(), getConstraint(), getText());
  454.         }
  455.     }
  456.     /**
  457.      * @inheritDoc
  458.      */
  459.     public void pointerHover(int[] x, int[] y) {
  460.         requestFocus();
  461.     }
  462.     /**
  463.      * @inheritDoc
  464.      */
  465.     public void pointerHoverReleased(int[] x, int[] y) {
  466.         requestFocus();
  467.     }
  468.     
  469.     /**
  470.      * @inheritDoc
  471.      */
  472.     public void pointerReleased(int x, int y) {
  473.         // prevent a drag operation from going into edit mode
  474.         if(isDragActivated()) {
  475.             super.pointerReleased(x, y);
  476.         } else {
  477.             super.pointerReleased(x, y);
  478.             if(isEditable()){
  479.                 onClick();
  480.             }
  481.         }
  482.     }
  483.     /**
  484.      * @inheritDoc
  485.      */
  486.     void focusGainedInternal() {
  487.         super.focusGainedInternal();
  488.         setHandlesInput(isScrollableY());
  489.     }
  490.     /**
  491.      * @inheritDoc
  492.      */
  493.     void focusLostInternal() {
  494.         super.focusLostInternal();
  495.         setHandlesInput(false);
  496.     }
  497.     
  498.     /**
  499.      * Returns the number of columns in the text area
  500.      * 
  501.      * @return the number of columns in the text area
  502.      */
  503.     public int getColumns() {
  504.         return columns;
  505.     }
  506.     
  507.     /**
  508.      * Returns the number of actual rows in the text area taking into consideration
  509.      * growsByContent
  510.      * 
  511.      * @return the number of rows in the text area
  512.      */
  513.     public int getActualRows() {
  514.         if(growByContent) {
  515.             return Math.max(rows, getLines());
  516.         }
  517.         return rows;
  518.     }
  519.     
  520.     /**
  521.      * Returns the number of rows in the text area
  522.      * 
  523.      * @return the number of rows in the text area
  524.      */
  525.     public int getRows() {
  526.         return rows;
  527.     }
  528.     
  529.     /**
  530.      * Sets the number of columns in the text area
  531.      * 
  532.      * @param columns number of columns
  533.      */
  534.     public void setColumns(int columns) {
  535.         setShouldCalcPreferredSize(true);
  536.         this.columns = columns;
  537.     }
  538.     
  539.     /**
  540.      * Sets the number of rows in the text area
  541.      * 
  542.      * @param rows number of rows
  543.      */
  544.     public void setRows(int rows) {
  545.         setShouldCalcPreferredSize(true);
  546.         this.rows = rows;
  547.     }
  548.     
  549.     void initComponentImpl() {
  550.         getRowStrings();
  551.         super.initComponentImpl();
  552.     }
  553.     
  554.     private Vector getRowStrings() {
  555.         if(rowStrings == null || widthForRowCalculations != getWidth() - getStyle().getPadding(false, RIGHT) - getStyle().getPadding(false, LEFT)){
  556.             initRowString();
  557.             setShouldCalcPreferredSize(true);
  558.         }
  559.         return rowStrings;
  560.     }
  561.     
  562.     
  563.     /**
  564.      * Returns the number of text lines in the TextArea
  565.      * 
  566.      * @return the number of text lines in the TextArea
  567.      */
  568.     public int getLines(){
  569.         int retVal;
  570.         Vector v = getRowStrings();
  571.         retVal = v.size();
  572.         return retVal;
  573.     }
  574.     
  575.     /**
  576.      * Returns the text in the given row of the text box
  577.      * 
  578.      * @param line the line number in the text box
  579.      * @return the text of the line
  580.      */
  581.     public String getTextAt(int line){
  582.         Vector rowsV = getRowStrings();
  583.         return (String)rowsV.elementAt(line);
  584.     }
  585.     
  586.     private int indexOf(char[] t, char c, int offset, int length) {
  587.         for(int iter = offset ; iter < t.length && iter < offset+length; iter++) {
  588.             if(t[iter] == c) {
  589.                 return iter;
  590.            }
  591.        }
  592.        return -1;
  593.    }
  594.     
  595.     private boolean fastCharWidthCheck(char[] chrs, int off, int length, int width, int charWidth, Font f) {
  596.         if(length * charWidth < width) {
  597.             return true;
  598.         }
  599.         length = Math.min(chrs.length, length);
  600.         return f.charsWidth(chrs, off, length) < width;
  601.     }
  602.     
  603.     /**
  604.      * Override this to modify the text for rendering in cases of invalid characters 
  605.      * for display, this method allows the developer to replace such characters e.g.:
  606.      * replace "\t" with 4 spaces
  607.      * 
  608.      * @param text the text to process
  609.      * @return the given string as a processed char array ready for rendering
  610.      */
  611.     protected char[] preprocess(String text) {
  612.         return text.toCharArray();
  613.     }
  614.     
  615.     private void initRowString() {
  616.         Style style = getStyle();
  617.         rowStrings= new Vector();
  618.         widthForRowCalculations = getWidth() - style.getPadding(false, RIGHT) - style.getPadding(false, LEFT);
  619.         // single line text area is essentially a text field, we call the method
  620.         // to allow subclasses to override it
  621.         if(isSingleLineTextArea()) {
  622.             rowStrings.addElement(getText());
  623.             return;
  624.         }
  625.         if(text == null || text.equals("")){
  626.             return;
  627.         }
  628.         char[] text = preprocess(getText());
  629.         int rows = this.rows;
  630.         if(growByContent) {
  631.             rows = Math.max(rows, getLines());
  632.         }
  633.         
  634.         Font font = style.getFont();
  635.         int charWidth = font.charWidth(widestChar);
  636.         int tPadding = style.getPadding(false, RIGHT) + style.getPadding(false, LEFT);
  637.         int textAreaWidth = getWidth() - tPadding;
  638.         if(textAreaWidth <= 0) {
  639.             if(columns < 1) {
  640.                 textAreaWidth = Math.min(Display.getInstance().getDisplayWidth() - tPadding, getText().length()) * charWidth;
  641.             } else {
  642.                 textAreaWidth = Math.min(Display.getInstance().getDisplayWidth() - tPadding, columns) * charWidth;
  643.             }
  644.         }
  645.         
  646.         int minCharactersInRow = Math.max(1, textAreaWidth / charWidth);
  647.         int rowIndex=0;
  648.         int from=0;
  649.         int to=from+minCharactersInRow;
  650.         int textLength=text.length;
  651.         String rowText;
  652.         int i,spaceIndex;
  653.         
  654.         // if there is any possibility of a scrollbar we need to reduce the textArea
  655.         // width to accommodate it
  656.         if(textLength / minCharactersInRow > Math.max(2, rows)) {
  657.             textAreaWidth -= UIManager.getInstance().getLookAndFeel().getVerticalScrollWidth();
  658.             textAreaWidth -= charWidth/2;
  659.         }
  660.         String unsupported = getUnsupportedChars();
  661.         
  662.         /*
  663.         iteration over the string using indexes, from - the beginning of the row , to - end of a row
  664.         for each row we will try to search for a "space" character at the end of the row ( row is text area available width)
  665.         indorder to improve the efficiency we do not search an entire row but we start from minCharactersInRow which indicates
  666.         what is the minimum amount of characters that can feet in the text area width.
  667.         if we dont find we will go backwards and search for the first space available,
  668.         if there is no space in the entire row we will cut the line inorder to fit in.
  669.          */
  670.         //Don't rely on the fact that short text has no newline character. we always have to parse the text.
  671.         to = Math.max( Math.min(textLength-1,to), 0 );
  672.         while(to<textLength) {
  673.             if(to>textLength){
  674.                 to=textLength;
  675.             }
  676.             spaceIndex=-1;
  677.             rowText="";
  678.             int maxLength = to;
  679.             // search for "space" character at close as possible to the end of the row
  680.             for( i=to; i < textLength && fastCharWidthCheck(text, from, i - from + 1, textAreaWidth, charWidth, font)  ; i++){
  681.                 char c = text[i];
  682.                 if(unsupported.indexOf(c) > -1) {
  683.                     text[i] = ' ';
  684.                     c = ' ';
  685.                 }
  686.                 if(c == ' ' || c == 'n') {
  687.                     spaceIndex=i;
  688.                     // newline has been found. We can end the loop here as the line cannot grow more
  689.                     if (c == 'n')
  690.                         break; 
  691.                 }
  692.                 maxLength++;
  693.             }
  694.             
  695.             // if we got to the end of the text use the entire row,
  696.             // also if space is next character (in the next row) we can cut the line
  697.             if(i == textLength || text[i] == ' ' || text[i] == 'n') {
  698.                 spaceIndex=i;
  699.             }
  700.             // if we found space in the limit width of the row (searched only from minCharactersInRow)
  701.             if(spaceIndex!=-1){
  702.                 // make sure that if we have a newline character before the end of the line we should
  703.                 // break there instead
  704.                 int newLine = indexOf(text, 'n', from, spaceIndex - from);
  705.                 if(newLine > -1 && newLine < spaceIndex) {
  706.                     spaceIndex = newLine;
  707.                 }
  708.                 rowText = new String(text, from, spaceIndex - from);
  709.                 from=spaceIndex+1;
  710.             } // if there is no space from minCharactersInRow to limit need to search backwards
  711.             else{
  712.                 for( i=to; spaceIndex==-1 && i>=from ; i--){
  713.                     char chr = text[i];
  714.                     if(chr == ' ' || chr == 'n' || chr == 't') {
  715.                         spaceIndex=i;
  716.                         
  717.                         // don't forget to search for line breaks in the
  718.                         // remaining part. otherwise we overlook possible
  719.                         // line breaks!
  720.                         int newLine = indexOf(text, 'n', from, i - from);
  721.                         if(newLine > -1 && newLine < spaceIndex) {
  722.                            spaceIndex = newLine;
  723.                         }
  724.                         rowText = new String(text, from, spaceIndex - from);
  725.                         from=spaceIndex+1;
  726.                     }
  727.                 }
  728.                 if(spaceIndex==-1) {
  729.                     // from = to + 1;
  730.                     if(maxLength <= 0) {
  731.                         maxLength = 1;
  732.                     }
  733.                     spaceIndex = maxLength;
  734.                     rowText = new String(text, from, spaceIndex - from);
  735.                     from = spaceIndex;
  736.                 }
  737.             }
  738.             rowStrings.addElement(rowText); 
  739.             //adding minCharactersInRow doesn't work if what is left is less
  740.             //then minCharactersInRow
  741.             to=from;//+minCharactersInRow;
  742.             rowIndex++;
  743.         }
  744.     }
  745.     
  746.     /**
  747.      * Gets the num of pixels gap between the rows
  748.      * 
  749.      * @return the gap between rows in pixels
  750.      */
  751.     public int getRowsGap() {
  752.         return rowsGap;
  753.     }
  754.     /**
  755.      * The gap in pixels between rows
  756.      * 
  757.      * @param rowsGap num of pixels to gap between rows
  758.      */
  759.     public void setRowsGap(int rowsGap) {
  760.         this.rowsGap = rowsGap;
  761.     }
  762.     
  763.     /**
  764.      * @inheritDoc
  765.      */
  766.     public void paint(Graphics g) {
  767.         UIManager.getInstance().getLookAndFeel().drawTextArea(g, this);
  768.     }
  769.     
  770.     /**
  771.      * @inheritDoc
  772.      */
  773.     protected Dimension calcPreferredSize(){
  774.         return UIManager.getInstance().getLookAndFeel().getTextAreaSize(this, true);
  775.     }
  776.         
  777.     /**
  778.      * @inheritDoc
  779.      */
  780.     protected Dimension calcScrollSize(){
  781.         return UIManager.getInstance().getLookAndFeel().getTextAreaSize(this, false);
  782.     }
  783.         
  784.     /**
  785.      * Add an action listener which is invoked when the text area was modified not during
  786.      * modification. A text <b>field</b> might never fire an action event if it is edited
  787.      * in place and the user never leaves the text field!
  788.      * 
  789.      * @param a actionListener
  790.      */
  791.     public void addActionListener(ActionListener a) {
  792.         if(actionListeners == null) {
  793.             actionListeners = new Vector();
  794.         }
  795.         if(!actionListeners.contains(a)) {
  796.             actionListeners.addElement(a);
  797.         }
  798.     }
  799.     /**
  800.      * Removes an action listener
  801.      * 
  802.      * @param a actionListener
  803.      */
  804.     public void removeActionListener(ActionListener a) {
  805.         if(actionListeners == null) {
  806.             actionListeners = new Vector();
  807.         }
  808.         actionListeners.removeElement(a);
  809.     }
  810.     
  811.     /**
  812.      * Notifies listeners of a change to the text area
  813.      */
  814.     void fireActionEvent() {
  815.         if(actionListeners != null) {
  816.             ActionEvent evt = new ActionEvent(this);
  817.             for(int iter = 0 ; iter < actionListeners.size() ; iter++) {
  818.                 ActionListener a = (ActionListener)actionListeners.elementAt(iter);
  819.                 a.actionPerformed(evt);
  820.             }
  821.         }
  822.     }
  823.     
  824.     /**
  825.      * @inheritDoc
  826.      */
  827.     void onEditComplete(String text) {
  828.         setText(text);
  829.         getParent().revalidate();
  830.     }
  831.     
  832.     /**
  833.      * Sets the default limit for the native text box size
  834.      * 
  835.      * @param value default value for the size of the native text box
  836.      */
  837.     public static void setDefaultMaxSize(int value) {
  838.         defaultMaxSize = value;
  839.     }
  840.     /**
  841.      * Indicates that the text area should "grow" in height based on the content beyond the
  842.      * limits indicate by the rows variable
  843.      * 
  844.      * @return true if the text component should grow and false otherwise
  845.      */
  846.     public boolean isGrowByContent() {
  847.         return growByContent;
  848.     }
  849.     /**
  850.      * Indicates that the text area should "grow" in height based on the content beyond the
  851.      * limits indicate by the rows variable
  852.      * 
  853.      * @param growByContent true if the text component should grow and false otherwise
  854.      */
  855.     public void setGrowByContent(boolean growByContent) {
  856.         this.growByContent = growByContent;
  857.     }
  858.     
  859.     /**
  860.      * Indicates whether a high value for default maxSize will be reduced to a lower
  861.      * value if the underlying platform throws an exception.
  862.      * 
  863.      * @param value new value for autoDegradeMaxSize
  864.      */
  865.     public static void setAutoDegradeMaxSize(boolean value) {
  866.         autoDegradeMaxSize = value;
  867.     }
  868.     /**
  869.      * Indicates whether a high value for default maxSize will be reduced to a lower
  870.      * value if the underlying platform throws an exception.
  871.      * 
  872.      * @return value for autoDegradeMaxSize
  873.      */
  874.     public static boolean isAutoDegradeMaxSize() {
  875.         return autoDegradeMaxSize;
  876.     }
  877.     /**
  878.      * Unsupported characters is a string that contains characters that cause issues 
  879.      * when rendering on some problematic fonts. The rendering engine can thus remove them
  880.      * when drawing.
  881.      * 
  882.      * @return unsupported characters string
  883.      */
  884.     public String getUnsupportedChars() {
  885.         return unsupportedChars;
  886.     }
  887.     /**
  888.      * Unsupported characters is a string that contains characters that cause issues 
  889.      * when rendering on some problematic fonts. The rendering engine can thus remove them
  890.      * when drawing.
  891.      * 
  892.      * @param unsupportedChars the unsupported character string
  893.      */
  894.     public void setUnsupportedChars(String unsupportedChars) {
  895.         this.unsupportedChars = unsupportedChars;
  896.     }
  897.     /**
  898.      * Indicates the number of lines to scroll with every scroll operation
  899.      * 
  900.      * @return number bigger or equal to 1
  901.      */
  902.     public int getLinesToScroll() {
  903.         return linesToScroll;
  904.     }
  905.     /**
  906.      * Indicates the number of lines to scroll with every scroll operation
  907.      * 
  908.      * @param linesToScroll number bigger or equal to 1
  909.      */
  910.     public void setLinesToScroll(int linesToScroll) {
  911.         if (linesToScroll < 1) {
  912.             throw new IllegalArgumentException("lines to scroll has to be >= 1");
  913.         }
  914.         this.linesToScroll = linesToScroll;
  915.     }
  916.     /**
  917.      * Indicates the widest character in the alphabet, this is useful for detecting
  918.      * linebreaks internally. In CJK languages the widest char is different than W
  919.      * hence this functionality is exposed to developers.
  920.      * 
  921.      * @param widestC the widest character
  922.      */
  923.     public static void setWidestChar(char widestC) {
  924.         widestChar = widestC;
  925.     }
  926.     /**
  927.      * Indicates the widest character in the alphabet, this is useful for detecting
  928.      * linebreaks internally. In CJK languages the widest char is different than W
  929.      * hence this functionality is exposed to developers.
  930.      * 
  931.      * @return the widest character
  932.      */
  933.     public static char getWidestChar() {
  934.         return widestChar;
  935.     }
  936.     /**
  937.      * Indicates whether this is a single line text area, in which case "growing" won't
  938.      * work as expected.
  939.      *
  940.      * @param singleLineTextArea set to true to force a single line text
  941.      */
  942.     public void setSingleLineTextArea(boolean singleLineTextArea) {
  943.         this.singleLineTextArea = singleLineTextArea;
  944.     }
  945.     /**
  946.      * Indicates whether this is a single line text area, in which case "growing" won't
  947.      * work as expected.
  948.      *
  949.      * @return  true if this is a single line text area
  950.      */
  951.     public boolean isSingleLineTextArea() {
  952.         return singleLineTextArea;
  953.     }
  954.     /**
  955.      * Sets the Alignment of the TextArea to one of: CENTER, LEFT, RIGHT
  956.      *
  957.      * @param align alignment value
  958.      * @see #CENTER
  959.      * @see #LEFT
  960.      * @see #RIGHT
  961.      */
  962.     public void setAlignment(int align){
  963.         if(align != CENTER && align != RIGHT && align != LEFT){
  964.             throw new IllegalArgumentException("Alignment can't be set to " + align);
  965.         }
  966.         this.align = align;
  967.         absAlign=align;
  968.         if (isRTL()) {
  969.             switch(align) {
  970.                 case LEFT:
  971.                     absAlign = RIGHT;
  972.                     break;
  973.                 case RIGHT:
  974.                     absAlign = LEFT;
  975.                     break;
  976.             }
  977.         }
  978.     }
  979.     /**
  980.      * Returns the alignment of the TextArea
  981.      *
  982.      * @return the alignment of the TextArea one of: CENTER, LEFT, RIGHT
  983.      * @see #CENTER
  984.      * @see #LEFT
  985.      * @see #RIGHT
  986.      */
  987.     public int getAlignment(){
  988.         return align;
  989.     }
  990.     /**
  991.      * Returns the absolute alignment of the TextArea
  992.      * In RTL LEFT alignment is actually RIGHT, but this method returns the actual alignment
  993.      *
  994.      * @return the alignment of the TextArea one of: CENTER, LEFT, RIGHT
  995.      * @see #CENTER
  996.      * @see #LEFT
  997.      * @see #RIGHT
  998.      */
  999.     public int getAbsoluteAlignment(){
  1000.         return absAlign;
  1001.     }
  1002.     /**
  1003.      * Returns true if the text field is waiting for a commit on editing
  1004.      *
  1005.      * @return true if a commit is pending
  1006.      */
  1007.     public boolean isPendingCommit() {
  1008.         return false;
  1009.     }
  1010.     /**
  1011.      * Returns the position of the cursor
  1012.      *
  1013.      * @return the cursor position
  1014.      */
  1015.     public int getCursorPosition() {
  1016.         return -1;
  1017.     }
  1018.     /**
  1019.      * True is this is a qwerty device or a device that is currently in
  1020.      * qwerty mode.
  1021.      *
  1022.      * @return currently defaults to false
  1023.      */
  1024.     public boolean isQwertyInput() {
  1025.         return false;
  1026.     }
  1027.     /**
  1028.      * Returns the currently selected input mode
  1029.      *
  1030.      * @return the display name of the input mode by default the following modes
  1031.      * are supported: Abc, ABC, abc, 123
  1032.      */
  1033.     public String getInputMode() {
  1034.         return null;
  1035.     }
  1036.     /**
  1037.      * Returns the order in which input modes are toggled
  1038.      *
  1039.      * @return the order of the input modes
  1040.      */
  1041.     public String[] getInputModeOrder() {
  1042.         return null;
  1043.     }
  1044.     /**
  1045.      * Indicates whether text field input should scroll to the right side when no
  1046.      * more room for the input is present.
  1047.      *
  1048.      * @return true if scrolling is enabled
  1049.      */
  1050.     public boolean isEnableInputScroll() {
  1051.         return false;
  1052.     }
  1053.     /**
  1054.      * Indicates the enter key to be used for editing the text area and by the
  1055.      * text field
  1056.      *
  1057.      * @param keyCode the key tested
  1058.      */
  1059.     protected boolean isEnterKey(int keyCode) {
  1060.         return keyCode == ENTER_KEY;
  1061.     }
  1062.     /**
  1063.      * Searches the given string for the widest character using char width, this operation should only
  1064.      * be performed once and it solves cases where a devices language might have a char bigger than 'W'
  1065.      * that isn't consistently bigger.
  1066.      * Notice that this method will use the TextArea style font which might differ when switching themes etc.
  1067.      *
  1068.      * @param s string to search using charWidth
  1069.      */
  1070.     public static void autoDetectWidestChar(String s) {
  1071.         Font f = UIManager.getInstance().getComponentStyle("TextArea").getFont();
  1072.         int widest = 0;
  1073.         for(int iter = 0 ; iter < s.length() ; iter++) {
  1074.             char c = s.charAt(iter);
  1075.             int w = f.charWidth(c);
  1076.             if(w > widest) {
  1077.                 widest = w;
  1078.                 setWidestChar(c);
  1079.             }
  1080.         }
  1081.     }
  1082. }