calculator refactor

This commit is contained in:
serso 2011-09-13 00:19:56 +04:00
parent a410225198
commit 7f4b5d00dd
5 changed files with 348 additions and 210 deletions

View File

@ -1,59 +1,42 @@
package org.solovyev.android.calculator; package org.solovyev.android.calculator;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.text.ClipboardManager;
import android.util.Log;
import android.util.TypedValue;
import android.view.*;
import android.widget.EditText;
import android.widget.TextView;
import bsh.EvalError;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.view.*;
import org.solovyev.util.StringUtils;
import org.solovyev.util.math.Point2d;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.text.ClipboardManager;
import android.text.InputType;
import android.util.TypedValue;
import android.view.*;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
import android.widget.Toast;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.view.*;
import org.solovyev.util.StringUtils;
import org.solovyev.util.math.MathEntityType;
import bsh.EvalError;
import bsh.Interpreter;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.EditText;
import org.solovyev.util.math.MathUtils;
import org.solovyev.util.math.Point2d;
public class CalculatorActivity extends Activity implements FontSizeAdjuster { public class CalculatorActivity extends Activity implements FontSizeAdjuster {
private static final int HVGA_WIDTH_PIXELS = 320; private static final int HVGA_WIDTH_PIXELS = 320;
@NotNull
private EditText editText;
@NotNull
private TextView resultEditText;
@NotNull
private Interpreter interpreter;
@NotNull
private HistoryHelper<CalculatorHistoryState> historyHelper;
@NotNull
private BroadcastReceiver preferencesChangesReceiver;
@NotNull @NotNull
private List<SimpleOnDragListener> onDragListeners = new ArrayList<SimpleOnDragListener>(); private List<SimpleOnDragListener> onDragListeners = new ArrayList<SimpleOnDragListener>();
@NotNull
private CalculatorView view;
@NotNull
private CalculatorModel calculator;
/** /**
* Called when the activity is first created. * Called when the activity is first created.
*/ */
@ -62,25 +45,14 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.main); setContentView(R.layout.main);
final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); try {
this.calculator = new CalculatorModel();
this.editText = (EditText) findViewById(R.id.editText); } catch (EvalError evalError) {
this.editText.setInputType(InputType.TYPE_NULL); // todo serso: create serso runtime exception
imm.hideSoftInputFromWindow(this.editText.getWindowToken(), 0); throw new RuntimeException("Could not initialize interpreter!");
this.resultEditText = (TextView) findViewById(R.id.resultEditText);
this.resultEditText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final CharSequence text = ((TextView) v).getText();
if (!StringUtils.isEmpty(text)) {
final ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
clipboard.setText(text);
Toast.makeText(CalculatorActivity.this, "Result copied to clipboard!", Toast.LENGTH_SHORT).show();
} }
}
});
this.view = new CalculatorView(this, this.calculator);
final DragButtonCalibrationActivity.Preferences dragPreferences = DragButtonCalibrationActivity.getPreferences(this); final DragButtonCalibrationActivity.Preferences dragPreferences = DragButtonCalibrationActivity.getPreferences(this);
@ -88,7 +60,7 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster {
@Override @Override
public boolean processDragEvent(@NotNull DragDirection dragDirection, @NotNull DragButton dragButton, @NotNull Point2d startPoint2d, @NotNull MotionEvent motionEvent) { public boolean processDragEvent(@NotNull DragDirection dragDirection, @NotNull DragButton dragButton, @NotNull Point2d startPoint2d, @NotNull MotionEvent motionEvent) {
assert dragButton instanceof DirectionDragButton; assert dragButton instanceof DirectionDragButton;
processButtonAction(dragButton, getActionText((DirectionDragButton) dragButton, dragDirection)); view.processButtonAction(getActionText((DirectionDragButton) dragButton, dragDirection));
return true; return true;
} }
@ -127,9 +99,9 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster {
if (dragButton instanceof DirectionDragButton) { if (dragButton instanceof DirectionDragButton) {
String text = ((DirectionDragButton) dragButton).getText(dragDirection); String text = ((DirectionDragButton) dragButton).getText(dragDirection);
if ("".equals(text)) { if ("".equals(text)) {
CalculatorActivity.this.editText.setSelection(0); CalculatorActivity.this.view.setCursorOnStart();
} else if ("".equals(text)) { } else if ("".equals(text)) {
CalculatorActivity.this.editText.setSelection(CalculatorActivity.this.editText.getText().length()); CalculatorActivity.this.view.setCursorOnEnd();
} }
} }
@ -140,18 +112,8 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster {
((DragButton) findViewById(R.id.leftButton)).setOnDragListener(toPositionOnDragListener); ((DragButton) findViewById(R.id.leftButton)).setOnDragListener(toPositionOnDragListener);
onDragListeners.add(toPositionOnDragListener); onDragListeners.add(toPositionOnDragListener);
this.interpreter = new Interpreter();
try { final BroadcastReceiver preferencesChangesReceiver = new BroadcastReceiver() {
interpreter.eval(Preprocessor.wrap(JsclOperation.importCommands, "/jscl/editorengine/commands"));
} catch (EvalError e) {
Log.e(CalculatorActivity.class.getName(), e.getMessage());
}
this.historyHelper = new SimpleHistoryHelper<CalculatorHistoryState>();
saveHistoryState();
this.preferencesChangesReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@ -164,94 +126,67 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster {
} }
}; };
registerReceiver(this.preferencesChangesReceiver, new IntentFilter(DragButtonCalibrationActivity.INTENT_ACTION)); registerReceiver(preferencesChangesReceiver, new IntentFilter(DragButtonCalibrationActivity.INTENT_ACTION));
}
private void saveHistoryState() {
historyHelper.addState(getCurrentHistoryState());
} }
@SuppressWarnings({"UnusedDeclaration"})
public void elementaryButtonClickHandler(@NotNull View v) { public void elementaryButtonClickHandler(@NotNull View v) {
eval(JsclOperation.elementary, true); throw new UnsupportedOperationException("Not implemented yet!");
} }
@SuppressWarnings({"UnusedDeclaration"})
public void numericButtonClickHandler(@NotNull View v) { public void numericButtonClickHandler(@NotNull View v) {
eval(JsclOperation.numeric, true); this.view.evaluate();
} }
@SuppressWarnings({"UnusedDeclaration"})
public void eraseButtonClickHandler(@NotNull View v) { public void eraseButtonClickHandler(@NotNull View v) {
if (editText.getSelectionStart() > 0) { view.doTextOperation(new CalculatorView.TextOperation() {
editText.getText().delete(editText.getSelectionStart() - 1, editText.getSelectionStart()); @Override
saveHistoryState(); public void doOperation(@NotNull EditText editor) {
if (editor.getSelectionStart() > 0) {
editor.getText().delete(editor.getSelectionStart() - 1, editor.getSelectionStart());
} }
} }
});
}
@SuppressWarnings({"UnusedDeclaration"})
public void simplifyButtonClickHandler(@NotNull View v) { public void simplifyButtonClickHandler(@NotNull View v) {
eval(JsclOperation.simplify, true); throw new UnsupportedOperationException("Not implemented yet!");
} }
@SuppressWarnings({"UnusedDeclaration"})
public void moveLeftButtonClickHandler(@NotNull View v) { public void moveLeftButtonClickHandler(@NotNull View v) {
if (editText.getSelectionStart() > 0) { view.moveCursorLeft();
editText.setSelection(editText.getSelectionStart() - 1);
}
} }
@SuppressWarnings({"UnusedDeclaration"})
public void moveRightButtonClickHandler(@NotNull View v) { public void moveRightButtonClickHandler(@NotNull View v) {
if (editText.getSelectionStart() < editText.getText().length()) { view.moveCursorRight();
editText.setSelection(editText.getSelectionStart() + 1);
}
} }
@SuppressWarnings({"UnusedDeclaration"})
public void pasteButtonClickHandler(@NotNull View v) { public void pasteButtonClickHandler(@NotNull View v) {
view.doTextOperation(new CalculatorView.TextOperation() {
@Override
public void doOperation(@NotNull EditText editor) {
final ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); final ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
if ( clipboard.hasText() ) { if (clipboard.hasText()) {
editText.getText().insert(editText.getSelectionStart(), clipboard.getText()); editor.getText().insert(editor.getSelectionStart(), clipboard.getText());
eval(JsclOperation.numeric, false);
saveHistoryState();
} }
} }
});
}
@SuppressWarnings({"UnusedDeclaration"})
public void clearButtonClickHandler(@NotNull View v) { public void clearButtonClickHandler(@NotNull View v) {
if (!StringUtils.isEmpty(editText.getText()) || !StringUtils.isEmpty(resultEditText.getText())) { view.clear();
editText.getText().clear();
resultEditText.setText("");
saveHistoryState();
}
}
private void eval(@NotNull JsclOperation operation, boolean showError) {
try {
final String preprocessedString = Preprocessor.process(String.valueOf(editText.getText()));
String result = String.valueOf(interpreter.eval(Preprocessor.wrap(operation, preprocessedString))).trim();
try {
final Double dResult = Double.valueOf(result);
result = String.valueOf(MathUtils.round(dResult, 5));
} catch (NumberFormatException e) {
}
resultEditText.setText(result);
// result editor might be changed (but main editor - no) => make undo and add new state with saved result
CalculatorHistoryState currentHistoryState = getCurrentHistoryState();
if (this.historyHelper.isUndoAvailable()) {
this.historyHelper.undo(currentHistoryState);
}
this.historyHelper.addState(currentHistoryState);
} catch (EvalError e) {
if (showError) {
Toast.makeText(CalculatorActivity.this, R.string.syntax_error, Toast.LENGTH_SHORT).show();
Log.e(CalculatorActivity.class.getName(), e.getMessage());
}
}
} }
@SuppressWarnings({"UnusedDeclaration"})
public void digitButtonClickHandler(@NotNull View v) { public void digitButtonClickHandler(@NotNull View v) {
processButtonAction(v, ((DirectionDragButton) v).getTextMiddle()); view.processButtonAction(((DirectionDragButton) v).getTextMiddle());
} }
private final class HistoryDragProcessor implements SimpleOnDragListener.DragProcessor { private final class HistoryDragProcessor implements SimpleOnDragListener.DragProcessor {
@ -269,7 +204,7 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster {
result = true; result = true;
final HistoryAction historyAction = HistoryAction.valueOf(actionText); final HistoryAction historyAction = HistoryAction.valueOf(actionText);
doHistoryAction(historyAction); view.doHistoryAction(historyAction);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Log.e(String.valueOf(dragButton.getId()), "Unsupported history action: " + actionText); Log.e(String.valueOf(dragButton.getId()), "Unsupported history action: " + actionText);
} }
@ -279,15 +214,6 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster {
} }
} }
private void doHistoryAction(@NotNull HistoryAction historyAction) {
if (historyHelper.isActionAvailable(historyAction)) {
final CalculatorHistoryState newState = historyHelper.doAction(historyAction, getCurrentHistoryState());
if (newState != null) {
setCurrentHistoryState(newState);
}
}
}
@Nullable @Nullable
private static String getActionText(@NotNull DirectionDragButton dragButton, @NotNull DragDirection direction) { private static String getActionText(@NotNull DirectionDragButton dragButton, @NotNull DragDirection direction) {
final String result; final String result;
@ -309,67 +235,10 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster {
return result; return result;
} }
public void setCurrentHistoryState(@NotNull CalculatorHistoryState editorHistoryState) {
setValuesFromHistory(this.editText, editorHistoryState.getEditorState());
setValuesFromHistory(this.resultEditText, editorHistoryState.getResultEditorState());
}
private void setValuesFromHistory(@NotNull TextView editText, EditorHistoryState editorHistoryState) {
editText.setText(editorHistoryState.getText());
if (editText instanceof EditText) {
((EditText) editText).setSelection(editorHistoryState.getCursorPosition());
}
}
@NotNull
public CalculatorHistoryState getCurrentHistoryState() {
return new CalculatorHistoryState(getEditorHistoryState(this.editText), getEditorHistoryState(this.resultEditText));
}
private EditorHistoryState getEditorHistoryState(@NotNull TextView textView) {
final EditorHistoryState result = new EditorHistoryState();
result.setText(String.valueOf(textView.getText()));
result.setCursorPosition(textView.getSelectionStart());
return result;
}
private void processButtonAction(@NotNull View v, @Nullable String text) {
//Toast.makeText(CalculatorActivity.this, text, Toast.LENGTH_SHORT).show();
if (!StringUtils.isEmpty(text)) {
final MathEntityType type = MathEntityType.getType(text);
int cursorPositionOffset = 0;
if (type != null) {
switch (type) {
case function:
text += "()";
cursorPositionOffset = -1;
break;
case group_symbols:
cursorPositionOffset = -1;
break;
default:
break;
}
}
this.editText.getText().insert(this.editText.getSelectionStart(), text);
this.editText.setSelection(this.editText.getSelectionStart() + cursorPositionOffset, this.editText.getSelectionEnd() + cursorPositionOffset);
saveHistoryState();
eval(JsclOperation.numeric, false);
}
}
@Override @Override
public boolean onKeyDown(int keyCode, KeyEvent event) { public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) { if (keyCode == KeyEvent.KEYCODE_BACK) {
doHistoryAction(HistoryAction.undo); view.doHistoryAction(HistoryAction.undo);
return true; return true;
} }
return super.onKeyDown(keyCode, event); return super.onKeyDown(keyCode, event);
@ -392,9 +261,11 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster {
case R.id.menu_item_settings: case R.id.menu_item_settings:
showSettings(); showSettings();
result = true; result = true;
break;
case R.id.menu_item_help: case R.id.menu_item_help:
showHelp(); showHelp();
result = true; result = true;
break;
default: default:
result = super.onOptionsItemSelected(item); result = super.onOptionsItemSelected(item);
} }

View File

@ -13,11 +13,11 @@ public class CalculatorHistoryState {
private EditorHistoryState editorState; private EditorHistoryState editorState;
@NotNull @NotNull
private EditorHistoryState resultEditorState; private EditorHistoryState displayState;
public CalculatorHistoryState(@NotNull EditorHistoryState editorState, @NotNull EditorHistoryState resultEditorState) { public CalculatorHistoryState(@NotNull EditorHistoryState editorState, @NotNull EditorHistoryState displayState) {
this.editorState = editorState; this.editorState = editorState;
this.resultEditorState = resultEditorState; this.displayState = displayState;
} }
@NotNull @NotNull
@ -30,11 +30,11 @@ public class CalculatorHistoryState {
} }
@NotNull @NotNull
public EditorHistoryState getResultEditorState() { public EditorHistoryState getDisplayState() {
return resultEditorState; return displayState;
} }
public void setResultEditorState(@NotNull EditorHistoryState resultEditorState) { public void setDisplayState(@NotNull EditorHistoryState displayState) {
this.resultEditorState = resultEditorState; this.displayState = displayState;
} }
} }

View File

@ -0,0 +1,44 @@
package org.solovyev.android.calculator;
import android.util.Log;
import android.widget.Toast;
import bsh.EvalError;
import bsh.Interpreter;
import org.jetbrains.annotations.NotNull;
import org.solovyev.util.math.MathUtils;
/**
* User: serso
* Date: 9/12/11
* Time: 11:38 PM
*/
public class CalculatorModel {
@NotNull
private Interpreter interpreter;
private int NUMBER_OF_FRACTION_DIGITS = 5;
public CalculatorModel() throws EvalError {
interpreter = new Interpreter();
interpreter.eval(Preprocessor.wrap(JsclOperation.importCommands, "/jscl/editorengine/commands"));
}
public String evaluate(@NotNull JsclOperation operation, @NotNull String expression ) throws EvalError {
final String preprocessedString = Preprocessor.process(String.valueOf(expression));
String result = String.valueOf(interpreter.eval(Preprocessor.wrap(operation, preprocessedString))).trim();
try {
final Double dResult = Double.valueOf(result);
result = String.valueOf(MathUtils.round(dResult, NUMBER_OF_FRACTION_DIGITS));
} catch (NumberFormatException e) {
// do nothing => it's normal if sometimes we don't have doubles as result
}
return result;
}
}

View File

@ -0,0 +1,206 @@
package org.solovyev.android.calculator;
import android.app.Activity;
import android.content.Context;
import android.text.ClipboardManager;
import android.text.InputType;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import bsh.EvalError;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.util.StringUtils;
import org.solovyev.util.math.MathEntityType;
/**
* User: serso
* Date: 9/12/11
* Time: 11:15 PM
*/
public class CalculatorView implements CursorControl{
@NotNull
private final EditText editor;
@NotNull
private final TextView display;
@NotNull
private final Activity activity;
@NotNull
private final CalculatorModel calculator;
@NotNull
private HistoryHelper<CalculatorHistoryState> history;
public CalculatorView(@NotNull final Activity activity, @NotNull CalculatorModel calculator) {
this.activity = activity;
this.calculator = calculator;
final InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
this.editor = (EditText) activity.findViewById(R.id.editText);
this.editor.setInputType(InputType.TYPE_NULL);
imm.hideSoftInputFromWindow(this.editor.getWindowToken(), 0);
this.display = (TextView) activity.findViewById(R.id.resultEditText);
this.display.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
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, "Result copied to clipboard!", Toast.LENGTH_SHORT).show();
}
}
});
this.history = new SimpleHistoryHelper<CalculatorHistoryState>();
saveHistoryState();
}
private void saveHistoryState() {
history.addState(getCurrentHistoryState());
}
public void setCursorOnStart() {
editor.setSelection(0);
}
public void setCursorOnEnd() {
editor.setSelection(editor.getText().length());
}
public void moveCursorLeft() {
if (editor.getSelectionStart() > 0) {
editor.setSelection(editor.getSelectionStart() - 1);
}
}
public void moveCursorRight() {
if (editor.getSelectionStart() < editor.getText().length()) {
editor.setSelection(editor.getSelectionStart() + 1);
}
}
public void doTextOperation(@NotNull TextOperation operation) {
final String editorStateBefore = this.editor.getText().toString();
operation.doOperation(this.editor);
final String editorStateAfter = this.editor.getText().toString();
if (!editorStateBefore.equals(editorStateAfter)) {
try {
evaluate(editorStateAfter);
} catch (EvalError evalError) {
// actually nothing shall be logged while text operations are done
}
}
}
private void evaluate(@Nullable String expression) throws EvalError {
if (!StringUtils.isEmpty(expression)) {
display.setText(calculator.evaluate(JsclOperation.numeric, expression));
saveHistoryState();
}
}
public void clear() {
if (!StringUtils.isEmpty(editor.getText()) || !StringUtils.isEmpty(editor.getText())) {
editor.getText().clear();
display.setText("");
saveHistoryState();
}
}
public void evaluate() {
try {
evaluate(editor.getText().toString());
} catch (EvalError evalError) {
Toast.makeText(this.activity, R.string.syntax_error, Toast.LENGTH_SHORT).show();
}
}
public void processButtonAction(@Nullable final String text) {
//Toast.makeText(CalculatorActivity.this, text, Toast.LENGTH_SHORT).show();
if (!StringUtils.isEmpty(text)) {
doTextOperation(new CalculatorView.TextOperation() {
@Override
public void doOperation(@NotNull EditText editor) {
final MathEntityType type = MathEntityType.getType(text);
int cursorPositionOffset = 0;
final StringBuilder textToBeInserted = new StringBuilder(text);
if (type != null) {
switch (type) {
case function:
textToBeInserted.append("()");
cursorPositionOffset = -1;
break;
case group_symbols:
cursorPositionOffset = -1;
break;
default:
break;
}
}
editor.getText().insert(editor.getSelectionStart(), textToBeInserted.toString());
editor.setSelection(editor.getSelectionStart() + cursorPositionOffset, editor.getSelectionEnd() + cursorPositionOffset);
}
});
}
}
public static interface TextOperation {
void doOperation(@NotNull EditText editor);
}
public void doHistoryAction(@NotNull HistoryAction historyAction) {
if (history.isActionAvailable(historyAction)) {
final CalculatorHistoryState newState = history.doAction(historyAction, getCurrentHistoryState());
if (newState != null) {
setCurrentHistoryState(newState);
}
}
}
public void setCurrentHistoryState(@NotNull CalculatorHistoryState editorHistoryState) {
setValuesFromHistory(this.editor, editorHistoryState.getEditorState());
setValuesFromHistory(this.display, editorHistoryState.getDisplayState());
}
private void setValuesFromHistory(@NotNull TextView editText, EditorHistoryState editorHistoryState) {
editText.setText(editorHistoryState.getText());
if (editText instanceof EditText) {
((EditText) editText).setSelection(editorHistoryState.getCursorPosition());
}
}
@NotNull
public CalculatorHistoryState getCurrentHistoryState() {
return new CalculatorHistoryState(getEditorHistoryState(this.editor), getEditorHistoryState(this.display));
}
private EditorHistoryState getEditorHistoryState(@NotNull TextView textView) {
final EditorHistoryState result = new EditorHistoryState();
result.setText(String.valueOf(textView.getText()));
result.setCursorPosition(textView.getSelectionStart());
return result;
}
}

View File

@ -0,0 +1,17 @@
package org.solovyev.android.calculator;
/**
* User: serso
* Date: 9/13/11
* Time: 12:08 AM
*/
public interface CursorControl {
public void setCursorOnStart();
public void setCursorOnEnd();
public void moveCursorLeft();
public void moveCursorRight();
}