This commit is contained in:
serso
2011-10-09 20:22:44 +04:00
parent cfe1adad08
commit 2c27a10540
23 changed files with 354 additions and 121 deletions

View File

@@ -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&currency_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) {

View File

@@ -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;
}
}

View File

@@ -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<CalculatorHistoryState> {
instance;
private final HistoryHelper<CalculatorHistoryState> historyHelper = new SimpleHistoryHelper<CalculatorHistoryState>();
@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);
}
}

View File

@@ -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<CalculatorH
@NotNull
private final CalculatorModel calculatorModel;
@NotNull
private final HistoryHelper<CalculatorHistoryState> 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<CalculatorHistoryState>();
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<CalculatorH
// allow only one runner at one time
synchronized (currentRunner) {
//lock all operations with history
synchronized (history) {
synchronized (CalculatorHistory.instance) {
// do only if nothing was post delayed before current instance was posted
if (currentRunner.getObject() == this) {
// actually nothing shall be logged while text operations are done
evaluate(editorStateAfter);
if (history.isUndoAvailable()) {
history.undo(getCurrentHistoryState());
if (CalculatorHistory.instance.isUndoAvailable()) {
CalculatorHistory.instance.undo(getCurrentHistoryState());
}
saveHistoryState();
@@ -212,9 +220,9 @@ public class CalculatorView implements CursorControl, HistoryControl<CalculatorH
@Override
public void doHistoryAction(@NotNull HistoryAction historyAction) {
synchronized (history) {
if (history.isActionAvailable(historyAction)) {
final CalculatorHistoryState newState = history.doAction(historyAction, getCurrentHistoryState());
synchronized (CalculatorHistory.instance) {
if (CalculatorHistory.instance.isActionAvailable(historyAction)) {
final CalculatorHistoryState newState = CalculatorHistory.instance.doAction(historyAction, getCurrentHistoryState());
if (newState != null) {
setCurrentHistoryState(newState);
}
@@ -224,7 +232,7 @@ public class CalculatorView implements CursorControl, HistoryControl<CalculatorH
@Override
public void setCurrentHistoryState(@NotNull CalculatorHistoryState editorHistoryState) {
synchronized (history) {
synchronized (CalculatorHistory.instance) {
setValuesFromHistory(this.editor, editorHistoryState.getEditorState());
setValuesFromHistory(this.display, editorHistoryState.getDisplayState());
@@ -233,7 +241,7 @@ public class CalculatorView implements CursorControl, HistoryControl<CalculatorH
}
private void setValuesFromHistory(@NotNull CalculatorDisplay display, CalculatorDisplayHistoryState editorHistoryState) {
setValuesFromHistory(display, (EditorHistoryState)editorHistoryState);
setValuesFromHistory(display, editorHistoryState.getEditorHistoryState());
display.setValid(editorHistoryState.isValid());
}
@@ -247,7 +255,7 @@ public class CalculatorView implements CursorControl, HistoryControl<CalculatorH
@Override
@NotNull
public CalculatorHistoryState getCurrentHistoryState() {
synchronized (history) {
synchronized (CalculatorHistory.instance) {
return new CalculatorHistoryState(getEditorHistoryState(this.editor), getCalculatorDisplayHistoryState(this.display));
}
}
@@ -264,8 +272,8 @@ public class CalculatorView implements CursorControl, HistoryControl<CalculatorH
private CalculatorDisplayHistoryState getCalculatorDisplayHistoryState(@NotNull CalculatorDisplay display) {
final CalculatorDisplayHistoryState result = new CalculatorDisplayHistoryState();
result.setText(String.valueOf(display.getText()));
result.setCursorPosition(display.getSelectionStart());
result.getEditorHistoryState().setText(String.valueOf(display.getText()));
result.getEditorHistoryState().setCursorPosition(display.getSelectionStart());
result.setValid(display.isValid());
return result;

View File

@@ -39,5 +39,23 @@ public class EditorHistoryState {
return cursorPosition;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof EditorHistoryState)) return false;
EditorHistoryState that = (EditorHistoryState) o;
if (cursorPosition != that.cursorPosition) return false;
if (text != null ? !text.equals(that.text) : that.text != null) return false;
return true;
}
@Override
public int hashCode() {
int result = cursorPosition;
result = 31 * result + (text != null ? text.hashCode() : 0);
return result;
}
}

View File

@@ -37,11 +37,13 @@ public class Functions {
public final static String EXP = "exp";
public final static String SQRT_SIGN = "";
public final static String SQRT = "sqrt";
public final static String E = "E";
public final static String E_POWER = "10^";
public static final List<String> allPrefix;
static {
final List<String> functions = new ArrayList<String>(Arrays.asList(SIN, SINH, ASIN, ASINH, COS, COSH, ACOS, ACOSH, TAN, TANH, ATAN, ATANH, LOG, LN, MOD, SQRT, SQRT_SIGN, EXP));
final List<String> functions = new ArrayList<String>(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;
}

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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));
}
}