Android project initiated

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

18
AndroidManifest.xml Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:a="http://schemas.android.com/apk/res/android"
package="org.solovyev.android.calculator"
versionCode="1"
versionName="1.0">
<a:uses-sdk a:minSdkVersion="8"/>
<a:application a:icon="@drawable/icon" a:label="@string/app_name_caption">
<a:activity a:name=".Calculator" a:label="@string/app_name_caption">
<a:intent-filter>
<a:action a:name="android.intent.action.MAIN" />
<a:category a:name="android.intent.category.LAUNCHER" />
</a:intent-filter>
</a:activity>
</a:application>
</manifest>

BIN
bin/android_calculator.apk Normal file

Binary file not shown.

BIN
bin/classes.dex Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
bin/resources.ap_ Normal file

Binary file not shown.

11
default.properties Normal file
View File

@ -0,0 +1,11 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system use,
# "build.properties", and override values to adapt the script to your
# project structure.
# Project target.
target=android-8

View File

@ -0,0 +1,5 @@
<FindBugsFilter>
<Match>
<Class name="org.solovyev.android.calculator.R" />
</Match>
</FindBugsFilter>

View File

@ -0,0 +1,116 @@
/* AUTO-GENERATED FILE. DO NOT MODIFY.
*
* This class was automatically generated by the
* aapt tool from the resource data it found. It
* should not be modified by hand.
*/
package org.solovyev.android.calculator;
public final class R {
public static final class attr {
/** <p>Must be a string value, using '\\;' to escape characters such as '\\n' or '\\uxxxx' for a unicode character.
<p>This may also be a reference to a resource (in the form
"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>") or
theme attribute (in the form
"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>")
containing a value of this type.
*/
public static final int textDown=0x7f010001;
/** <p>Must be a string value, using '\\;' to escape characters such as '\\n' or '\\uxxxx' for a unicode character.
<p>This may also be a reference to a resource (in the form
"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>") or
theme attribute (in the form
"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>")
containing a value of this type.
*/
public static final int textUp=0x7f010000;
}
public static final class drawable {
public static final int icon=0x7f020000;
}
public static final class id {
public static final int curlyBracketsButton=0x7f060013;
public static final int editText=0x7f060000;
public static final int eigthDigitButton=0x7f06000f;
public static final int equalsButton=0x7f060011;
public static final int fiveDigitButton=0x7f060009;
public static final int fourDigitButton=0x7f060008;
public static final int historyButton=0x7f060018;
public static final int minusButton=0x7f06000c;
public static final int muliplicationButton=0x7f060005;
public static final int nineDigitButton=0x7f060010;
public static final int numericButton=0x7f060016;
public static final int oneDigitButton=0x7f060002;
public static final int piButton=0x7f060019;
public static final int plusButton=0x7f060006;
public static final int pointDigitButton=0x7f060015;
public static final int resultEditText=0x7f060001;
public static final int roundBracketsButton=0x7f060007;
public static final int sevenDigitButton=0x7f06000e;
public static final int simplifyButton=0x7f060017;
public static final int sixDigitButton=0x7f06000a;
public static final int sqrtButton=0x7f060012;
public static final int squareBracketsButton=0x7f06000d;
public static final int subtractionButton=0x7f06000b;
public static final int threeDigitButton=0x7f060004;
public static final int twoDigitButton=0x7f060003;
public static final int zeroDigitButton=0x7f060014;
}
public static final class layout {
public static final int main=0x7f030000;
}
public static final class string {
public static final int app_name_caption=0x7f040000;
public static final int syntax_error=0x7f040001;
}
public static final class style {
public static final int digitButtonStyle=0x7f050001;
public static final int editTextInputStyle=0x7f050000;
}
public static final class styleable {
/** Attributes that can be used with a DragButton.
<p>Includes the following attributes:</p>
<table>
<colgroup align="left" />
<colgroup align="left" />
<tr><th>Attribute</th><th>Description</th></tr>
<tr><td><code>{@link #DragButton_textDown org.solovyev.android.calculator:textDown}</code></td><td></td></tr>
<tr><td><code>{@link #DragButton_textUp org.solovyev.android.calculator:textUp}</code></td><td></td></tr>
</table>
@see #DragButton_textDown
@see #DragButton_textUp
*/
public static final int[] DragButton = {
0x7f010000, 0x7f010001
};
/**
<p>This symbol is the offset where the {@link org.solovyev.android.calculator.R.attr#textDown}
attribute's value can be found in the {@link #DragButton} array.
<p>Must be a string value, using '\\;' to escape characters such as '\\n' or '\\uxxxx' for a unicode character.
<p>This may also be a reference to a resource (in the form
"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>") or
theme attribute (in the form
"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>")
containing a value of this type.
@attr name android:textDown
*/
public static final int DragButton_textDown = 1;
/**
<p>This symbol is the offset where the {@link org.solovyev.android.calculator.R.attr#textUp}
attribute's value can be found in the {@link #DragButton} array.
<p>Must be a string value, using '\\;' to escape characters such as '\\n' or '\\uxxxx' for a unicode character.
<p>This may also be a reference to a resource (in the form
"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>") or
theme attribute (in the form
"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>")
containing a value of this type.
@attr name android:textUp
*/
public static final int DragButton_textUp = 0;
};
}

BIN
res/drawable-hdpi/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
res/drawable-ldpi/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
res/drawable-mdpi/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

65
res/layout/main.xml Normal file
View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:a="http://schemas.android.com/apk/res/android"
xmlns:calc="http://schemas.android.com/apk/res/org.solovyev.android.calculator"
a:orientation="vertical"
a:layout_width="fill_parent"
a:layout_height="fill_parent">
<EditText
a:id="@+id/editText"
style="@style/editTextInputStyle"
a:maxLines="5"
a:minLines="5"
a:gravity="top|left"
a:textAppearance="?android:attr/textAppearanceMedium"
a:typeface="monospace"
a:singleLine="false" />
<EditText
a:id="@+id/resultEditText"
style="@style/editTextInputStyle"
a:gravity="top|left"
a:textAppearance="?android:attr/textAppearanceMedium"
a:typeface="monospace"
a:editable="false"
a:focusable="false"/>
<TableLayout
a:layout_width="fill_parent"
a:layout_height="fill_parent">
<TableRow>
<org.solovyev.android.view.DragButton a:id="@+id/oneDigitButton" a:text="1" calc:textUp="sin" calc:textDown="asin" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/twoDigitButton" a:text="2" calc:textUp="cos" calc:textDown="acos" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/threeDigitButton" a:text="3" calc:textUp="tg" calc:textDown="atg" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/muliplicationButton" a:text="*" calc:textUp="^" calc:textDown="^2" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/plusButton" a:text="+" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/roundBracketsButton" a:text="()" calc:textUp="(" calc:textDown=")" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
</TableRow>
<TableRow>
<org.solovyev.android.view.DragButton a:id="@+id/fourDigitButton" a:text="4" calc:textUp="exp" calc:textDown="mod" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/fiveDigitButton" a:text="5" calc:textUp="log" calc:textDown="ln" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/sixDigitButton" a:text="6" calc:textUp="!" calc:textDown="" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/subtractionButton" a:text="/" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/minusButton" a:text="-" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/squareBracketsButton" a:text="[]" calc:textUp="[" calc:textDown="]" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
</TableRow>
<TableRow>
<org.solovyev.android.view.DragButton a:id="@+id/sevenDigitButton" a:text="7" calc:textUp="" calc:textDown="" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/eigthDigitButton" a:text="8" calc:textUp="" calc:textDown="" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/nineDigitButton" a:text="9" calc:textUp="" calc:textDown="" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/equalsButton" a:text="=" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/sqrtButton" a:text="sqrt" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/curlyBracketsButton" a:text="{}" calc:textUp="{" calc:textDown="}" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
</TableRow>
<TableRow>
<org.solovyev.android.view.DragButton a:id="@+id/zeroDigitButton" a:text="0" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/pointDigitButton" a:text="." calc:textDown="," style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/numericButton" a:text="numeric" style="@style/digitButtonStyle" a:onClick="numericButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/simplifyButton" a:text="simplify" style="@style/digitButtonStyle" a:onClick="simplifyButtonClickHandler"/>
<org.solovyev.android.view.DragButton a:id="@+id/historyButton" calc:textUp="undo" calc:textDown="redo" style="@style/digitButtonStyle"/>
<org.solovyev.android.view.DragButton a:id="@+id/piButton" a:text="pi" calc:textUp="e" style="@style/digitButtonStyle" a:onClick="digitButtonClickHandler"/>
</TableRow>
</TableLayout>
</LinearLayout>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name_caption">Калькулятор</string>
</resources>

7
res/values/attrs.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DragButton">
<attr name="textUp" format="string" />
<attr name="textDown" format="string" />
</declare-styleable>
</resources>

5
res/values/strings.xml Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name_caption">Calculator</string>
<string name="syntax_error">Syntax error</string>
</resources>

15
res/values/styles.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="editTextInputStyle">
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:typeface">monospace</item>
</style>
<style name="digitButtonStyle">
<item name="android:width">50dp</item>
<item name="android:height">50dp</item>
<item name="android:typeface">monospace</item>
</style>
</resources>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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