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 a7ac1257..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 @@ -48,9 +48,7 @@ import android.widget.PopupWindow; import butterknife.Bind; import butterknife.ButterKnife; import dagger.Lazy; -import jscl.JsclMathEngine; import jscl.math.function.IConstant; -import midpcalc.Real; import org.solovyev.android.Check; import org.solovyev.android.calculator.*; import org.solovyev.android.calculator.functions.FunctionsRegistry; @@ -58,6 +56,7 @@ 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; @@ -313,7 +312,7 @@ public class EditVariableFragment extends BaseDialogFragment implements View.OnF valueView.setEditableFactory(new Editable.Factory() { @Override public Editable newEditable(CharSequence source) { - return new NoFiltersEditable(source); + return new NumberEditable(source); } }); exponentButton.setOnClickListener(this); @@ -397,9 +396,10 @@ public class EditVariableFragment extends BaseDialogFragment implements View.OnF keyboardWindow.show(new GreekFloatingKeyboard(keyboardUser), getDialog()); } - private static class NoFiltersEditable extends SpannableStringBuilder { - public NoFiltersEditable(CharSequence source) { + private static class NumberEditable extends SpannableStringBuilder { + public NumberEditable(CharSequence source) { super(source); + super.setFilters(new InputFilter[]{NumberInputFilter.getInstance()}); } @Override 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; + } +}