diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 187f8c57..cf708767 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,27 +1,33 @@ + package="org.solovyev.android.calculator" + versionCode="1" + versionName="1.0"> - + - - - - + + + + - - + + - - - + - + + + + + \ No newline at end of file diff --git a/res/drawable/copy.png b/res/drawable/copy.png new file mode 100644 index 00000000..eb5e59c3 Binary files /dev/null and b/res/drawable/copy.png differ diff --git a/res/drawable/heart.png b/res/drawable/heart.png new file mode 100644 index 00000000..7ab281ec Binary files /dev/null and b/res/drawable/heart.png differ diff --git a/res/drawable/heart_original.png b/res/drawable/heart_original.png new file mode 100644 index 00000000..60e7d5d0 Binary files /dev/null and b/res/drawable/heart_original.png differ diff --git a/res/drawable/paste.png b/res/drawable/paste.png index 910bed20..db3d9300 100644 Binary files a/res/drawable/paste.png and b/res/drawable/paste.png differ diff --git a/res/layout-land/main.xml b/res/layout-land/main.xml index 114fbc00..72a3b525 100644 --- a/res/layout-land/main.xml +++ b/res/layout-land/main.xml @@ -17,73 +17,68 @@ - + - - - + - + - + + + + - - - + + - - + + - - diff --git a/res/layout-port/main.xml b/res/layout-port/main.xml index de58d175..63210bfb 100644 --- a/res/layout-port/main.xml +++ b/res/layout-port/main.xml @@ -17,15 +17,15 @@ - + - + @@ -65,7 +65,7 @@ - + @@ -75,11 +75,8 @@ - - - - - + + diff --git a/res/layout/calc_copy_button.xml b/res/layout/calc_copy_button.xml new file mode 100644 index 00000000..4ed715d9 --- /dev/null +++ b/res/layout/calc_copy_button.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/res/layout/calc_display.xml b/res/layout/calc_display.xml index 4cd8b0ed..220bc7fc 100644 --- a/res/layout/calc_display.xml +++ b/res/layout/calc_display.xml @@ -8,7 +8,7 @@ + + + + \ No newline at end of file diff --git a/res/layout/calc_editor.xml b/res/layout/calc_editor.xml index 96b36dea..ff0122f2 100644 --- a/res/layout/calc_editor.xml +++ b/res/layout/calc_editor.xml @@ -12,7 +12,7 @@ a:layout_height="0dp"> Отношение длины окружности к диаметру Вещесвтенное число, такое что производная функции f(x) = e^x в точке x = 0 равно 1 Мнимая единица, определённая как i^2 = −1 + Введите новое выражение diff --git a/res/values/strings.xml b/res/values/strings.xml index e531f851..33bb44c2 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -58,5 +58,6 @@ Ratio of any circle\'s circumference to its diameter Unique real number such that the value of the derivative (slope of the tangent line) of the function f(x) = e^x at the point x = 0 is equal to 1 Imaginary unit, defined such that i^2 = −1 - + Enter new expression + Press to copy diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java b/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java index d38b3849..d6651d25 100644 --- a/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java +++ b/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java @@ -7,6 +7,7 @@ package org.solovyev.android.calculator; import android.app.Activity; import android.content.*; +import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; import android.text.ClipboardManager; @@ -100,6 +101,9 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster, Sh } private void init() { + + calculatorView = new CalculatorView(this, CalculatorModel.instance); + insertTextReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -128,9 +132,6 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster, Sh // todo serso: create serso runtime exception throw new RuntimeException("Could not initialize interpreter!"); } - - this.calculatorView = new CalculatorView(this, CalculatorModel.instance); - initialized = true; } } @@ -191,6 +192,11 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster, Sh }); } + @SuppressWarnings({"UnusedDeclaration"}) + public void copyButtonClickHandler(@NotNull View v) { + calculatorView.copyResult(this); + } + @SuppressWarnings({"UnusedDeclaration"}) public void clearButtonClickHandler(@NotNull View v) { calculatorView.clear(); @@ -207,6 +213,14 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster, Sh startActivity(new Intent(this, CalculatorVarsActivity.class)); } + @SuppressWarnings({"UnusedDeclaration"}) + public void donateButtonClickHandler(@NotNull View v) { + final String paypalDonateUrl = "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=se%2esolovyev%40gmail%2ecom&lc=RU&item_name=android%2ecalculator%40se%2esolovyev¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted"; + final Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse(paypalDonateUrl)); + startActivity(i); + } + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorDisplayHistoryState.java b/src/main/java/org/solovyev/android/calculator/CalculatorDisplayHistoryState.java index e1c05a1c..83106328 100644 --- a/src/main/java/org/solovyev/android/calculator/CalculatorDisplayHistoryState.java +++ b/src/main/java/org/solovyev/android/calculator/CalculatorDisplayHistoryState.java @@ -5,6 +5,7 @@ package org.solovyev.android.calculator; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** @@ -12,19 +13,24 @@ import org.jetbrains.annotations.Nullable; * Date: 9/17/11 * Time: 11:05 PM */ -public class CalculatorDisplayHistoryState extends EditorHistoryState { +public class CalculatorDisplayHistoryState { private boolean valid = true; + @NotNull + private final EditorHistoryState editorHistoryState; + public CalculatorDisplayHistoryState() { + this.editorHistoryState = new EditorHistoryState(); } public CalculatorDisplayHistoryState(boolean valid) { + this.editorHistoryState = new EditorHistoryState(); this.valid = valid; } public CalculatorDisplayHistoryState(int cursorPosition, @Nullable String text, boolean valid) { - super(cursorPosition, text); + this.editorHistoryState = new EditorHistoryState(cursorPosition, text); this.valid = valid; } @@ -35,4 +41,29 @@ public class CalculatorDisplayHistoryState extends EditorHistoryState { public void setValid(boolean valid) { this.valid = valid; } + + public EditorHistoryState getEditorHistoryState() { + return editorHistoryState; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CalculatorDisplayHistoryState)) return false; + + CalculatorDisplayHistoryState that = (CalculatorDisplayHistoryState) o; + + if (valid != that.valid) return false; + if (editorHistoryState != null ? !editorHistoryState.equals(that.editorHistoryState) : that.editorHistoryState != null) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = (valid ? 1 : 0); + result = 31 * result + (editorHistoryState != null ? editorHistoryState.hashCode() : 0); + return result; + } } diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorHistory.java b/src/main/java/org/solovyev/android/calculator/CalculatorHistory.java new file mode 100644 index 00000000..b0deb3b0 --- /dev/null +++ b/src/main/java/org/solovyev/android/calculator/CalculatorHistory.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2009-2011. Created by serso aka se.solovyev. + * For more information, please, contact se.solovyev@gmail.com + * or visit http://se.solovyev.org + */ + +package org.solovyev.android.calculator; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.solovyev.common.utils.history.HistoryAction; +import org.solovyev.common.utils.history.HistoryHelper; +import org.solovyev.common.utils.history.SimpleHistoryHelper; + +/** + * User: serso + * Date: 10/9/11 + * Time: 6:35 PM + */ +public enum CalculatorHistory implements HistoryHelper { + + instance; + + private final HistoryHelper historyHelper = new SimpleHistoryHelper(); + + @Override + public boolean isEmpty() { + return this.historyHelper.isEmpty(); + } + + @Override + public CalculatorHistoryState getLastHistoryState() { + return this.historyHelper.getLastHistoryState(); + } + + @Override + public boolean isUndoAvailable() { + return historyHelper.isUndoAvailable(); + } + + @Override + public CalculatorHistoryState undo(@Nullable CalculatorHistoryState currentState) { + return historyHelper.undo(currentState); + } + + @Override + public boolean isRedoAvailable() { + return historyHelper.isRedoAvailable(); + } + + @Override + public CalculatorHistoryState redo(@Nullable CalculatorHistoryState currentState) { + return historyHelper.redo(currentState); + } + + @Override + public boolean isActionAvailable(@NotNull HistoryAction historyAction) { + return historyHelper.isActionAvailable(historyAction); + } + + @Override + public CalculatorHistoryState doAction(@NotNull HistoryAction historyAction, @Nullable CalculatorHistoryState currentState) { + return historyHelper.doAction(historyAction, currentState); + } + + @Override + public void addState(@Nullable CalculatorHistoryState currentState) { + historyHelper.addState(currentState); + } + + +} diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorView.java b/src/main/java/org/solovyev/android/calculator/CalculatorView.java index 8bb34d08..fd597233 100644 --- a/src/main/java/org/solovyev/android/calculator/CalculatorView.java +++ b/src/main/java/org/solovyev/android/calculator/CalculatorView.java @@ -6,6 +6,7 @@ package org.solovyev.android.calculator; import android.app.Activity; +import android.content.Context; import android.os.Handler; import android.text.ClipboardManager; import android.util.Log; @@ -21,11 +22,10 @@ import org.solovyev.android.calculator.model.CalculatorModel; import org.solovyev.android.calculator.model.ParseException; import org.solovyev.android.view.CursorControl; import org.solovyev.android.view.HistoryControl; +import org.solovyev.android.view.widgets.SoftKeyboardDisabler; import org.solovyev.common.utils.MutableObject; import org.solovyev.common.utils.StringUtils; import org.solovyev.common.utils.history.HistoryAction; -import org.solovyev.common.utils.history.HistoryHelper; -import org.solovyev.common.utils.history.SimpleHistoryHelper; /** * User: serso @@ -46,35 +46,43 @@ public class CalculatorView implements CursorControl, HistoryControl history; - public CalculatorView(@NotNull final Activity activity, @NotNull CalculatorModel calculator) { this.calculatorModel = calculator; - this.editor = (CalculatorEditor) activity.findViewById(R.id.editText); + this.editor = (CalculatorEditor) activity.findViewById(R.id.calculatorEditor); + this.editor.setOnTouchListener(new SoftKeyboardDisabler()); - this.display = (CalculatorDisplay) activity.findViewById(R.id.resultEditText); + this.display = (CalculatorDisplay) activity.findViewById(R.id.calculatorDisplay); this.display.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (((CalculatorDisplay) v).isValid()) { - final CharSequence text = ((TextView) v).getText(); - if (!StringUtils.isEmpty(text)) { - final ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Activity.CLIPBOARD_SERVICE); - clipboard.setText(text); - Toast.makeText(activity, activity.getText(R.string.c_result_copied), Toast.LENGTH_SHORT).show(); - } - } + copyResult(activity); } }); - this.history = new SimpleHistoryHelper(); - saveHistoryState(); + + final CalculatorHistoryState lastState = CalculatorHistory.instance.getLastHistoryState(); + if ( lastState == null ) { + saveHistoryState(); + } else { + setCurrentHistoryState(lastState); + } + + } + + public void copyResult(@NotNull Context context) { + if (display.isValid()) { + final CharSequence text = display.getText(); + if (!StringUtils.isEmpty(text)) { + final ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Activity.CLIPBOARD_SERVICE); + clipboard.setText(text); + Toast.makeText(context, context.getText(R.string.c_result_copied), Toast.LENGTH_SHORT).show(); + } + } } private void saveHistoryState() { - history.addState(getCurrentHistoryState()); + CalculatorHistory.instance.addState(getCurrentHistoryState()); } @@ -117,14 +125,14 @@ public class CalculatorView implements CursorControl, HistoryControl allPrefix; static { - final List functions = new ArrayList(Arrays.asList(SIN, SINH, ASIN, ASINH, COS, COSH, ACOS, ACOSH, TAN, TANH, ATAN, ATANH, LOG, LN, MOD, SQRT, SQRT_SIGN, EXP)); + final List functions = new ArrayList(Arrays.asList(SIN, SINH, ASIN, ASINH, COS, COSH, ACOS, ACOSH, TAN, TANH, ATAN, ATANH, LOG, LN, MOD, SQRT, SQRT_SIGN, EXP, E)); Collections.sort(functions, new MathEntityComparator()); allPrefix = functions; } diff --git a/src/main/java/org/solovyev/android/calculator/model/ToJsclTextProcessor.java b/src/main/java/org/solovyev/android/calculator/model/ToJsclTextProcessor.java index 14d49cfe..12b37c9f 100644 --- a/src/main/java/org/solovyev/android/calculator/model/ToJsclTextProcessor.java +++ b/src/main/java/org/solovyev/android/calculator/model/ToJsclTextProcessor.java @@ -39,10 +39,10 @@ class ToJsclTextProcessor implements TextProcessor { sb.append(')'); } else if (ch == '×' || ch == '∙') { sb.append("*"); - } else if ( mathType == MathType.function ){ + } else if (mathType == MathType.function) { sb.append(toJsclFunction(mathTypeResult.getMatch())); i += mathTypeResult.getMatch().length() - 1; - } else if ( mathType == MathType.constant ) { + } else if (mathType == MathType.constant) { sb.append(mathTypeResult.getMatch()); i += mathTypeResult.getMatch().length() - 1; } else { @@ -148,6 +148,8 @@ class ToJsclTextProcessor implements TextProcessor { result = Functions.LOG; } else if (function.equals(Functions.SQRT_SIGN)) { result = Functions.SQRT; + } else if (function.equals(Functions.E)) { + result = Functions.E_POWER; } else { result = function; } @@ -178,9 +180,9 @@ class ToJsclTextProcessor implements TextProcessor { @NotNull private static MathType.Result checkMultiplicationSignBeforeFunction(@NotNull StringBuilder sb, - @NotNull String s, - int i, - @Nullable MathType.Result mathTypeBeforeResult) { + @NotNull String s, + int i, + @Nullable MathType.Result mathTypeBeforeResult) { MathType.Result result = MathType.getType(s, i); if (i > 0) { diff --git a/src/main/java/org/solovyev/android/view/widgets/DirectionDragButton.java b/src/main/java/org/solovyev/android/view/widgets/DirectionDragButton.java index a5ad49ab..1a41e425 100644 --- a/src/main/java/org/solovyev/android/view/widgets/DirectionDragButton.java +++ b/src/main/java/org/solovyev/android/view/widgets/DirectionDragButton.java @@ -13,8 +13,6 @@ import android.text.TextPaint; import android.util.AttributeSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.solovyev.android.view.widgets.DragButton; -import org.solovyev.android.view.widgets.DragDirection; import org.solovyev.common.utils.Point2d; import org.solovyev.common.utils.StringUtils; @@ -101,11 +99,11 @@ public class DirectionDragButton extends DragButton { initUpDownTextPaint(basePaint); if (textUp != null) { - textUpPosition = getTextPosition(upDownTextPaint, basePaint, textUp, 1); + textUpPosition = getTextPosition(upDownTextPaint, basePaint, textUp, getText(), 1, getWidth(), getHeight()); } if (textDown != null) { - textDownPosition = getTextPosition(upDownTextPaint, basePaint, textDown, -1); + textDownPosition = getTextPosition(upDownTextPaint, basePaint, textDown, getText(), -1, getWidth(), getHeight()); } if ( textDownPosition != null && textUpPosition != null ) { @@ -118,21 +116,21 @@ public class DirectionDragButton extends DragButton { } - private Point2d getTextPosition(@NotNull Paint paint, @NotNull Paint basePaint, @NotNull CharSequence text, float direction) { + public static Point2d getTextPosition(@NotNull Paint paint, @NotNull Paint basePaint, @NotNull CharSequence text, CharSequence baseText, float direction, int w, int h) { final Point2d result = new Point2d(); float width = paint.measureText(text.toString() + " "); - result.setX(getWidth() - width); + result.setX(w - width); float selfHeight = paint.ascent() + paint.descent(); - basePaint.measureText(StringUtils.getNotEmpty(getText(), "|")); + basePaint.measureText(StringUtils.getNotEmpty(baseText, "|")); - float height = getHeight() - basePaint.ascent() - basePaint.descent(); + float height = h - basePaint.ascent() - basePaint.descent(); if (direction < 0) { - result.setY(height / 2 - direction * height / 3 + selfHeight); + result.setY(height / 2 + height / 3 + selfHeight); } else { - result.setY(height / 2 - direction * height / 3); + result.setY(height / 2 - height / 3); } return result; diff --git a/src/main/java/org/solovyev/android/view/widgets/SoftKeyboardDisabler.java b/src/main/java/org/solovyev/android/view/widgets/SoftKeyboardDisabler.java new file mode 100644 index 00000000..b0f04776 --- /dev/null +++ b/src/main/java/org/solovyev/android/view/widgets/SoftKeyboardDisabler.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2009-2011. Created by serso aka se.solovyev. + * For more information, please, contact se.solovyev@gmail.com + * or visit http://se.solovyev.org + */ + +package org.solovyev.android.view.widgets; + +import android.text.InputType; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.widget.EditText; + +/** + * User: serso + * Date: 10/9/11 + * Time: 4:27 PM + */ +public class SoftKeyboardDisabler implements View.OnTouchListener { + + @Override + public boolean onTouch(View v, MotionEvent event) { + boolean result; + Log.d(this.getClass().getName(), "org.solovyev.android.view.widgets.SoftKeyboardDisabler.onTouch(): action=" + event.getAction() + ", event=" + event); + + if (v instanceof EditText) { + final EditText editText = (EditText) v; + int inputType = editText.getInputType(); + int selectionStart = editText.getSelectionStart(); + int selectionEnd = editText.getSelectionEnd(); + + // disable soft input + editText.setInputType(InputType.TYPE_NULL); + editText.onTouchEvent(event); + + // restore input type + editText.setInputType(inputType); + editText.setSelection(selectionStart, selectionEnd); + + result = true; + } else { + result = false; + } + + return result; + } + + +} diff --git a/src/test/java/org/solovyev/android/calculator/model/ToJsclPreprocessorTest.java b/src/test/java/org/solovyev/android/calculator/model/ToJsclPreprocessorTest.java index 977841dd..9be3b77f 100644 --- a/src/test/java/org/solovyev/android/calculator/model/ToJsclPreprocessorTest.java +++ b/src/test/java/org/solovyev/android/calculator/model/ToJsclPreprocessorTest.java @@ -7,6 +7,7 @@ package org.solovyev.android.calculator.model; import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; /** @@ -16,16 +17,23 @@ import org.junit.Test; */ public class ToJsclPreprocessorTest { + @BeforeClass + public static void setUp() throws Exception { + CalculatorModel.instance.init(null); + } + @Test public void testProcess() throws Exception { final ToJsclTextProcessor preprocessor = new ToJsclTextProcessor(); Assert.assertEquals( "sin(4)*cos(5)", preprocessor.process("sin(4)cos(5)")); - Assert.assertEquals( "pi*sin(4)*pi*cos(sqrt(5))", preprocessor.process("πsin(4)πcos(√(5))")); - Assert.assertEquals( "pi*sin(4)+pi*cos(sqrt(5))", preprocessor.process("πsin(4)+πcos(√(5))")); - Assert.assertEquals( "pi*sin(4)+pi*cos(sqrt(5+sqrt(-1)))", preprocessor.process("πsin(4)+πcos(√(5+i))")); - Assert.assertEquals( "pi*sin(4.01)+pi*cos(sqrt(5+sqrt(-1)))", preprocessor.process("πsin(4.01)+πcos(√(5+i))")); - Assert.assertEquals( "exp(1)^pi*sin(4.01)+pi*cos(sqrt(5+sqrt(-1)))", preprocessor.process("e^πsin(4.01)+πcos(√(5+i))")); + Assert.assertEquals( "3.141592653589793*sin(4)*3.141592653589793*cos(sqrt(5))", preprocessor.process("πsin(4)πcos(√(5))")); + Assert.assertEquals( "3.141592653589793*sin(4)+3.141592653589793*cos(sqrt(5))", preprocessor.process("πsin(4)+πcos(√(5))")); + Assert.assertEquals( "3.141592653589793*sin(4)+3.141592653589793*cos(sqrt(5+sqrt(-1)))", preprocessor.process("πsin(4)+πcos(√(5+i))")); + Assert.assertEquals( "3.141592653589793*sin(4.01)+3.141592653589793*cos(sqrt(5+sqrt(-1)))", preprocessor.process("πsin(4.01)+πcos(√(5+i))")); + Assert.assertEquals( "2.718281828459045^3.141592653589793*sin(4.01)+3.141592653589793*cos(sqrt(5+sqrt(-1)))", preprocessor.process("e^πsin(4.01)+πcos(√(5+i))")); + Assert.assertEquals( "2.718281828459045^3.141592653589793*sin(4.01)+3.141592653589793*cos(sqrt(5+sqrt(-1)))*10^2", preprocessor.process("e^πsin(4.01)+πcos(√(5+i))E2")); + Assert.assertEquals( "2.718281828459045^3.141592653589793*sin(4.01)+3.141592653589793*cos(sqrt(5+sqrt(-1)))*10^-2", preprocessor.process("e^πsin(4.01)+πcos(√(5+i))E-2")); } @Test @@ -44,7 +52,7 @@ public class ToJsclPreprocessorTest { Assert.assertEquals(4, preprocessor.getPostfixFunctionStart("2.23+(5.4434234*sin(5.1+1))!", 26)); Assert.assertEquals(0, preprocessor.getPostfixFunctionStart("sin(5)!", 5)); Assert.assertEquals(0, preprocessor.getPostfixFunctionStart("sin(5sin(5sin(5)))!", 17)); - Assert.assertEquals(1, preprocessor.getPostfixFunctionStart("2+sin(5sin(5sin(5)))!", 19)); - Assert.assertEquals(4, preprocessor.getPostfixFunctionStart("2.23+sin(5.4434234*sin(5.1+1))!", 29)); + Assert.assertEquals(2, preprocessor.getPostfixFunctionStart("2+sin(5sin(5sin(5)))!", 19)); + Assert.assertEquals(5, preprocessor.getPostfixFunctionStart("2.23+sin(5.4434234*sin(5.1+1))!", 29)); } }