From c9a641652369d96df84ea9758ac046d2dd383d2c Mon Sep 17 00:00:00 2001 From: serso Date: Mon, 12 Sep 2011 22:54:54 +0400 Subject: [PATCH] some weird moving --- .../calculator/CalculatorActivity.java | 426 ++++++++++++++++++ .../calculator/CalculatorHistoryState.java | 40 ++ .../CalculatorPreferencesActivity.java | 35 ++ .../DragButtonCalibrationActivity.java | 393 ++++++++++++++++ .../calculator/EditorHistoryState.java | 37 ++ .../android/calculator/HistoryAction.java | 8 + .../android/calculator/HistoryHelper.java | 24 + .../android/calculator/JsclOperation.java | 10 + .../android/calculator/Preprocessor.java | 81 ++++ .../calculator/SimpleHistoryHelper.java | 91 ++++ .../solovyev/android/view/ColorButton.java | 155 +++++++ .../android/view/DirectionDragButton.java | 184 ++++++++ .../org/solovyev/android/view/DragButton.java | 89 ++++ .../solovyev/android/view/DragDirection.java | 9 + .../org/solovyev/android/view/DragEvent.java | 38 ++ .../android/view/FontSizeAdjuster.java | 14 + .../solovyev/android/view/OnDragListener.java | 22 + .../android/view/SimpleOnDragListener.java | 122 +++++ .../java/org/solovyev/util/StringUtils.java | 17 + .../org/solovyev/util/date/DateUtils.java | 11 + .../org/solovyev/util/math/MathEntity.java | 37 ++ .../solovyev/util/math/MathEntityType.java | 79 ++++ .../org/solovyev/util/math/MathUtils.java | 102 +++++ .../java/org/solovyev/util/math/Point2d.java | 37 ++ 24 files changed, 2061 insertions(+) create mode 100644 src/main/java/org/solovyev/android/calculator/CalculatorActivity.java create mode 100644 src/main/java/org/solovyev/android/calculator/CalculatorHistoryState.java create mode 100644 src/main/java/org/solovyev/android/calculator/CalculatorPreferencesActivity.java create mode 100644 src/main/java/org/solovyev/android/calculator/DragButtonCalibrationActivity.java create mode 100644 src/main/java/org/solovyev/android/calculator/EditorHistoryState.java create mode 100644 src/main/java/org/solovyev/android/calculator/HistoryAction.java create mode 100644 src/main/java/org/solovyev/android/calculator/HistoryHelper.java create mode 100644 src/main/java/org/solovyev/android/calculator/JsclOperation.java create mode 100644 src/main/java/org/solovyev/android/calculator/Preprocessor.java create mode 100644 src/main/java/org/solovyev/android/calculator/SimpleHistoryHelper.java create mode 100644 src/main/java/org/solovyev/android/view/ColorButton.java create mode 100644 src/main/java/org/solovyev/android/view/DirectionDragButton.java create mode 100644 src/main/java/org/solovyev/android/view/DragButton.java create mode 100644 src/main/java/org/solovyev/android/view/DragDirection.java create mode 100644 src/main/java/org/solovyev/android/view/DragEvent.java create mode 100644 src/main/java/org/solovyev/android/view/FontSizeAdjuster.java create mode 100644 src/main/java/org/solovyev/android/view/OnDragListener.java create mode 100644 src/main/java/org/solovyev/android/view/SimpleOnDragListener.java create mode 100644 src/main/java/org/solovyev/util/StringUtils.java create mode 100644 src/main/java/org/solovyev/util/date/DateUtils.java create mode 100644 src/main/java/org/solovyev/util/math/MathEntity.java create mode 100644 src/main/java/org/solovyev/util/math/MathEntityType.java create mode 100644 src/main/java/org/solovyev/util/math/MathUtils.java create mode 100644 src/main/java/org/solovyev/util/math/Point2d.java diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java b/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java new file mode 100644 index 00000000..75bd56a1 --- /dev/null +++ b/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java @@ -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 historyHelper; + + @NotNull + private BroadcastReceiver preferencesChangesReceiver; + + @NotNull + private List onDragListeners = new ArrayList(); + + /** + * 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(); + 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); + } +} \ No newline at end of file diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorHistoryState.java b/src/main/java/org/solovyev/android/calculator/CalculatorHistoryState.java new file mode 100644 index 00000000..f4468778 --- /dev/null +++ b/src/main/java/org/solovyev/android/calculator/CalculatorHistoryState.java @@ -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; + } +} diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorPreferencesActivity.java b/src/main/java/org/solovyev/android/calculator/CalculatorPreferencesActivity.java new file mode 100644 index 00000000..436c889c --- /dev/null +++ b/src/main/java/org/solovyev/android/calculator/CalculatorPreferencesActivity.java @@ -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); + } +} diff --git a/src/main/java/org/solovyev/android/calculator/DragButtonCalibrationActivity.java b/src/main/java/org/solovyev/android/calculator/DragButtonCalibrationActivity.java new file mode 100644 index 00000000..a2efa2b2 --- /dev/null +++ b/src/main/java/org/solovyev/android/calculator/DragButtonCalibrationActivity.java @@ -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 dragHistory = new ArrayList(); + + private final Map map = new HashMap(); + + 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 anglesByDirection = new ManyValuedHashMap(); + final ManyValuedMap distancesByDirection = new ManyValuedHashMap(); + final ManyValuedMap timesByDirection = new ManyValuedHashMap(); + 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 angleStatData = getStatDataByDirection(anglesByDirection); + final Map distanceStatData = getStatDataByDirection(distancesByDirection); + final Map 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 statData, @NotNull SharedPreferences.Editor editor, @NotNull PreferenceType preferenceType) { + for (Map.Entry 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 directionPreferences = new HashMap(); + + + 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 getDirectionPreferences() { + return directionPreferences; + } + + public void setDirectionPreferences(@NotNull Map directionPreferences) { + this.directionPreferences = directionPreferences; + } + } + + + public static class Preferences { + + private final Map preferencesMap = new HashMap(); + + public Map getPreferencesMap() { + return preferencesMap; + } + } + + private void logStatData(@NotNull Map statData) { + for (Map.Entry entry : statData.entrySet()) { + Log.d(this.getClass().getName(), entry.getKey() + "-> m: " + entry.getValue().getMean() + ", d: " + entry.getValue().getStandardDeviation()); + } + } + + private Map getStatDataByDirection(@NotNull ManyValuedMap valuesByDirection) { + final Map result = new HashMap(); + + for (Map.Entry> 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; + } + } +} diff --git a/src/main/java/org/solovyev/android/calculator/EditorHistoryState.java b/src/main/java/org/solovyev/android/calculator/EditorHistoryState.java new file mode 100644 index 00000000..3afee4d5 --- /dev/null +++ b/src/main/java/org/solovyev/android/calculator/EditorHistoryState.java @@ -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; + } + + +} diff --git a/src/main/java/org/solovyev/android/calculator/HistoryAction.java b/src/main/java/org/solovyev/android/calculator/HistoryAction.java new file mode 100644 index 00000000..8377d0e8 --- /dev/null +++ b/src/main/java/org/solovyev/android/calculator/HistoryAction.java @@ -0,0 +1,8 @@ +package org.solovyev.android.calculator; + +public enum HistoryAction { + + redo, + undo; + +} diff --git a/src/main/java/org/solovyev/android/calculator/HistoryHelper.java b/src/main/java/org/solovyev/android/calculator/HistoryHelper.java new file mode 100644 index 00000000..3c4bad6a --- /dev/null +++ b/src/main/java/org/solovyev/android/calculator/HistoryHelper.java @@ -0,0 +1,24 @@ +package org.solovyev.android.calculator; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface HistoryHelper { + + 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); +} diff --git a/src/main/java/org/solovyev/android/calculator/JsclOperation.java b/src/main/java/org/solovyev/android/calculator/JsclOperation.java new file mode 100644 index 00000000..a92e7797 --- /dev/null +++ b/src/main/java/org/solovyev/android/calculator/JsclOperation.java @@ -0,0 +1,10 @@ +package org.solovyev.android.calculator; + +public enum JsclOperation { + + simplify, + elementary, + importCommands, + numeric; + +} diff --git a/src/main/java/org/solovyev/android/calculator/Preprocessor.java b/src/main/java/org/solovyev/android/calculator/Preprocessor.java new file mode 100644 index 00000000..5769b9b7 --- /dev/null +++ b/src/main/java/org/solovyev/android/calculator/Preprocessor.java @@ -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 + "\");"; + } +} diff --git a/src/main/java/org/solovyev/android/calculator/SimpleHistoryHelper.java b/src/main/java/org/solovyev/android/calculator/SimpleHistoryHelper.java new file mode 100644 index 00000000..04df8fc1 --- /dev/null +++ b/src/main/java/org/solovyev/android/calculator/SimpleHistoryHelper.java @@ -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 implements HistoryHelper { + + private List history = new ArrayList(); + + 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; + } +} diff --git a/src/main/java/org/solovyev/android/view/ColorButton.java b/src/main/java/org/solovyev/android/view/ColorButton.java new file mode 100644 index 00000000..449d0e64 --- /dev/null +++ b/src/main/java/org/solovyev/android/view/ColorButton.java @@ -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; + } +} diff --git a/src/main/java/org/solovyev/android/view/DirectionDragButton.java b/src/main/java/org/solovyev/android/view/DirectionDragButton.java new file mode 100644 index 00000000..312f1ecd --- /dev/null +++ b/src/main/java/org/solovyev/android/view/DirectionDragButton.java @@ -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; + } +} diff --git a/src/main/java/org/solovyev/android/view/DragButton.java b/src/main/java/org/solovyev/android/view/DragButton.java new file mode 100644 index 00000000..f1347fe3 --- /dev/null +++ b/src/main/java/org/solovyev/android/view/DragButton.java @@ -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; + } + } +} diff --git a/src/main/java/org/solovyev/android/view/DragDirection.java b/src/main/java/org/solovyev/android/view/DragDirection.java new file mode 100644 index 00000000..a45467b0 --- /dev/null +++ b/src/main/java/org/solovyev/android/view/DragDirection.java @@ -0,0 +1,9 @@ +package org.solovyev.android.view; + +public enum DragDirection { + + up, + down, + left, + right; +} diff --git a/src/main/java/org/solovyev/android/view/DragEvent.java b/src/main/java/org/solovyev/android/view/DragEvent.java new file mode 100644 index 00000000..4f49bd29 --- /dev/null +++ b/src/main/java/org/solovyev/android/view/DragEvent.java @@ -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; + } + + +} diff --git a/src/main/java/org/solovyev/android/view/FontSizeAdjuster.java b/src/main/java/org/solovyev/android/view/FontSizeAdjuster.java new file mode 100644 index 00000000..932b6272 --- /dev/null +++ b/src/main/java/org/solovyev/android/view/FontSizeAdjuster.java @@ -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); +} diff --git a/src/main/java/org/solovyev/android/view/OnDragListener.java b/src/main/java/org/solovyev/android/view/OnDragListener.java new file mode 100644 index 00000000..0605f129 --- /dev/null +++ b/src/main/java/org/solovyev/android/view/OnDragListener.java @@ -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); + +} diff --git a/src/main/java/org/solovyev/android/view/SimpleOnDragListener.java b/src/main/java/org/solovyev/android/view/SimpleOnDragListener.java new file mode 100644 index 00000000..bf186d1b --- /dev/null +++ b/src/main/java/org/solovyev/android/view/SimpleOnDragListener.java @@ -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 directionEntry : distancePreferences.getDirectionPreferences().entrySet()) { + + if (isInInterval(directionEntry.getValue().getInterval(), distance)) { + for (Map.Entry 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); + } +} \ No newline at end of file diff --git a/src/main/java/org/solovyev/util/StringUtils.java b/src/main/java/org/solovyev/util/StringUtils.java new file mode 100644 index 00000000..62dda574 --- /dev/null +++ b/src/main/java/org/solovyev/util/StringUtils.java @@ -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(); + } +} + diff --git a/src/main/java/org/solovyev/util/date/DateUtils.java b/src/main/java/org/solovyev/util/date/DateUtils.java new file mode 100644 index 00000000..f7a7ab7f --- /dev/null +++ b/src/main/java/org/solovyev/util/date/DateUtils.java @@ -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; + } + +} diff --git a/src/main/java/org/solovyev/util/math/MathEntity.java b/src/main/java/org/solovyev/util/math/MathEntity.java new file mode 100644 index 00000000..b9170c81 --- /dev/null +++ b/src/main/java/org/solovyev/util/math/MathEntity.java @@ -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; + } +} diff --git a/src/main/java/org/solovyev/util/math/MathEntityType.java b/src/main/java/org/solovyev/util/math/MathEntityType.java new file mode 100644 index 00000000..757b34e7 --- /dev/null +++ b/src/main/java/org/solovyev/util/math/MathEntityType.java @@ -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 constants = Arrays.asList('e', 'π'); + + public static final List dots = Arrays.asList('.', ','); + + public static final List unaryOperations = Arrays.asList('-', '=', '!'); + + public static final List binaryOperations = Arrays.asList('-', '+', '*', '×', '∙', '/', '^' ); + + public static final List functions = Arrays.asList("sin", "asin", "cos", "acos", "tg", "atg", "log", "ln", "mod", "√"); + + public static final List groupSymbols = Arrays.asList("[]", "()", "{}"); + + public static final List openGroupSymbols = Arrays.asList('[', '(', '{'); + + public static final List closeGroupSymbols = Arrays.asList(']', ')', '}'); + + public static final List singleGroupSymbols; + static { + final List list = new ArrayList(); + 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; + } +} diff --git a/src/main/java/org/solovyev/util/math/MathUtils.java b/src/main/java/org/solovyev/util/math/MathUtils.java new file mode 100644 index 00000000..cea513f8 --- /dev/null +++ b/src/main/java/org/solovyev/util/math/MathUtils.java @@ -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 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 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 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; + } + + } + +} diff --git a/src/main/java/org/solovyev/util/math/Point2d.java b/src/main/java/org/solovyev/util/math/Point2d.java new file mode 100644 index 00000000..cbd6a09a --- /dev/null +++ b/src/main/java/org/solovyev/util/math/Point2d.java @@ -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 + "]"; + } +}