package com.llamacorp.equate.view;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.SystemClock;
import android.text.InputType;
import android.util.AttributeSet;
import android.widget.EditText;
import android.widget.Toast;
import com.llamacorp.equate.Calculator;
import com.llamacorp.equate.ExpSeparatorHandler;
import com.llamacorp.equate.Expression;
import com.llamacorp.equate.R;
import com.llamacorp.equate.SISuffixHelper;
import java.util.ArrayList;
public class EditTextDisplay extends EditText {
private Calculator mCalc;
private Context mContext;
private float mTextSize = 0f;
private float mMinTextSize;
private int mSelStart = 0;
private int mSelEnd = 0;
private String mTextPrefix = "";
private String mExpressionText = "";
private String mTextSuffix = "";
private ExpSeparatorHandler mSepHandler;
private ValueAnimator mColorAnimation;
//TODO might not need this
// (This was in the original TextView) System wide time for last cut or copy action.
static long LAST_CUT_OR_COPY_TIME;
public EditTextDisplay(Context context) {
this(context, null);
}
public EditTextDisplay(Context context, AttributeSet attrs) {
super(context, attrs);
setUpEditText(context, attrs);
}
public EditTextDisplay(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setUpEditText(context, attrs);
}
private void setUpEditText(Context context, AttributeSet attrs) {
mContext = context;
mSepHandler = new ExpSeparatorHandler();
//grab custom resource variable
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.DynamicText, 0, 0);
try {
mMinTextSize = ta.getDimension(R.styleable.DynamicText_minimumTextSize,
getTextSize());
} finally {
ta.recycle();
}
}
/**
* Set the singleton calc to this EditText for its own use
*/
public void setCalc(Calculator calc) {
mCalc = calc;
}
/**
* Disable soft keyboard from appearing, use in conjunction with
* android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
*/
public void disableSoftInputFromAppearing() {
setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
setTextIsSelectable(true);
}
/**
* Updates the text and selection with current value from calc
*/
public void updateTextFromCalc() {
mTextPrefix = "";
mExpressionText = getSepDispText();
mTextSuffix = "";
//setText will reset selection to 0,0, so save it right now
mSelStart = mSepHandler.translateToSepIndex(mCalc.getSelectionStart());
mSelEnd = mSepHandler.translateToSepIndex(mCalc.getSelectionEnd());
//if expression not invalid and unit selected, display it after the expression
if (!mCalc.isExpressionInvalid() && mCalc.isUnitSelected()){
mTextSuffix = " " + mCalc.getCurrUnitType().getCurrUnit().toString();
//about to do conversion
if (!mCalc.isSolved()){
mTextPrefix = getResources().getString(R.string.word_Convert) + " ";
mTextSuffix = mTextSuffix + " " + getResources().getString(R.string.word_to) + ":";
//bump cursor position to the right by prefix text length
mSelStart = mSelStart + mTextPrefix.length();
mSelEnd = mSelEnd + mTextPrefix.length();
}
}
if (mCalc.isSolved() &&
mCalc.getNumberFormat() == Expression.NumFormat.ENGINEERING){
mTextSuffix = " " + SISuffixHelper.getSuffixName(mExpressionText);
}
//update the main display
setTextHtml(mExpressionText);
//Set up a animator to highlight parts red and fade to white
setupHighlighting();
//updating the text restarts selection to 0,0, so load in the current selection
setSelection(mSelStart, mSelEnd);
//if expression not solved, set cursor to visible (and visa-versa)
setCursorVisible(!mCalc.isSolved());
}
/**
* Helper method to setup the highlighting
*/
public void setupHighlighting() {
if (mCalc.isHighlighted()){
Integer colorFrom = Color.RED;
Integer colorTo = Color.WHITE;
final int ANIMATE_DURR = 600; //ms
mColorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
mColorAnimation.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
String coloredExp;
//if the highlight got canceled during the async animation update, cancel
if (!mCalc.isHighlighted()){
animator.cancel();
coloredExp = mExpressionText;
} else {
ArrayList<Integer> highList = mCalc.getHighlighted();
highList = mSepHandler.translateIndexListToSep(highList);
int color = (Integer) animator.getAnimatedValue();
int len = highList.size();
coloredExp = mExpressionText.substring(0, highList.get(0));
for (int i = 0; i < len; i++) {
int finish = mExpressionText.length();
if (i != len - 1) finish = highList.get(i + 1);
coloredExp = coloredExp + "<font color='" + color + "'>" +
mExpressionText.substring(highList.get(i), highList.get(i) + 1) +
"</font>" + mExpressionText.substring(highList.get(i) + 1, finish);
}
}
//update the main display
setTextHtml(coloredExp);
//updating the text restarts selection to 0,0, so load in the current selection
setSelection(mSelStart, mSelEnd);
}
});
mColorAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
clearHighlighted();
}
});
mColorAnimation.setDuration(ANIMATE_DURR);
mColorAnimation.start();
}
}
public void clearHighlighted() {
mCalc.clearHighlighted();
//only need to change the text if we have a animator running
if (mColorAnimation != null && mColorAnimation.isRunning()){
//update the main display
setTextHtml(mExpressionText);
//updating the text restarts selection to 0,0, so load in the current selection
setSelection(mSelStart, mSelEnd);
}
}
/**
* Helper method used to set the main display with HTML formatting, without
* highlighting
*
* @param expStr is the main expression to update
*/
private void setTextHtml(String expStr) {
setText(ViewUtils.fromHtml("<font color='gray'>" + mTextPrefix + "</font>" +
expStr +
"<font color='gray'>" + mTextSuffix + "</font>"));
}
/**
* Sets the current selection to the end of the expression
*/
public void setSelectionToEnd() {
int expLen = mExpressionText.length() + mTextPrefix.length();
setSelection(expLen, expLen);
}
/**
* Helper method returns the Expression text from calc seperated by commas
* This function will also set up update mSepHandler such that getting
* shiftedIndexs will work with the current text.
*/
private String getSepDispText() {
//return mCalc.toString();
return mSepHandler.getSepText(mCalc.toString());
}
@Override
protected void onTextChanged(CharSequence text, int start, int before, int after) {
super.onTextChanged(text, start, before, after);
layoutText();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) layoutText();
}
/**
* Helper method to size text
*/
private void layoutText() {
Paint paint = getPaint();
if (mTextSize != 0f) paint.setTextSize(mTextSize);
//if min text size is the same as normal size, just leave
if (mMinTextSize == getTextSize()) return;
float textWidth = paint.measureText(getText().toString());
float boxWidth = getWidth() - getPaddingLeft() - getPaddingRight();
float textSize = getTextSize();
if (textWidth > boxWidth){
float scaled = textSize * boxWidth / textWidth;
if (scaled < mMinTextSize)
scaled = mMinTextSize;
paint.setTextSize(scaled);
mTextSize = textSize;
}
}
/**
* Custom paste and cut commands, leave the default copy operation
*/
@Override
public boolean onTextContextMenuItem(int id) {
boolean consumed = true;
switch (id) {
case android.R.id.cut:
onTextCut();
break;
case android.R.id.paste:
onTextPaste();
break;
case android.R.id.copy:
consumed = super.onTextContextMenuItem(id);
}
//update the view with calc's selection and text
updateTextFromCalc();
return consumed;
}
/**
* Try to cut the current clipboard text
*/
private void onTextCut() {
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
CharSequence copiedText = getText().subSequence(selStart, selEnd);
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setPrimaryClip(ClipData.newPlainText(null, copiedText));
//cut deletes the selected text
mCalc.parseKeyPressed("b");
//this was in the original function, keep for now
LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis();
Toast.makeText(mContext, "Cut: \"" + copiedText + "\"", Toast.LENGTH_SHORT).show();
}
/**
* Try to paste the current clipboard text into this EditText
*/
private void onTextPaste() {
String textToPaste;
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = clipboard.getPrimaryClip();
textToPaste = clip.getItemAt(0).coerceToText(getContext()).toString();
Toast.makeText(mContext, "Pasted: \"" + textToPaste + "\"", Toast.LENGTH_SHORT).show();
mCalc.pasteIntoExpression(textToPaste);
}
@Override
protected void onSelectionChanged(int selStart, int selEnd) {
if (mCalc != null){
int preLen = mTextPrefix.length();
int expLen = mExpressionText.length();
int fixedSelStart = mSepHandler.makeIndexValid(selStart - preLen) + preLen;
int fixedSelEnd = mSepHandler.makeIndexValid((selEnd - preLen)) + preLen;
if (fixedSelStart != selStart || fixedSelEnd != selEnd){
setSelection(fixedSelStart, fixedSelEnd);
return;
}
//check to see if the unit part of the expression has been selected
if (selEnd > expLen + preLen){
setSelection(selStart, expLen + preLen);
return;
}
if (selStart > expLen + preLen){
setSelection(expLen + preLen, selEnd);
return;
}
if (selEnd < preLen){
setSelection(selStart, preLen);
return;
}
if (selStart < preLen){
setSelection(preLen, selEnd);
return;
}
//save the new selection in the calc class
mCalc.setSelection(mSepHandler.translateFromSepIndex(selStart - preLen),
mSepHandler.translateFromSepIndex(selEnd - preLen));
setCursorVisible(true);
}
}
}