Android project initiated

This commit is contained in:
serso
2011-06-24 01:09:13 +04:00
commit eb16062296
61 changed files with 1164 additions and 0 deletions

View File

@@ -0,0 +1,196 @@
package org.solovyev.android.calculator;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.R;
import org.solovyev.android.view.DragButton;
import org.solovyev.android.view.DragEvent;
import org.solovyev.android.view.OnDragListener;
import org.solovyev.android.view.SimpleOnDragListener;
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.view.View;
import android.widget.EditText;
public class Calculator extends Activity {
@NotNull
private EditText editText;
@NotNull
private EditText resultEditText;
@NotNull
private Interpreter interpreter;
@NotNull
private HistoryHelper<EditorHistoryState> historyHelper;
/** Called when the activity is first created. */
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
this.editText = (EditText) findViewById(R.id.editText);
this.resultEditText = (EditText) findViewById(R.id.resultEditText);
final SimpleOnDragListener onDragListener = new SimpleOnDragListener();
// todo serso: check if there is more convenient method for doing this
final R.id ids = new R.id();
for (Field field : R.id.class.getDeclaredFields()) {
int modifiers = field.getModifiers();
if (Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers)) {
try {
final View view = findViewById(field.getInt(ids));
if (view instanceof DragButton) {
((DragButton) view).setOnDragListener(onDragListener);
}
} catch (IllegalArgumentException e) {
Log.e(Calculator.class.getName(), e.getMessage());
} catch (IllegalAccessException e) {
Log.e(Calculator.class.getName(), e.getMessage());
}
}
}
((DragButton) findViewById(R.id.historyButton)).setOnDragListener(new HistoryOnDragListener());
this.interpreter = new Interpreter();
try {
interpreter.eval(Preprocessor.wrap(JsclOperation.importCommands, "/jscl/editorengine/commands"));
} catch (EvalError e) {
Log.e(Calculator.class.getName(), e.getMessage());
}
this.historyHelper = new SimpleHistoryHelper<EditorHistoryState>();
this.historyHelper.addState(getCurrentHistoryState());
}
public void elementaryButtonClickHandler(@NotNull View v) {
eval(JsclOperation.elementary);
}
public void numericButtonClickHandler(@NotNull View v) {
eval(JsclOperation.numeric);
}
public void simplifyButtonClickHandler(@NotNull View v) {
eval(JsclOperation.simplify);
}
private void eval(@NotNull JsclOperation operation) {
try {
final String preprocessedString = Preprocessor.process(String.valueOf(editText.getText()));
resultEditText.setText(String.valueOf(interpreter.eval(Preprocessor.wrap(operation, preprocessedString))));
} catch (EvalError e) {
Log.e(Calculator.class.getName(), e.getMessage());
resultEditText.setText(R.string.syntax_error);
}
}
public void digitButtonClickHandler(@NotNull View v) {
processButtonAction(v, ((DragButton) v).getTextMiddle());
}
private final class HistoryOnDragListener implements OnDragListener {
@Override
public void onDrag(@NotNull DragButton dragButton, @NotNull DragEvent event) {
Log.d(String.valueOf(dragButton.getId()), "History on drag event start: " + event.getDirection());
String actionText = getActionText(dragButton, event);
if (!StringUtils.isEmpty(actionText)) {
try {
final HistoryAction historyAction = HistoryAction.valueOf(actionText);
if ( historyHelper.isActionAvailable(historyAction) ){
final EditorHistoryState newState = historyHelper.doAction(historyAction, getCurrentHistoryState());
if (newState != null) {
setCurrentHistoryState(newState);
}
}
} catch (IllegalArgumentException e) {
Log.e(String.valueOf(dragButton.getId()), "Unsupported history action: " + actionText);
}
}
}
}
@Nullable
private static String getActionText(@NotNull DragButton dragButton, @NotNull DragEvent event) {
final String result;
switch(event.getDirection()) {
case up:
result = dragButton.getTextUp();
break;
case down:
result = dragButton.getTextDown();
break;
default:
result = null;
break;
}
return result;
}
public void setCurrentHistoryState(@Nullable EditorHistoryState editorHistoryState) {
this.editText.setText(editorHistoryState.getText());
this.editText.setSelection(editorHistoryState.getCursorPosition(), editorHistoryState.getCursorPosition());
}
@NotNull
public EditorHistoryState getCurrentHistoryState() {
final EditorHistoryState result = new EditorHistoryState();
result.setText(String.valueOf(this.editText.getText()));
result.setCursorPosition(this.editText.getSelectionStart());
return result;
}
private void processButtonAction(@NotNull View v, @Nullable String text) {
//Toast.makeText(Calculator.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);
this.historyHelper.addState(getCurrentHistoryState());
}
}
}

View File

@@ -0,0 +1,37 @@
package org.solovyev.android.calculator;
import org.jetbrains.annotations.Nullable;
public class EditorHistoryState {
private int cursorPosition;
@Nullable
private String text;
public EditorHistoryState() {
}
public EditorHistoryState( int cursorPosition, @Nullable String text ) {
this.cursorPosition = cursorPosition;
this.text = text;
}
public void setText(String text) {
this.text = text;
}
public String getText() {
return text;
}
public void setCursorPosition(int cursorPosition) {
this.cursorPosition = cursorPosition;
}
public int getCursorPosition() {
return cursorPosition;
}
}

View File

@@ -0,0 +1,8 @@
package org.solovyev.android.calculator;
public enum HistoryAction {
redo,
undo;
}

View File

@@ -0,0 +1,24 @@
package org.solovyev.android.calculator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface HistoryHelper<T> {
boolean isUndoAvailable();
@Nullable
T undo (@Nullable T currentState);
boolean isRedoAvailable();
@Nullable
T redo (@Nullable T currentState);
boolean isActionAvailable(@NotNull HistoryAction historyAction);
@Nullable
T doAction(@NotNull HistoryAction historyAction, @Nullable T currentState);
void addState(@Nullable T currentState);
}

View File

@@ -0,0 +1,10 @@
package org.solovyev.android.calculator;
public enum JsclOperation {
simplify,
elementary,
importCommands,
numeric;
}

View File

@@ -0,0 +1,31 @@
package org.solovyev.android.calculator;
import org.jetbrains.annotations.NotNull;
public class Preprocessor {
@NotNull
public static String process(@NotNull String s) {
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (ch == '[' || ch == '{') {
sb.append('(');
} else if (ch == ']' || ch == '}') {
sb.append(')');
} else if ( ch == ',' ) {
sb.append('.');
} else {
sb.append(ch);
}
}
return sb.toString();
}
public static String wrap (@NotNull JsclOperation operation, @NotNull String s) {
return operation.name() + "(\"" + s + "\");";
}
}

View File

@@ -0,0 +1,91 @@
package org.solovyev.android.calculator;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SimpleHistoryHelper<T> implements HistoryHelper<T> {
private List<T> history = new ArrayList<T>();
private int currentStateIndex = -1;
@Override
public T undo(@Nullable T currentState) {
if ( !isUndoAvailable() ) {
throw new IndexOutOfBoundsException();
}
currentStateIndex--;
return history.get(currentStateIndex);
}
@Override
public T redo(@Nullable T currentState) {
if (!isRedoAvailable()) {
throw new IndexOutOfBoundsException();
}
currentStateIndex++;
return history.get(currentStateIndex);
}
@Override
public void addState(@Nullable T currentState) {
if (currentStateIndex == history.size() - 1) {
currentStateIndex++;
history.add(currentState);
} else {
assert currentStateIndex < history.size() - 1 : "Invalid history state index!";
currentStateIndex++;
history.set(currentStateIndex, currentState);
while( history.size() > currentStateIndex + 1 ) {
history.remove(history.size() - 1);
}
}
}
@Override
public boolean isUndoAvailable() {
return currentStateIndex > 0;
}
@Override
public boolean isRedoAvailable() {
return currentStateIndex < history.size() - 1;
}
@Override
public boolean isActionAvailable(@NotNull HistoryAction historyAction) {
boolean result = false;
switch (historyAction) {
case undo:
result = isUndoAvailable();
break;
case redo:
result = isRedoAvailable();
break;
}
return result;
}
@Override
public T doAction(@NotNull HistoryAction historyAction, @Nullable T currentState) {
T result = null;
switch (historyAction) {
case undo:
result = undo(currentState);
break;
case redo:
result = redo(currentState);
break;
}
return result;
}
}

View File

@@ -0,0 +1,168 @@
package org.solovyev.android.view;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.R;
import org.solovyev.util.StringUtils;
import org.solovyev.util.math.MathUtils;
import org.solovyev.util.math.Point2d;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.Html;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
public class DragButton extends Button {
// max time in ms to register drag event
private long maxTime = 700;
@Nullable
private Point2d startPoint = null;
@Nullable
private OnDragListener onDragListener;
private final OnTouchListener onTouchListener = new OnTouchListenerImpl();
@Nullable
private String textUp;
@Nullable
private String textDown;
@Nullable
private String textMiddle;
public DragButton(Context context, @NotNull AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public DragButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private void init(@NotNull Context context, @NotNull AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DragButton);
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.DragButton_textUp:
this.textUp = a.getString(attr);
break;
case R.styleable.DragButton_textDown:
this.textDown = a.getString(attr);
break;
}
}
// backup text
this.textMiddle = String.valueOf(getText());
setText(Html.fromHtml(getStyledUpDownText(this.textUp) + "<br><b>" + StringUtils.getNotEmpty(this.textMiddle, "&nbsp;") + "</b><br>" + getStyledUpDownText(this.textDown)));
// change top padding in order to show all text
setPadding(getPaddingLeft(), -7, getPaddingRight(), getPaddingBottom());
setOnTouchListener(this.onTouchListener);
}
private String getStyledUpDownText(@Nullable String text) {
final StringBuilder sb = new StringBuilder();
sb.append("<font color='#585858'><small><small>");
sb.append(StringUtils.getNotEmpty(text, "&nbsp;"));
sb.append("</small></small></font>");
return sb.toString();
}
public void setOnDragListener(@Nullable OnDragListener onDragListener) {
this.onDragListener = onDragListener;
}
@Nullable
public OnDragListener getOnDragListener() {
return onDragListener;
}
public void setTextUp(String textUp) {
this.textUp = textUp;
}
public String getTextUp() {
return textUp;
}
public void setTextDown(String textDown) {
this.textDown = textDown;
}
public String getTextDown() {
return textDown;
}
public void setTextMiddle(String textMiddle) {
this.textMiddle = textMiddle;
}
public String getTextMiddle() {
return textMiddle;
}
/**
* OnTouchListener implementation that fires onDrag()
*
* @author serso
*
*/
private final class OnTouchListenerImpl implements OnTouchListener {
@Override
public boolean onTouch(@NotNull View v, @NotNull MotionEvent event) {
// processing on touch event
if (onDragListener != null) {
// only if onDrag() listener specified
Log.d(String.valueOf(getId()), "onTouch() for: " + getId() + " . Motion event: " + event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// start tracking: set start point
startPoint = new Point2d(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
if (event.getEventTime() - event.getDownTime() > maxTime) {
// do not allow very long touch movements
startPoint = null;
}
break;
case MotionEvent.ACTION_UP:
// stop tracking
if (onDragListener.onDrag(DragButton.this, new DragEvent(startPoint, event))) {
if (onDragListener.isSuppressOnClickEvent()) {
// prevent on click action
setPressed(false);
}
}
startPoint = null;
break;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,7 @@
package org.solovyev.android.view;
public enum DragDirection {
up,
down;
}

View File

@@ -0,0 +1,33 @@
package org.solovyev.android.view;
import org.jetbrains.annotations.NotNull;
import org.solovyev.util.math.Point2d;
import android.view.MotionEvent;
public class DragEvent {
@NotNull
private final Point2d startPoint;
@NotNull
private final MotionEvent motionEvent;
public DragEvent(@NotNull Point2d startPoint, @NotNull MotionEvent motionEvent) {
this.startPoint = startPoint;
this.motionEvent = motionEvent;
}
@NotNull
public MotionEvent getMotionEvent() {
return motionEvent;
}
@NotNull
public Point2d getStartPoint() {
return startPoint;
}
}

View File

@@ -0,0 +1,22 @@
package org.solovyev.android.view;
import org.jetbrains.annotations.NotNull;
public interface OnDragListener {
/**
*
* @return 'true': if drag event has taken place (i.e. onDrag() method returned true) then click action will be suppresed
*/
boolean isSuppressOnClickEvent();
/**
* @param dragButton drag button object for which onDrag listener was set
* @param event drag event
*
* @return 'true' if drag event occurred, 'false' otherwise
*/
boolean onDrag(@NotNull DragButton dragButton, @NotNull DragEvent event);
}

View File

@@ -0,0 +1,93 @@
package org.solovyev.android.view;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.util.StringUtils;
import org.solovyev.util.math.MathUtils;
import org.solovyev.util.math.Point2d;
import android.util.Log;
import android.view.MotionEvent;
public class SimpleOnDragListener implements OnDragListener {
@NotNull
private final Point2d axis = new Point2d(0, 1);
private float minDragDist = 20f;
private float maxDragDist = 80f;
// max angle (in degrees!) between start and end point vector and axis
// vector to register drag event
private double maxAngle = 30;
@Override
public boolean onDrag(@NotNull DragButton dragButton, @NotNull DragEvent event) {
logDragEvent(dragButton, event);
processButtonAction(dragButton, getActionText(dragButton, event));
}
@Override
public boolean isSuppressOnClickEvent() {
return true;
}
/**
* Method creates drag event in case if all conditions are satisfied
*
* @param event
* motion event
*
* @return filled drag event object only if drag event is possible, null
* otherwise
*/
@Nullable
protected DragEvent getDragEvent(@NotNull MotionEvent event) {
DragEvent result = null;
if (startPoint != null) {
final Point2d endPoint = new Point2d(event.getX(), event.getY());
float distance = MathUtils.getDistance(startPoint, endPoint);
if (minDragDist < distance && distance < maxDragDist) {
double angle = Math.toDegrees(MathUtils.getAngle(startPoint, MathUtils.sum(startPoint, axis), endPoint));
final DragDirection direction;
if (angle < maxAngle) {
direction = DragDirection.down;
} else if (180 - angle < maxAngle) {
direction = DragDirection.up;
} else {
direction = null;
}
if (direction != null) {
if ( direction == DragDirection.up && StringUtils.isEmpty(textUp) ) {
// no action if text is empty
} else if (direction == DragDirection.down && StringUtils.isEmpty(textDown)) {
// no action if text is empty
} else {
result = new DragEvent(direction);
}
}
}
}
return result;
}
private void logDragEvent(@NotNull DragButton dragButton, @NotNull DragEvent event) {
final Point2d startPoint = event.getStartPoint();
final MotionEvent motionEvent = event.getMotionEvent();
final Point2d endPoint = new Point2d(motionEvent.getX(), motionEvent.getY());
Log.d(String.valueOf(dragButton.getId()), "Start point: " + startPoint + ", End point: " + endPoint);
Log.d(String.valueOf(dragButton.getId()), "Distance: " + MathUtils.getDistance(startPoint, endPoint));
Log.d(String.valueOf(dragButton.getId()), "Angle: " + Math.toDegrees(MathUtils.getAngle(startPoint, MathUtils.sum(startPoint, axis), endPoint)));
Log.d(String.valueOf(dragButton.getId()), "Axis: " + axis + " Vector: " + MathUtils.subtract(endPoint, startPoint));
Log.d(String.valueOf(dragButton.getId()), "Total time: " + (motionEvent.getEventTime() - motionEvent.getDownTime()) + " ms");
}
}

View File

@@ -0,0 +1,17 @@
package org.solovyev.util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class StringUtils {
public static boolean isEmpty ( @Nullable String s ){
return s == null || s.length() == 0;
}
@NotNull
public static String getNotEmpty ( @Nullable String s, @NotNull String defaultValue ){
return isEmpty(s) ? defaultValue : s;
}
}

View File

@@ -0,0 +1,11 @@
package org.solovyev.util.date;
public class DateUtils {
public static long MS_IN_SECONDS = 1000l;
public static long msToSeconds (long ms) {
return ms / 1000l;
}
}

View File

@@ -0,0 +1,37 @@
package org.solovyev.util.math;
import org.jetbrains.annotations.NotNull;
public enum MathEntity {
minus("-"),
equals("="),
factorial("!"),
plus("+"),
multiply("*"),
divide("/"),
power("^"),
sin("sin"),
asin("asin"),
cos("cos"),
acos("acos"),
tg("tg"),
atg("atg"),
exp("exp"),
log("log"),
ln("ln"),
mod("mod"),
sqrt("sqrt");
@NotNull
private final String text;
private MathEntity (@NotNull String text) {
this.text = text;
}
@NotNull
public String getText() {
return text;
}
}

View File

@@ -0,0 +1,57 @@
package org.solovyev.util.math;
import java.util.Arrays;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public enum MathEntityType {
digit,
function,
unary_operation,
binary_operation,
group_symbols,
group_symbol;
private static final List<Character> unaryOperations = Arrays.asList('-', '=', '!');
private static final List<Character> binaryOperations = Arrays.asList('-', '+', '*', '/', '^' );
private static final List<String> functions = Arrays.asList("sin", "asin", "cos", "acos", "tg", "atg", "exp", "log", "ln", "mod", "sqrt");
private static final List<String> groupSymbols = Arrays.asList("[]", "()", "{}");
private static final List<Character> singleGroupSymbols = Arrays.asList('[', ']', '(', ')', '{', '}');
@Nullable
public static MathEntityType getType( @NotNull String s ) {
MathEntityType result = null;
if ( s.length() == 1 ) {
char ch = s.charAt(0);
if ( Character.isDigit(ch) ) {
result = MathEntityType.digit;
} else if ( unaryOperations.contains(ch) ) {
result = MathEntityType.unary_operation;
} else if ( binaryOperations.contains(ch) ) {
result = MathEntityType.binary_operation;
} else if ( singleGroupSymbols.contains(ch) ) {
result = MathEntityType.group_symbol;
}
}
if ( result == null ) {
if ( functions.contains(s) ) {
result = MathEntityType.function;
} else if ( groupSymbols.contains(s) ) {
result = MathEntityType.group_symbols;
}
}
return result;
}
}

View File

@@ -0,0 +1,39 @@
package org.solovyev.util.math;
import org.jetbrains.annotations.NotNull;
public class MathUtils {
public static float getDistance(@NotNull Point2d startPoint,
@NotNull Point2d endPoint) {
return getNorm(subtract(endPoint, startPoint));
}
public static Point2d subtract(@NotNull Point2d p1, @NotNull Point2d p2) {
return new Point2d(p1.getX() - p2.getX(), p1.getY() - p2.getY());
}
public static Point2d sum(@NotNull Point2d p1, @NotNull Point2d p2) {
return new Point2d(p1.getX() + p2.getX(), p1.getY() + p2.getY());
}
public static float getNorm(@NotNull Point2d point) {
return (float) Math.pow(
Math.pow(point.getX(), 2) + Math.pow(point.getY(), 2), 0.5);
}
public static float getAngle(@NotNull Point2d startPoint,
@NotNull Point2d axisEndPoint, @NotNull Point2d endPoint) {
final Point2d axisVector = subtract(axisEndPoint, startPoint);
final Point2d vector = subtract(endPoint, startPoint);
double a_2 = Math.pow(getDistance(vector, axisVector), 2);
double b = getNorm(vector);
double b_2 = Math.pow(b, 2);
double c = getNorm(axisVector);
double c_2 = Math.pow(c, 2);
return (float) Math.acos((-a_2 + b_2 + c_2) / (2 * b * c));
}
}

View File

@@ -0,0 +1,37 @@
package org.solovyev.util.math;
public class Point2d {
private float x = 0;
private float y = 0;
public Point2d() {
}
public Point2d( float x, float y ) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public float getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
@Override
public String toString() {
return "Point2d [x=" + x + ", y=" + y + "]";
}
}