FileDocCategorySizeDatePackage
TextDialog.javaAPI DocAndroid 1.5 API23870Wed May 06 22:41:56 BST 2009android.webkit

TextDialog

public class TextDialog extends android.widget.AutoCompleteTextView
TextDialog is a specialized version of EditText used by WebView to overlay html textfields (and textareas) to use our standard text editing.

Fields Summary
private WebView
mWebView
private boolean
mSingle
private int
mWidthSpec
private int
mHeightSpec
private int
mNodePointer
private boolean
mGotEnterDown
private boolean
mScrollToAccommodateCursor
private int
mMaxLength
private String
mPreChange
private char[]
mCharacter
private static final android.text.InputFilter[]
NO_FILTERS
Constructors Summary
TextDialog(android.content.Context context, WebView webView)
Create a new TextDialog.

param
context The Context for this TextDialog.
param
webView The WebView that created this.


                               
    /* package */     
        super(context);
        mWebView = webView;
        ShapeDrawable background = new ShapeDrawable(new RectShape());
        Paint shapePaint = background.getPaint();
        shapePaint.setStyle(Paint.Style.STROKE);
        ColorDrawable color = new ColorDrawable(Color.WHITE);
        Drawable[] array = new Drawable[2];
        array[0] = color;
        array[1] = background;
        LayerDrawable layers = new LayerDrawable(array);
        // Hide WebCore's text behind this and allow the WebView
        // to draw its own focusring.
        setBackgroundDrawable(layers);
        // Align the text better with the text behind it, so moving
        // off of the textfield will not appear to move the text.
        setPadding(3, 2, 0, 0);
        mMaxLength = -1;
        // Turn on subpixel text, and turn off kerning, so it better matches
        // the text in webkit.
        TextPaint paint = getPaint();
        int flags = paint.getFlags() | Paint.SUBPIXEL_TEXT_FLAG |
                Paint.ANTI_ALIAS_FLAG & ~Paint.DEV_KERN_TEXT_FLAG;
        paint.setFlags(flags);
        // Set the text color to black, regardless of the theme.  This ensures
        // that other applications that use embedded WebViews will properly
        // display the text in textfields.
        setTextColor(Color.BLACK);
        setImeOptions(EditorInfo.IME_ACTION_NONE);
    
Methods Summary
voidbringIntoView()

        if (getLayout() != null) {
            bringPointIntoView(Selection.getSelectionEnd(getText()));
        }
    
public booleandispatchKeyEvent(android.view.KeyEvent event)

        if (event.isSystem()) {
            return super.dispatchKeyEvent(event);
        }
        // Treat ACTION_DOWN and ACTION MULTIPLE the same
        boolean down = event.getAction() != KeyEvent.ACTION_UP;
        int keyCode = event.getKeyCode();
        Spannable text = (Spannable) getText();
        int oldLength = text.length();
        // Normally the delete key's dom events are sent via onTextChanged.
        // However, if the length is zero, the text did not change, so we 
        // go ahead and pass the key down immediately.
        if (KeyEvent.KEYCODE_DEL == keyCode && 0 == oldLength) {
            sendDomEvent(event);
            return true;
        }

        if ((mSingle && KeyEvent.KEYCODE_ENTER == keyCode)) {
            if (isPopupShowing()) {
                return super.dispatchKeyEvent(event);
            }
            if (!down) {
                // Hide the keyboard, since the user has just submitted this
                // form.  The submission happens thanks to the two calls
                // to sendDomEvent.
                InputMethodManager.getInstance(mContext)
                        .hideSoftInputFromWindow(getWindowToken(), 0);
                sendDomEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
                sendDomEvent(event);
            }
            return super.dispatchKeyEvent(event);
        } else if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
            // Note that this handles center key and trackball.
            if (isPopupShowing()) {
                return super.dispatchKeyEvent(event);
            }
            // Center key should be passed to a potential onClick
            if (!down) {
                mWebView.shortPressOnTextField();
            }
            // Pass to super to handle longpress.
            return super.dispatchKeyEvent(event);
        }

        // Ensure there is a layout so arrow keys are handled properly.
        if (getLayout() == null) {
            measure(mWidthSpec, mHeightSpec);
        }
        int oldStart = Selection.getSelectionStart(text);
        int oldEnd = Selection.getSelectionEnd(text);

        boolean maxedOut = mMaxLength != -1 && oldLength == mMaxLength;
        // If we are at max length, and there is a selection rather than a
        // cursor, we need to store the text to compare later, since the key
        // may have changed the string.
        String oldText;
        if (maxedOut && oldEnd != oldStart) {
            oldText = text.toString();
        } else {
            oldText = "";
        }
        if (super.dispatchKeyEvent(event)) {
            // If the TextDialog handled the key it was either an alphanumeric
            // key, a delete, or a movement within the text. All of those are
            // ok to pass to javascript.

            // UNLESS there is a max length determined by the html.  In that
            // case, if the string was already at the max length, an
            // alphanumeric key will be erased by the LengthFilter,
            // so do not pass down to javascript, and instead
            // return true.  If it is an arrow key or a delete key, we can go
            // ahead and pass it down.
            boolean isArrowKey;
            switch(keyCode) {
                case KeyEvent.KEYCODE_DPAD_LEFT:
                case KeyEvent.KEYCODE_DPAD_RIGHT:
                case KeyEvent.KEYCODE_DPAD_UP:
                case KeyEvent.KEYCODE_DPAD_DOWN:
                    isArrowKey = true;
                    break;
                case KeyEvent.KEYCODE_ENTER:
                    // For multi-line text boxes, newlines will
                    // trigger onTextChanged for key down (which will send both
                    // key up and key down) but not key up.
                    mGotEnterDown = true;
                default:
                    isArrowKey = false;
                    break;
            }
            if (maxedOut && !isArrowKey && keyCode != KeyEvent.KEYCODE_DEL) {
                if (oldEnd == oldStart) {
                    // Return true so the key gets dropped.
                    mScrollToAccommodateCursor = true;
                    return true;
                } else if (!oldText.equals(getText().toString())) {
                    // FIXME: This makes the text work properly, but it
                    // does not pass down the key event, so it may not
                    // work for a textfield that has the type of
                    // behavior of GoogleSuggest.  That said, it is
                    // unlikely that a site would combine the two in
                    // one textfield.
                    Spannable span = (Spannable) getText();
                    int newStart = Selection.getSelectionStart(span);
                    int newEnd = Selection.getSelectionEnd(span);
                    mWebView.replaceTextfieldText(0, oldLength, span.toString(),
                            newStart, newEnd);
                    mScrollToAccommodateCursor = true;
                    return true;
                }
            }
            if (isArrowKey) {
                // Arrow key does not change the text, but we still want to send
                // the DOM events.
                sendDomEvent(event);
            }
            mScrollToAccommodateCursor = true;
            return true;
        }
        // FIXME: TextViews return false for up and down key events even though
        // they change the selection. Since we don't want the get out of sync
        // with WebCore's notion of the current selection, reset the selection
        // to what it was before the key event.
        Selection.setSelection(text, oldStart, oldEnd);
        // Ignore the key up event for newlines. This prevents
        // multiple newlines in the native textarea.
        if (mGotEnterDown && !down) {
            return true;
        }
        // if it is a navigation key, pass it to WebView
        if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
                || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
                || keyCode == KeyEvent.KEYCODE_DPAD_UP
                || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
            // WebView check the trackballtime in onKeyDown to avoid calling
            // native from both trackball and key handling. As this is called 
            // from TextDialog, we always want WebView to check with native. 
            // Reset trackballtime to ensure it.
            mWebView.resetTrackballTime();
            return down ? mWebView.onKeyDown(keyCode, event) : mWebView
                    .onKeyUp(keyCode, event);
        }
        return false;
    
voidenableScrollOnScreen(boolean enable)

        mScrollToAccommodateCursor = enable;
    
voidfakeTouchEvent(float x, float y)
Create a fake touch up event at (x,y) with respect to this TextDialog. This is used by WebView to act as though a touch event which happened before we placed the TextDialog actually hit it, so that it can place the cursor accordingly.

        // We need to ensure that there is a Layout, since the Layout is used
        // in determining where to place the cursor.
        if (getLayout() == null) {
            measure(mWidthSpec, mHeightSpec);
        }
        // Create a fake touch up, which is used to place the cursor.
        MotionEvent ev = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP,
                x, y, 0);
        onTouchEvent(ev);
        ev.recycle();
    
booleanisSameTextField(int ptr)
Determine whether this TextDialog currently represents the node represented by ptr.

param
ptr Pointer to a node to compare to.
return
boolean Whether this TextDialog already represents the node pointed to by ptr.

        return ptr == mNodePointer;
    
public booleanonPreDraw()

        if (getLayout() == null) {
            measure(mWidthSpec, mHeightSpec);
        }
        return super.onPreDraw();
    
protected voidonTextChanged(java.lang.CharSequence s, int start, int before, int count)

        super.onTextChanged(s, start, before, count);
        String postChange = s.toString();
        // Prevent calls to setText from invoking onTextChanged (since this will
        // mean we are on a different textfield).  Also prevent the change when
        // going from a textfield with a string of text to one with a smaller 
        // limit on text length from registering the onTextChanged event.
        if (mPreChange == null || mPreChange.equals(postChange) ||
                (mMaxLength > -1 && mPreChange.length() > mMaxLength &&
                mPreChange.substring(0, mMaxLength).equals(postChange))) {
            return;
        }
        mPreChange = postChange;
        // This was simply a delete or a cut, so just delete the 
        // selection.
        if (before > 0 && 0 == count) {
            mWebView.deleteSelection(start, start + before);
            // For this and all changes to the text, update our cache
            updateCachedTextfield();
            return;
        }
        // Find the last character being replaced.  If it can be represented by
        // events, we will pass them to native (after replacing the beginning
        // of the changed text), so we can see javascript events.
        // Otherwise, replace the text being changed (including the last
        // character) in the textfield.
        TextUtils.getChars(s, start + count - 1, start + count, mCharacter, 0);
        KeyCharacterMap kmap =
                KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
        KeyEvent[] events = kmap.getEvents(mCharacter);
        boolean cannotUseKeyEvents = null == events;
        int charactersFromKeyEvents = cannotUseKeyEvents ? 0 : 1;
        if (count > 1 || cannotUseKeyEvents) {
            String replace = s.subSequence(start,
                    start + count - charactersFromKeyEvents).toString();
            mWebView.replaceTextfieldText(start, start + before, replace,
                    start + count - charactersFromKeyEvents,
                    start + count - charactersFromKeyEvents);
        } else {
            // This corrects the selection which may have been affected by the 
            // trackball or auto-correct.
            mWebView.setSelection(start, start + before);
        }
        updateCachedTextfield();
        if (cannotUseKeyEvents) {
            return;
        }
        int length = events.length;
        for (int i = 0; i < length; i++) {
            // We never send modifier keys to native code so don't send them
            // here either.
            if (!KeyEvent.isModifierKey(events[i].getKeyCode())) {
                sendDomEvent(events[i]);
            }
        }
    
public booleanonTrackballEvent(android.view.MotionEvent event)

        if (isPopupShowing()) {
            return super.onTrackballEvent(event);
        }
        if (event.getAction() != MotionEvent.ACTION_MOVE) {
            return false;
        }
        Spannable text = (Spannable) getText();
        MovementMethod move = getMovementMethod();
        if (move != null && getLayout() != null &&
            move.onTrackballEvent(this, text, event)) {
            // Need to pass down the selection, which has changed.
            // FIXME: This should work, but does not, so we set the selection
            // in onTextChanged.
            //int start = Selection.getSelectionStart(text);
            //int end = Selection.getSelectionEnd(text);
            //mWebView.setSelection(start, end);
            return true;
        }
        // If the user is in a textfield, and the movement method is not
        // handling the trackball events, it means they are at the end of the
        // field and continuing to move the trackball.  In this case, we should
        // not scroll the cursor on screen bc the user may be attempting to
        // scroll the page, possibly in the opposite direction of the cursor.
        mScrollToAccommodateCursor = false;
        return false;
    
voidremove()
Remove this TextDialog from its host WebView, and return focus to the host.

        // hide the soft keyboard when the edit text is out of focus
        InputMethodManager.getInstance(mContext).hideSoftInputFromWindow(
                getWindowToken(), 0);
        mWebView.removeView(this);
        mWebView.requestFocus();
        mScrollToAccommodateCursor = false;
    
public booleanrequestRectangleOnScreen(android.graphics.Rect rectangle)

        if (mScrollToAccommodateCursor) {
            return super.requestRectangleOnScreen(rectangle);
        }
        return false;
    
private voidsendDomEvent(android.view.KeyEvent event)
Send the DOM events for the specified event.

param
event KeyEvent to be translated into a DOM event.

        mWebView.passToJavaScript(getText().toString(), event);
    
public voidsetAdapterCustom(android.webkit.TextDialog$AutoCompleteAdapter adapter)
Always use this instead of setAdapter, as this has features specific to the TextDialog.

        if (adapter != null) {
            setInputType(EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE);
            adapter.setTextView(this);
        }
        super.setAdapter(adapter);
    
voidsetInPassword(boolean inPassword)
Determine whether to use the system-wide password disguising method, or to use none.

param
inPassword True if the textfield is a password field.

        if (inPassword) {
            setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.
                    TYPE_TEXT_VARIATION_PASSWORD);
        }
    
voidsetMaxLength(int maxLength)

        mMaxLength = maxLength;
        if (-1 == maxLength) {
            setFilters(NO_FILTERS);
        } else {
            setFilters(new InputFilter[] {
                new InputFilter.LengthFilter(maxLength) });
        }
    
voidsetNodePointer(int ptr)
Set the pointer for this node so it can be determined which node this TextDialog represents.

param
ptr Integer representing the pointer to the node which this TextDialog represents.

        mNodePointer = ptr;
    
voidsetRect(int x, int y, int width, int height)
Determine the position and size of TextDialog, and add it to the WebView's view heirarchy. All parameters are presumed to be in view coordinates. Also requests Focus and sets the cursor to not request to be in view.

param
x x-position of the textfield.
param
y y-position of the textfield.
param
width width of the textfield.
param
height height of the textfield.

        LayoutParams lp = (LayoutParams) getLayoutParams();
        if (null == lp) {
            lp = new LayoutParams(width, height, x, y);
        } else {
            lp.x = x;
            lp.y = y;
            lp.width = width;
            lp.height = height;
        }
        if (getParent() == null) {
            mWebView.addView(this, lp);
        } else {
            setLayoutParams(lp);
        }
        // Set up a measure spec so a layout can always be recreated.
        mWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
        mHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
        requestFocus();
    
public voidsetSingleLine(boolean single)
Set whether this is a single-line textfield or a multi-line textarea. Textfields scroll horizontally, and do not handle the enter key. Textareas behave oppositely. Do NOT call this after calling setInPassword(true). This will result in removing the password input type.

        int inputType = EditorInfo.TYPE_CLASS_TEXT;
        if (!single) {
            inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
                    | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
                    | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT;
        }
        mSingle = single;
        setHorizontallyScrolling(single);
        setInputType(inputType);
    
voidsetText(java.lang.CharSequence text, int start, int end)
Set the text for this TextDialog, and set the selection to (start, end)

param
text Text to go into this TextDialog.
param
start Beginning of the selection.
param
end End of the selection.

        mPreChange = text.toString();
        setText(text);
        Spannable span = (Spannable) getText();
        int length = span.length();
        if (end > length) {
            end = length;
        }
        if (start < 0) {
            start = 0;
        } else if (start > length) {
            start = length;
        }
        Selection.setSelection(span, start, end);
    
voidsetTextAndKeepSelection(java.lang.String text)
Set the text to the new string, but use the old selection, making sure to keep it within the new string.

param
text The new text to place in the textfield.

        mPreChange = text.toString();
        Editable edit = (Editable) getText();
        edit.replace(0, edit.length(), text);
        updateCachedTextfield();
    
voidupdateCachedTextfield()
Update the cache to reflect the current text.

        mWebView.updateCachedTextfield(getText().toString());