// TextField.java
// By Ned Etcode
// Copyright 1995, 1996 Netscape Communications Corp. All rights reserved.

package netscape.application;

import netscape.util.*;


/** View subclass that displays and, optionally, lets the user edit a mono-
  * font string.
  * TextFields have owners that are notified of important events within the
  * TextField, such as the completion of an editing session. They also have
  * filters that can filter or process each key event received by the
  * TextField. On certain events, such as receiving a tab key, the TextField
  * can send a command to a Target interested in reacting to that event.
  */

public class TextField extends View implements Target, Component {
    TextFieldOwner      _owner;
    TextFilter          _filter;
    Target              _tabTarget, _backtabTarget, _contentsChangedTarget,
                        _commitTarget;
    Vector              _keyVector;
    Border              border = BezelBorder.loweredBezel();
    Font                _font;
    Color               _textColor, _backgroundColor, _selectionColor,
                        _caretColor;
    String              _tabCommand, _backtabCommand, _contentsChangedCommand,
                        _commitCommand;
    FastStringBuffer    _contents, _oldContents;
    Timer               blinkTimer;
    int                 _selectionAnchorChar = -1, _selectionEndChar = -1,
                        _justification, _scrollOffset, _fontHeight,
                        _initialAnchorChar = -1,_clickCount;
    boolean             _editing, _caretShowing, _canBlink, _editable,
                        _selectable, _mouseDragging, _shadowed,
                        _textChanged = false, _canWrap, transparent = false,
                        wantsAutoscrollEvents = false,
                        shouldShowSelection = false;

    /** Command to select the TextField's entire contents. */
    final public static String  SELECT_TEXT = "selectText";
    final static String         BLINK_CARET = "blinkCaret";

    final static String         justificationNameArray[] = { "Left Justified",
                                                            "Centered",
                                                            "Right Justified"};
    final static int            justificationValueArray[] = {
                                                     Graphics.LEFT_JUSTIFIED,
                                                     Graphics.CENTERED,
                                                     Graphics.RIGHT_JUSTIFIED};
    final static String         textFieldCommands[] = { SELECT_TEXT };


    final static String OWNER_KEY = "owner",
                        FILTER_KEY = "filter",
                        TABREC_KEY = "tabTarget",
                        BACKTABREC_KEY = "backtabTarget",
                        CONTENTSCHANGEDREC_KEY = "contentsChangedTarget",
                        COMMITREC_KEY = "commitTarget",
                        BORDER_KEY = "border",
                        FONT_KEY = "font",
                        TEXTC_KEY = "textColor",
                        BACKGROUNDC_KEY = "backgroundColor",
                        SELECTIONC_KEY = "selectionColor",
                        CARETC_KEY = "caretColor",
                        TABCOM_KEY = "tabCommand",
                        BACKTABCOM_KEY = "backtabCommand",
                        CONTENTSCHANGEDCOM_KEY = "contentsChangedCommand",
                        COMMITCOM_KEY = "commitCommand",
                        CONTENTS_KEY = "contents",
                        SELECTIONANCH_KEY = "selectionAnchorChar",
                        SELECTIONEND_KEY = "selectionEndChar",
                        JUSTIFICATION_KEY = "justification",
                        SCROLLOFFSET_KEY = "scrollOffset",
                        EDITABLE_KEY = "editable",
                        SELECTABLE_KEY = "selectable",
                        SHADOWED_KEY = "shadowed",
                        CANWRAP_KEY = "canWrap",
                        AUTOSCROLLEVENT_KEY = "wantsAutoscrollEvents",
                        TRANSPARENT_KEY = "transparent";




    /* constructors */

    /** Constructs a TextField with origin (<b>0</b>, <b>0</b>) and zero
      * width and height.
      */
    public TextField() {
        this(0, 0, 0, 0);
    }

    /** Constructs a TextField with bounds <B>rect</B>.
      */
    public TextField(Rect rect) {
        this(rect.x, rect.y, rect.width, rect.height);
    }

    /** Constructs a TextField with
      * bounds (<B>x</B>, <B>y</B>, <B>width</B>, <B>height</B>).
      */
    public TextField(int x, int y, int width, int height) {
        super(x, y, width, height);

        _keyVector = new Vector();

        _contents = new FastStringBuffer();

        _textColor = Color.black;
        _backgroundColor = Color.white;
        _selectionColor = Color.lightGray;
        _caretColor = Color.black;

        setEditable(true);
        setFont(Font.defaultFont());
    }

    /** Creates a new TextField that is suitable for use as a "label": it has
      * no Border, is transparent, and is not editable or selectable. The
      * label displays <b>string</b> using <b>font</b>.
      */
    public static TextField createLabel(String string, Font font) {
        FontMetrics metrics = font.fontMetrics();
        int width = metrics.stringWidth(string),
            height = metrics.stringHeight();
        TextField label = new TextField(0, 0, width, height);

        label.setBorder(null);
        label.setStringValue(string);
        label.setFont(font);
        label.setTransparent(true);
        label.setEditable(false);
        label.setSelectable(false);
        return label;
    }

    /** Creates a new TextField that is suitable for use as a "label": it has
      * no Border, is transparent, and is not editable or selectable. The label
      * displays <b>string</b> using the default font.
      */
    public static TextField createLabel(String string) {
        return createLabel(string, Font.defaultFont());
    }

    private static int parseInt(String s) {
        try {
            return Integer.parseInt(s);
        } catch (NumberFormatException e) {
            return 0;
        }
    }

/* dimensional attributes */

   /** Returns the distance between the interior edge of the TextField's
    * left border and the first character. By default, this method returns
    * the left margin of the TextField's Border.
    */
    public int leftIndent() {
        int leftIndent;

        // ALERT!
        leftIndent = border.leftMargin();
        if (leftIndent > 2)
            leftIndent = 2;

        return leftIndent;
    }

    /** Returns the distance between the interior edge of the TextField's right
      * border and the last character. By default, returns the right margin
      * plus 1 of the TextField's Border.
      */
    public int rightIndent() {
        int rightIndent;

        // ALERT!
        rightIndent = border.rightMargin() + 1;
        if (rightIndent > 3)
            rightIndent = 3;

        return rightIndent;
    }

    /** Returns the distance between the interior edge of the TextField's left
      * border and the first character, and the right border and the last
      * character. Equivalent to the code:
      * <pre>
      *     leftIndent() + rightIndent()
      * </pre>
      */
    private int widthIndent() {
        return leftIndent() + rightIndent();
    }

    /** Returns the TextField's minimum size.
     */
    public Size minSize() {
        Vector  stringVector;
        Rect    interiorRect;
        Size    theSize;

        if (_minSize != null) {
            return new Size(_minSize);
        }

        theSize = _font.fontMetrics().stringSize(_contents.toString());
        interiorRect = interiorRect();

        /* if text can wrap and width is fixed, compute min height */
        if ((horizResizeInstruction() != WIDTH_CAN_CHANGE) && _canWrap &&
            !isEditable() && theSize.width > interiorRect.width) {
            stringVector = stringVectorForContents(interiorRect.width);
            theSize.sizeTo(interiorRect.width,
                           theSize.height * stringVector.count());
        }
        Rect.returnRect(interiorRect);

        theSize.sizeBy(border.widthMargin() + widthIndent(),
                       border.heightMargin());

        return theSize;
    }



/* appearance attributes */

    /** Sets the TextField's Font and redraws the TextField.  The default Font
      * is Font.defaultFont().
      * @see Font#defaultFont
      */
    public void setFont(Font aFont) {
        Size    stringSize;

        if (aFont == null) {
            _font = Font.defaultFont();
        } else {
            _font = aFont;
        }

        stringSize = _font.fontMetrics().stringSize(null);
        _fontHeight = stringSize.height;

        draw();
    }

    /** Returns the TextField's font.
      * @see #setFont
      */
    public Font font() {
        return _font;
    }

    /** Sets the Color of the TextField's text and redraws the TextField.
      * Default text color is Color.black.
      */
    public void setTextColor(Color aColor) {
        _textColor = aColor;
        if (_textColor == null) {
            _textColor = Color.black;
        }
        draw();
    }

    /** Returns the TextField's text Color.
      * @see #setTextColor
      */
    public Color textColor() {
        return _textColor;
    }

    /** Sets the TextField's background Color and redraws the TextField.
      * Default background Color is Color.white.
      */
    public void setBackgroundColor(Color aColor) {
        _backgroundColor = aColor;
        if (_backgroundColor == null) {
            _backgroundColor = Color.white;
        }
        draw();
    }

    /** Returns the TextField's background Color.
      * @see #setBackgroundColor
      */
    public Color backgroundColor() {
        return _backgroundColor;
    }

    /** Sets the Color the TextField uses to indicate selection and redraws the
      * TextField.  Default selection Color is Color.lightGray.
      */
    public void setSelectionColor(Color aColor) {
        _selectionColor = aColor;
        if (_selectionColor == null) {
            _selectionColor = Color.lightGray;
        }
        draw();
    }

    /** Returns the TextField's selection Color.
      * @see #setSelectionColor
      */
    public Color selectionColor() {
        return _selectionColor;
    }

    /** Sets the Color of the TextField's caret.  Default is Color.black.
      */
    public void setCaretColor(Color aColor) {
        _caretColor = aColor;
        if (_caretColor == null) {
            _caretColor = Color.black;
        }
    }

    /** Returns the TextField's caret Color.
      * @see #setCaretColor
      */
    public Color caretColor() {
        return _caretColor;
    }

    /** Sets the Border the TextField draws around its perimeter.
      * @see Border
      */
    public void setBorder(Border newBorder) {
        if (newBorder == null)
            newBorder = EmptyBorder.emptyBorder();

        border = newBorder;
    }

    /** Returns the TextField's Border.
      * @see #setBorder
      */
    public Border border() {
        return border;
    }

    /** Configures the TextField to draw its text with a drop shadow.
      */
    public void setDrawsDropShadow(boolean flag) {
        _shadowed = flag;
        draw();
    }

    /** Returns <b>true</b> if the TextField draws its text with a drop shadow.
      * @see #setDrawsDropShadow
      */
    public boolean drawsDropShadow() {
        return _shadowed;
    }

    /** Sets the justification (Graphics.LEFT_JUSTIFIED,
      * Graphics.CENTERED, Graphics.RIGHT_JUSTIFIED) the
      * TextField uses to draw its text.
      */
    public void setJustification(int aJustification) {
        if (aJustification < Graphics.LEFT_JUSTIFIED ||
            aJustification > Graphics.RIGHT_JUSTIFIED) {
            return;
        }

        if( aJustification != _justification ) {
           _justification = aJustification;
           _scrollOffset  = 0;
        }
    }

    /** Returns the justification the TextField uses to draw its text.
      */
    public int justification() {
        return _justification;
    }

    /** Overridden to automatically remove the TextField's Border if
      * <b>flag</b> is <b>true</b>.
      */
    public void setTransparent(boolean flag) {
        transparent = flag;
        if (transparent) {
            setBorder(null);
        }
    }

    /** Overridden to return <b>true</b> if the TextField is transparent.
      * @see #setTransparent
      */
    public boolean isTransparent() {
        return transparent;
    }


/* behavioral attributes */

    /** Configures the TextField to allow the user to select its text.
      */
    public void setSelectable(boolean flag) {
        RootView        rootView;

        if (_selectable != flag) {
            _selectable = flag;
            wantsAutoscrollEvents = flag;
            if (!_selectable && _scrollOffset != 0) {
                _scrollOffset = 0;
                drawInterior();
            }

            rootView = rootView();
            if (rootView != null) {
                rootView.updateCursor();
            }
        }
    }

    /** Returns <b>true</b> if the TextField allows the user to select its
      * text.
      * @see #setSelectable
      */
    public boolean isSelectable() {
        return _selectable;
    }

    /** Overridden to return <B>true</B> if the TextField wants to
      * automatically receive mouse dragged Events when the user clicks and
      * drags outside of its bounds.
      */
    public boolean wantsAutoscrollEvents() {
        return wantsAutoscrollEvents;
    }

    /** Configures the TextField to wrap its contents if too long to fit
      * on a single line.
      */
    public void setWrapsContents(boolean flag) {
        _canWrap = flag;
        if( flag && isEditable())
            setEditable(false);
        drawInterior();
    }

    /** Returns <b>true</b> if the TextField wraps its contents if too long to
      * fit on a single line.
      * @see #setWrapsContents
      */
    public boolean wrapsContents() {
        return _canWrap;
    }

   /** Configures the TextField to allow the user to edit its text. */
    public void setEditable(boolean aFlag) {
        if (_editable != aFlag) {
            _editable = aFlag;
            setSelectable(aFlag);
            if( aFlag && wrapsContents() )
                setWrapsContents(false);
        }
    }

    /** Returns <b>true</b> if the TextField is editable.
      * @see #setEditable
      */
    public boolean isEditable() {
        return _editable;
    }

    /** Returns <b>true</b> if the TextField's contents are currently being
      * edited.
      */
    public boolean isBeingEdited() {
        return _editing;
    }

    /** Overridden to return TEXT_CURSOR when the TextField is selectable or
      * editable.
      */
    public int cursorForPoint(int x, int y) {
        if (isEditable() || isSelectable()) {
            return TEXT_CURSOR;
        }
        return ARROW_CURSOR;
    }



/* other attributes */

    /** Sets the TextField's owner, the object that it notifies of important
      * events such as receiving a new key Event.
      * @see TextFieldOwner
      */
    public void setOwner(TextFieldOwner owner) {
        _owner = owner;
    }

    /** Returns the TextField's owner.
      * @see #setOwner
      */
    public TextFieldOwner owner() {
        return _owner;
    }

    /** Sets the TextField's filter, the object that inspects each key
      * Event received by the TextField.
      * @see TextFilter
      */
    public void setFilter(TextFilter aFilter) {
        _filter = aFilter;
    }

    /** Returns the TextField's text filter.
      * @see #setFilter
      */
    public TextFilter filter() {
        return _filter;
    }

    /** Sets the Target and the command it should receive when the TextField's
      * contents change because editing has ended.
      */
    public void setContentsChangedCommandAndTarget(String aCommand,
                                                   Target aTarget) {
        _contentsChangedCommand = aCommand;
        _contentsChangedTarget = aTarget;
    }

    /** Returns the contents changed Target.
      * @see #setContentsChangedCommandAndTarget
      */
    public Target contentsChangedTarget() {
        return _contentsChangedTarget;
    }

    /** Returns the contents changed command.
      * @see #setContentsChangedCommandAndTarget
      */
    public String contentsChangedCommand() {
        return _contentsChangedCommand;
    }

    /** Sets the TextField whose text is selected when the TextField
      * receives a Return or Tab key Event.
      */
    public void setTabField(TextField aTextField) {
        if (aTextField == null) {
            _tabTarget = null;
            _tabCommand = null;
        } else if (aTextField != this) {
            _tabTarget = aTextField;
            _tabCommand = SELECT_TEXT;
        }
    }

    /** Returns the TextField whose text is selected when the TextField
      * receives a Return or Tab key Event.
      * @see #setTabField
      */
    public TextField tabField() {
        if (_tabTarget instanceof TextField) {
            return (TextField)_tabTarget;
        }

        return null;
    }

    /** Sets the TextField whose text is selected when the TextField
      * receives a Backtab key Event.
      */
    public void setBacktabField(TextField aTextField) {
        if (aTextField == null) {
            _backtabTarget = null;
            _backtabCommand = null;
        } else if (aTextField != this) {
            _backtabTarget = aTextField;
            _backtabCommand = SELECT_TEXT;
        }
    }

    /** Returns the TextField whose text is selected when the TextField
      * receives a Backtab key Event.
      * @see #setBacktabField
      */
    public TextField backtabField() {
        if (_backtabTarget instanceof TextField) {
            return (TextField)_backtabTarget;
        }

        return null;
    }

    /** Sets the Target for the TextField's Return key Event.
      */
    public void setTarget(Target aTarget) {
        _commitTarget = aTarget;
    }

    /** Sets the command the Target should receive when the TextField
      * receives a Return key Event.
      */
    public void setCommand(String aCommand) {
        _commitCommand = aCommand;
    }

    /** Returns the Return Target.
      * @see #setTarget
      */
    public Target target() {
        return _commitTarget;
    }

    /** Returns the Return command.
      * @see #setCommand
      */
    public String command() {
        return _commitCommand;
    }


/* content */

    /** Sets the contents of the TextField to <b>aString</b> */
    public void setStringValue(String aString) {
        if (aString != null && aString.equals(stringValue())) {
            if (isBeingEdited()) {
                cancelEditing();
            }
            return;
        }

        if (aString == null) {
            _contents = new FastStringBuffer();
        } else {
            _contents = new FastStringBuffer(aString);
        }
        _oldContents = null;

        if (isBeingEdited()) {
            cancelEditing();
        } else {
            draw();
        }
    }

    /** Returns the TextField's contents.
      * @see #setStringValue
      */
    public String stringValue() {
        if (_contents == null) {
            return "";
        }
        return _contents.toString();
    }

    /** Replaces the string enclosed by <b>aRange</b> with <b>aString</b>.  If
      * <b>aRange</b> is a null range, this method inserts <b>aString</b> into
      * the text.  If <b>aString</b> is <b>null</b> or the empty string, this
      * method removes the text defined by <b>aRange</b>.
      */
    public void replaceRangeWithString(Range aRange, String aString) {
        String contents = stringValue();
        Range  r = new Range();
        String before,after;

        r.index  = aRange.index;
        r.length = aRange.length;
        r.intersectWith(new Range(0,contents.length()));

        if( r.isNullRange()) {
            r.index = contents.length();
            r.length= 0;
        }

        before = contents.substring(0,r.index);
        after  = contents.substring(r.index+r.length);

        if( aString != null )
            _contents = new FastStringBuffer(before + aString + after );
        else
            _contents = new FastStringBuffer(before + after);

        if( isBeingEdited() ) {
            Range selectedRange;

            r.index = 0;
            r.length = _contents.length();
            selectedRange = selectedRange();
            selectedRange.intersectWith(r);
            if( selectedRange.isNullRange())
                selectRange(new Range(_contents.length,0));
            else
                selectRange(selectedRange);
        } else
            draw();
    }

    /** Returns the string included in <b>aRange</b>. */
    public String stringForRange(Range aRange) {
        String contents = stringValue();
        Range r = new Range();
        r.index = aRange.index;
        r.length = aRange.length;
        r.intersectWith( new Range(0,contents.length()));
        if( r.isNullRange())
            return "";
        return contents.substring(r.index,r.index+r.length());
    }

    /** Returns the selected portion of the TextField's contents.  If none,
      * returns the empty string.
      * @see #stringValue
      */
    public String selectedStringValue() {
        int     start, stop;

        if (hasInsertionPoint()) {
            return "";
        }

        start = selectionStart();
        stop = selectionStop();

        if (start == -1 || stop == -1) {
            return "";
        }

        if (stop == _contents.length()) {
            return _contents.toString().substring(start);
        }

        return _contents.toString().substring(start, stop);
    }

    /** Sets the contents of the TextField to the string version of
      * <b>anInt</b>.
      */
    public void setIntValue(int anInt) {
        setStringValue(Integer.toString(anInt));
    }

    /** Returns the integer value of the TextField's contents, or <b>0</b> if
      * not a number.
      */
    public int intValue() {
        return parseInt(_contents.toString());
    }

    /** Returns <b>true</b> if the TextField contains no text.
      */
    public boolean isEmpty() {
        return (charCount() == 0);
    }

    /** Returns the number of characters the TextField contains. */
    public int charCount() {
        return _contents.length();
    }

    /** Returns the baseline of the first line of text.
      */
    public int baseline() {
        String  contentString;
        Rect    interiorRect;
        Size    stringSize;
        int     y;

        contentString = _contents.toString();
        stringSize = _font.fontMetrics().stringSize(contentString);

        interiorRect = Rect.newRect();
        border.computeInteriorRect(0, 0, bounds.width, bounds.height,
                                   interiorRect);
        y = interiorRect.maxY() -
                        (interiorRect.height - stringSize.height) / 2 -
                        _font.fontMetrics().descent();

        Rect.returnRect(interiorRect);

        return y;
    }



/* selection */

    /** Selects characters in the Range <b>aRange</b>, unless the TextField is
      * not selectable.
      */
    public void selectRange(Range aRange) {
        if( aRange.length < 0 || aRange.index < 0 )
            throw new InconsistencyException("TextField - invalid range: " +
                                                                    aRange);
        selectRange(aRange.index,aRange.index + aRange.length);
    }

    /** Selects characters <b>start</b> through <b>stop</b> of the
      * TextField's contents, unless the TextField is not selectable.
      * @see #selectText
      */
    void selectRange(int start, int stop) {
        if (!isSelectable()) {
            return;
        }

        if (start < 0) {
            start = 0;
        } else if (start > _contents.length()) {
            start = _contents.length();
        }
        if (stop < 0) {
            stop = 0;
        } else if (stop > _contents.length()) {
            stop = _contents.length();
        }

        _selectionAnchorChar = start;
        _selectionEndChar = stop;

        drawInterior();
        if (isEditable() && !isBeingEdited()) {
            _startEditing(true);

            if (hasInsertionPoint()) {
                showCaret();
            }
        }
    }

    /** Selects the TextField's contents, unless it is not selectable.
      * @see #selectRange
      */
    public void selectText() {
        selectRange(0, charCount());
    }

    /** Places the TextField's insertion point at <b>position</b>, unless
      * the TextField is not selectable.
      */
    public void setInsertionPoint(int position) {
        selectRange(position, position);
    }

    int selectionAnchorPoint() {
        return _selectionAnchorChar;
    }

    int selectionEndPoint() {
        return _selectionEndChar;
    }

    /** Returns a Range containing the current selection. If no selection
      * exists, this method returns a null range.
      */
    public Range selectedRange() {
        if( hasInsertionPoint() ) {
            return new Range(_selectionAnchorChar,0);
        }
        if( _selectionAnchorChar == -1 ||
            _selectionEndChar == -1 )
            return new Range();
        else
            return Range.rangeFromIndices(selectionStart(),selectionStop());
    }

    /** Returns the location of the first selected character, or <b>-1</b> if no
      * selection or insertion point.
      */
    int selectionStart() {
        return (_selectionAnchorChar < _selectionEndChar) ?
                                    _selectionAnchorChar : _selectionEndChar;
    }

    /** Returns the location of the last selected character, or <b>-1</b> if no
      * selection or insertion point.
      */
    int selectionStop() {
        return (_selectionAnchorChar < _selectionEndChar) ?
                                    _selectionEndChar : _selectionAnchorChar;
    }

    /** Returns <b>true</b> if the TextField has a selection. */
    public boolean hasSelection() {
        return (_selectionAnchorChar != _selectionEndChar);
    }

    /** Returns <b>true</b> if the TextField has an insertion point. */
    public boolean hasInsertionPoint() {
        return (_selectionAnchorChar == _selectionEndChar &&
                _selectionAnchorChar != -1);
    }

    Rect caretRect() {
        FontMetrics  fontMetrics;
        Rect            interiorRect;
        int             y1, y2, cutoff;

        fontMetrics = _font.fontMetrics();
        if (fontMetrics == null) {
            return null;
        }

        interiorRect = interiorRect();
        y2 = interiorRect.maxY() - (interiorRect.height - _fontHeight) / 2;

        y1 = y2 - fontMetrics.charHeight();
        cutoff = border.topMargin();
        if (y1 < cutoff) {
            y1 = cutoff;
        }

        Rect.returnRect(interiorRect);

        /* we need to take endChar to make scroll offset computation work */
        return Rect.newRect(xPositionOfCharacter(_selectionEndChar), y1, 1,
                            y2 - y1);
    }

    Rect interiorRect() {
        Rect interiorRect = Rect.newRect();

        border.computeInteriorRect(0, 0, width(), height(), interiorRect);
        return interiorRect;
    }

    Rect rectForRange(int start, int stop) {
        Rect    tmpRect;
        int     startX;

        startX = xPositionOfCharacter(start);
        tmpRect = interiorRect();
        tmpRect.setBounds(startX, tmpRect.y,
                          xPositionOfCharacter(stop) - startX + 1,
                          tmpRect.height);

        return tmpRect;
    }



/* drawing */

    /** Returns the X-coordinate of character number <b>charNumber</b>. */
    public int xPositionOfCharacter(int charNumber) {
        FontMetrics  fontMetrics;
        String       contentString;
        int          startX;
        int          stringWidth;

        fontMetrics = _font.fontMetrics();
        contentString = _contents.toString();
        stringWidth   = fontMetrics.stringWidth(contentString);

        startX = absoluteXOriginForStringWithWidth(stringWidth);

        if( charNumber <= 0 )
            return startX;
        else
            return startX + fontMetrics.stringWidth(
                             contentString.substring(0, charNumber));
    }

    /** Returns the character number for X-coordinate <b>x</b>. */
    public int charNumberForPoint(int x) {
        FontMetrics     fontMetrics;
        String          contentString;
        int             contentLength, stringWidth, i, width,oldWidth,delta;
        int             startX;

        contentLength = _contents.length();
        if (contentLength == 0) {
            return 0;
        }

        fontMetrics = _font.fontMetrics();
        contentString = _contents.toString();
        stringWidth = fontMetrics.stringWidth(contentString);

        startX = absoluteXOriginForStringWithWidth(stringWidth);
        if (x < startX) {
            return 0;
        } else if (x > (startX + stringWidth)) {
            return contentLength;
        }

        oldWidth = 0;
        for (i = 1; i < contentLength; i++) {
            width = fontMetrics.stringWidth(contentString.substring(0, i));
            delta = width - oldWidth;
            if (x <= (startX + width)) {
                if (x > (startX + oldWidth + (delta / 2))) {
                    return i;
                } else {
                    return i - 1;
                }
            }

            oldWidth = width;
        }

        return contentLength;
    }

    void drawViewCaret(Graphics g) {
        Rect    caretRect;

        if (!_caretShowing || _selectionAnchorChar == -1 ||
            _selectionAnchorChar != _selectionEndChar ||
            !shouldShowSelection ) {
            return;
        }

        g.setColor(_caretColor);
        caretRect = caretRect();
        g.drawLine(caretRect.x, caretRect.y, caretRect.x, caretRect.maxY()-1);
        Rect.returnRect(caretRect);
    }

    Vector stringVectorForContents(int maxWidth) {
        int charWidths[];
        FontMetrics fm;
        int currentWidth;
        Vector result = new Vector();
        String contents = stringValue();
        int    index,firstIndex,length,lastSpace;
        int    size;
        char   buf[] = new char[1];
        char   ch;

        fm = font().fontMetrics();
        charWidths = fm.widthsArray();

        index = 0;
        length = contents.length();
        firstIndex = index;
        currentWidth = 0;
        lastSpace = -1;

        while( index < length ) {
            ch = contents.charAt(index);
            if( ch == ' ' || ch == '\t' )
                lastSpace = index;
            if( ch == '\n' ) {
                result.addElement(contents.substring(firstIndex,index));
                index++;
                firstIndex = index;
                currentWidth = 0;
                lastSpace = -1;
            } else {
                if( ch < 256 )
                    currentWidth += charWidths[ch];
                else {
                    buf[0] = ch;
                    currentWidth += fm.stringWidth(new String(buf));
                }
                if( currentWidth > maxWidth ) {
                    if( index == firstIndex ) {/* One char per line minimum */
                        result.addElement(contents.substring(firstIndex,firstIndex+1));
                        index++;
                        firstIndex = index;
                    } if( lastSpace == -1 ) {
                        result.addElement(contents.substring(firstIndex,index));
                        firstIndex = index;
                    } else {
                        result.addElement(contents.substring(firstIndex,lastSpace));
                        firstIndex = lastSpace+1;
                        index = firstIndex;
                    }
                    currentWidth = 0;
                    lastSpace = -1;
                } else
                    index++;
            }
        }

        if( firstIndex < length) {
            result.addElement(contents.substring(firstIndex));
        }
        return result;
    }


    /** Draws the String at position (<b>x</b>, <b>y</b>).  You never call this
      * method directly, but should override to produce custom string drawing.
      */
    public void drawViewStringAt(Graphics g, String aString, int x, int y) {
        if (_shadowed) {
            g.setColor(Color.black);
            g.drawString(aString, x + 2, y + 2);
        }
        g.setColor(_textColor);
        g.drawString(aString, x, y);
    }

    int absoluteXOriginForStringWithWidth(int stringWidth) {
        int x;
        if (_justification == Graphics.RIGHT_JUSTIFIED)
            x = width() - border.rightMargin() - rightIndent()
                - stringWidth - _scrollOffset;
        else if (_justification == Graphics.CENTERED)
            x = border.leftMargin() + leftIndent() +
                ((width() - (border.widthMargin() + widthIndent())
                  - stringWidth) / 2) - _scrollOffset;
        else
            x = border.leftMargin() + leftIndent() - _scrollOffset;
        return x;
    }


    void drawViewLine(Graphics g, String aString, Size stringSize, int y) {
        int     x, offset;

        if (stringSize == null) {
            stringSize = _font.fontMetrics().stringSize(aString);
        }

        x = absoluteXOriginForStringWithWidth(stringSize.width);

        drawViewStringAt(g, aString, x, y);
    }

    /** Draws the portion of the TextField's interior specified by
      * <b>interiorRect</b>.  Calls <b>drawViewStringAt()</b> to draw the text.
      * You never call this method directly, but should override to produce
      * custom TextField drawing.
      */
    public void drawViewInterior(Graphics g, Rect interiorRect) {
        Vector          stringVector;
        String          contentString;
        Size            stringSize;
        Rect            caretRect;
        int             y, x1, x2, i, count, delta;

        /* get the baseline of the first line of text */
        y = baseline();

        /* clip to within bezel */
        g.pushState();
        g.setClipRect(interiorRect);

        /* draw the background */
        if (!isTransparent()) {
            /* we want different color for non-editable, but no way to
                * enforce it here and get the right behavior for people who
                * don't want to follow the "standard" look; developer should
                * explicitly set the background non-editable and selection
                * colors
                */
            g.setColor(_backgroundColor);
            g.fillRect(interiorRect);
        }

      /* draw the selection rectangle */
        if (_selectionAnchorChar != _selectionEndChar &&
            shouldShowSelection && isSelectable()) {
            x1 = xPositionOfCharacter(selectionStart());
            x2 = xPositionOfCharacter(selectionStop());
            caretRect = caretRect();

            g.setColor(_selectionColor);
            g.fillRect(x1, caretRect.y, x2 - x1, caretRect.height);

            Rect.returnRect(caretRect);
        }

        /* draw the contents */
        g.setFont(_font);

        contentString = _contents.toString();
        stringSize = _font.fontMetrics().stringSize(contentString);

        if (!_canWrap || isEditable()) {
            drawViewLine(g, contentString, stringSize, y);
        } else {
            stringVector = stringVectorForContents(interiorRect.width);
            count = stringVector.count();

            if (count > 1) {
                y += (interiorRect.height - stringSize.height) / 2;
                delta = (interiorRect.height -
                                (int)(stringSize.height * count)) / 2;
                y -= delta + (count - 1) * stringSize.height;
            }
            for (i = 0; i < count; i++) {
                drawViewLine(g, (String)stringVector.elementAt(i), null, y);
                y += stringSize.height;
            }
        }

        if (isBeingEdited() && _caretShowing) {
            drawViewCaret(g);
        }
        g.popState();
    }

    /** Draws the TextField's border using its Border.  You never call this
      * method directly, but should override to produce custom border drawing.
      */
    public void drawViewBorder(Graphics g) {
        if (border != null) {
            border.drawInRect(g, 0, 0, width(), height());
            return;
        }

        // ALERT why drawing the background from here?

        if (!isTransparent() && _backgroundColor != null) {
            g.setColor(_backgroundColor);
            g.fillRect(0, 0, width(), height());
        }
    }

    /** Draws the TextField.  Calls <b>drawViewBorder()</b> and
      * <b>drawViewInterior()</b>.  You never call this method directly - call
      * the TextField's <b>draw()</b> method to draw the TextField.
      */
    public void drawView(Graphics g) {
        Rect    interiorRect;

        drawViewBorder(g);

        interiorRect = interiorRect();
        drawViewInterior(g, interiorRect);
        Rect.returnRect(interiorRect);
    }

    /* Redraws the area occupied by the caret. */
    void drawCaret() {
        Rect    caretRect;

        caretRect = caretRect();
        draw(caretRect);
        Rect.returnRect(caretRect);
    }

    /* Hides the caret. */
    void hideCaret() {
        _caretShowing = false;
        drawCaret();
    }

    /* Shows the caret. */
    void showCaret() {
        _caretShowing = true;
        drawCaret();
    }

    /** Redraws the TextField's interior. */
    public void drawInterior() {
        Rect    interiorRect;

        interiorRect = interiorRect();
        draw(interiorRect);
        Rect.returnRect(interiorRect);
    }



/* mouse events */

    /** Overridden to process mouse Events within the TextField. */
    public boolean mouseDown(MouseEvent event) {
        Rect            redrawRect = null;
        boolean         caretWasShowing;
        Range r,wasSelected;

        _clickCount = event.clickCount();
        if( _clickCount > 3 )
            return true;

        if (!isSelectable()) {
            return false;
        }

        if (!isBeingEdited()) {
            if(isEditable())
                _startEditing(true);
            else if(isSelectable())
                _startEditing(false);
        }

        /** This should be performed after _startEditing to
         *  take in account this last change.
         */
        caretWasShowing = _caretShowing;

        _canBlink = _caretShowing = false;
        _mouseDragging = true;

        if (hasSelection()) {
            redrawRect = rectForRange(selectionStart(), selectionStop());
        } else if (caretWasShowing) {
            hideCaret();
        }

        wasSelected = selectedRange();

        if(event.isShiftKeyDown() && _clickCount == 1) {
            _selectionEndChar = charNumberForPoint(event.x);
            if( redrawRect != null )
                redrawRect.unionWith(rectForRange(selectionStart(),selectionStop()));
            else
                redrawRect = rectForRange(selectionStart(),selectionStop());
        } else {
            _selectionAnchorChar = _selectionEndChar = _initialAnchorChar =
              charNumberForPoint(event.x);
        }

        switch( _clickCount ) {
        case 2:
            r = groupForIndex(_selectionAnchorChar);
            if(!r.isNullRange()) {
              if(event.isShiftKeyDown()) {
                  Range otherRange = new Range(wasSelected);
                  otherRange.unionWith(r);
                  selectRange(otherRange);
              } else
                  selectRange(r);
            }
            redrawRect = null;
            break;
        case 3:
            selectRange(new Range(0,charCount()));
            redrawRect = null;
            break;
        default:
            break;
        }

        if (redrawRect != null) {
            draw(redrawRect);
            Rect.returnRect(redrawRect);
        }

        return true;
    }

    void _computeScrollOffset() {
        Rect    caretRect,interiorRect;
        int     leftOffset, rightOffset;

        leftOffset = border.leftMargin() + leftIndent();
        rightOffset = border.rightMargin() + rightIndent();

        caretRect = caretRect();
        interiorRect = interiorRect();

        /* Always cancel any scrolling if the text now fit */
        if( (interiorRect.width - (leftIndent() + rightIndent())) >
            _font.fontMetrics().stringWidth(stringValue())) {
            _scrollOffset = 0;
            return;
        }

        if (caretRect.x >= leftOffset &&
            caretRect.x < bounds.width - rightOffset) {
            Rect.returnRect(caretRect);

            return;
        }

        if (caretRect.x <  leftOffset) {
            _scrollOffset += caretRect.x - leftOffset;
        } else {
            _scrollOffset += caretRect.x - (bounds.width - rightOffset);
        }

        Rect.returnRect(caretRect);
    }

    /** Overridden to process mouse Events within the TextField. */
    public void mouseDragged(MouseEvent event) {
        Rect    redrawRect;
        Size    stringSize;
        int     oldOffset, offset, minOffset, maxOffset, sideWidths,
                stringWidth, oldEndChar, left, right, delta = 0;
        boolean shouldRefresh = true;

        if (!isSelectable())
            return;

        /* If clickCount > 2, we have selected everything. Nothing
         * should happend during mouseDrag
         */
        if( _clickCount > 2 )
            return;

        oldEndChar = _selectionEndChar;
        _selectionEndChar = charNumberForPoint(event.x);

        if (_clickCount == 2 ) { /* Per word selection dragging */
            Range startRange  = groupForIndex(_initialAnchorChar);
            Range endRange    = groupForIndex(_selectionEndChar);
            Range unionRange  = Range.rangeFromUnion(startRange,endRange);
            if(!unionRange.isNullRange()) {
                if( endRange.index > startRange.index ) {
                    _selectionAnchorChar = startRange.index;
                    _selectionEndChar    = endRange.index + endRange.length;

                } else {
                    _selectionAnchorChar = startRange.index +
                                           startRange.length;
                    _selectionEndChar    = endRange.index;
                }
            }
        }

        if (!containsPointInVisibleRect(event.x, 1)) {
            oldOffset = _scrollOffset;
            _computeScrollOffset();
            if(_scrollOffset != oldOffset ) {
                drawInterior();
                shouldRefresh = false;
            }
        }

        if (shouldRefresh && _selectionEndChar != oldEndChar) {
            if (_selectionEndChar < _selectionAnchorChar &&
                oldEndChar > _selectionAnchorChar ||
                _selectionEndChar > _selectionAnchorChar &&
                oldEndChar < _selectionAnchorChar) {
                left = selectionStart();
                right = selectionStop();
                if (oldEndChar < left) {
                    left = oldEndChar;
                }
                if (oldEndChar > right) {
                    right = oldEndChar;
                }
            } else {
                if (_selectionEndChar > oldEndChar) {
                    left = oldEndChar;
                    right = _selectionEndChar;
                } else {
                    left = _selectionEndChar;
                    right = oldEndChar;
                }
            }

            redrawRect = rectForRange(left, right);
            draw(redrawRect);
            Rect.returnRect(redrawRect);
        }
    }

    /** Overridden to process mouse Events within the TextField. */
    public void mouseUp(MouseEvent event) {
        _mouseDragging = false;

        if (!hasSelection() && isEditable()) {
            _caretShowing = _canBlink = true;
            drawCaret();
        }

        _initialAnchorChar = -1;
        _clickCount = 0;
    }



/* key events */

    void _keyDown(KeyEvent event) {
        String          contentString;
        int             oldAnchor, oldOffset, start, condition;
        boolean         didChange, wasASelection;

        if (event.isReturnKey() || event.isTabKey() || event.isBackTabKey()) {
            if (event.isReturnKey()) {
                condition = TextFieldOwner.RETURN_KEY;
            } else if (event.isTabKey()) {
                condition = TextFieldOwner.TAB_KEY;
            } else {
                condition = TextFieldOwner.BACKTAB_KEY;
            }
            if (_owner != null &&
                !_owner.textEditingWillEnd(this, condition, _textChanged)) {
                return;
            }

            didChange = _textChanged;
            _completeEditing();

            if (_owner != null) {
                _owner.textEditingDidEnd(this, condition, didChange);
            }

            if (event.isBackTabKey()) {
                sendBacktabCommand();
            } else if (event.isTabKey()) {
                sendTabCommand();
            } else {
                sendCommitCommand();
            }

            return;
        } else if (event.isLeftArrowKey()) {
            if( event.isShiftKeyDown() ) {
              int oldScrollOffset = _scrollOffset;
              selectRange(_selectionAnchorChar,_selectionEndChar - 1);
              _computeScrollOffset();
              if( _scrollOffset != oldScrollOffset ) {
                  drawInterior();
              }
            } else {
                oldAnchor = _selectionAnchorChar;
                wasASelection = false;
                if (_selectionAnchorChar != _selectionEndChar) {
                    wasASelection = true;
                    _selectionAnchorChar = selectionStart();
                    oldAnchor = -1;
                } else if (_selectionAnchorChar > 0) {
                    hideCaret();
                    _selectionAnchorChar--;
                }
                _selectionEndChar = _selectionAnchorChar;

                if (oldAnchor != _selectionAnchorChar) {
                    oldOffset = _scrollOffset;
                    _computeScrollOffset();
                    if (oldOffset != _scrollOffset || wasASelection) {
                        _caretShowing = true;
                        drawInterior();
                    } else {
                        showCaret();
                    }
                }
            }
            return;
        } else if (event.isRightArrowKey()) {
            if( event.isShiftKeyDown() ) {
              int oldScrollOffset = _scrollOffset;
              selectRange(_selectionAnchorChar,_selectionEndChar + 1);
              _computeScrollOffset();
              if( _scrollOffset != oldScrollOffset ) {
                  drawInterior();
              }
            } else {
                oldAnchor = _selectionAnchorChar;
                wasASelection = false;
                if (_selectionAnchorChar != _selectionEndChar) {
                  wasASelection = true;
                  _selectionAnchorChar = selectionStop();
                  oldAnchor = -1;
                } else if (_selectionAnchorChar < _contents.length()) {
                  hideCaret();
                  _selectionAnchorChar++;
                  if (_selectionAnchorChar > _contents.length()) {
                    _selectionAnchorChar = _contents.length();
                  }
                }
                _selectionEndChar = _selectionAnchorChar;

                if (oldAnchor != _selectionAnchorChar) {
                  oldOffset = _scrollOffset;
                  _computeScrollOffset();
                  if (oldOffset != _scrollOffset || wasASelection) {
                    _caretShowing = true;
                    drawInterior();
                  } else {
                    showCaret();
                  }
                }
            }
            return;
        } else if(event.isHomeKey()) {
            Range r = selectedRange();
            int oldScrollOffset;
            if( event.isShiftKeyDown() )
              selectRange(_selectionAnchorChar,0);
            else
              selectRange(new Range(0,0));

            oldScrollOffset = _scrollOffset;
            _computeScrollOffset();
            if( _scrollOffset != oldScrollOffset )
              drawInterior();
            return;
        } else if(event.isEndKey())  {
            Range r = selectedRange();
            int oldScrollOffset;
            int lastIndex = _contents.length();

            if( event.isShiftKeyDown())
                selectRange(_selectionAnchorChar,lastIndex);
            else
              selectRange(new Range(lastIndex,0));

            oldScrollOffset = _scrollOffset;
            _computeScrollOffset();
            if( _scrollOffset != oldScrollOffset )
              drawInterior();
            return;
        } else if (!event.isBackspaceKey() &&
                   !event.isDeleteKey() &&
                   !event.isPrintableKey()) {
            return;
        }

        if (_oldContents == null) {
            _oldContents = new FastStringBuffer(_contents.toString());
        }

        hideCaret();

        /* delete the selection */
        if (_selectionAnchorChar != _selectionEndChar) {
            contentString = _contents.toString();

            start = selectionStart();

            _contents = new FastStringBuffer(
                                        contentString.substring(0, start));
            _contents.append(contentString.substring(selectionStop()));
            _selectionAnchorChar = _selectionEndChar = start;

            if (event.isBackspaceKey() || event.isDeleteKey()) {
                event = null;
            }
        }

        if (event != null) {
           if (event.isBackspaceKey()) {
                if (_contents.length() == 0 || _selectionAnchorChar == 0) {
                    showCaret();
                    return;
                }
                _contents.removeCharAt(_selectionAnchorChar - 1);
                _selectionAnchorChar--;
            } else if(event.isDeleteKey()) {
                if(_selectionAnchorChar < _contents.length())
                  _contents.removeCharAt(_selectionAnchorChar);
                else
                  showCaret();
            } else {
                _contents.insert((char)event.key, _selectionAnchorChar++);
            }

            _selectionEndChar = _selectionAnchorChar;
        }

        _computeScrollOffset();

        drawInterior();
        showCaret();
        if (_owner != null) {
            _owner.textWasModified(this);
            _textChanged = true;
        } else if (!_textChanged) {
            _textChanged = true;
        }
    }

    /** Overridden to process key Events within the TextField. If you want
      * to process or filter key Events yourself, implement the TextFilter
      * interface and set yourself as the TextField's filter.
      * @see #setFilter
      */
    public void keyDown(KeyEvent event) {
        KeyEvent        nextKey;

        if( !isEditable())
            return;

        if (_filter != null) {
            if (_filter.acceptsEvent(this, event, _keyVector)) {
                _keyVector.addElement(event);
            }
        } else {
            _keyVector.addElement(event);
        }

        while (!_keyVector.isEmpty()) {
            nextKey = (KeyEvent)_keyVector.removeFirstElement();
            _keyDown(nextKey);
        }
    }

    /** Override this method to start an editing session if necessary */
    public void setFocusedView() {
        if( _editing == false && isEditable() )
            _startEditing(true); /* Will call setFocusedView with _editing set to true*/
        else
            super.setFocusedView();
    }

    /* start editing */
    private void _startEditing(boolean sendMessage) {
        if (_superview != null) {
            if( isEditable() )
                _canBlink = _caretShowing = hasInsertionPoint();
            else
                _canBlink = _caretShowing = false;

            _editing = true;
            this.setFocusedView();

            if (sendMessage && _owner != null) {
                _owner.textEditingDidBegin(this);
            }
        }
    }

    /* stop all editing, saving changes */
    private void _completeEditing() {
        boolean         shouldPerformAction;

        _oldContents = null;

        shouldPerformAction = _textChanged;

        _editing = false;
        if (_superview != null) {
            _superview.setFocusedView(null);
        }

        if (shouldPerformAction) {
            sendContentsChangedCommand();
        }
    }

    void _startBlinkTimer() {
        if (blinkTimer == null) {
            blinkTimer = new Timer(this, BLINK_CARET, 750);
            blinkTimer.start();
        }
    }

    /** Make sure that the selection is defined */
    void _validateSelection() {
        String contents = stringValue();
        Range r;

        if( _selectionAnchorChar == -1 )
            selectRange( new Range(0,0));
        else {
            if( _selectionAnchorChar < 0 )
                _selectionAnchorChar = 0;
            else if( _selectionAnchorChar > _contents.length())
                _selectionAnchorChar = _contents.length();

            if( _selectionEndChar < 0 )
                _selectionEndChar = 0;
            else if( _selectionEndChar > _contents.length())
                _selectionEndChar = _contents.length();
        }
    }

    /** Overridden to notify the TextField it has become the focus of
      * KeyEvents.
      */
    public void startFocus() {
        _validateSelection();
        shouldShowSelection = true;
        if( isEditable())
            _startBlinkTimer();
    }

    /** Overridden to notify the TextField it has ceased being the focus of
      * KeyEvents.
      */
    public void stopFocus() {
        if (blinkTimer != null) {
            blinkTimer.stop();
            blinkTimer = null;
        }

        shouldShowSelection = false;
        _scrollOffset = 0;

        /* if editing, we didn't cause the loss of focus */
        if (_editing && _owner != null && isEditable()) {
            _owner.textEditingWillEnd(this, TextFieldOwner.LOST_FOCUS,
                                      _textChanged);
        }

        _caretShowing = _canBlink = false;

        if (_editing && isEditable()) {
            if (_owner != null) {
                _owner.textEditingDidEnd(this, TextFieldOwner.LOST_FOCUS,
                                         _textChanged);
            }

            if (_textChanged) {
                sendCommitCommand();
            }
        }
        _editing = _textChanged = false;

        drawInterior();
    }


    /** Overridden to notify the TextField that it has ceased being the focus
      * of KeyEvents, but that it will regain focus when the user clicks on
      * its InternalWindow.
      */
    public void pauseFocus() {
        if (blinkTimer != null) {
            blinkTimer.stop();
            blinkTimer = null;
            hideCaret();
        }
    }

    /** Overridden to notify the TextField it has become the focus of
      * KeyEvents, because the user clicked on its InternalWindow.
      */
    public void resumeFocus() {
        if(isEditable())
            _startBlinkTimer();
    }

   /** Causes the TextField to cancel editing by discarding any changes
     * that the user made.
     */
    public void cancelEditing() {
        if (!isBeingEdited()) {
            return;
        }

        if (_oldContents != null) {
            _contents = _oldContents;
            _oldContents = null;
        }

        /* cause stopFocus() to get called */
        _editing = false;
        if (_superview != null) {
            _superview.setFocusedView(null);
        }
    }

    /** Causes the TextField to complete any editing by retaining all
      * changes the user made to the TextField's text.
      * @see #cancelEditing
      */
    public void completeEditing() {
        if (!isBeingEdited()) {
            return;
        }

        if (_owner != null &&
            !_owner.textEditingWillEnd(this, TextFieldOwner.RESIGNED_FOCUS,
                                       _textChanged)) {
            return;
        }

        _completeEditing();

        if (_owner != null) {
            _owner.textEditingDidEnd(this, TextFieldOwner.RESIGNED_FOCUS,
                                     _textChanged);
        }
    }



/* commands */


    void sendCommand(String command, Target aTarget) {
        if (aTarget != null) {
            if (!(aTarget instanceof TextField) ||
                !command.equals(SELECT_TEXT) ||
                rootView().focusedView() == null) {
                aTarget.performCommand(command, this);
            }
        }
    }

    void sendTabCommand() {
        sendCommand(_tabCommand, _tabTarget);
    }

    void sendBacktabCommand() {
        sendCommand(_backtabCommand, _backtabTarget);
    }

    void sendContentsChangedCommand() {
        sendCommand(_contentsChangedCommand, _contentsChangedTarget);
    }

    void sendCommitCommand() {
        if (_commitCommand == null && _commitTarget == null &&
            _tabCommand != null && _tabTarget != null) {
            sendTabCommand();
        }

        sendCommand(_commitCommand, _commitTarget);
    }

    /** Implements the TextField's commands:
      * <ul>
      * <li>SELECT_TEXT - calls the <b>selectText()</b> method.
      * </ul>
      * @see #selectText
      */
    public void performCommand(String command, Object data) {
        if (BLINK_CARET.equals(command)) {
            blinkCaret();
        } else if (SELECT_TEXT.equals(command)) {
            selectText();
        } else {
            throw new NoSuchMethodError("unknown command: " + command);
        }
    }

    private void blinkCaret() {
        if (_canBlink) {
            _caretShowing = !_caretShowing;
            drawCaret();
        } else if (!_mouseDragging && hasInsertionPoint()) {
            _canBlink = true;
        }
    }

/* archiving */


    /** Describes the TextField class' information.
     * @see Codable#describeClassInfo
     */
    public void describeClassInfo(ClassInfo info) {
        super.describeClassInfo(info);

        info.addClass("netscape.application.TextField", 1);
        info.addPublicField(CONTENTS_KEY, STRING_TYPE);
        info.addField(OWNER_KEY, OBJECT_TYPE);
        info.addField(FILTER_KEY, OBJECT_TYPE);
        info.addField(TABREC_KEY, OBJECT_TYPE);
        info.addField(BACKTABREC_KEY, OBJECT_TYPE);
        info.addField(CONTENTSCHANGEDREC_KEY, OBJECT_TYPE);
        info.addField(COMMITREC_KEY, OBJECT_TYPE);
        info.addPublicField(BORDER_KEY, OBJECT_TYPE);
        info.addPublicField(FONT_KEY, OBJECT_TYPE);
        info.addPublicField(TEXTC_KEY, OBJECT_TYPE);
        info.addPublicField(BACKGROUNDC_KEY, OBJECT_TYPE);
        info.addPublicField(SELECTIONC_KEY, OBJECT_TYPE);
        info.addPublicField(CARETC_KEY, OBJECT_TYPE);
        info.addPublicField(TABCOM_KEY, STRING_TYPE);
        info.addPublicField(BACKTABCOM_KEY, STRING_TYPE);
        info.addPublicField(CONTENTSCHANGEDCOM_KEY, STRING_TYPE);
        info.addPublicField(COMMITCOM_KEY, STRING_TYPE);
        info.addField(SELECTIONANCH_KEY, INT_TYPE);
        info.addField(SELECTIONEND_KEY, INT_TYPE);
        info.addPublicField(JUSTIFICATION_KEY, INT_TYPE);
        info.addField(SCROLLOFFSET_KEY, INT_TYPE);
        info.addPublicField(EDITABLE_KEY, BOOLEAN_TYPE);
        info.addPublicField(SELECTABLE_KEY, BOOLEAN_TYPE);
        info.addPublicField(SHADOWED_KEY, BOOLEAN_TYPE);
        info.addPublicField(CANWRAP_KEY, BOOLEAN_TYPE);
        info.addField(AUTOSCROLLEVENT_KEY, BOOLEAN_TYPE);
        info.addPublicField(TRANSPARENT_KEY, BOOLEAN_TYPE);
    }

    /** Encodes the TextField instance.
     * @see Codable#decode
     */
    public void encode(Encoder encoder) throws CodingException {
        super.encode(encoder);

        encoder.encodeString(CONTENTS_KEY, _contents.toString());

        encoder.encodeObject(OWNER_KEY, (Codable)_owner);
        encoder.encodeObject(FILTER_KEY, (Codable)_filter);

        encoder.encodeObject(TABREC_KEY, (Codable)_tabTarget);
        encoder.encodeObject(BACKTABREC_KEY, (Codable)_backtabTarget);
        encoder.encodeObject(CONTENTSCHANGEDREC_KEY,
                               (Codable)_contentsChangedTarget);
        encoder.encodeObject(COMMITREC_KEY, (Codable)_commitTarget);

        if (border instanceof EmptyBorder) {
            encoder.encodeObject(BORDER_KEY, null);
        } else {
            encoder.encodeObject(BORDER_KEY, border);
        }

        encoder.encodeObject(FONT_KEY, _font);

        encoder.encodeObject(TEXTC_KEY, _textColor);
        encoder.encodeObject(BACKGROUNDC_KEY, _backgroundColor);
        encoder.encodeObject(SELECTIONC_KEY, _selectionColor);
        encoder.encodeObject(CARETC_KEY, _caretColor);

        encoder.encodeString(TABCOM_KEY, _tabCommand);
        encoder.encodeString(BACKTABCOM_KEY, _backtabCommand);
        encoder.encodeString(CONTENTSCHANGEDCOM_KEY,
                               _contentsChangedCommand);
        encoder.encodeString(COMMITCOM_KEY, _commitCommand);

        encoder.encodeInt(SELECTIONANCH_KEY, _selectionAnchorChar);
        encoder.encodeInt(SELECTIONEND_KEY, _selectionEndChar);
        encoder.encodeInt(JUSTIFICATION_KEY, _justification);
        encoder.encodeInt(SCROLLOFFSET_KEY, _scrollOffset);

        encoder.encodeBoolean(EDITABLE_KEY, _editable);
        encoder.encodeBoolean(SELECTABLE_KEY, _selectable);
        encoder.encodeBoolean(SHADOWED_KEY, _shadowed);
        encoder.encodeBoolean(CANWRAP_KEY, _canWrap);
        encoder.encodeBoolean(AUTOSCROLLEVENT_KEY, wantsAutoscrollEvents);
        encoder.encodeBoolean(TRANSPARENT_KEY, transparent);
    }

    /** Decodes the TextField instance.
     * @see Codable#decode
     */
    public void decode(Decoder decoder) throws CodingException {
        String          contentString;

        super.decode(decoder);

        contentString = decoder.decodeString(CONTENTS_KEY);
        if (contentString != null) {
            _contents = new FastStringBuffer(contentString);
        }

        _owner = (TextFieldOwner)decoder.decodeObject(OWNER_KEY);
        _filter = (TextFilter)decoder.decodeObject(FILTER_KEY);

        _tabTarget = (Target)decoder.decodeObject(TABREC_KEY);
        _backtabTarget = (Target)decoder.decodeObject(BACKTABREC_KEY);
        _contentsChangedTarget =
                    (Target)decoder.decodeObject(CONTENTSCHANGEDREC_KEY);
        _commitTarget = (Target)decoder.decodeObject(COMMITREC_KEY);

        setBorder((Border)decoder.decodeObject(BORDER_KEY));

        _font = (Font)decoder.decodeObject(FONT_KEY);

        _textColor = (Color)decoder.decodeObject(TEXTC_KEY);
        _backgroundColor = (Color)decoder.decodeObject(BACKGROUNDC_KEY);
        _selectionColor = (Color)decoder.decodeObject(SELECTIONC_KEY);
        _caretColor = (Color)decoder.decodeObject(CARETC_KEY);

        _tabCommand = decoder.decodeString(TABCOM_KEY);
        _backtabCommand = decoder.decodeString(BACKTABCOM_KEY);
        _contentsChangedCommand = decoder.decodeString(CONTENTSCHANGEDCOM_KEY);
        _commitCommand = decoder.decodeString(COMMITCOM_KEY);

        _selectionAnchorChar = decoder.decodeInt(SELECTIONANCH_KEY);
        _selectionEndChar = decoder.decodeInt(SELECTIONEND_KEY);
        _justification = decoder.decodeInt(JUSTIFICATION_KEY);
        _scrollOffset = decoder.decodeInt(SCROLLOFFSET_KEY);

        _editable = decoder.decodeBoolean(EDITABLE_KEY);
        _selectable = decoder.decodeBoolean(SELECTABLE_KEY);
        _shadowed = decoder.decodeBoolean(SHADOWED_KEY);
        _canWrap = decoder.decodeBoolean(CANWRAP_KEY);
        wantsAutoscrollEvents = decoder.decodeBoolean(AUTOSCROLLEVENT_KEY);
        transparent = decoder.decodeBoolean(TRANSPARENT_KEY);
    }

    /** Finishes the TextField instance decoding.
      * @see Codable#finishDecoding
      */
    public void finishDecoding() throws CodingException {
        super.finishDecoding();

        setFont(_font);
    }



/* components */

    /** @private */
    public void describeBuilderInfo(BuilderInfo info) {
        info.addFieldInfo(BORDER_KEY, BuilderInfo.BORDER);
        info.addFieldInfo(FONT_KEY, BuilderInfo.FONT);
        info.addFieldInfo(TEXTC_KEY, BuilderInfo.COLOR);
        info.addFieldInfo(BACKGROUNDC_KEY, BuilderInfo.COLOR,
                          BuilderInfo.ALTERNATE_KEY);
        info.addFieldInfo(SELECTIONC_KEY, BuilderInfo.COLOR,
                          BuilderInfo.SHIFT_KEY);
        info.addFieldInfo(CARETC_KEY, BuilderInfo.COLOR,
                          BuilderInfo.CONTROL_KEY);

        info.addEventInfo(TABREC_KEY, TABCOM_KEY);
        info.addEventInfo(BACKTABREC_KEY, BACKTABCOM_KEY);
        info.addEventInfo(CONTENTSCHANGEDREC_KEY, CONTENTSCHANGEDCOM_KEY);
        info.addEventInfo(COMMITREC_KEY, COMMITCOM_KEY);

        info.addFieldEnumeration(JUSTIFICATION_KEY, justificationNameArray,
                                 justificationValueArray, BuilderInfo.POPUP);

        info.setCommands(textFieldCommands);

        info.addPublicField(View.BUFFERED_KEY, BOOLEAN_TYPE);
    }

    /** @private */
    public void builderEncode(Encoder encoder) throws CodingException {
        encoder.encodeString(CONTENTS_KEY, _contents.toString());

        encoder.encodeObject(OWNER_KEY, (Codable)_owner);
        encoder.encodeObject(FILTER_KEY, (Codable)_filter);

        encoder.encodeObject(TABREC_KEY, (Codable)_tabTarget);
        encoder.encodeObject(BACKTABREC_KEY, (Codable)_backtabTarget);
        encoder.encodeObject(CONTENTSCHANGEDREC_KEY,
                               (Codable)_contentsChangedTarget);
        encoder.encodeObject(COMMITREC_KEY, (Codable)_commitTarget);
        encoder.encodeObject(BORDER_KEY, border);

        encoder.encodeObject(FONT_KEY, _font);

        encoder.encodeObject(TEXTC_KEY, _textColor);
        encoder.encodeObject(BACKGROUNDC_KEY, _backgroundColor);
        encoder.encodeObject(SELECTIONC_KEY, _selectionColor);
        encoder.encodeObject(CARETC_KEY, _caretColor);

        encoder.encodeString(TABCOM_KEY, _tabCommand);
        encoder.encodeString(BACKTABCOM_KEY, _backtabCommand);
        encoder.encodeString(CONTENTSCHANGEDCOM_KEY,
                               _contentsChangedCommand);
        encoder.encodeString(COMMITCOM_KEY, _commitCommand);

        encoder.encodeInt(SELECTIONANCH_KEY, _selectionAnchorChar);
        encoder.encodeInt(SELECTIONEND_KEY, _selectionEndChar);
        encoder.encodeInt(JUSTIFICATION_KEY, _justification);
        encoder.encodeInt(SCROLLOFFSET_KEY, _scrollOffset);

        encoder.encodeBoolean(EDITABLE_KEY, _editable);
        encoder.encodeBoolean(SELECTABLE_KEY, _selectable);
        encoder.encodeBoolean(SHADOWED_KEY, _shadowed);
        encoder.encodeBoolean(CANWRAP_KEY, _canWrap);

        encoder.encodeBoolean(TRANSPARENT_KEY, transparent);
        encoder.encodeBoolean(View.BUFFERED_KEY, isBuffered());
    }

    /** @private */
    public void builderDecode(Decoder decoder, String changedField) throws
                                                            CodingException {
        if (changedField.equals(CONTENTS_KEY)) {
            setStringValue(decoder.decodeString(CONTENTS_KEY));
        }

        _owner = (TextFieldOwner)decoder.decodeObject(OWNER_KEY);
        _filter = (TextFilter)decoder.decodeObject(FILTER_KEY);

        _tabTarget = (Target)decoder.decodeObject(TABREC_KEY);
        _backtabTarget = (Target)decoder.decodeObject(BACKTABREC_KEY);
        _contentsChangedTarget =
                    (Target)decoder.decodeObject(CONTENTSCHANGEDREC_KEY);
        _commitTarget = (Target)decoder.decodeObject(COMMITREC_KEY);

        if (changedField.equals(BORDER_KEY)) {
            setBorder((Border)decoder.decodeObject(BORDER_KEY));
        }
        if (changedField.equals(FONT_KEY)) {
            _font = (Font)decoder.decodeObject(FONT_KEY);
        }

        _textColor = (Color)decoder.decodeObject(TEXTC_KEY);
        _backgroundColor = (Color)decoder.decodeObject(BACKGROUNDC_KEY);
        _selectionColor = (Color)decoder.decodeObject(SELECTIONC_KEY);
        _caretColor = (Color)decoder.decodeObject(CARETC_KEY);

        _tabCommand = decoder.decodeString(TABCOM_KEY);
        _backtabCommand = decoder.decodeString(BACKTABCOM_KEY);
        _contentsChangedCommand = decoder.decodeString(CONTENTSCHANGEDCOM_KEY);
        _commitCommand = decoder.decodeString(COMMITCOM_KEY);

        _selectionAnchorChar = decoder.decodeInt(SELECTIONANCH_KEY);
        _selectionEndChar = decoder.decodeInt(SELECTIONEND_KEY);
        _justification = decoder.decodeInt(JUSTIFICATION_KEY);
        _scrollOffset = decoder.decodeInt(SCROLLOFFSET_KEY);

        if (changedField.equals(EDITABLE_KEY)) {
            setEditable(decoder.decodeBoolean(EDITABLE_KEY));
        } else if (changedField.equals(SELECTABLE_KEY)) {
            setSelectable(decoder.decodeBoolean(SELECTABLE_KEY));
        } else if (changedField.equals(SHADOWED_KEY)) {
            setDrawsDropShadow(decoder.decodeBoolean(SHADOWED_KEY));
        } else if (changedField.equals(CANWRAP_KEY)) {
            setWrapsContents(decoder.decodeBoolean(CANWRAP_KEY));
        } else if (changedField.equals(TRANSPARENT_KEY)) {
            setTransparent(decoder.decodeBoolean(TRANSPARENT_KEY));
        } else if (changedField.equals(View.BUFFERED_KEY)) {
            setBuffered(decoder.decodeBoolean(View.BUFFERED_KEY));
        }
    }

    boolean isWordCharacter(char c) {
        if( c >= '0' && c <= '9' ||
            c >= 'A' && c <= 'Z' ||
            c >= 'a' && c <= 'z' )
            return true;
        else
            return false;
    }


    Range groupForIndex(int index) {
        int length = charCount();
        int relFirstIndex,relLastIndex;
        int i,l;
        char c;

        if( length == 0 )
            return new Range();
        if( index < 0 )
            index = 0;
        else if( index >= length )
            index = length-1;

        i = index;
        c = _contents.charAt(i);

        if( c == '\n') {
            return new Range(i,1);
        }

        if( c == ' ' || c == '\t' ) {
            while(i>0) {
                c = _contents.charAt(i);
                if( c == ' ' || c == '\t' )
                    i--;
                else
                    break;
            }
            relFirstIndex = i+1;

            i = index;

            while(i<length) {
                c = _contents.charAt(i);
                if( c == ' ' || c == '\t')
                    i++;
                else
                    break;
            }
            relLastIndex = i-1;
            return new Range(relFirstIndex,relLastIndex - relFirstIndex +1);
        }

        if( !isWordCharacter(c) ) {
            return new Range( index, 1);
        }

        relFirstIndex = i;
        while( relFirstIndex > 0 ) {
            c = _contents.charAt(relFirstIndex-1);
            if(!isWordCharacter(c))
                break;
            relFirstIndex--;
        }


        relLastIndex = i;
        while( relLastIndex < (length-1)) {
            c = _contents.charAt(relLastIndex+1);
            if(!isWordCharacter(c))
                break;
            relLastIndex++;
        }

        return new Range(relFirstIndex, relLastIndex - relFirstIndex + 1);
    }
}
