/* * Copyright (C) 2010 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 com.numix.calculator.view; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Paint; import android.os.Handler; import android.os.SystemClock; import android.text.Editable; import android.text.Html; import android.text.Spanned; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.ActionMode; import android.view.Gravity; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.TextView; import com.numix.calculator.BaseModule; import com.numix.calculator.CalculatorSettings; import com.numix.calculator.EquationFormatter; import com.numix.calculator.R; public class CalculatorEditText extends EditText { private static final int BLINK = 500; private EquationFormatter mEquationFormatter; private AdvancedDisplay mDisplay; private final long mShowCursor = SystemClock.uptimeMillis(); Paint mHighlightPaint = new Paint(); Handler mHandler = new Handler(); Runnable mRefresher = new Runnable() { @Override public void run() { CalculatorEditText.this.invalidate(); } }; private String mInput = ""; private int mSelectionHandle = 0; String mDecSeparator; String mBinSeparator; String mHexSeparator; public CalculatorEditText(Context context, AttributeSet attrs) { super(context, attrs); setUp(); } public CalculatorEditText(final AdvancedDisplay display) { super(display.getContext()); setUp(); mDisplay = display; setOnFocusChangeListener(new OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if(hasFocus) display.mActiveEditText = CalculatorEditText.this; } }); } private void setUp() { final Resources r = getContext().getResources(); mDecSeparator = r.getString(R.string.dec_separator); mBinSeparator = r.getString(R.string.bin_separator); mHexSeparator = r.getString(R.string.hex_separator); // Hide the keyboard setCustomSelectionActionModeCallback(new NoTextSelectionMode()); InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT); // Display ^ , and other visual cues mEquationFormatter = new EquationFormatter(); addTextChangedListener(new TextWatcher() { boolean updating = false; @Override public void onTextChanged(CharSequence s, int start, int before, int count) {} @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void afterTextChanged(Editable s) { if(updating) return; mInput = s.toString().replace(EquationFormatter.PLACEHOLDER, EquationFormatter.POWER).replace(mDecSeparator, "").replace(mBinSeparator, "") .replace(mHexSeparator, ""); updating = true; // Get the selection handle, since we're setting text and // that'll overwrite it mSelectionHandle = getSelectionStart(); // Adjust the handle by removing any comas or spacing to the // left String cs = s.subSequence(0, mSelectionHandle).toString(); mSelectionHandle -= countOccurrences(cs, mDecSeparator.charAt(0)); if(!mBinSeparator.equals(mDecSeparator)) { mSelectionHandle -= countOccurrences(cs, mBinSeparator.charAt(0)); } if(!mHexSeparator.equals(mBinSeparator) && !mHexSeparator.equals(mDecSeparator)) { mSelectionHandle -= countOccurrences(cs, mHexSeparator.charAt(0)); } setText(formatText(mInput)); setSelection(Math.min(mSelectionHandle, getText().length())); updating = false; } }); // Listen for the enter button on physical keyboards setOnEditorActionListener(new EditText.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { mDisplay.mLogic.onEnter(); return true; } }); } class NoTextSelectionMode implements ActionMode.Callback { @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { return false; } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { // Prevents the selection action mode on double tap. return false; } @Override public void onDestroyActionMode(ActionMode mode) {} @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; } } @Override public String toString() { return mInput; } @Override public View focusSearch(int direction) { View v; switch(direction) { case View.FOCUS_FORWARD: v = mDisplay.nextView(this); while(!v.isFocusable()) v = mDisplay.nextView(v); return v; case View.FOCUS_BACKWARD: v = mDisplay.previousView(this); while(!v.isFocusable()) v = mDisplay.previousView(v); if(MatrixView.class.isAssignableFrom(v.getClass())) { v = ((ViewGroup) v).getChildAt(((ViewGroup) v).getChildCount() - 1); v = ((ViewGroup) v).getChildAt(((ViewGroup) v).getChildCount() - 1); } return v; } return super.focusSearch(direction); } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); // TextViews don't draw the cursor if textLength is 0. Because we're an // array of TextViews, we'd prefer that it did. if(getText().length() == 0 && isEnabled() && (isFocused() || isPressed())) { if((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK) { mHighlightPaint.setColor(getCurrentTextColor()); mHighlightPaint.setStyle(Paint.Style.STROKE); canvas.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight(), mHighlightPaint); mHandler.postAtTime(mRefresher, SystemClock.uptimeMillis() + BLINK); } } } private Spanned formatText(String input) { BaseModule bm = mDisplay.mLogic.mBaseModule; if(CalculatorSettings.digitGrouping(getContext())) { // Add grouping, and then split on the selection handle // which is saved as a unique char String grouped = bm.groupSentence(input, mSelectionHandle); if(grouped.contains(String.valueOf(BaseModule.SELECTION_HANDLE))) { String[] temp = grouped.split(String.valueOf(BaseModule.SELECTION_HANDLE)); mSelectionHandle = temp[0].length(); input = ""; for(String s : temp) { input += s; } } else { input = grouped; mSelectionHandle = input.length(); } } return Html.fromHtml(mEquationFormatter.insertSupscripts(input)); } private int countOccurrences(String haystack, char needle) { int count = 0; for(int i = 0; i < haystack.length(); i++) { if(haystack.charAt(i) == needle) { count++; } } return count; } public static String load(final AdvancedDisplay parent) { return CalculatorEditText.load("", parent); } public static String load(String text, final AdvancedDisplay parent) { return CalculatorEditText.load(text, parent, parent.getChildCount()); } public static String load(String text, final AdvancedDisplay parent, final int pos) { final CalculatorEditText et = new CalculatorEditText(parent); et.setText(text); et.setSelection(0); if(parent.mKeyListener != null) et.setKeyListener(parent.mKeyListener); if(parent.mFactory != null) et.setEditableFactory(parent.mFactory); et.setBackgroundResource(android.R.color.transparent); et.setTextAppearance(parent.getContext(), CalculatorSettings.useLightTheme(parent.getContext()) ? R.style.Theme_Calculator_Display_Light : R.style.Theme_Calculator_Display); et.setPadding(5, 0, 5, 0); et.setEnabled(parent.isEnabled()); AdvancedDisplay.LayoutParams params = new AdvancedDisplay.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); params.gravity = Gravity.CENTER_VERTICAL; et.setLayoutParams(params); parent.addView(et, pos); return ""; } }