some weird moving
This commit is contained in:
parent
6ee636bb5d
commit
7702fa3db9
@ -0,0 +1,426 @@
|
|||||||
|
package org.solovyev.android.calculator;
|
||||||
|
|
||||||
|
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<CalculatorHistoryState> historyHelper;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private BroadcastReceiver preferencesChangesReceiver;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private List<SimpleOnDragListener> onDragListeners = new ArrayList<SimpleOnDragListener>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity is first created.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
final DragButtonCalibrationActivity.Preferences dragPreferences = DragButtonCalibrationActivity.getPreferences(this);
|
||||||
|
|
||||||
|
final SimpleOnDragListener onDragListener = new SimpleOnDragListener(new SimpleOnDragListener.DragProcessor() {
|
||||||
|
@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));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}, dragPreferences);
|
||||||
|
|
||||||
|
onDragListeners.add(onDragListener);
|
||||||
|
|
||||||
|
// 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(CalculatorActivity.class.getName(), e.getMessage());
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
Log.e(CalculatorActivity.class.getName(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final SimpleOnDragListener historyOnDragListener = new SimpleOnDragListener(new HistoryDragProcessor(), dragPreferences);
|
||||||
|
((DragButton) findViewById(R.id.clearButton)).setOnDragListener(historyOnDragListener);
|
||||||
|
((DragButton) findViewById(R.id.pasteButton)).setOnDragListener(historyOnDragListener);
|
||||||
|
onDragListeners.add(historyOnDragListener);
|
||||||
|
|
||||||
|
final SimpleOnDragListener toPositionOnDragListener = new SimpleOnDragListener(new SimpleOnDragListener.DragProcessor() {
|
||||||
|
@Override
|
||||||
|
public boolean processDragEvent(@NotNull DragDirection dragDirection, @NotNull DragButton dragButton, @NotNull Point2d startPoint2d, @NotNull MotionEvent motionEvent) {
|
||||||
|
boolean result = false;
|
||||||
|
|
||||||
|
if (dragButton instanceof DirectionDragButton) {
|
||||||
|
String text = ((DirectionDragButton) dragButton).getText(dragDirection);
|
||||||
|
if ("↞".equals(text)) {
|
||||||
|
CalculatorActivity.this.editText.setSelection(0);
|
||||||
|
} else if ("↠".equals(text)) {
|
||||||
|
CalculatorActivity.this.editText.setSelection(CalculatorActivity.this.editText.getText().length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}, dragPreferences);
|
||||||
|
((DragButton) findViewById(R.id.rightButton)).setOnDragListener(toPositionOnDragListener);
|
||||||
|
((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<CalculatorHistoryState>();
|
||||||
|
saveHistoryState();
|
||||||
|
|
||||||
|
this.preferencesChangesReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
|
||||||
|
if (DragButtonCalibrationActivity.INTENT_ACTION.equals(intent.getAction())) {
|
||||||
|
final DragButtonCalibrationActivity.Preferences preferences = DragButtonCalibrationActivity.getPreferences(CalculatorActivity.this);
|
||||||
|
for (SimpleOnDragListener dragListener : onDragListeners) {
|
||||||
|
dragListener.setPreferences(preferences);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
registerReceiver(this.preferencesChangesReceiver, new IntentFilter(DragButtonCalibrationActivity.INTENT_ACTION));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveHistoryState() {
|
||||||
|
historyHelper.addState(getCurrentHistoryState());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void elementaryButtonClickHandler(@NotNull View v) {
|
||||||
|
eval(JsclOperation.elementary, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void numericButtonClickHandler(@NotNull View v) {
|
||||||
|
eval(JsclOperation.numeric, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void eraseButtonClickHandler(@NotNull View v) {
|
||||||
|
if (editText.getSelectionStart() > 0) {
|
||||||
|
editText.getText().delete(editText.getSelectionStart() - 1, editText.getSelectionStart());
|
||||||
|
saveHistoryState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simplifyButtonClickHandler(@NotNull View v) {
|
||||||
|
eval(JsclOperation.simplify, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveLeftButtonClickHandler(@NotNull View v) {
|
||||||
|
if (editText.getSelectionStart() > 0) {
|
||||||
|
editText.setSelection(editText.getSelectionStart() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveRightButtonClickHandler(@NotNull View v) {
|
||||||
|
if (editText.getSelectionStart() < editText.getText().length()) {
|
||||||
|
editText.setSelection(editText.getSelectionStart() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void digitButtonClickHandler(@NotNull View v) {
|
||||||
|
processButtonAction(v, ((DirectionDragButton) v).getTextMiddle());
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class HistoryDragProcessor implements SimpleOnDragListener.DragProcessor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean processDragEvent(@NotNull DragDirection dragDirection, @NotNull DragButton dragButton, @NotNull Point2d startPoint2d, @NotNull MotionEvent motionEvent) {
|
||||||
|
boolean result = false;
|
||||||
|
|
||||||
|
Log.d(String.valueOf(dragButton.getId()), "History on drag event start: " + dragDirection);
|
||||||
|
|
||||||
|
assert dragButton instanceof DirectionDragButton;
|
||||||
|
String actionText = getActionText((DirectionDragButton) dragButton, dragDirection);
|
||||||
|
if (!StringUtils.isEmpty(actionText)) {
|
||||||
|
try {
|
||||||
|
result = true;
|
||||||
|
|
||||||
|
final HistoryAction historyAction = HistoryAction.valueOf(actionText);
|
||||||
|
doHistoryAction(historyAction);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.e(String.valueOf(dragButton.getId()), "Unsupported history action: " + actionText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
switch (direction) {
|
||||||
|
case up:
|
||||||
|
result = dragButton.getTextUp();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case down:
|
||||||
|
result = dragButton.getTextDown();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
result = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onKeyDown(keyCode, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
// todo serso: inflate menu as soon as it will implemented in proper way
|
||||||
|
/* final MenuInflater menuInflater = getMenuInflater();
|
||||||
|
menuInflater.inflate(R.menu.main_menu, menu);*/
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
boolean result;
|
||||||
|
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.menu_item_settings:
|
||||||
|
showSettings();
|
||||||
|
result = true;
|
||||||
|
case R.id.menu_item_help:
|
||||||
|
showHelp();
|
||||||
|
result = true;
|
||||||
|
default:
|
||||||
|
result = super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSettings() {
|
||||||
|
startActivity(new Intent(this, CalculatorPreferencesActivity.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showHelp() {
|
||||||
|
Log.d(CalculatorActivity.class + "showHelp()", "Show help!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The font sizes in the layout files are specified for a HVGA display.
|
||||||
|
* Adjust the font sizes accordingly if we are running on a different
|
||||||
|
* display.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void adjustFontSize(@NotNull TextView view) {
|
||||||
|
float fontPixelSize = view.getTextSize();
|
||||||
|
Display display = getWindowManager().getDefaultDisplay();
|
||||||
|
int h = Math.min(display.getWidth(), display.getHeight());
|
||||||
|
float ratio = (float) h / HVGA_WIDTH_PIXELS;
|
||||||
|
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontPixelSize * ratio);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package org.solovyev.android.calculator;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User: serso
|
||||||
|
* Date: 9/11/11
|
||||||
|
* Time: 12:16 AM
|
||||||
|
*/
|
||||||
|
public class CalculatorHistoryState {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private EditorHistoryState editorState;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private EditorHistoryState resultEditorState;
|
||||||
|
|
||||||
|
public CalculatorHistoryState(@NotNull EditorHistoryState editorState, @NotNull EditorHistoryState resultEditorState) {
|
||||||
|
this.editorState = editorState;
|
||||||
|
this.resultEditorState = resultEditorState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public EditorHistoryState getEditorState() {
|
||||||
|
return editorState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEditorState(@NotNull EditorHistoryState editorState) {
|
||||||
|
this.editorState = editorState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public EditorHistoryState getResultEditorState() {
|
||||||
|
return resultEditorState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResultEditorState(@NotNull EditorHistoryState resultEditorState) {
|
||||||
|
this.resultEditorState = resultEditorState;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package org.solovyev.android.calculator;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.preference.Preference;
|
||||||
|
import android.preference.PreferenceActivity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User: serso
|
||||||
|
* Date: 7/16/11
|
||||||
|
* Time: 6:37 PM
|
||||||
|
*/
|
||||||
|
public class CalculatorPreferencesActivity extends PreferenceActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
addPreferencesFromResource(R.xml.preferences);
|
||||||
|
|
||||||
|
final Preference dragButtonCalibration = findPreference("dragButtonCalibration");
|
||||||
|
dragButtonCalibration.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
|
|
||||||
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
|
startActivity(new Intent(CalculatorPreferencesActivity.this, DragButtonCalibrationActivity.class));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRestoreInstanceState(Bundle state) {
|
||||||
|
super.onRestoreInstanceState(state);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,393 @@
|
|||||||
|
package org.solovyev.android.calculator;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.solovyev.android.view.*;
|
||||||
|
import org.solovyev.common.collections.ManyValuedHashMap;
|
||||||
|
import org.solovyev.common.collections.ManyValuedMap;
|
||||||
|
import org.solovyev.common.utils.Interval;
|
||||||
|
import org.solovyev.util.math.MathUtils;
|
||||||
|
import org.solovyev.util.math.Point2d;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User: serso
|
||||||
|
* Date: 7/16/11
|
||||||
|
* Time: 7:28 PM
|
||||||
|
*/
|
||||||
|
public class DragButtonCalibrationActivity extends Activity {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private final List<DragData> dragHistory = new ArrayList<DragData>();
|
||||||
|
|
||||||
|
private final Map<DragButton, CalibrationArrow> map = new HashMap<DragButton, CalibrationArrow>();
|
||||||
|
|
||||||
|
public static final String PREFERENCES = "dragButtonPreferences";
|
||||||
|
|
||||||
|
public static final String PREFERENCES_FIRST_RUN = "firstRun";
|
||||||
|
|
||||||
|
public static final String PREFERENCES_MIN = "min";
|
||||||
|
public static final String PREFERENCES_MAX = "max";
|
||||||
|
|
||||||
|
private static final float DEFAULT_VALUE = -999;
|
||||||
|
private static final int MIN_HISTORY_FOR_CALIBRATION = 10;
|
||||||
|
public static final String INTENT_ACTION = "org.solovyev.android.calculator.DragButtonPreferencesChanged";
|
||||||
|
|
||||||
|
public static enum PreferenceType {
|
||||||
|
angle,
|
||||||
|
distance,
|
||||||
|
duration
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setContentView(R.layout.drag_button_calibration);
|
||||||
|
|
||||||
|
createCalibrationButton(R.id.calibrationButtonRight, R.id.calibrationArrowRight);
|
||||||
|
createCalibrationButton(R.id.calibrationButtonLeft, R.id.calibrationArrowLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createCalibrationButton(int buttonId, int arrowId) {
|
||||||
|
final DragButton calibrationButton = (DragButton) findViewById(buttonId);
|
||||||
|
calibrationButton.setOnDragListener(new CalibrationOnDragListener());
|
||||||
|
|
||||||
|
ImageView imageView = (ImageView) findViewById(arrowId);
|
||||||
|
CalibrationArrow calibrationArrow = new CalibrationArrow(imageView);
|
||||||
|
|
||||||
|
createDragDirection(0, calibrationArrow);
|
||||||
|
|
||||||
|
map.put(calibrationButton, calibrationArrow);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createDragDirection(long timeout, @NotNull final CalibrationArrow calibrationArrow) {
|
||||||
|
new Handler().postDelayed(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
calibrationArrow.dragDirection = Math.random() > 0.5 ? DragDirection.up : DragDirection.down;
|
||||||
|
|
||||||
|
calibrationArrow.calibrationArrow.setImageResource(calibrationArrow.dragDirection == DragDirection.down ? R.drawable.down : R.drawable.up);
|
||||||
|
}
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restartClickHandler(View v) {
|
||||||
|
for (CalibrationArrow calibrationArrow : map.values()) {
|
||||||
|
createDragDirection(0, calibrationArrow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class CalibrationOnDragListener implements OnDragListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSuppressOnClickEvent() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onDrag(@NotNull DragButton dragButton, @NotNull DragEvent event) {
|
||||||
|
final Point2d startPoint = event.getStartPoint();
|
||||||
|
final MotionEvent motionEvent = event.getMotionEvent();
|
||||||
|
|
||||||
|
// init end point
|
||||||
|
final Point2d endPoint = new Point2d(motionEvent.getX(), motionEvent.getY());
|
||||||
|
|
||||||
|
float distance = MathUtils.getDistance(startPoint, endPoint);
|
||||||
|
|
||||||
|
double angle = Math.toDegrees(MathUtils.getAngle(startPoint, MathUtils.sum(startPoint, SimpleOnDragListener.axis), endPoint));
|
||||||
|
|
||||||
|
final CalibrationArrow calibrationArrow = map.get(dragButton);
|
||||||
|
final DragDirection dragDirection = calibrationArrow.dragDirection;
|
||||||
|
|
||||||
|
assert dragDirection == DragDirection.up || dragDirection == DragDirection.down;
|
||||||
|
|
||||||
|
double deviationAngle = angle;
|
||||||
|
if (dragDirection == DragDirection.up) {
|
||||||
|
deviationAngle = 180 - deviationAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviationAngle > 45) {
|
||||||
|
calibrationArrow.calibrationArrow.setImageResource(R.drawable.not_ok);
|
||||||
|
} else {
|
||||||
|
calibrationArrow.calibrationArrow.setImageResource(R.drawable.ok);
|
||||||
|
dragHistory.add(new DragData(dragDirection, distance, angle, (motionEvent.getEventTime() - motionEvent.getDownTime())));
|
||||||
|
}
|
||||||
|
|
||||||
|
createDragDirection(500, calibrationArrow);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||||
|
|
||||||
|
if (dragHistory.size() > MIN_HISTORY_FOR_CALIBRATION) {
|
||||||
|
final ManyValuedMap<DragDirection, Double> anglesByDirection = new ManyValuedHashMap<DragDirection, Double>();
|
||||||
|
final ManyValuedMap<DragDirection, Double> distancesByDirection = new ManyValuedHashMap<DragDirection, Double>();
|
||||||
|
final ManyValuedMap<DragDirection, Double> timesByDirection = new ManyValuedHashMap<DragDirection, Double>();
|
||||||
|
for (DragData dragData : dragHistory) {
|
||||||
|
anglesByDirection.put(dragData.getDirection(), dragData.getAngle());
|
||||||
|
distancesByDirection.put(dragData.getDirection(), (double) dragData.getDistance());
|
||||||
|
timesByDirection.put(dragData.getDirection(), dragData.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<DragDirection, MathUtils.StatData> angleStatData = getStatDataByDirection(anglesByDirection);
|
||||||
|
final Map<DragDirection, MathUtils.StatData> distanceStatData = getStatDataByDirection(distancesByDirection);
|
||||||
|
final Map<DragDirection, MathUtils.StatData> timeStatData = getStatDataByDirection(timesByDirection);
|
||||||
|
|
||||||
|
Log.d(this.getClass().getName(), "Angle statistics: ");
|
||||||
|
logStatData(angleStatData);
|
||||||
|
|
||||||
|
Log.d(this.getClass().getName(), "Distance statistics: ");
|
||||||
|
logStatData(distanceStatData);
|
||||||
|
|
||||||
|
Log.d(this.getClass().getName(), "Time statistics: ");
|
||||||
|
logStatData(timeStatData);
|
||||||
|
|
||||||
|
final SharedPreferences settings = getSharedPreferences(PREFERENCES, 0);
|
||||||
|
final SharedPreferences.Editor editor = settings.edit();
|
||||||
|
|
||||||
|
setPreferences(angleStatData, editor, PreferenceType.angle);
|
||||||
|
setPreferences(distanceStatData, editor, PreferenceType.distance);
|
||||||
|
setPreferences(timeStatData, editor, PreferenceType.duration);
|
||||||
|
|
||||||
|
editor.commit();
|
||||||
|
|
||||||
|
sendOrderedBroadcast(new Intent(INTENT_ACTION), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return super.onKeyDown(keyCode, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPreferences(@NotNull Map<DragDirection, MathUtils.StatData> statData, @NotNull SharedPreferences.Editor editor, @NotNull PreferenceType preferenceType) {
|
||||||
|
for (Map.Entry<DragDirection, MathUtils.StatData> entry : statData.entrySet()) {
|
||||||
|
final float min = (float) entry.getValue().getMean() - 2 * (float) entry.getValue().getStandardDeviation();
|
||||||
|
final float max = (float) entry.getValue().getMean() + 2 * (float) entry.getValue().getStandardDeviation();
|
||||||
|
editor.putFloat(preferenceType.name() + "_" + entry.getKey().name() + "_" + PREFERENCES_MIN, Math.max(0, min));
|
||||||
|
editor.putFloat(preferenceType.name() + "_" + entry.getKey().name() + "_" + PREFERENCES_MAX, max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static Preferences getPreferences(@NotNull Context context) {
|
||||||
|
SharedPreferences preferences = context.getSharedPreferences(PREFERENCES, MODE_PRIVATE);
|
||||||
|
|
||||||
|
final Preferences result = new Preferences();
|
||||||
|
|
||||||
|
for (PreferenceType preferenceType : PreferenceType.values()) {
|
||||||
|
for (DragDirection dragDirection : DragDirection.values()) {
|
||||||
|
|
||||||
|
final float defaultMin;
|
||||||
|
final float defaultMax;
|
||||||
|
switch (preferenceType) {
|
||||||
|
case angle:
|
||||||
|
switch (dragDirection) {
|
||||||
|
case up:
|
||||||
|
defaultMin = 150f;
|
||||||
|
defaultMax = 180f;
|
||||||
|
break;
|
||||||
|
case down:
|
||||||
|
defaultMin = 0f;
|
||||||
|
defaultMax = 30f;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
defaultMin = DEFAULT_VALUE;
|
||||||
|
defaultMax = DEFAULT_VALUE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case distance:
|
||||||
|
defaultMin = 60f;
|
||||||
|
defaultMax = 140f;
|
||||||
|
break;
|
||||||
|
case duration:
|
||||||
|
defaultMin = 100f;
|
||||||
|
defaultMax = 300f;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
defaultMin = DEFAULT_VALUE;
|
||||||
|
defaultMax = DEFAULT_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
final float min = preferences.getFloat(preferenceType.name() + "_" + dragDirection.name() + "_" + PREFERENCES_MIN, defaultMin);
|
||||||
|
final float max = preferences.getFloat(preferenceType.name() + "_" + dragDirection.name() + "_" + PREFERENCES_MAX, defaultMax);
|
||||||
|
|
||||||
|
if (min != DEFAULT_VALUE && max != DEFAULT_VALUE) {
|
||||||
|
final DragPreference directionPreference = new DragPreference(dragDirection, new Interval(min, max));
|
||||||
|
|
||||||
|
Preference preference = result.getPreferencesMap().get(preferenceType);
|
||||||
|
if (preference == null) {
|
||||||
|
preference = new Preference(preferenceType);
|
||||||
|
result.getPreferencesMap().put(preferenceType, preference);
|
||||||
|
}
|
||||||
|
|
||||||
|
preference.getDirectionPreferences().put(dragDirection, directionPreference);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Log.e(DragButtonCalibrationActivity.class.getName(), "New preference type added: default preferences should be defined!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DragPreference {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private DragDirection direction;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Interval interval;
|
||||||
|
|
||||||
|
|
||||||
|
public DragPreference(@NotNull DragDirection direction, @NotNull Interval interval) {
|
||||||
|
this.direction = direction;
|
||||||
|
this.interval = interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public DragDirection getDirection() {
|
||||||
|
return direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirection(@NotNull DragDirection direction) {
|
||||||
|
this.direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public Interval getInterval() {
|
||||||
|
return interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInterval(@NotNull Interval interval) {
|
||||||
|
this.interval = interval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Preference {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private PreferenceType preferenceType;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Map<DragDirection, DragPreference> directionPreferences = new HashMap<DragDirection, DragPreference>();
|
||||||
|
|
||||||
|
|
||||||
|
public Preference(@NotNull PreferenceType preferenceType) {
|
||||||
|
this.preferenceType = preferenceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public PreferenceType getPreferenceType() {
|
||||||
|
return preferenceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPreferenceType(@NotNull PreferenceType preferenceType) {
|
||||||
|
this.preferenceType = preferenceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public Map<DragDirection, DragPreference> getDirectionPreferences() {
|
||||||
|
return directionPreferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirectionPreferences(@NotNull Map<DragDirection, DragPreference> directionPreferences) {
|
||||||
|
this.directionPreferences = directionPreferences;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class Preferences {
|
||||||
|
|
||||||
|
private final Map<PreferenceType, Preference> preferencesMap = new HashMap<PreferenceType, Preference>();
|
||||||
|
|
||||||
|
public Map<PreferenceType, Preference> getPreferencesMap() {
|
||||||
|
return preferencesMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logStatData(@NotNull Map<DragDirection, MathUtils.StatData> statData) {
|
||||||
|
for (Map.Entry<DragDirection, MathUtils.StatData> entry : statData.entrySet()) {
|
||||||
|
Log.d(this.getClass().getName(), entry.getKey() + "-> m: " + entry.getValue().getMean() + ", d: " + entry.getValue().getStandardDeviation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<DragDirection, MathUtils.StatData> getStatDataByDirection(@NotNull ManyValuedMap<DragDirection, Double> valuesByDirection) {
|
||||||
|
final Map<DragDirection, MathUtils.StatData> result = new HashMap<DragDirection, MathUtils.StatData>();
|
||||||
|
|
||||||
|
for (Map.Entry<DragDirection, List<Double>> entry : valuesByDirection.entrySet()) {
|
||||||
|
result.put(entry.getKey(), MathUtils.getStatData(entry.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class DragData {
|
||||||
|
|
||||||
|
private float distance;
|
||||||
|
|
||||||
|
private double angle;
|
||||||
|
|
||||||
|
private double time;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private DragDirection direction;
|
||||||
|
|
||||||
|
private DragData(@NotNull DragDirection direction, float distance, double angle, double time) {
|
||||||
|
this.distance = distance;
|
||||||
|
this.angle = angle;
|
||||||
|
this.direction = direction;
|
||||||
|
this.time = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getDistance() {
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getAngle() {
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public DragDirection getDirection() {
|
||||||
|
return direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getTime() {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CalibrationArrow {
|
||||||
|
@NotNull
|
||||||
|
private ImageView calibrationArrow;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private DragDirection dragDirection = DragDirection.up;
|
||||||
|
|
||||||
|
private CalibrationArrow(@NotNull ImageView calibrationArrow) {
|
||||||
|
this.calibrationArrow = calibrationArrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package org.solovyev.android.calculator;
|
||||||
|
|
||||||
|
public enum HistoryAction {
|
||||||
|
|
||||||
|
redo,
|
||||||
|
undo;
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.solovyev.android.calculator;
|
||||||
|
|
||||||
|
public enum JsclOperation {
|
||||||
|
|
||||||
|
simplify,
|
||||||
|
elementary,
|
||||||
|
importCommands,
|
||||||
|
numeric;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package org.solovyev.android.calculator;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.solovyev.util.math.MathEntityType;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
checkMultiplicationSignBeforeFunction(sb, s, i);
|
||||||
|
|
||||||
|
if (ch == '[' || ch == '{') {
|
||||||
|
sb.append('(');
|
||||||
|
} else if (ch == ']' || ch == '}') {
|
||||||
|
sb.append(')');
|
||||||
|
} else if (ch == 'π') {
|
||||||
|
sb.append("pi");
|
||||||
|
} else if (ch == '×' || ch == '∙') {
|
||||||
|
sb.append("*");
|
||||||
|
} else if (s.startsWith("ln", i)) {
|
||||||
|
sb.append("log");
|
||||||
|
i += 1;
|
||||||
|
} else if (s.startsWith("tg", i)) {
|
||||||
|
sb.append("tan");
|
||||||
|
i += 1;
|
||||||
|
} else if (s.startsWith("atg", i)) {
|
||||||
|
sb.append("atan");
|
||||||
|
i += 2;
|
||||||
|
} else if (s.startsWith("e(", i)) {
|
||||||
|
sb.append("exp(");
|
||||||
|
i += 1;
|
||||||
|
} else if (ch == 'e') {
|
||||||
|
sb.append("exp(1)");
|
||||||
|
} else if (ch == '√') {
|
||||||
|
sb.append("sqrt");
|
||||||
|
} else {
|
||||||
|
sb.append(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkMultiplicationSignBeforeFunction(@NotNull StringBuilder sb, @NotNull String s, int i) {
|
||||||
|
if (i > 0) {
|
||||||
|
// get character before function
|
||||||
|
char chBefore = s.charAt(i - 1);
|
||||||
|
char ch = s.charAt(i);
|
||||||
|
|
||||||
|
final MathEntityType mathTypeBefore = MathEntityType.getType(String.valueOf(chBefore));
|
||||||
|
final MathEntityType mathType = MathEntityType.getType(String.valueOf(ch));
|
||||||
|
|
||||||
|
if (mathTypeBefore != MathEntityType.binary_operation &&
|
||||||
|
mathTypeBefore != MathEntityType.unary_operation &&
|
||||||
|
!MathEntityType.openGroupSymbols.contains(chBefore)) {
|
||||||
|
|
||||||
|
if (mathType == MathEntityType.constant) {
|
||||||
|
sb.append("*");
|
||||||
|
} else if (mathType == MathEntityType.digit && mathTypeBefore != MathEntityType.digit && mathTypeBefore != MathEntityType.dot) {
|
||||||
|
sb.append("*");
|
||||||
|
} else {
|
||||||
|
for (String function : MathEntityType.functions) {
|
||||||
|
if (s.startsWith(function, i)) {
|
||||||
|
sb.append("*");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String wrap(@NotNull JsclOperation operation, @NotNull String s) {
|
||||||
|
return operation.name() + "(\"" + s + "\");";
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
155
src/main/java/org/solovyev/android/view/ColorButton.java
Normal file
155
src/main/java/org/solovyev/android/view/ColorButton.java
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2008 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.solovyev.android.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Paint.Style;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.solovyev.util.math.Point2d;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: copied from com.android.calculator2.ColorButton
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button with click-animation effect.
|
||||||
|
*/
|
||||||
|
public class ColorButton extends Button {
|
||||||
|
int CLICK_FEEDBACK_COLOR;
|
||||||
|
static final int CLICK_FEEDBACK_INTERVAL = 10;
|
||||||
|
static final int CLICK_FEEDBACK_DURATION = 350;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Point2d textPosition;
|
||||||
|
private long mAnimStart;
|
||||||
|
private Paint mFeedbackPaint;
|
||||||
|
|
||||||
|
public ColorButton(Context context, AttributeSet attrs) {
|
||||||
|
this(context, attrs, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColorButton(Context context, AttributeSet attrs, boolean init) {
|
||||||
|
super(context, attrs);
|
||||||
|
if (init) {
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void init(Context context) {
|
||||||
|
Resources res = getResources();
|
||||||
|
|
||||||
|
CLICK_FEEDBACK_COLOR = res.getColor(org.solovyev.android.calculator.R.color.magic_flame);
|
||||||
|
mFeedbackPaint = new Paint();
|
||||||
|
mFeedbackPaint.setStyle(Style.STROKE);
|
||||||
|
mFeedbackPaint.setStrokeWidth(2);
|
||||||
|
getPaint().setColor(res.getColor(org.solovyev.android.calculator.R.color.button_text));
|
||||||
|
|
||||||
|
mAnimStart = -1;
|
||||||
|
|
||||||
|
if (context instanceof FontSizeAdjuster) {
|
||||||
|
((FontSizeAdjuster) context).adjustFontSize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSizeChanged(int w, int h, int oldW, int oldH) {
|
||||||
|
measureText();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void measureText() {
|
||||||
|
Paint paint = getPaint();
|
||||||
|
|
||||||
|
if (getText() != null) {
|
||||||
|
textPosition = getTextPosition(paint, getText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point2d getTextPosition(@NotNull Paint paint, @NotNull CharSequence text) {
|
||||||
|
final Point2d result = new Point2d();
|
||||||
|
|
||||||
|
result.setX((getWidth() - paint.measureText(text.toString())) / 2);
|
||||||
|
|
||||||
|
float height = getHeight() - paint.ascent() - paint.descent();
|
||||||
|
|
||||||
|
result.setY(height / 2);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onTextChanged(CharSequence text, int start, int before, int after) {
|
||||||
|
measureText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawMagicFlame(int duration, Canvas canvas) {
|
||||||
|
int alpha = 255 - 255 * duration / CLICK_FEEDBACK_DURATION;
|
||||||
|
int color = CLICK_FEEDBACK_COLOR | (alpha << 24);
|
||||||
|
|
||||||
|
mFeedbackPaint.setColor(color);
|
||||||
|
canvas.drawRect(1, 1, getWidth() - 1, getHeight() - 1, mFeedbackPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDraw(Canvas canvas) {
|
||||||
|
if (mAnimStart != -1) {
|
||||||
|
int animDuration = (int) (System.currentTimeMillis() - mAnimStart);
|
||||||
|
|
||||||
|
if (animDuration >= CLICK_FEEDBACK_DURATION) {
|
||||||
|
mAnimStart = -1;
|
||||||
|
} else {
|
||||||
|
drawMagicFlame(animDuration, canvas);
|
||||||
|
postInvalidateDelayed(CLICK_FEEDBACK_INTERVAL);
|
||||||
|
}
|
||||||
|
} else if (isPressed()) {
|
||||||
|
drawMagicFlame(0, canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
CharSequence text = getText();
|
||||||
|
if (text != null && textPosition != null) {
|
||||||
|
canvas.drawText(text, 0, text.length(), textPosition.getX(), textPosition.getY(), getPaint());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void animateClickFeedback() {
|
||||||
|
mAnimStart = System.currentTimeMillis();
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
|
boolean result = super.onTouchEvent(event);
|
||||||
|
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
animateClickFeedback();
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
invalidate();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
184
src/main/java/org/solovyev/android/view/DirectionDragButton.java
Normal file
184
src/main/java/org/solovyev/android/view/DirectionDragButton.java
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
package org.solovyev.android.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.text.TextPaint;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
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.Point2d;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User: serso
|
||||||
|
* Date: 7/17/11
|
||||||
|
* Time: 10:25 PM
|
||||||
|
*/
|
||||||
|
public class DirectionDragButton extends DragButton {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String textUp;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String textDown;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String textMiddle;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Point2d textUpPosition;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Point2d textDownPosition;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private TextPaint upDownTextPaint;
|
||||||
|
|
||||||
|
public DirectionDragButton(Context context, @NotNull AttributeSet attrs) {
|
||||||
|
super(context, attrs, false);
|
||||||
|
init(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void init(@NotNull Context context, @NotNull AttributeSet attrs) {
|
||||||
|
|
||||||
|
TypedArray a = context.obtainStyledAttributes(attrs, org.solovyev.android.calculator.R.styleable.DragButton);
|
||||||
|
|
||||||
|
final int N = a.getIndexCount();
|
||||||
|
for (int i = 0; i < N; i++) {
|
||||||
|
int attr = a.getIndex(i);
|
||||||
|
switch (attr) {
|
||||||
|
case org.solovyev.android.calculator.R.styleable.DragButton_textUp:
|
||||||
|
this.textUp = a.getString(attr);
|
||||||
|
break;
|
||||||
|
case org.solovyev.android.calculator.R.styleable.DragButton_textDown:
|
||||||
|
this.textDown = a.getString(attr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup text
|
||||||
|
this.textMiddle = String.valueOf(getText());
|
||||||
|
|
||||||
|
super.init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void measureText() {
|
||||||
|
super.measureText();
|
||||||
|
|
||||||
|
final Paint basePaint = getPaint();
|
||||||
|
initUpDownTextPaint(basePaint);
|
||||||
|
|
||||||
|
if (textUp != null) {
|
||||||
|
textUpPosition = getTextPosition(upDownTextPaint, basePaint, textUp, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textDown != null) {
|
||||||
|
textDownPosition = getTextPosition(upDownTextPaint, basePaint, textDown, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( textDownPosition != null && textUpPosition != null ) {
|
||||||
|
if ( textDownPosition.getX() > textUpPosition.getX() ) {
|
||||||
|
textDownPosition.setX(textUpPosition.getX());
|
||||||
|
} else {
|
||||||
|
textUpPosition.setX(textDownPosition.getX());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point2d getTextPosition(@NotNull Paint paint, @NotNull Paint basePaint, @NotNull CharSequence text, float direction) {
|
||||||
|
final Point2d result = new Point2d();
|
||||||
|
|
||||||
|
float width = paint.measureText(text.toString() + " ");
|
||||||
|
result.setX(getWidth() - width);
|
||||||
|
|
||||||
|
float selfHeight = paint.ascent() + paint.descent();
|
||||||
|
|
||||||
|
basePaint.measureText(StringUtils.getNotEmpty(getText(), "|"));
|
||||||
|
|
||||||
|
float height = getHeight() - basePaint.ascent() - basePaint.descent();
|
||||||
|
if (direction < 0) {
|
||||||
|
result.setY(height / 2 - direction * height / 3 + selfHeight);
|
||||||
|
} else {
|
||||||
|
result.setY(height / 2 - direction * height / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDraw(Canvas canvas) {
|
||||||
|
super.onDraw(canvas);
|
||||||
|
|
||||||
|
initUpDownTextPaint(null);
|
||||||
|
|
||||||
|
if (textUp != null && textUpPosition != null) {
|
||||||
|
canvas.drawText(textUp, 0, textUp.length(), textUpPosition.getX(), textUpPosition.getY(), upDownTextPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textDown != null && textDownPosition != null) {
|
||||||
|
canvas.drawText(textDown, 0, textDown.length(), textDownPosition.getX(), textDownPosition.getY(), upDownTextPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initUpDownTextPaint(@Nullable Paint paint) {
|
||||||
|
if (paint == null) {
|
||||||
|
paint = getPaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
upDownTextPaint = new TextPaint(paint);
|
||||||
|
upDownTextPaint.setAlpha(150);
|
||||||
|
upDownTextPaint.setTextSize(paint.getTextSize() / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getStyledUpDownText(@Nullable String text) {
|
||||||
|
return StringUtils.getNotEmpty(text, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTextUp(@Nullable String textUp) {
|
||||||
|
this.textUp = textUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getTextUp() {
|
||||||
|
return textUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTextDown(@Nullable String textDown) {
|
||||||
|
this.textDown = textDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getTextDown() {
|
||||||
|
return textDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTextMiddle(@Nullable String textMiddle) {
|
||||||
|
this.textMiddle = textMiddle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getTextMiddle() {
|
||||||
|
return textMiddle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getText(@NotNull DragDirection direction) {
|
||||||
|
final String result;
|
||||||
|
|
||||||
|
if (direction == DragDirection.up) {
|
||||||
|
result = getTextUp();
|
||||||
|
} else if ( direction == DragDirection.down ) {
|
||||||
|
result = getTextDown();
|
||||||
|
} else {
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
89
src/main/java/org/solovyev/android/view/DragButton.java
Normal file
89
src/main/java/org/solovyev/android/view/DragButton.java
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package org.solovyev.android.view;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.solovyev.util.math.Point2d;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
public class DragButton extends ColorButton {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Point2d startPoint = null;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private OnDragListener onDragListener;
|
||||||
|
|
||||||
|
private final OnTouchListener onTouchListener = new OnTouchListenerImpl();
|
||||||
|
|
||||||
|
public DragButton(Context context, @NotNull AttributeSet attrs) {
|
||||||
|
this(context, attrs, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DragButton(Context context, @NotNull AttributeSet attrs, boolean init) {
|
||||||
|
super(context, attrs, false);
|
||||||
|
setOnTouchListener(this.onTouchListener);
|
||||||
|
if ( init ) {
|
||||||
|
super.init(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnDragListener(@Nullable OnDragListener onDragListener) {
|
||||||
|
this.onDragListener = onDragListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public OnDragListener getOnDragListener() {
|
||||||
|
return onDragListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
|
||||||
|
// in order to avoid possible NPEs
|
||||||
|
final Point2d localStartPoint = startPoint;
|
||||||
|
final OnDragListener localOnDragListener = onDragListener;
|
||||||
|
|
||||||
|
if (localOnDragListener != 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_UP:
|
||||||
|
// stop tracking
|
||||||
|
|
||||||
|
if (localStartPoint != null && localOnDragListener.onDrag(DragButton.this, new DragEvent(localStartPoint, event))) {
|
||||||
|
if (localOnDragListener.isSuppressOnClickEvent()) {
|
||||||
|
// prevent on click action
|
||||||
|
setPressed(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startPoint = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package org.solovyev.android.view;
|
||||||
|
|
||||||
|
public enum DragDirection {
|
||||||
|
|
||||||
|
up,
|
||||||
|
down,
|
||||||
|
left,
|
||||||
|
right;
|
||||||
|
}
|
38
src/main/java/org/solovyev/android/view/DragEvent.java
Normal file
38
src/main/java/org/solovyev/android/view/DragEvent.java
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return motion event started at start point
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public MotionEvent getMotionEvent() {
|
||||||
|
return motionEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return start point of dragging
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public Point2d getStartPoint() {
|
||||||
|
return startPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package org.solovyev.android.view;
|
||||||
|
|
||||||
|
import android.widget.TextView;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User: serso
|
||||||
|
* Date: 9/10/11
|
||||||
|
* Time: 7:21 PM
|
||||||
|
*/
|
||||||
|
public interface FontSizeAdjuster {
|
||||||
|
|
||||||
|
void adjustFontSize(@NotNull TextView textView);
|
||||||
|
}
|
22
src/main/java/org/solovyev/android/view/OnDragListener.java
Normal file
22
src/main/java/org/solovyev/android/view/OnDragListener.java
Normal 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);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
package org.solovyev.android.view;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.solovyev.android.calculator.DragButtonCalibrationActivity;
|
||||||
|
import org.solovyev.common.utils.Interval;
|
||||||
|
import org.solovyev.util.math.MathUtils;
|
||||||
|
import org.solovyev.util.math.Point2d;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class SimpleOnDragListener implements OnDragListener {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static final Point2d axis = new Point2d(0, 1);
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private DragProcessor dragProcessor;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private DragButtonCalibrationActivity.Preferences preferences;
|
||||||
|
|
||||||
|
public SimpleOnDragListener(@NotNull DragButtonCalibrationActivity.Preferences preferences) {
|
||||||
|
this.preferences = preferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleOnDragListener(@NotNull DragProcessor dragProcessor, @NotNull DragButtonCalibrationActivity.Preferences preferences) {
|
||||||
|
this.dragProcessor = dragProcessor;
|
||||||
|
this.preferences = preferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPreferences(@NotNull DragButtonCalibrationActivity.Preferences preferences) {
|
||||||
|
this.preferences = preferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onDrag(@NotNull DragButton dragButton, @NotNull DragEvent event) {
|
||||||
|
boolean result = false;
|
||||||
|
|
||||||
|
logDragEvent(dragButton, event);
|
||||||
|
|
||||||
|
final Point2d startPoint = event.getStartPoint();
|
||||||
|
final MotionEvent motionEvent = event.getMotionEvent();
|
||||||
|
|
||||||
|
// init end point
|
||||||
|
final Point2d endPoint = new Point2d(motionEvent.getX(), motionEvent.getY());
|
||||||
|
|
||||||
|
final float distance = MathUtils.getDistance(startPoint, endPoint);
|
||||||
|
final double angle = Math.toDegrees(MathUtils.getAngle(startPoint, MathUtils.sum(startPoint, axis), endPoint));
|
||||||
|
final double duration = motionEvent.getEventTime() - motionEvent.getDownTime();
|
||||||
|
|
||||||
|
final DragButtonCalibrationActivity.Preference distancePreferences = preferences.getPreferencesMap().get(DragButtonCalibrationActivity.PreferenceType.distance);
|
||||||
|
final DragButtonCalibrationActivity.Preference anglePreferences = preferences.getPreferencesMap().get(DragButtonCalibrationActivity.PreferenceType.angle);
|
||||||
|
|
||||||
|
DragDirection direction = null;
|
||||||
|
for (Map.Entry<DragDirection, DragButtonCalibrationActivity.DragPreference> directionEntry : distancePreferences.getDirectionPreferences().entrySet()) {
|
||||||
|
|
||||||
|
if (isInInterval(directionEntry.getValue().getInterval(), distance)) {
|
||||||
|
for (Map.Entry<DragDirection, DragButtonCalibrationActivity.DragPreference> angleEntry : anglePreferences.getDirectionPreferences().entrySet()) {
|
||||||
|
if (isInInterval(angleEntry.getValue().getInterval(), (float)angle)) {
|
||||||
|
direction = angleEntry.getKey();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction != null) {
|
||||||
|
final DragButtonCalibrationActivity.Preference durationPreferences = preferences.getPreferencesMap().get(DragButtonCalibrationActivity.PreferenceType.duration);
|
||||||
|
|
||||||
|
final DragButtonCalibrationActivity.DragPreference durationDragPreferences = durationPreferences.getDirectionPreferences().get(direction);
|
||||||
|
|
||||||
|
if (isInInterval(durationDragPreferences.getInterval(), (float)duration)) {
|
||||||
|
result = dragProcessor.processDragEvent(direction, dragButton, startPoint, motionEvent);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInInterval(@NotNull Interval interval, float value) {
|
||||||
|
return interval.getStart() - MathUtils.MIN_AMOUNT <= value && value <= interval.getEnd() + MathUtils.MIN_AMOUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSuppressOnClickEvent() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public DragProcessor getDragProcessor() {
|
||||||
|
return dragProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDragProcessor(@NotNull DragProcessor dragProcessor) {
|
||||||
|
this.dragProcessor = dragProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface DragProcessor {
|
||||||
|
|
||||||
|
boolean processDragEvent(@NotNull DragDirection dragDirection, @NotNull DragButton dragButton, @NotNull Point2d startPoint2d, @NotNull MotionEvent motionEvent);
|
||||||
|
}
|
||||||
|
}
|
17
src/main/java/org/solovyev/util/StringUtils.java
Normal file
17
src/main/java/org/solovyev/util/StringUtils.java
Normal 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 CharSequence s ){
|
||||||
|
return s == null || s.length() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static String getNotEmpty ( @Nullable CharSequence s, @NotNull String defaultValue ){
|
||||||
|
return isEmpty(s) ? defaultValue : s.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
11
src/main/java/org/solovyev/util/date/DateUtils.java
Normal file
11
src/main/java/org/solovyev/util/date/DateUtils.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
src/main/java/org/solovyev/util/math/MathEntity.java
Normal file
37
src/main/java/org/solovyev/util/math/MathEntity.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
79
src/main/java/org/solovyev/util/math/MathEntityType.java
Normal file
79
src/main/java/org/solovyev/util/math/MathEntityType.java
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package org.solovyev.util.math;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public enum MathEntityType {
|
||||||
|
|
||||||
|
digit,
|
||||||
|
constant,
|
||||||
|
dot,
|
||||||
|
function,
|
||||||
|
unary_operation,
|
||||||
|
binary_operation,
|
||||||
|
group_symbols,
|
||||||
|
group_symbol;
|
||||||
|
|
||||||
|
public static final List<Character> constants = Arrays.asList('e', 'π');
|
||||||
|
|
||||||
|
public static final List<Character> dots = Arrays.asList('.', ',');
|
||||||
|
|
||||||
|
public static final List<Character> unaryOperations = Arrays.asList('-', '=', '!');
|
||||||
|
|
||||||
|
public static final List<Character> binaryOperations = Arrays.asList('-', '+', '*', '×', '∙', '/', '^' );
|
||||||
|
|
||||||
|
public static final List<String> functions = Arrays.asList("sin", "asin", "cos", "acos", "tg", "atg", "log", "ln", "mod", "√");
|
||||||
|
|
||||||
|
public static final List<String> groupSymbols = Arrays.asList("[]", "()", "{}");
|
||||||
|
|
||||||
|
public static final List<Character> openGroupSymbols = Arrays.asList('[', '(', '{');
|
||||||
|
|
||||||
|
public static final List<Character> closeGroupSymbols = Arrays.asList(']', ')', '}');
|
||||||
|
|
||||||
|
public static final List<Character> singleGroupSymbols;
|
||||||
|
static {
|
||||||
|
final List<Character> list = new ArrayList<Character>();
|
||||||
|
list.addAll(openGroupSymbols);
|
||||||
|
list.addAll(closeGroupSymbols);
|
||||||
|
singleGroupSymbols = Collections.unmodifiableList(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
} else if ( constants.contains(ch) ) {
|
||||||
|
result = MathEntityType.constant;
|
||||||
|
} else if ( dots.contains(ch) ) {
|
||||||
|
result = MathEntityType.dot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( result == null ) {
|
||||||
|
if ( functions.contains(s) ) {
|
||||||
|
result = MathEntityType.function;
|
||||||
|
} else if ( groupSymbols.contains(s) ) {
|
||||||
|
result = MathEntityType.group_symbols;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
102
src/main/java/org/solovyev/util/math/MathUtils.java
Normal file
102
src/main/java/org/solovyev/util/math/MathUtils.java
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package org.solovyev.util.math;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MathUtils {
|
||||||
|
|
||||||
|
public static final float MIN_AMOUNT = 0.05f;
|
||||||
|
|
||||||
|
public static double round(@NotNull Double value, int numberOfFractionDigits) {
|
||||||
|
double roundFactor = Math.pow(10, numberOfFractionDigits);
|
||||||
|
|
||||||
|
if (value < Double.MAX_VALUE / roundFactor) {
|
||||||
|
return ((double)Math.round(value * roundFactor)) / roundFactor;
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double countMean(@NotNull List<Double> objects) {
|
||||||
|
|
||||||
|
double sum = 0d;
|
||||||
|
for (Double object : objects) {
|
||||||
|
sum += object;
|
||||||
|
}
|
||||||
|
|
||||||
|
return objects.size() == 0 ? 0d : (sum / objects.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double countStandardDeviation(@NotNull Double mean, @NotNull List<Double> objects) {
|
||||||
|
double sum = 0d;
|
||||||
|
|
||||||
|
for (Double object : objects) {
|
||||||
|
sum += Math.pow(object - mean, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return objects.size() == 0 ? 0d : Math.sqrt(sum / objects.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StatData getStatData(@NotNull List<Double> objects) {
|
||||||
|
|
||||||
|
final double mean = countMean(objects);
|
||||||
|
final double standardDeviation = countStandardDeviation(mean, objects);
|
||||||
|
|
||||||
|
return new StatData(mean, standardDeviation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StatData {
|
||||||
|
|
||||||
|
private final double mean;
|
||||||
|
|
||||||
|
private final double standardDeviation;
|
||||||
|
|
||||||
|
public StatData(double mean, double standardDeviation) {
|
||||||
|
this.mean = mean;
|
||||||
|
this.standardDeviation = standardDeviation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getMean() {
|
||||||
|
return mean;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getStandardDeviation() {
|
||||||
|
return standardDeviation;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
src/main/java/org/solovyev/util/math/Point2d.java
Normal file
37
src/main/java/org/solovyev/util/math/Point2d.java
Normal 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 + "]";
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user