/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jecelyin.android.v2.widget;
import java.util.zip.CRC32;
import com.jecelyin.editor.R;
import com.jecelyin.util.JecLog;
import com.jecelyin.widget.TouchZoom;
import jecelyin.android.compat.EditTextInterface;
import jecelyin.android.compat.EditableFactory;
import jecelyin.android.v2.text.Layout;
import jecelyin.android.v2.text.Selection;
import jecelyin.android.v2.text.TextUtils;
import jecelyin.android.v2.text.method.ArrowKeyMovementMethod;
import jecelyin.android.v2.text.method.MovementMethod;
import jecelyin.android.v2.text.method.Touch;
import android.content.Context;
import android.text.Editable;
import android.text.Spannable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.Scroller;
/*
* This is supposed to be a *very* thin veneer over TextView.
* Do not make any changes here that do anything that a TextView
* with a key listener and a movement method wouldn't do!
*/
/**
* EditText is a thin veneer over TextView that configures itself
* to be editable.
*
* <p>See the <a href="{@docRoot}resources/tutorials/views/hello-formstuff.html">Form Stuff
* tutorial</a>.</p>
* <p>
* <b>XML attributes</b>
* <p>
* See {@link android.R.styleable#EditText EditText Attributes},
* {@link android.R.styleable#TextView TextView Attributes},
* {@link android.R.styleable#View View Attributes}
*/
public class EditText extends TextView implements EditTextInterface {
public EditText(Context context) {
this(context, null);
}
public EditText(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.editTextStyle);
}
public EditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setEditableFactory(EditableFactory.getInstance());
}
@Override
protected boolean getDefaultEditable() {
return true;
}
@Override
protected MovementMethod getDefaultMovementMethod() {
return ArrowKeyMovementMethod.getInstance();
}
@Override
public Editable getText() {
return (Editable) super.getText();
}
@Override
public void setText(CharSequence text, BufferType type) {
super.setText(text, BufferType.EDITABLE);
}
/**
* Convenience for {@link Selection#setSelection(Spannable, int, int)}.
*/
public void setSelection(int start, int stop) {
Selection.setSelection(getText(), start, stop);
}
/**
* Convenience for {@link Selection#setSelection(Spannable, int)}.
*/
public void setSelection(int index) {
Selection.setSelection(getText(), index);
}
/**
* Convenience for {@link Selection#selectAll}.
*/
public void selectAll() {
Selection.selectAll(getText());
}
/**
* Convenience for {@link Selection#extendSelection}.
*/
public void extendSelection(int index) {
Selection.extendSelection(getText(), index);
}
@Override
public void setEllipsize(TextUtils.TruncateAt ellipsis) {
if (ellipsis == TextUtils.TruncateAt.MARQUEE) {
throw new IllegalArgumentException("EditText cannot use the ellipsize mode "
+ "TextUtils.TruncateAt.MARQUEE");
}
super.setEllipsize(ellipsis);
}
//jec+: fast touch scroll
/**
* Responsible for fling behavior. Use {@link #start(int)} to initiate a
* fling. Each frame of the fling is handled in {@link #run()}. A
* FlingRunnable will keep re-posting itself until the fling is done.
*
*/
private static class FlingRunnable implements Runnable
{
static final int TOUCH_MODE_REST = -1;
static final int TOUCH_MODE_FLING = 3;
int mTouchMode = TOUCH_MODE_REST;
/**
* Tracks the decay of a fling scroll
*/
private final Scroller mScroller;
/**
* Y value reported by mScroller on the previous fling
*/
private int mLastFlingY;
private EditText mWidget = null;
FlingRunnable(Context context)
{
mScroller = new Scroller(context);
}
void start(EditText parent, int initialVelocity)
{
mWidget = parent;
int initialX = parent.getScrollX(); // initialVelocity < 0 ?
// Integer.MAX_VALUE : 0;
int initialY = parent.getScrollY(); // initialVelocity < 0 ?
// Integer.MAX_VALUE : 0;
mLastFlingY = initialY;
mScroller.fling(initialX, initialY, 0, initialVelocity, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
mTouchMode = TOUCH_MODE_FLING;
mWidget.post(this);
}
private void endFling()
{
mTouchMode = TOUCH_MODE_REST;
if(mWidget != null)
{
try
{
mWidget.removeCallbacks(this);
mWidget.moveCursorToVisibleOffset();
}catch (Exception e)
{
e.printStackTrace();
}
mWidget = null;
}
}
public void run()
{
switch(mTouchMode)
{
default:
return;
case TOUCH_MODE_FLING:
{
final Scroller scroller = mScroller;
boolean more = scroller.computeScrollOffset();
int x = scroller.getCurrX();
int y = scroller.getCurrY();
Layout layout = mWidget.getLayout();
if(layout == null)
break;
int padding;
try
{
padding = mWidget.getTotalPaddingTop() + mWidget.getTotalPaddingBottom();
}catch (Exception e)
{
padding = 0;
}
y = Math.min(y, layout.getHeight() - (mWidget.getHeight() - padding));
y = Math.max(y, 0);
Touch.scrollTo(mWidget, layout, x, y);
int delta = mLastFlingY - y;
if(more && delta != 0)
{
mWidget.invalidate();
mLastFlingY = y;
mWidget.post(this);
}else
{
endFling();
}
break;
}
}
}
}
private VelocityTracker mVelocityTracker;
private FlingRunnable mFlingRunnable;
public boolean onTouchEvent(MotionEvent event)
{
//滚动条优先
if (mFastScroller != null) {
if (mFastScroller.onInterceptTouchEvent(event))
return true;
if (mFastScroller.onTouchEvent(event))
return true;
}
//放大第二
if(mTouchZoom.onTouchEvent(event))
return true;
//快速滚动第三
if(mVelocityTracker == null)
{
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
// 处理文本快速顺畅地滚动
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN:
if(mFlingRunnable != null)
{
mFlingRunnable.endFling();
cancelLongPress();
}
break;
case MotionEvent.ACTION_UP:
// cancelLongPress();
int mMinimumVelocity = ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity();
int mMaximumVelocity = ViewConfiguration.get(getContext()).getScaledMaximumFlingVelocity();
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
final int initialVelocity = (int) mVelocityTracker.getYVelocity();
if(Math.abs(initialVelocity) > mMinimumVelocity)
{
try
{
if(mFlingRunnable == null)
{
mFlingRunnable = new FlingRunnable(getContext());
}
mFlingRunnable.start(this, -initialVelocity);
}catch (Exception e)
{
}
}else
{
//moveCursorToVisibleOffset();
}
if(mVelocityTracker != null)
{
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
case MotionEvent.ACTION_MOVE:
break;
}
return super.onTouchEvent(event);
}
@Override
public void init()
{
super.init();
mCRC32 = new CRC32();
mTouchZoom = new TouchZoom(this);
}
@Override
public void show()
{
setVisibility(View.VISIBLE);
updateUndoRedoButtonStatus();
}
@Override
public void hide()
{
setVisibility(View.GONE);
}
@Override
public String getString()
{
String text = "";
try
{
text = getText().toString();
}catch (OutOfMemoryError e)
{
JecLog.msg(getContext().getString(R.string.out_of_memory));
}
return text;
}
private int mOldTextlength = 0;
private long mOldTextCrc32 = 0;
@Override
public void updateTextFinger()
{
mOldTextlength = getText().length();
byte bytes[] = getString().getBytes();
mCRC32.reset();
mCRC32.update(bytes,0,bytes.length);
mOldTextCrc32 = mCRC32.getValue();
}
@Override
public boolean isTextChanged()
{
CharSequence text = getText();
int hash = text.length();
//长度不相等,肯定是有更改了
if(mOldTextlength != hash)
{
return true;
}
//进行CRC检验
mCRC32.reset();
byte bytes[] = getString().getBytes();
mCRC32.update(bytes,0,bytes.length);
return mOldTextCrc32 != mCRC32.getValue();
}
private CRC32 mCRC32;
private TouchZoom mTouchZoom;
}