TextDialogpublic 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.
/* 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 |
---|
void | bringIntoView()
if (getLayout() != null) {
bringPointIntoView(Selection.getSelectionEnd(getText()));
}
| public boolean | dispatchKeyEvent(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;
| void | enableScrollOnScreen(boolean enable)
mScrollToAccommodateCursor = enable;
| void | fakeTouchEvent(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();
| boolean | isSameTextField(int ptr)Determine whether this TextDialog currently represents the node
represented by ptr.
return ptr == mNodePointer;
| public boolean | onPreDraw()
if (getLayout() == null) {
measure(mWidthSpec, mHeightSpec);
}
return super.onPreDraw();
| protected void | onTextChanged(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 boolean | onTrackballEvent(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;
| void | remove()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 boolean | requestRectangleOnScreen(android.graphics.Rect rectangle)
if (mScrollToAccommodateCursor) {
return super.requestRectangleOnScreen(rectangle);
}
return false;
| private void | sendDomEvent(android.view.KeyEvent event)Send the DOM events for the specified event.
mWebView.passToJavaScript(getText().toString(), event);
| public void | setAdapterCustom(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);
| void | setInPassword(boolean inPassword)Determine whether to use the system-wide password disguising method,
or to use none.
if (inPassword) {
setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.
TYPE_TEXT_VARIATION_PASSWORD);
}
| void | setMaxLength(int maxLength)
mMaxLength = maxLength;
if (-1 == maxLength) {
setFilters(NO_FILTERS);
} else {
setFilters(new InputFilter[] {
new InputFilter.LengthFilter(maxLength) });
}
| void | setNodePointer(int ptr)Set the pointer for this node so it can be determined which node this
TextDialog represents.
mNodePointer = ptr;
| void | setRect(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.
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 void | setSingleLine(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);
| void | setText(java.lang.CharSequence text, int start, int end)Set the text for this TextDialog, and set the selection to (start, end)
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);
| void | setTextAndKeepSelection(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.
mPreChange = text.toString();
Editable edit = (Editable) getText();
edit.replace(0, edit.length(), text);
updateCachedTextfield();
| void | updateCachedTextfield()Update the cache to reflect the current text.
mWebView.updateCachedTextfield(getText().toString());
|
|