diff --git a/app/src/main/java/org/solovyev/android/calculator/EditorView.java b/app/src/main/java/org/solovyev/android/calculator/EditorView.java index 685eabef..2f928218 100644 --- a/app/src/main/java/org/solovyev/android/calculator/EditorView.java +++ b/app/src/main/java/org/solovyev/android/calculator/EditorView.java @@ -22,14 +22,11 @@ package org.solovyev.android.calculator; -import android.annotation.TargetApi; import android.content.Context; -import android.os.Build; import android.text.Editable; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.ContextMenu; - import org.solovyev.android.Check; import org.solovyev.android.calculator.view.EditTextCompat; import org.solovyev.android.views.Adjuster; @@ -58,12 +55,6 @@ public class EditorView extends EditTextCompat { init(); } - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public EditorView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - init(); - } - private void init() { if (!App.isFloatingCalculator(getContext())) { Adjuster.adjustText(this, 0.22f, diff --git a/app/src/main/java/org/solovyev/android/calculator/variables/EditVariableFragment.java b/app/src/main/java/org/solovyev/android/calculator/variables/EditVariableFragment.java index 79c4e738..6170e276 100644 --- a/app/src/main/java/org/solovyev/android/calculator/variables/EditVariableFragment.java +++ b/app/src/main/java/org/solovyev/android/calculator/variables/EditVariableFragment.java @@ -34,6 +34,9 @@ import android.support.design.widget.TextInputLayout; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v7.app.AlertDialog; +import android.text.Editable; +import android.text.InputFilter; +import android.text.SpannableStringBuilder; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -48,12 +51,12 @@ import dagger.Lazy; import jscl.math.function.IConstant; import org.solovyev.android.Check; import org.solovyev.android.calculator.*; -import org.solovyev.android.calculator.RemovalConfirmationDialog; import org.solovyev.android.calculator.functions.FunctionsRegistry; import org.solovyev.android.calculator.keyboard.FloatingKeyboard; import org.solovyev.android.calculator.keyboard.FloatingKeyboardWindow; import org.solovyev.android.calculator.math.MathType; import org.solovyev.android.calculator.view.EditTextCompat; +import org.solovyev.android.text.method.NumberInputFilter; import org.solovyev.common.text.Strings; import javax.annotation.Nonnull; @@ -87,6 +90,8 @@ public class EditVariableFragment extends BaseDialogFragment implements View.OnF TextInputLayout valueLabel; @Bind(R.id.variable_value) EditText valueView; + @Bind(R.id.variable_exponent_button) + Button exponentButton; @Bind(R.id.variable_description) EditText descriptionView; @Inject @@ -304,6 +309,13 @@ public class EditVariableFragment extends BaseDialogFragment implements View.OnF nameView.setOnFocusChangeListener(this); nameView.setOnKeyListener(this); valueView.setOnFocusChangeListener(this); + valueView.setEditableFactory(new Editable.Factory() { + @Override + public Editable newEditable(CharSequence source) { + return new NumberEditable(source); + } + }); + exponentButton.setOnClickListener(this); descriptionView.setOnFocusChangeListener(this); keyboardButton.setOnClickListener(this); @@ -352,6 +364,11 @@ public class EditVariableFragment extends BaseDialogFragment implements View.OnF showKeyboard(); } break; + case R.id.variable_exponent_button: + final int start = Math.max(valueView.getSelectionStart(), 0); + final int end = Math.max(valueView.getSelectionEnd(), 0); + valueView.getText().replace(Math.min(start, end), Math.max(start, end), "E", 0, 1); + break; default: super.onClick(v); break; @@ -379,6 +396,18 @@ public class EditVariableFragment extends BaseDialogFragment implements View.OnF keyboardWindow.show(new GreekFloatingKeyboard(keyboardUser), getDialog()); } + private static class NumberEditable extends SpannableStringBuilder { + public NumberEditable(CharSequence source) { + super(source); + super.setFilters(new InputFilter[]{NumberInputFilter.getInstance()}); + } + + @Override + public void setFilters(InputFilter[] filters) { + // we don't want filters as we want to support numbers in scientific notation + } + } + private class KeyboardUser implements FloatingKeyboard.User { @NonNull @Override diff --git a/app/src/main/java/org/solovyev/android/calculator/view/EditTextCompat.java b/app/src/main/java/org/solovyev/android/calculator/view/EditTextCompat.java index 79f393b6..81a52e2a 100644 --- a/app/src/main/java/org/solovyev/android/calculator/view/EditTextCompat.java +++ b/app/src/main/java/org/solovyev/android/calculator/view/EditTextCompat.java @@ -1,8 +1,8 @@ package org.solovyev.android.calculator.view; -import android.annotation.TargetApi; import android.content.Context; import android.os.Build; +import android.support.design.widget.TextInputEditText; import android.text.InputType; import android.util.AttributeSet; import android.util.Log; @@ -12,7 +12,7 @@ import org.solovyev.android.Check; import javax.annotation.Nullable; import java.lang.reflect.Method; -public class EditTextCompat extends EditText { +public class EditTextCompat extends TextInputEditText { @Nullable private static Method setShowSoftInputOnFocusMethod; @@ -30,11 +30,6 @@ public class EditTextCompat extends EditText { super(context, attrs, defStyleAttr); } - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public EditTextCompat(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - public void dontShowSoftInputOnFocusCompat() { setShowSoftInputOnFocusCompat(false); } diff --git a/app/src/main/java/org/solovyev/android/text/method/NumberInputFilter.java b/app/src/main/java/org/solovyev/android/text/method/NumberInputFilter.java new file mode 100644 index 00000000..5600c096 --- /dev/null +++ b/app/src/main/java/org/solovyev/android/text/method/NumberInputFilter.java @@ -0,0 +1,176 @@ +package org.solovyev.android.text.method; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.InputFilter; +import android.text.SpannableStringBuilder; +import android.text.Spanned; + +public class NumberInputFilter implements InputFilter { + + private static final int[] CHARS = new int[]{-1, -1, -1}; + private static final int CHAR_SIGN = 0; + private static final int CHAR_POINT = 1; + private static final int CHAR_EXP = 2; + + private static final char[] ACCEPTED = {'E', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+', '.'}; + private static final NumberInputFilter INSTANCE = new NumberInputFilter(); + + private static boolean isSignChar(final char c) { + return c == '-' || c == '+'; + } + + private static boolean isDecimalPointChar(final char c) { + return c == '.'; + } + + private static boolean isExponentChar(final char c) { + return c == 'E'; + } + + /** + * Returns a NumberInputFilter that accepts the digits 0 through 9. + */ + public static NumberInputFilter getInstance() { + return INSTANCE; + } + + private static boolean accepted(char c) { + for (int i = ACCEPTED.length - 1; i >= 0; i--) { + if (ACCEPTED[i] == c) { + return true; + } + } + + return false; + } + + public CharSequence filter(CharSequence source, int start, int end, + Spanned dest, int dstart, int dend) { + final CharSequence out = filterIllegalCharacters(source, start, end); + if (out != null) { + source = out; + start = 0; + end = out.length(); + } + + CHARS[CHAR_SIGN] = -1; + CHARS[CHAR_POINT] = -1; + CHARS[CHAR_EXP] = -1; + findChars(dest, 0, dstart, 0, CHARS); + findChars(dest, dend, dest.length(), end - start, CHARS); + + SpannableStringBuilder filtered = null; + for (int i = end - 1; i >= start; i--) { + final char c = source.charAt(i); + + boolean filter = false; + if (isSignChar(c)) { + if (i == start && dstart == 0) { + if (CHARS[CHAR_SIGN] >= 0) { + filter = true; + } else { + CHARS[CHAR_SIGN] = i + dstart; + } + } else if (CHARS[CHAR_EXP] == i + dstart - 1) { + // allow sign after exponent symbol + filter = false; + } else { + filter = true; + } + } else if (isDecimalPointChar(c)) { + if (CHARS[CHAR_POINT] >= 0) { + filter = true; + } else if (CHARS[CHAR_EXP] >= 0 && CHARS[CHAR_EXP] < i + dstart) { + // no decimal point after exponent + filter = true; + } else { + CHARS[CHAR_POINT] = i + dstart; + } + } else if (isExponentChar(c)) { + if (CHARS[CHAR_EXP] >= 0) { + filter = true; + } else if (CHARS[CHAR_POINT] >= 0 && CHARS[CHAR_POINT] > i + dstart) { + // no exponent before decimal point + filter = true; + } else { + CHARS[CHAR_EXP] = i + dstart; + } + } + + if (filter) { + if (end == start + 1) { + return ""; // Only one character, and it was stripped. + } + + if (filtered == null) { + filtered = new SpannableStringBuilder(source, start, end); + } + + filtered.delete(i - start, i + 1 - start); + } + } + + if (filtered != null) { + return filtered; + } else if (out != null) { + return out; + } else { + return null; + } + } + + private void findChars(@NonNull Spanned s, int start, int end, int offset, int[] out) { + for (int i = start; i < end; i++) { + final char c = s.charAt(i); + + if (isSignChar(c)) { + if (out[CHAR_SIGN] == -1 && out[CHAR_EXP] == -1) { + // count in only signs before exponent + out[CHAR_SIGN] = i + offset; + } + } else if (isDecimalPointChar(c)) { + if (out[CHAR_POINT] == -1) { + out[CHAR_POINT] = i + offset; + } + } else if (isExponentChar(c)) { + if (out[CHAR_EXP] == -1) { + out[CHAR_EXP] = i + offset; + } + } + } + } + + @Nullable + private CharSequence filterIllegalCharacters(CharSequence source, int start, int end) { + final int illegal = findIllegalChar(source, start, end); + if (illegal == end) { + // all OK + return null; + } + if (end - start == 1) { + // it was not OK, and there is only one char, so nothing remains. + return ""; + } + + final SpannableStringBuilder filtered = new SpannableStringBuilder(source, start, end); + final int newEnd = end - start - 1; + // only count down to "illegal" because the chars before that were all OK. + final int newIllegal = illegal - start; + for (int j = newEnd; j >= newIllegal; j--) { + if (!accepted(source.charAt(j))) { + filtered.delete(j, j + 1); + } + } + return filtered; + } + + private int findIllegalChar(CharSequence s, int start, int end) { + for (int i = start; i < end; i++) { + if (!accepted(s.charAt(i))) { + return i; + } + } + return end; + } +} diff --git a/app/src/main/res/layout/fragment_variable_edit.xml b/app/src/main/res/layout/fragment_variable_edit.xml index 7c39891e..26b9ce45 100644 --- a/app/src/main/res/layout/fragment_variable_edit.xml +++ b/app/src/main/res/layout/fragment_variable_edit.xml @@ -65,18 +65,36 @@ tools:ignore="UnusedAttribute" /> - - + + + + +