diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java b/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java index 75bd56a1..35a35a3f 100644 --- a/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java +++ b/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java @@ -1,59 +1,42 @@ 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.Modifier; import java.util.ArrayList; 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 { private static final int HVGA_WIDTH_PIXELS = 320; - @NotNull - private EditText editText; - - @NotNull - private TextView resultEditText; - - @NotNull - private Interpreter interpreter; - - @NotNull - private HistoryHelper historyHelper; - - @NotNull - private BroadcastReceiver preferencesChangesReceiver; - @NotNull private List onDragListeners = new ArrayList(); + @NotNull + private CalculatorView view; + + @NotNull + private CalculatorModel calculator; + /** * Called when the activity is first created. */ @@ -62,25 +45,14 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster { super.onCreate(savedInstanceState); setContentView(R.layout.main); - final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - - this.editText = (EditText) findViewById(R.id.editText); - this.editText.setInputType(InputType.TYPE_NULL); - imm.hideSoftInputFromWindow(this.editText.getWindowToken(), 0); - - 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(); - } - } - }); + try { + this.calculator = new CalculatorModel(); + } catch (EvalError evalError) { + // todo serso: create serso runtime exception + throw new RuntimeException("Could not initialize interpreter!"); + } + this.view = new CalculatorView(this, this.calculator); final DragButtonCalibrationActivity.Preferences dragPreferences = DragButtonCalibrationActivity.getPreferences(this); @@ -88,7 +60,7 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster { @Override public boolean processDragEvent(@NotNull DragDirection dragDirection, @NotNull DragButton dragButton, @NotNull Point2d startPoint2d, @NotNull MotionEvent motionEvent) { assert dragButton instanceof DirectionDragButton; - processButtonAction(dragButton, getActionText((DirectionDragButton) dragButton, dragDirection)); + view.processButtonAction(getActionText((DirectionDragButton) dragButton, dragDirection)); return true; } @@ -127,9 +99,9 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster { if (dragButton instanceof DirectionDragButton) { String text = ((DirectionDragButton) dragButton).getText(dragDirection); if ("↞".equals(text)) { - CalculatorActivity.this.editText.setSelection(0); + CalculatorActivity.this.view.setCursorOnStart(); } 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); onDragListeners.add(toPositionOnDragListener); - this.interpreter = new Interpreter(); - try { - interpreter.eval(Preprocessor.wrap(JsclOperation.importCommands, "/jscl/editorengine/commands")); - } catch (EvalError e) { - Log.e(CalculatorActivity.class.getName(), e.getMessage()); - } - - this.historyHelper = new SimpleHistoryHelper(); - saveHistoryState(); - - this.preferencesChangesReceiver = new BroadcastReceiver() { + final BroadcastReceiver preferencesChangesReceiver = new BroadcastReceiver() { @Override 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)); - } - - private void saveHistoryState() { - historyHelper.addState(getCurrentHistoryState()); + registerReceiver(preferencesChangesReceiver, new IntentFilter(DragButtonCalibrationActivity.INTENT_ACTION)); } + @SuppressWarnings({"UnusedDeclaration"}) public void elementaryButtonClickHandler(@NotNull View v) { - eval(JsclOperation.elementary, true); + throw new UnsupportedOperationException("Not implemented yet!"); } + @SuppressWarnings({"UnusedDeclaration"}) public void numericButtonClickHandler(@NotNull View v) { - eval(JsclOperation.numeric, true); + this.view.evaluate(); } + @SuppressWarnings({"UnusedDeclaration"}) public void eraseButtonClickHandler(@NotNull View v) { - if (editText.getSelectionStart() > 0) { - editText.getText().delete(editText.getSelectionStart() - 1, editText.getSelectionStart()); - saveHistoryState(); - } + view.doTextOperation(new CalculatorView.TextOperation() { + @Override + 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) { - eval(JsclOperation.simplify, true); + throw new UnsupportedOperationException("Not implemented yet!"); } + @SuppressWarnings({"UnusedDeclaration"}) public void moveLeftButtonClickHandler(@NotNull View v) { - if (editText.getSelectionStart() > 0) { - editText.setSelection(editText.getSelectionStart() - 1); - } + view.moveCursorLeft(); } + @SuppressWarnings({"UnusedDeclaration"}) public void moveRightButtonClickHandler(@NotNull View v) { - if (editText.getSelectionStart() < editText.getText().length()) { - editText.setSelection(editText.getSelectionStart() + 1); - } + view.moveCursorRight(); } + @SuppressWarnings({"UnusedDeclaration"}) public void pasteButtonClickHandler(@NotNull View v) { - final ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - if ( clipboard.hasText() ) { - editText.getText().insert(editText.getSelectionStart(), clipboard.getText()); - eval(JsclOperation.numeric, false); - saveHistoryState(); - } + view.doTextOperation(new CalculatorView.TextOperation() { + @Override + public void doOperation(@NotNull EditText editor) { + final ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + if (clipboard.hasText()) { + editor.getText().insert(editor.getSelectionStart(), clipboard.getText()); + } + } + }); } - + @SuppressWarnings({"UnusedDeclaration"}) public void clearButtonClickHandler(@NotNull View v) { - if (!StringUtils.isEmpty(editText.getText()) || !StringUtils.isEmpty(resultEditText.getText())) { - 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()); - } - } + view.clear(); } + @SuppressWarnings({"UnusedDeclaration"}) public void digitButtonClickHandler(@NotNull View v) { - processButtonAction(v, ((DirectionDragButton) v).getTextMiddle()); + view.processButtonAction(((DirectionDragButton) v).getTextMiddle()); } private final class HistoryDragProcessor implements SimpleOnDragListener.DragProcessor { @@ -269,7 +204,7 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster { result = true; final HistoryAction historyAction = HistoryAction.valueOf(actionText); - doHistoryAction(historyAction); + view.doHistoryAction(historyAction); } catch (IllegalArgumentException e) { 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 private static String getActionText(@NotNull DirectionDragButton dragButton, @NotNull DragDirection direction) { final String result; @@ -309,67 +235,10 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster { 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 public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { - doHistoryAction(HistoryAction.undo); + view.doHistoryAction(HistoryAction.undo); return true; } return super.onKeyDown(keyCode, event); @@ -392,9 +261,11 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster { case R.id.menu_item_settings: showSettings(); result = true; + break; case R.id.menu_item_help: showHelp(); result = true; + break; default: result = super.onOptionsItemSelected(item); } diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorHistoryState.java b/src/main/java/org/solovyev/android/calculator/CalculatorHistoryState.java index f4468778..5cecd1d0 100644 --- a/src/main/java/org/solovyev/android/calculator/CalculatorHistoryState.java +++ b/src/main/java/org/solovyev/android/calculator/CalculatorHistoryState.java @@ -13,11 +13,11 @@ public class CalculatorHistoryState { private EditorHistoryState editorState; @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.resultEditorState = resultEditorState; + this.displayState = displayState; } @NotNull @@ -30,11 +30,11 @@ public class CalculatorHistoryState { } @NotNull - public EditorHistoryState getResultEditorState() { - return resultEditorState; + public EditorHistoryState getDisplayState() { + return displayState; } - public void setResultEditorState(@NotNull EditorHistoryState resultEditorState) { - this.resultEditorState = resultEditorState; + public void setDisplayState(@NotNull EditorHistoryState displayState) { + this.displayState = displayState; } } diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorModel.java b/src/main/java/org/solovyev/android/calculator/CalculatorModel.java new file mode 100644 index 00000000..dd483038 --- /dev/null +++ b/src/main/java/org/solovyev/android/calculator/CalculatorModel.java @@ -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; + } +} diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorView.java b/src/main/java/org/solovyev/android/calculator/CalculatorView.java new file mode 100644 index 00000000..fa989021 --- /dev/null +++ b/src/main/java/org/solovyev/android/calculator/CalculatorView.java @@ -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 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(); + 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; + } +} diff --git a/src/main/java/org/solovyev/android/calculator/CursorControl.java b/src/main/java/org/solovyev/android/calculator/CursorControl.java new file mode 100644 index 00000000..f5e8768f --- /dev/null +++ b/src/main/java/org/solovyev/android/calculator/CursorControl.java @@ -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(); +}