new plotter
This commit is contained in:
parent
3dab118a1b
commit
5f7ee1e64e
@ -9,7 +9,9 @@ import org.solovyev.android.calculator.jscl.JsclOperation;
|
||||
import org.solovyev.android.calculator.plot.PlotInput;
|
||||
import org.solovyev.android.calculator.view.NumeralBaseConverterDialog;
|
||||
import org.solovyev.android.menu.LabeledMenuItem;
|
||||
import org.solovyev.common.collections.CollectionsUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: Solovyev_S
|
||||
@ -82,10 +84,17 @@ public enum CalculatorDisplayMenuItem implements LabeledMenuItem<CalculatorDispl
|
||||
final Generic generic = data.getResult();
|
||||
assert generic != null;
|
||||
|
||||
final Constant constant = CollectionsUtils.getFirstCollectionElement(CalculatorUtils.getNotSystemConstants(generic));
|
||||
assert constant != null;
|
||||
final List<Constant> variables = new ArrayList<Constant>(CalculatorUtils.getNotSystemConstants(generic));
|
||||
final Constant xVariable = variables.get(0);
|
||||
|
||||
Locator.getInstance().getCalculator().fireCalculatorEvent(CalculatorEventType.plot_graph, PlotInput.newInstance(generic, constant), context);
|
||||
final Constant yVariable;
|
||||
if ( variables.size() > 1 ) {
|
||||
yVariable = variables.get(1);
|
||||
} else {
|
||||
yVariable = null;
|
||||
}
|
||||
|
||||
Locator.getInstance().getCalculator().fireCalculatorEvent(CalculatorEventType.plot_graph, PlotInput.newInstance(generic, xVariable, yVariable), context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -3,6 +3,7 @@ package org.solovyev.android.calculator.plot;
|
||||
import jscl.math.Generic;
|
||||
import jscl.math.function.Constant;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
@ -15,19 +16,25 @@ public class PlotInput {
|
||||
private Generic function;
|
||||
|
||||
@NotNull
|
||||
private Constant constant;
|
||||
private Constant xVariable;
|
||||
|
||||
@Nullable
|
||||
private Constant yVariable;
|
||||
|
||||
public PlotInput() {
|
||||
}
|
||||
|
||||
private PlotInput(@NotNull Generic function, @NotNull Constant constant) {
|
||||
this.function = function;
|
||||
this.constant = constant;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static PlotInput newInstance(@NotNull Generic function, @NotNull Constant constant) {
|
||||
return new PlotInput(function, constant);
|
||||
public static PlotInput newInstance(@NotNull Generic function,
|
||||
@NotNull Constant xVariable,
|
||||
@Nullable Constant yVariable) {
|
||||
PlotInput result = new PlotInput();
|
||||
|
||||
result.function = function;
|
||||
result.xVariable = xVariable;
|
||||
result.yVariable = yVariable;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@ -36,7 +43,12 @@ public class PlotInput {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Constant getConstant() {
|
||||
return constant;
|
||||
public Constant getXVariable() {
|
||||
return xVariable;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Constant getYVariable() {
|
||||
return yVariable;
|
||||
}
|
||||
}
|
||||
|
BIN
android-app/misc/lib/arity-2.1.6.jar
Executable file
BIN
android-app/misc/lib/arity-2.1.6.jar
Executable file
Binary file not shown.
@ -111,6 +111,14 @@
|
||||
<version>0.7.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>arity</groupId>
|
||||
<artifactId>arity</artifactId>
|
||||
<version>2.1.6</version>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/misc/lib/arity-2.1.6.jar</systemPath>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>admob</groupId>
|
||||
<artifactId>admob</artifactId>
|
||||
|
496
android-app/src/main/java/arity/calculator/Calculator.java
Executable file
496
android-app/src/main/java/arity/calculator/Calculator.java
Executable file
@ -0,0 +1,496 @@
|
||||
// Copyright (C) 2009 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import org.javia.arity.*;
|
||||
import org.javia.arity.Util;
|
||||
import org.solovyev.android.calculator.Locator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Calculator extends Activity implements TextWatcher,
|
||||
View.OnKeyListener,
|
||||
View.OnClickListener,
|
||||
AdapterView.OnItemClickListener,
|
||||
SharedPreferences.OnSharedPreferenceChangeListener
|
||||
{
|
||||
static final char MINUS = '\u2212', TIMES = '\u00d7', DIV = '\u00f7', SQRT = '\u221a', PI = '\u03c0',
|
||||
UP_ARROW = '\u21e7', DN_ARROW = '\u21e9', ARROW = '\u21f3';
|
||||
|
||||
private static final int MSG_INPUT_CHANGED = 1;
|
||||
private static final String INFINITY = "Infinity";
|
||||
private static final String INFINITY_UNICODE = "\u221e";
|
||||
|
||||
static Symbols symbols = new Symbols();
|
||||
static Function function;
|
||||
|
||||
private TextView result;
|
||||
private EditText input;
|
||||
private ListView historyView;
|
||||
private Graph2dView graphView;
|
||||
private Graph3dView graph3dView;
|
||||
private History history;
|
||||
private int nDigits = 0;
|
||||
private boolean pendingClearResult;
|
||||
private boolean isAlphaVisible;
|
||||
static ArrayList<Function> graphedFunction;
|
||||
static Defs defs;
|
||||
private ArrayList<Function> auxFuncs = new ArrayList<Function>();
|
||||
static boolean useHighQuality3d = true;
|
||||
|
||||
private static final char[][] ALPHA = {
|
||||
{'q', 'w', '=', ',', ';', SQRT, '!', '\''},
|
||||
{'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'},
|
||||
{'a', 's', 'd', 'f', 'g', 'h', 'j', 'k'},
|
||||
{'z', 'x', 'c', 'v', 'b', 'n', 'm', 'l'},
|
||||
};
|
||||
|
||||
private static final char[][] DIGITS = {
|
||||
{'7', '8', '9', '%', '^', ARROW},
|
||||
{'4', '5', '6','(', ')', 'C'},
|
||||
{'1', '2', '3', TIMES, DIV, 'E'},
|
||||
{'0', '0', '.', '+', MINUS, 'E'},
|
||||
};
|
||||
|
||||
private static final char[][] DIGITS2 = {
|
||||
{'0', '.', '+', MINUS, TIMES, DIV, '^', '(', ')', 'C'},
|
||||
{'1', '2', '3', '4', '5', '6', '7', '8', '9', 'E'},
|
||||
};
|
||||
|
||||
/*
|
||||
private static final char[][] DIGITS3 = {
|
||||
{'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'},
|
||||
{'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', PI},
|
||||
{'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '=', '%'},
|
||||
{'0', '.', '+', MINUS, TIMES, DIV, '^', '(', ')', 'C'},
|
||||
{'1', '2', '3', '4', '5', '6', '7', '8', '9', 'E'},
|
||||
};
|
||||
*/
|
||||
|
||||
public void onConfigurationChanged(Configuration config) {
|
||||
super.onConfigurationChanged(config);
|
||||
internalConfigChange(config);
|
||||
}
|
||||
|
||||
private void internalConfigChange(Configuration config) {
|
||||
/*setContentView(R.layout.main);
|
||||
graphView = (GraphView) findViewById(R.id.graph);
|
||||
graph3dView = (Graph3dView) findViewById(R.id.graph3d);
|
||||
historyView = (ListView) findViewById(R.id.history);
|
||||
|
||||
final boolean isLandscape = config.orientation == Configuration.ORIENTATION_LANDSCAPE;
|
||||
// final boolean hasKeyboard = config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO;
|
||||
|
||||
alpha = (KeyboardView) findViewById(R.id.alpha);
|
||||
digits = (KeyboardView) findViewById(R.id.digits);
|
||||
if (isLandscape) {
|
||||
digits.init(DIGITS2, false, true);
|
||||
isAlphaVisible = false;
|
||||
} else {
|
||||
alpha.init(ALPHA, false, false);
|
||||
digits.init(DIGITS, true, true);
|
||||
updateAlpha();
|
||||
}
|
||||
|
||||
result = (TextView) findViewById(R.id.result);
|
||||
|
||||
Editable oldText = input != null ? input.getText() : null;
|
||||
input = (EditText) findViewById(R.id.input);
|
||||
input.setOnKeyListener(this);
|
||||
input.addTextChangedListener(this);
|
||||
input.setEditableFactory(new CalculatorEditable.Factory());
|
||||
input.setInputType(0);
|
||||
changeInput(history.getText());
|
||||
if (oldText != null) {
|
||||
input.setText(oldText);
|
||||
}
|
||||
input.requestFocus();
|
||||
graphView.setOnClickListener(this);
|
||||
graph3dView.setOnClickListener(this);
|
||||
if (historyView != null) {
|
||||
historyView.setAdapter(adapter);
|
||||
historyView.setOnItemClickListener(this);
|
||||
}*/
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
history = new History(this);
|
||||
internalConfigChange(getResources().getConfiguration());
|
||||
|
||||
defs = new Defs(this, symbols);
|
||||
if (history.fileNotFound) {
|
||||
String[] init = {
|
||||
"sqrt(pi)\u00f70.5!",
|
||||
"e^(i\u00d7pi)",
|
||||
"ln(e^100)",
|
||||
"sin(x)",
|
||||
"x^2"
|
||||
};
|
||||
nDigits = 10;
|
||||
for (String s : init) {
|
||||
onEnter(s);
|
||||
}
|
||||
nDigits = 0;
|
||||
}
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
String value = prefs.getString("quality", null);
|
||||
if (value == null) {
|
||||
useHighQuality3d = Build.VERSION.SDK_INT >= 5;
|
||||
prefs.edit().putString("quality", useHighQuality3d ? "high" : "low").commit();
|
||||
} else {
|
||||
useHighQuality3d = value.equals("high");
|
||||
}
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
graph3dView.onPause();
|
||||
history.updateEdited(input.getText().toString());
|
||||
history.save();
|
||||
defs.save();
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
graph3dView.onResume();
|
||||
}
|
||||
|
||||
//OnSharedPreferenceChangeListener
|
||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||
if (key.equals("quality")) {
|
||||
useHighQuality3d = prefs.getString(key, "high").equals("high");
|
||||
// Calculator.log("useHigh quality changed to " + useHighQuality3d);
|
||||
}
|
||||
}
|
||||
|
||||
//OnClickListener
|
||||
public void onClick(View target) {
|
||||
if (target == graphView || target == graph3dView) {
|
||||
startActivity(new Intent(this, ShowGraph.class));
|
||||
}
|
||||
}
|
||||
|
||||
// OnItemClickListener
|
||||
public void onItemClick(AdapterView parent, View view, int pos, long id) {
|
||||
history.moveToPos(pos, input.getText().toString());
|
||||
changeInput(history.getText());
|
||||
}
|
||||
|
||||
// TextWatcher
|
||||
public void afterTextChanged(Editable s) {
|
||||
// handler.removeMessages(MSG_INPUT_CHANGED);
|
||||
// handler.sendEmptyMessageDelayed(MSG_INPUT_CHANGED, 250);
|
||||
evaluate();
|
||||
/*
|
||||
if (pendingClearResult && s.length() != 0) {
|
||||
if (!(s.length() == 4 && s.toString().startsWith("ans"))) {
|
||||
result.setText(null);
|
||||
}
|
||||
showGraph(null);
|
||||
pendingClearResult = false;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
|
||||
// OnKeyListener
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
int action = event.getAction();
|
||||
if (action == KeyEvent.ACTION_DOWN) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_ENTER:
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
doEnter();
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
onUp();
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
onDown();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_ENTER:
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
private Handler handler = new Handler() {
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_INPUT_CHANGED:
|
||||
// String text = input.getText().toString();
|
||||
evaluate();
|
||||
}
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
static void log(String mes) {
|
||||
if (mes != null) {
|
||||
Locator.getInstance().getLogger().debug(null, mes);
|
||||
}
|
||||
}
|
||||
|
||||
void evaluate() {
|
||||
evaluate(input.getText().toString());
|
||||
}
|
||||
|
||||
private String formatEval(Complex value) {
|
||||
if (nDigits == 0) {
|
||||
nDigits = getResultSpace();
|
||||
}
|
||||
String res = Util.complexToString(value, nDigits, 2);
|
||||
return res.replace(INFINITY, INFINITY_UNICODE);
|
||||
}
|
||||
|
||||
private void evaluate(String text) {
|
||||
// log("evaluate " + text);
|
||||
if (text.length() == 0) {
|
||||
result.setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
auxFuncs.clear();
|
||||
int end = -1;
|
||||
do {
|
||||
text = text.substring(end+1);
|
||||
end = text.indexOf(';');
|
||||
String slice = end == -1 ? text : text.substring(0, end);
|
||||
try {
|
||||
Function f = symbols.compile(slice);
|
||||
auxFuncs.add(f);
|
||||
} catch (SyntaxException e) {
|
||||
continue;
|
||||
}
|
||||
} while (end != -1);
|
||||
|
||||
graphedFunction = auxFuncs;
|
||||
int size = auxFuncs.size();
|
||||
if (size == 0) {
|
||||
result.setEnabled(false);
|
||||
return;
|
||||
} else if (size == 1) {
|
||||
Function f = auxFuncs.get(0);
|
||||
int arity = f.arity();
|
||||
// Calculator.log("res " + f);
|
||||
if (arity == 1 || arity == 2) {
|
||||
result.setText(null);
|
||||
showGraph(f);
|
||||
} else if (arity == 0) {
|
||||
result.setText(formatEval(f.evalComplex()));
|
||||
result.setEnabled(true);
|
||||
showGraph(null);
|
||||
} else {
|
||||
result.setText("function");
|
||||
result.setEnabled(true);
|
||||
showGraph(null);
|
||||
}
|
||||
} else {
|
||||
graphView.setFunctions(auxFuncs);
|
||||
if (graphView.getVisibility() != View.VISIBLE) {
|
||||
if (isAlphaVisible) {
|
||||
isAlphaVisible = false;
|
||||
updateAlpha();
|
||||
}
|
||||
result.setVisibility(View.GONE);
|
||||
historyView.setVisibility(View.GONE);
|
||||
graph3dView.setVisibility(View.GONE);
|
||||
graph3dView.onPause();
|
||||
graphView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getResultSpace() {
|
||||
int width = result.getWidth() - result.getTotalPaddingLeft() - result.getTotalPaddingRight();
|
||||
float oneDigitWidth = result.getPaint().measureText("5555555555") / 10f;
|
||||
return (int) (width / oneDigitWidth);
|
||||
}
|
||||
|
||||
private void updateAlpha() {
|
||||
|
||||
}
|
||||
|
||||
private StringBuilder oneChar = new StringBuilder(" ");
|
||||
void onKey(char key) {
|
||||
if (key == 'E') {
|
||||
doEnter();
|
||||
} else if (key == 'C') {
|
||||
doBackspace();
|
||||
} else if (key == ARROW) {
|
||||
isAlphaVisible = !isAlphaVisible;
|
||||
updateAlpha();
|
||||
} else {
|
||||
int cursor = input.getSelectionStart();
|
||||
oneChar.setCharAt(0, key);
|
||||
input.getText().insert(cursor, oneChar);
|
||||
}
|
||||
}
|
||||
|
||||
private void showGraph(Function f) {
|
||||
if (f == null) {
|
||||
if (historyView.getVisibility() != View.VISIBLE) {
|
||||
graphView.setVisibility(View.GONE);
|
||||
graph3dView.setVisibility(View.GONE);
|
||||
graph3dView.onPause();
|
||||
historyView.setVisibility(View.VISIBLE);
|
||||
result.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
// graphedFunction = f;
|
||||
if (f.arity() == 1) {
|
||||
graphView.setFunction(f);
|
||||
if (graphView.getVisibility() != View.VISIBLE) {
|
||||
if (isAlphaVisible) {
|
||||
isAlphaVisible = false;
|
||||
updateAlpha();
|
||||
}
|
||||
result.setVisibility(View.GONE);
|
||||
historyView.setVisibility(View.GONE);
|
||||
graph3dView.setVisibility(View.GONE);
|
||||
graph3dView.onPause();
|
||||
graphView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
graph3dView.setFunction(f);
|
||||
if (graph3dView.getVisibility() != View.VISIBLE) {
|
||||
if (isAlphaVisible) {
|
||||
isAlphaVisible = false;
|
||||
updateAlpha();
|
||||
}
|
||||
result.setVisibility(View.GONE);
|
||||
historyView.setVisibility(View.GONE);
|
||||
graphView.setVisibility(View.GONE);
|
||||
graph3dView.setVisibility(View.VISIBLE);
|
||||
graph3dView.onResume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onEnter() {
|
||||
onEnter(input.getText().toString());
|
||||
}
|
||||
|
||||
void onEnter(String text) {
|
||||
boolean historyChanged = false;
|
||||
try {
|
||||
FunctionAndName fan = symbols.compileWithName(text);
|
||||
if (fan.name != null) {
|
||||
symbols.define(fan);
|
||||
defs.add(text);
|
||||
}
|
||||
Function f = fan.function;
|
||||
int arity = f.arity();
|
||||
Complex value = null;
|
||||
if (arity == 0) {
|
||||
value = f.evalComplex();
|
||||
symbols.define("ans", value);
|
||||
}
|
||||
historyChanged = arity == 0 ?
|
||||
history.onEnter(text, formatEval(value)) :
|
||||
history.onEnter(text, null);
|
||||
} catch (SyntaxException e) {
|
||||
historyChanged = history.onEnter(text, null);
|
||||
}
|
||||
showGraph(null);
|
||||
|
||||
if (text.length() == 0) {
|
||||
result.setText(null);
|
||||
}
|
||||
changeInput(history.getText());
|
||||
}
|
||||
|
||||
private void changeInput(String newInput) {
|
||||
input.setText(newInput);
|
||||
input.setSelection(newInput.length());
|
||||
/*
|
||||
if (newInput.length() > 0) {
|
||||
result.setText(null);
|
||||
} else {
|
||||
pendingClearResult = true;
|
||||
}
|
||||
*/
|
||||
/*
|
||||
if (result.getText().equals("function")) {
|
||||
result.setText(null);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
private void updateChecked() {
|
||||
int pos = history.getListPos();
|
||||
if (pos >= 0) {
|
||||
log("check " + pos);
|
||||
historyView.setItemChecked(pos, true);
|
||||
adapter.notifyDataSetInvalidated();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
void onUp() {
|
||||
if (history.moveUp(input.getText().toString())) {
|
||||
changeInput(history.getText());
|
||||
// updateChecked();
|
||||
}
|
||||
}
|
||||
|
||||
void onDown() {
|
||||
if (history.moveDown(input.getText().toString())) {
|
||||
changeInput(history.getText());
|
||||
// updateChecked();
|
||||
}
|
||||
}
|
||||
|
||||
private static final KeyEvent
|
||||
KEY_DEL = new KeyEvent(0, KeyEvent.KEYCODE_DEL),
|
||||
KEY_ENTER = new KeyEvent(0, KeyEvent.KEYCODE_ENTER);
|
||||
|
||||
void doEnter() {
|
||||
onEnter();
|
||||
}
|
||||
|
||||
void doBackspace() {
|
||||
input.dispatchKeyEvent(KEY_DEL);
|
||||
}
|
||||
|
||||
|
||||
}
|
85
android-app/src/main/java/arity/calculator/CalculatorEditable.java
Executable file
85
android-app/src/main/java/arity/calculator/CalculatorEditable.java
Executable file
@ -0,0 +1,85 @@
|
||||
// Copyright (C) 2009 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
import android.text.Editable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
class CalculatorEditable extends SpannableStringBuilder {
|
||||
static class Factory extends Editable.Factory {
|
||||
public Editable newEditable(CharSequence source) {
|
||||
return new CalculatorEditable(source);
|
||||
}
|
||||
}
|
||||
|
||||
static final char MINUS = '\u2212', TIMES = '\u00d7', DIV = '\u00f7';
|
||||
private boolean isRec;
|
||||
|
||||
public CalculatorEditable(CharSequence source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
public SpannableStringBuilder replace(int start, int end, CharSequence buf, int bufStart, int bufEnd) {
|
||||
if (isRec || bufEnd - bufStart != 1) {
|
||||
return super.replace(start, end, buf, bufStart, bufEnd);
|
||||
} else {
|
||||
isRec = true;
|
||||
try {
|
||||
char c = buf.charAt(bufStart);
|
||||
return internalReplace(start, end, c);
|
||||
} finally {
|
||||
isRec = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOperator(char c) {
|
||||
return "\u2212\u00d7\u00f7+-/*=^,".indexOf(c) != -1;
|
||||
}
|
||||
|
||||
private SpannableStringBuilder internalReplace(int start, int end, char c) {
|
||||
switch (c) {
|
||||
case '-': c = MINUS; break;
|
||||
case '*': c = TIMES; break;
|
||||
case '/': c = DIV; break;
|
||||
}
|
||||
if (c == '.') {
|
||||
int p = start - 1;
|
||||
while (p >= 0 && Character.isDigit(charAt(p))) {
|
||||
--p;
|
||||
}
|
||||
if (p >= 0 && charAt(p) == '.') {
|
||||
return super.replace(start, end, "");
|
||||
}
|
||||
}
|
||||
|
||||
char prevChar = start > 0 ? charAt(start-1) : '\0';
|
||||
|
||||
if (c == MINUS && prevChar == MINUS) {
|
||||
return super.replace(start, end, "");
|
||||
}
|
||||
|
||||
if (isOperator(c)) {
|
||||
while (isOperator(prevChar) &&
|
||||
(c != MINUS || prevChar == '+')) {
|
||||
--start;
|
||||
prevChar = start > 0 ? charAt(start-1) : '\0';
|
||||
}
|
||||
}
|
||||
|
||||
//don't allow leading operator + * /
|
||||
if (start == 0 && isOperator(c)) { // && c != MINUS
|
||||
return super.replace(start, end, "ans" + c);
|
||||
}
|
||||
|
||||
//allow at most one '='
|
||||
if (c == '=') {
|
||||
for (int pos = 0; pos < start; ++pos) {
|
||||
if (charAt(pos) == '=') {
|
||||
return super.replace(start, end, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.replace(start, end, "" + c);
|
||||
}
|
||||
}
|
145
android-app/src/main/java/arity/calculator/Data.java
Executable file
145
android-app/src/main/java/arity/calculator/Data.java
Executable file
@ -0,0 +1,145 @@
|
||||
// Copyright (C) 2009 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
class Data {
|
||||
float[] xs = new float[4];
|
||||
float[] ys = new float[4];
|
||||
int size = 0;
|
||||
int allocSize = 4;
|
||||
|
||||
void swap(Data o) {
|
||||
float savex[] = o.xs;
|
||||
float savey[] = o.ys;
|
||||
int ssize = o.size;
|
||||
int salloc = o.allocSize;
|
||||
|
||||
o.xs = xs;
|
||||
o.ys = ys;
|
||||
o.size = size;
|
||||
o.allocSize = allocSize;
|
||||
|
||||
xs = savex;
|
||||
ys = savey;
|
||||
size = ssize;
|
||||
allocSize = salloc;
|
||||
}
|
||||
|
||||
void push(float x, float y) {
|
||||
if (size >= allocSize) {
|
||||
makeSpace(size+1);
|
||||
}
|
||||
// Calculator.log("push " + size + ' ' + x + ' ' + y);
|
||||
xs[size] = x;
|
||||
ys[size] = y;
|
||||
++size;
|
||||
}
|
||||
|
||||
private void makeSpace(int sizeNeeded) {
|
||||
int oldAllocSize = allocSize;
|
||||
while (sizeNeeded > allocSize) {
|
||||
allocSize += allocSize;
|
||||
}
|
||||
if (oldAllocSize != allocSize) {
|
||||
float[] a = new float[allocSize];
|
||||
System.arraycopy(xs, 0, a, 0, size);
|
||||
xs = a;
|
||||
a = new float[allocSize];
|
||||
System.arraycopy(ys, 0, a, 0, size);
|
||||
ys = a;
|
||||
}
|
||||
}
|
||||
|
||||
float topX() {
|
||||
return xs[size-1];
|
||||
}
|
||||
|
||||
float topY() {
|
||||
return ys[size-1];
|
||||
}
|
||||
|
||||
float firstX() {
|
||||
return xs[0];
|
||||
}
|
||||
|
||||
float firstY() {
|
||||
return ys[0];
|
||||
}
|
||||
|
||||
void pop() {
|
||||
--size;
|
||||
}
|
||||
|
||||
boolean empty() {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
size = 0;
|
||||
}
|
||||
|
||||
void eraseBefore(float x) {
|
||||
int pos = 0;
|
||||
while (pos < size && xs[pos] < x) {
|
||||
++pos;
|
||||
}
|
||||
--pos;
|
||||
if (pos > 0) {
|
||||
size -= pos;
|
||||
System.arraycopy(xs, pos, xs, 0, size);
|
||||
System.arraycopy(ys, pos, ys, 0, size);
|
||||
}
|
||||
}
|
||||
|
||||
void eraseAfter(float x) {
|
||||
int pos = size-1;
|
||||
while (pos >= 0 && x < xs[pos]) {
|
||||
--pos;
|
||||
}
|
||||
++pos;
|
||||
if (pos < size-1) {
|
||||
size = pos+1;
|
||||
}
|
||||
}
|
||||
|
||||
int findPosAfter(float x, float y) {
|
||||
int pos = 0;
|
||||
while (pos < size && xs[pos] <= x) {
|
||||
++pos;
|
||||
}
|
||||
if (Float.isNaN(y)) {
|
||||
while (pos < size && ys[pos] != ys[pos]) {
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
// Calculator.log("pos " + pos);
|
||||
return pos;
|
||||
}
|
||||
|
||||
void append(Data d) {
|
||||
makeSpace(size + d.size);
|
||||
int pos = d.findPosAfter(xs[size-1], ys[size-1]);
|
||||
/*
|
||||
while (pos < d.size && d.xs[pos] <= last) {
|
||||
++pos;
|
||||
}
|
||||
if (last != last) {
|
||||
while (pos < d.size && d.ys[pos] != d.ys[pos]) {
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
*/
|
||||
System.arraycopy(d.xs, pos, xs, size, d.size-pos);
|
||||
System.arraycopy(d.ys, pos, ys, size, d.size-pos);
|
||||
size += d.size-pos;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append(size).append(": ");
|
||||
for (int i = 0; i < size; ++i) {
|
||||
b.append(xs[i]).append(", ");
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
}
|
62
android-app/src/main/java/arity/calculator/Defs.java
Executable file
62
android-app/src/main/java/arity/calculator/Defs.java
Executable file
@ -0,0 +1,62 @@
|
||||
// Copyright (C) 2009 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
import android.content.Context;
|
||||
import org.javia.arity.Symbols;
|
||||
import org.javia.arity.SyntaxException;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
class Defs extends FileHandler {
|
||||
private static final int SIZE_LIMIT = 50;
|
||||
ArrayList<String> lines = new ArrayList<String>();
|
||||
private Symbols symbols;
|
||||
|
||||
Defs(Context context, Symbols symbols) {
|
||||
super(context, "defs", 1);
|
||||
this.symbols = symbols;
|
||||
symbols.pushFrame();
|
||||
load();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
lines.clear();
|
||||
symbols.popFrame();
|
||||
symbols.pushFrame();
|
||||
}
|
||||
|
||||
int size() {
|
||||
return lines.size();
|
||||
}
|
||||
|
||||
void doRead(DataInputStream is) throws IOException {
|
||||
int size = is.readInt();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
String line = is.readUTF();
|
||||
lines.add(line);
|
||||
try {
|
||||
symbols.define(symbols.compileWithName(line));
|
||||
} catch (SyntaxException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void doWrite(DataOutputStream os) throws IOException {
|
||||
os.writeInt(lines.size());
|
||||
for (String s : lines) {
|
||||
os.writeUTF(s);
|
||||
}
|
||||
}
|
||||
|
||||
void add(String text) {
|
||||
if (lines.size() >= SIZE_LIMIT) {
|
||||
lines.remove(0);
|
||||
}
|
||||
lines.add(text);
|
||||
}
|
||||
}
|
22
android-app/src/main/java/arity/calculator/FPS.java
Executable file
22
android-app/src/main/java/arity/calculator/FPS.java
Executable file
@ -0,0 +1,22 @@
|
||||
package arity.calculator;
|
||||
|
||||
class FPS {
|
||||
private int drawCnt;
|
||||
private long lastTime;
|
||||
private int fps;
|
||||
|
||||
boolean incFrame() {
|
||||
if (--drawCnt > 0) {
|
||||
return false;
|
||||
}
|
||||
drawCnt = 100;
|
||||
long now = System.currentTimeMillis();
|
||||
fps = Math.round(100000f / (now - lastTime));
|
||||
lastTime = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
int getValue() {
|
||||
return fps;
|
||||
}
|
||||
}
|
62
android-app/src/main/java/arity/calculator/FileHandler.java
Executable file
62
android-app/src/main/java/arity/calculator/FileHandler.java
Executable file
@ -0,0 +1,62 @@
|
||||
// Copyright (C) 2009 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
import android.content.Context;
|
||||
import java.io.*;
|
||||
|
||||
abstract class FileHandler {
|
||||
private String fileName;
|
||||
private Context context;
|
||||
private int version;
|
||||
boolean fileNotFound;
|
||||
|
||||
private DataInputStream openInput() throws IOException {
|
||||
try {
|
||||
return new DataInputStream(new BufferedInputStream(context.openFileInput(fileName), 256));
|
||||
} catch (FileNotFoundException e) {
|
||||
fileNotFound = true;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private DataOutputStream openOutput() throws IOException {
|
||||
return new DataOutputStream(new BufferedOutputStream(context.openFileOutput(fileName, 0), 256));
|
||||
}
|
||||
|
||||
FileHandler(Context context, String fileName, int version) {
|
||||
this.context = context;
|
||||
this.fileName = fileName;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
void load() {
|
||||
try {
|
||||
DataInputStream is = openInput();
|
||||
if (is != null) {
|
||||
int readVersion = is.readInt();
|
||||
if (readVersion != version) {
|
||||
throw new IllegalStateException("invalid version " + readVersion);
|
||||
}
|
||||
doRead(is);
|
||||
is.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("" + e);
|
||||
}
|
||||
}
|
||||
|
||||
void save() {
|
||||
try {
|
||||
DataOutputStream os = openOutput();
|
||||
os.writeInt(version);
|
||||
doWrite(os);
|
||||
os.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("" + e);
|
||||
}
|
||||
}
|
||||
|
||||
abstract void doRead(DataInputStream is) throws IOException;
|
||||
abstract void doWrite(DataOutputStream os) throws IOException;
|
||||
}
|
183
android-app/src/main/java/arity/calculator/GLView.java
Executable file
183
android-app/src/main/java/arity/calculator/GLView.java
Executable file
@ -0,0 +1,183 @@
|
||||
// Copyright (C) 2009 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
|
||||
import javax.microedition.khronos.egl.*;
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
abstract class GLView extends SurfaceView implements SurfaceHolder.Callback {
|
||||
private boolean hasSurface;
|
||||
private boolean paused;
|
||||
private EGL10 egl;
|
||||
private EGLDisplay display;
|
||||
private EGLConfig config;
|
||||
private EGLSurface surface;
|
||||
private EGLContext eglContext;
|
||||
private GL11 gl;
|
||||
protected int width, height;
|
||||
private boolean mIsLooping;
|
||||
|
||||
abstract void onDrawFrame(GL10 gl);
|
||||
abstract void onSurfaceCreated(GL10 gl, int width, int height);
|
||||
|
||||
public String captureScreenshot() {
|
||||
Bitmap bitmap = getRawPixels(gl, width, height);
|
||||
Util.bitmapBGRtoRGB(bitmap, width, height);
|
||||
return Util.saveBitmap(bitmap, GraphView.SCREENSHOT_DIR, "calculator");
|
||||
}
|
||||
|
||||
private static Bitmap getRawPixels(GL10 gl, int width, int height) {
|
||||
int size = width * height;
|
||||
ByteBuffer buf = ByteBuffer.allocateDirect(size * 4);
|
||||
buf.order(ByteOrder.nativeOrder());
|
||||
gl.glReadPixels(0, 0, width, height, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, buf);
|
||||
int data[] = new int[size];
|
||||
buf.asIntBuffer().get(data);
|
||||
buf = null;
|
||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
|
||||
bitmap.setPixels(data, size-width, -width, 0, 0, width, height);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private Handler handler = new Handler() {
|
||||
public void handleMessage(Message msg) {
|
||||
glDraw();
|
||||
}
|
||||
};
|
||||
|
||||
public GLView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public GLView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
SurfaceHolder holder = getHolder();
|
||||
holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
|
||||
holder.addCallback(this);
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
Calculator.log("onResume " + this);
|
||||
paused = false;
|
||||
if (hasSurface) {
|
||||
initGL();
|
||||
}
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
Calculator.log("onPause " + this);
|
||||
deinitGL();
|
||||
}
|
||||
|
||||
private void initGL() {
|
||||
egl = (EGL10) EGLContext.getEGL();
|
||||
display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
|
||||
int[] ver = new int[2];
|
||||
egl.eglInitialize(display, ver);
|
||||
|
||||
int[] configSpec = {EGL10.EGL_NONE};
|
||||
EGLConfig[] configOut = new EGLConfig[1];
|
||||
int[] nConfig = new int[1];
|
||||
egl.eglChooseConfig(display, configSpec, configOut, 1, nConfig);
|
||||
config = configOut[0];
|
||||
eglContext = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, null);
|
||||
surface = egl.eglCreateWindowSurface(display, config, getHolder(), null);
|
||||
egl.eglMakeCurrent(display, surface, surface, eglContext);
|
||||
gl = (GL11) eglContext.getGL();
|
||||
onSurfaceCreated(gl, width, height);
|
||||
requestDraw();
|
||||
}
|
||||
|
||||
private void deinitGL() {
|
||||
paused = true;
|
||||
if (display != null) {
|
||||
egl.eglMakeCurrent(display, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
|
||||
egl.eglDestroySurface(display, surface);
|
||||
egl.eglDestroyContext(display, eglContext);
|
||||
egl.eglTerminate(display);
|
||||
|
||||
egl = null;
|
||||
config = null;
|
||||
eglContext = null;
|
||||
surface = null;
|
||||
display = null;
|
||||
gl = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void glDraw() {
|
||||
if (hasSurface && !paused) {
|
||||
onDrawFrame(gl);
|
||||
if (!egl.eglSwapBuffers(display, surface)) {
|
||||
Calculator.log("swapBuffers error " + egl.eglGetError());
|
||||
}
|
||||
if (egl.eglGetError() == EGL11.EGL_CONTEXT_LOST) {
|
||||
Calculator.log("egl context lost " + this);
|
||||
paused = true;
|
||||
}
|
||||
if (mIsLooping) {
|
||||
requestDraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
Calculator.log("surfaceCreated " + this);
|
||||
}
|
||||
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
Calculator.log("surfaceChanged " + format + ' ' + this);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
boolean doInit = !hasSurface && !paused;
|
||||
hasSurface = true;
|
||||
if (doInit) {
|
||||
initGL();
|
||||
}
|
||||
}
|
||||
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
Calculator.log("surfaceDestroyed " + this);
|
||||
hasSurface = false;
|
||||
deinitGL();
|
||||
}
|
||||
|
||||
public void startLooping() {
|
||||
if (!mIsLooping) {
|
||||
Calculator.log("start looping");
|
||||
mIsLooping = true;
|
||||
glDraw();
|
||||
}
|
||||
}
|
||||
|
||||
public void stopLooping() {
|
||||
if (mIsLooping) {
|
||||
Calculator.log("stop looping");
|
||||
mIsLooping = false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLooping() {
|
||||
return mIsLooping;
|
||||
}
|
||||
|
||||
public void requestDraw() {
|
||||
handler.sendEmptyMessage(1);
|
||||
}
|
||||
}
|
566
android-app/src/main/java/arity/calculator/Graph2dView.java
Executable file
566
android-app/src/main/java/arity/calculator/Graph2dView.java
Executable file
@ -0,0 +1,566 @@
|
||||
// Copyright (C) 2009-2010 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.*;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Scroller;
|
||||
import android.widget.ZoomButtonsController;
|
||||
import org.javia.arity.Function;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Graph2dView extends View implements
|
||||
GraphView,
|
||||
ZoomButtonsController.OnZoomListener,
|
||||
TouchHandler.TouchHandlerInterface {
|
||||
|
||||
private int width, height;
|
||||
private Matrix matrix = new Matrix();
|
||||
private Paint paint = new Paint(), textPaint = new Paint(), fillPaint = new Paint();
|
||||
private ArrayList<Function> funcs = new ArrayList<Function>();
|
||||
private Data next = new Data(), endGraph = new Data();
|
||||
private Data graphs[] = {new Data(), new Data(), new Data(), new Data(), new Data()};
|
||||
private static final int GRAPHS_SIZE = 5;
|
||||
private float gwidth = 8;
|
||||
private float currentX, currentY;
|
||||
private float lastMinX;
|
||||
private Scroller scroller;
|
||||
private float boundMinY, boundMaxY;
|
||||
protected ZoomButtonsController zoomController = new ZoomButtonsController(this);
|
||||
private ZoomTracker zoomTracker = new ZoomTracker();
|
||||
private TouchHandler touchHandler;
|
||||
private float lastTouchX, lastTouchY;
|
||||
|
||||
private static final int
|
||||
COL_AXIS = 0xff00a000,
|
||||
COL_GRID = 0xff004000,
|
||||
COL_TEXT = 0xff00ff00;
|
||||
|
||||
private static final int COL_GRAPH[] = {0xffffffff, 0xff00ffff, 0xffffff00, 0xffff00ff, 0xff80ff80};
|
||||
|
||||
private static final int
|
||||
COL_ZOOM = 0x40ffffff,
|
||||
COL_ZOOM_TEXT1 = 0xd0ffffff,
|
||||
COL_ZOOM_TEXT2 = 0x30ffffff;
|
||||
|
||||
public Graph2dView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public Graph2dView(Context context) {
|
||||
super(context);
|
||||
touchHandler = new TouchHandler(this);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
zoomController.setOnZoomListener(this);
|
||||
scroller = new Scroller(context);
|
||||
paint.setAntiAlias(false);
|
||||
textPaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
public String captureScreenshot() {
|
||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
onDraw(canvas);
|
||||
return Util.saveBitmap(bitmap, GraphView.SCREENSHOT_DIR, "calculator");
|
||||
}
|
||||
|
||||
private void clearAllGraph() {
|
||||
for (int i = 0; i < GRAPHS_SIZE; ++i) {
|
||||
graphs[i].clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void setFunctions(List<Function> fs) {
|
||||
funcs.clear();
|
||||
for (Function f : fs) {
|
||||
int arity = f.arity();
|
||||
if (arity == 0 || arity == 1) {
|
||||
funcs.add(f);
|
||||
}
|
||||
}
|
||||
clearAllGraph();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setFunction(Function f) {
|
||||
funcs.clear();
|
||||
if (f != null) {
|
||||
funcs.add(f);
|
||||
}
|
||||
clearAllGraph();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void onVisibilityChanged(boolean visible) {
|
||||
}
|
||||
|
||||
public void onZoom(boolean zoomIn) {
|
||||
if (zoomIn) {
|
||||
if (canZoomIn()) {
|
||||
gwidth /= 2;
|
||||
invalidateGraphs();
|
||||
}
|
||||
} else {
|
||||
if (canZoomOut()) {
|
||||
gwidth *= 2;
|
||||
invalidateGraphs();
|
||||
}
|
||||
}
|
||||
zoomController.setZoomInEnabled(canZoomIn());
|
||||
zoomController.setZoomOutEnabled(canZoomOut());
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
}
|
||||
|
||||
public void onDetachedFromWindow() {
|
||||
zoomController.setVisible(false);
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
protected void onSizeChanged(int w, int h, int ow, int oh) {
|
||||
width = w;
|
||||
height = h;
|
||||
clearAllGraph();
|
||||
// points = new float[w+w];
|
||||
}
|
||||
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (funcs.size() == 0) {
|
||||
return;
|
||||
}
|
||||
if (scroller.computeScrollOffset()) {
|
||||
final float scale = gwidth / width;
|
||||
currentX = scroller.getCurrX() * scale;
|
||||
currentY = scroller.getCurrY() * scale;
|
||||
if (!scroller.isFinished()) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
drawGraph(canvas);
|
||||
}
|
||||
|
||||
private float eval(Function f, float x) {
|
||||
float v = (float) f.eval(x);
|
||||
// Calculator.log("eval " + x + "; " + v);
|
||||
if (v < -10000f) {
|
||||
return -10000f;
|
||||
}
|
||||
if (v > 10000f) {
|
||||
return 10000f;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// distance from (x,y) to the line (x1,y1) to (x2,y2), squared, multiplied by 4
|
||||
/*
|
||||
private float distance(float x1, float y1, float x2, float y2, float x, float y) {
|
||||
float dx = x2 - x1;
|
||||
float dy = y2 - y1;
|
||||
float mx = x - x1;
|
||||
float my = y - y1;
|
||||
float up = dx*my - dy*mx;
|
||||
return up*up*4/(dx*dx + dy*dy);
|
||||
}
|
||||
*/
|
||||
|
||||
// distance as above when x==(x1+x2)/2.
|
||||
private float distance2(float x1, float y1, float x2, float y2, float y) {
|
||||
final float dx = x2 - x1;
|
||||
final float dy = y2 - y1;
|
||||
final float up = dx * (y1 + y2 - y - y);
|
||||
return up * up / (dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
private void computeGraph(Function f, float minX, float maxX, float minY, float maxY, Data graph) {
|
||||
if (f.arity() == 0) {
|
||||
float v = (float) f.eval();
|
||||
if (v < -10000f) {
|
||||
v = -10000f;
|
||||
}
|
||||
if (v > 10000f) {
|
||||
v = 10000f;
|
||||
}
|
||||
graph.clear();
|
||||
graph.push(minX, v);
|
||||
graph.push(maxX, v);
|
||||
return;
|
||||
}
|
||||
|
||||
final float scale = width / gwidth;
|
||||
final float maxStep = 15.8976f / scale;
|
||||
final float minStep = .05f / scale;
|
||||
float ythresh = 1 / scale;
|
||||
ythresh = ythresh * ythresh;
|
||||
// next.clear();
|
||||
// endGraph.clear();
|
||||
if (!graph.empty()) {
|
||||
// Calculator.log("last " + lastMinX + " min " + minX);
|
||||
if (minX >= lastMinX) {
|
||||
graph.eraseBefore(minX);
|
||||
} else {
|
||||
graph.eraseAfter(maxX);
|
||||
maxX = Math.min(maxX, graph.firstX());
|
||||
graph.swap(endGraph);
|
||||
}
|
||||
}
|
||||
if (graph.empty()) {
|
||||
graph.push(minX, eval(f, minX));
|
||||
}
|
||||
float leftX, leftY;
|
||||
float rightX = graph.topX(), rightY = graph.topY();
|
||||
int nEval = 1;
|
||||
while (true) {
|
||||
leftX = rightX;
|
||||
leftY = rightY;
|
||||
if (leftX > maxX) {
|
||||
break;
|
||||
}
|
||||
if (next.empty()) {
|
||||
float x = leftX + maxStep;
|
||||
next.push(x, eval(f, x));
|
||||
++nEval;
|
||||
}
|
||||
rightX = next.topX();
|
||||
rightY = next.topY();
|
||||
next.pop();
|
||||
|
||||
if (leftY != leftY && rightY != rightY) { // NaN
|
||||
continue;
|
||||
}
|
||||
|
||||
float dx = rightX - leftX;
|
||||
float middleX = (leftX + rightX) / 2;
|
||||
float middleY = eval(f, middleX);
|
||||
++nEval;
|
||||
boolean middleIsOutside = (middleY < leftY && middleY < rightY) || (leftY < middleY && rightY < middleY);
|
||||
if (dx < minStep) {
|
||||
// Calculator.log("minStep");
|
||||
if (middleIsOutside) {
|
||||
graph.push(rightX, Float.NaN);
|
||||
}
|
||||
graph.push(rightX, rightY);
|
||||
continue;
|
||||
}
|
||||
if (middleIsOutside && ((leftY < minY && rightY > maxY) || (leftY > maxY && rightY < minY))) {
|
||||
graph.push(rightX, Float.NaN);
|
||||
graph.push(rightX, rightY);
|
||||
// Calculator.log("+-inf");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!middleIsOutside) {
|
||||
/*
|
||||
float diff = leftY + rightY - middleY - middleY;
|
||||
float dy = rightY - leftY;
|
||||
float dx2 = dx*dx;
|
||||
float distance = dx2*diff*diff/(dx2+dy*dy);
|
||||
*/
|
||||
// Calculator.log("" + dx + ' ' + leftY + ' ' + middleY + ' ' + rightY + ' ' + distance + ' ' + ythresh);
|
||||
if (distance2(leftX, leftY, rightX, rightY, middleY) < ythresh) {
|
||||
graph.push(rightX, rightY);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
next.push(rightX, rightY);
|
||||
next.push(middleX, middleY);
|
||||
rightX = leftX;
|
||||
rightY = leftY;
|
||||
}
|
||||
if (!endGraph.empty()) {
|
||||
graph.append(endGraph);
|
||||
}
|
||||
long t2 = System.currentTimeMillis();
|
||||
// Calculator.log("graph points " + graph.size + " evals " + nEval + " time " + (t2-t1));
|
||||
|
||||
next.clear();
|
||||
endGraph.clear();
|
||||
}
|
||||
|
||||
private static Path path = new Path();
|
||||
|
||||
private Path graphToPath(Data graph) {
|
||||
boolean first = true;
|
||||
int size = graph.size;
|
||||
float[] xs = graph.xs;
|
||||
float[] ys = graph.ys;
|
||||
path.rewind();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
float y = ys[i];
|
||||
float x = xs[i];
|
||||
// Calculator.log("path " + x + ' ' + y);
|
||||
if (y == y) { // !NaN
|
||||
if (first) {
|
||||
path.moveTo(x, y);
|
||||
first = false;
|
||||
} else {
|
||||
path.lineTo(x, y);
|
||||
}
|
||||
} else {
|
||||
first = true;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private static final float NTICKS = 15;
|
||||
|
||||
private static float stepFactor(float w) {
|
||||
float f = 1;
|
||||
while (w / f > NTICKS) {
|
||||
f *= 10;
|
||||
}
|
||||
while (w / f < NTICKS / 10) {
|
||||
f /= 10;
|
||||
}
|
||||
float r = w / f;
|
||||
if (r < NTICKS / 5) {
|
||||
return f / 5;
|
||||
} else if (r < NTICKS / 2) {
|
||||
return f / 2;
|
||||
} else {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
private static StringBuilder b = new StringBuilder();
|
||||
private static char[] buf = new char[20];
|
||||
|
||||
private static StringBuilder format(float fv) {
|
||||
int pos = 0;
|
||||
boolean addDot = false;
|
||||
int v = Math.round(fv * 100);
|
||||
boolean isNeg = v < 0;
|
||||
v = isNeg ? -v : v;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
int digit = v % 10;
|
||||
v /= 10;
|
||||
if (digit != 0 || addDot) {
|
||||
buf[pos++] = (char) ('0' + digit);
|
||||
addDot = true;
|
||||
}
|
||||
}
|
||||
if (addDot) {
|
||||
buf[pos++] = '.';
|
||||
}
|
||||
if (v == 0) {
|
||||
buf[pos++] = '0';
|
||||
}
|
||||
while (v != 0) {
|
||||
buf[pos++] = (char) ('0' + (v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
if (isNeg) {
|
||||
buf[pos++] = '-';
|
||||
}
|
||||
b.setLength(0);
|
||||
b.append(buf, 0, pos);
|
||||
b.reverse();
|
||||
return b;
|
||||
}
|
||||
|
||||
private void drawGraph(Canvas canvas) {
|
||||
long t1 = System.currentTimeMillis();
|
||||
float minX = getXMin();
|
||||
float maxX = getXMax(minX);
|
||||
float ywidth = gwidth * height / width;
|
||||
float minY = currentY - ywidth / 2;
|
||||
float maxY = minY + ywidth;
|
||||
if (minY < boundMinY || maxY > boundMaxY) {
|
||||
float halfw = ywidth / 2;
|
||||
boundMinY = minY - halfw;
|
||||
boundMaxY = maxY + halfw;
|
||||
clearAllGraph();
|
||||
}
|
||||
|
||||
canvas.drawColor(0xff000000);
|
||||
|
||||
paint.setStrokeWidth(0);
|
||||
paint.setAntiAlias(false);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
|
||||
final float h2 = height / 2f;
|
||||
final float scale = width / gwidth;
|
||||
|
||||
float x0 = -minX * scale;
|
||||
boolean drawYAxis = true;
|
||||
if (x0 < 25) {
|
||||
x0 = 25;
|
||||
// drawYAxis = false;
|
||||
} else if (x0 > width - 3) {
|
||||
x0 = width - 3;
|
||||
// drawYAxis = false;
|
||||
}
|
||||
float y0 = maxY * scale;
|
||||
if (y0 < 3) {
|
||||
y0 = 3;
|
||||
} else if (y0 > height - 15) {
|
||||
y0 = height - 15;
|
||||
}
|
||||
|
||||
final float tickSize = 3;
|
||||
final float y2 = y0 + tickSize;
|
||||
paint.setColor(COL_GRID);
|
||||
float step = stepFactor(gwidth);
|
||||
// Calculator.log("width " + gwidth + " step " + step);
|
||||
float v = ((int) (minX / step)) * step;
|
||||
textPaint.setColor(COL_TEXT);
|
||||
textPaint.setTextSize(12);
|
||||
textPaint.setTextAlign(Paint.Align.CENTER);
|
||||
float stepScale = step * scale;
|
||||
for (float x = (v - minX) * scale; x <= width; x += stepScale, v += step) {
|
||||
canvas.drawLine(x, 0, x, height, paint);
|
||||
if (!(-.001f < v && v < .001f)) {
|
||||
StringBuilder b = format(v);
|
||||
canvas.drawText(b, 0, b.length(), x, y2 + 10, textPaint);
|
||||
}
|
||||
}
|
||||
|
||||
final float x1 = x0 - tickSize;
|
||||
v = ((int) (minY / step)) * step;
|
||||
textPaint.setTextAlign(Paint.Align.RIGHT);
|
||||
for (float y = height - (v - minY) * scale; y >= 0; y -= stepScale, v += step) {
|
||||
canvas.drawLine(0, y, width, y, paint);
|
||||
if (!(-.001f < v && v < .001f)) {
|
||||
StringBuilder b = format(v);
|
||||
canvas.drawText(b, 0, b.length(), x1, y + 4, textPaint);
|
||||
}
|
||||
}
|
||||
|
||||
paint.setColor(COL_AXIS);
|
||||
if (drawYAxis) {
|
||||
canvas.drawLine(x0, 0, x0, height, paint);
|
||||
}
|
||||
canvas.drawLine(0, y0, width, y0, paint);
|
||||
|
||||
matrix.reset();
|
||||
matrix.preTranslate(-currentX, -currentY);
|
||||
matrix.postScale(scale, -scale);
|
||||
matrix.postTranslate(width / 2, height / 2);
|
||||
|
||||
paint.setStrokeWidth(0);
|
||||
paint.setAntiAlias(false);
|
||||
|
||||
int n = Math.min(funcs.size(), GRAPHS_SIZE);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
computeGraph(funcs.get(i), minX, maxX, boundMinY, boundMaxY, graphs[i]);
|
||||
Path path = graphToPath(graphs[i]);
|
||||
path.transform(matrix);
|
||||
paint.setColor(COL_GRAPH[i]);
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
lastMinX = minX;
|
||||
}
|
||||
|
||||
private float getXMax(float minX) {
|
||||
return minX + gwidth;
|
||||
}
|
||||
|
||||
private float getXMax() {
|
||||
return getXMax(getXMin());
|
||||
}
|
||||
|
||||
private float getXMin() {
|
||||
return currentX - gwidth / 2;
|
||||
}
|
||||
|
||||
private boolean canZoomIn() {
|
||||
return gwidth > 1f;
|
||||
}
|
||||
|
||||
private boolean canZoomOut() {
|
||||
return gwidth < 50;
|
||||
}
|
||||
|
||||
private void invalidateGraphs() {
|
||||
clearAllGraph();
|
||||
boundMinY = boundMaxY = 0;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
return touchHandler != null ? touchHandler.onTouchEvent(event) : super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
public void onTouchDown(float x, float y) {
|
||||
zoomController.setVisible(true);
|
||||
if (!scroller.isFinished()) {
|
||||
scroller.abortAnimation();
|
||||
}
|
||||
lastTouchX = x;
|
||||
lastTouchY = y;
|
||||
}
|
||||
|
||||
public void onTouchMove(float x, float y) {
|
||||
float deltaX = x - lastTouchX;
|
||||
float deltaY = y - lastTouchY;
|
||||
if (deltaX < -1 || deltaX > 1 || deltaY < -1 || deltaY > 1) {
|
||||
scroll(-deltaX, deltaY);
|
||||
lastTouchX = x;
|
||||
lastTouchY = y;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void onTouchUp(float x, float y) {
|
||||
final float scale = width / gwidth;
|
||||
float sx = -touchHandler.velocityTracker.getXVelocity();
|
||||
float sy = touchHandler.velocityTracker.getYVelocity();
|
||||
final float asx = Math.abs(sx);
|
||||
final float asy = Math.abs(sy);
|
||||
if (asx < asy / 3) {
|
||||
sx = 0;
|
||||
} else if (asy < asx / 3) {
|
||||
sy = 0;
|
||||
}
|
||||
scroller.fling(Math.round(currentX * scale),
|
||||
Math.round(currentY * scale),
|
||||
Math.round(sx), Math.round(sy), -10000, 10000, -10000, 10000);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void onTouchZoomDown(float x1, float y1, float x2, float y2) {
|
||||
zoomTracker.start(gwidth, x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
public void onTouchZoomMove(float x1, float y1, float x2, float y2) {
|
||||
if (!zoomTracker.update(x1, y1, x2, y2)) {
|
||||
return;
|
||||
}
|
||||
float targetGwidth = zoomTracker.value;
|
||||
if (targetGwidth > .25f && targetGwidth < 200) {
|
||||
gwidth = targetGwidth;
|
||||
}
|
||||
// scroll(-zoomTracker.moveX, zoomTracker.moveY);
|
||||
invalidateGraphs();
|
||||
// Calculator.log("zoom redraw");
|
||||
}
|
||||
|
||||
private void scroll(float deltaX, float deltaY) {
|
||||
final float scale = gwidth / width;
|
||||
float dx = deltaX * scale;
|
||||
float dy = deltaY * scale;
|
||||
final float adx = Math.abs(dx);
|
||||
final float ady = Math.abs(dy);
|
||||
if (adx < ady / 3) {
|
||||
dx = 0;
|
||||
} else if (ady < adx / 3) {
|
||||
dy = 0;
|
||||
}
|
||||
currentX += dx;
|
||||
currentY += dy;
|
||||
}
|
||||
}
|
264
android-app/src/main/java/arity/calculator/Graph3d.java
Executable file
264
android-app/src/main/java/arity/calculator/Graph3d.java
Executable file
@ -0,0 +1,264 @@
|
||||
// Copyright (C) 2009-2010 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
import org.javia.arity.Function;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
class Graph3d {
|
||||
private final int N = Calculator.useHighQuality3d ? 36 : 24;
|
||||
private ShortBuffer verticeIdx;
|
||||
private FloatBuffer vertexBuf;
|
||||
private ByteBuffer colorBuf;
|
||||
private int vertexVbo, colorVbo, vertexElementVbo;
|
||||
private boolean useVBO;
|
||||
private int nVertex;
|
||||
|
||||
Graph3d(GL11 gl) {
|
||||
short[] b = new short[N*N];
|
||||
int p = 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
short v = 0;
|
||||
for (int j = 0; j < N; v += N+N, j+=2) {
|
||||
b[p++] = (short)(v+i);
|
||||
b[p++] = (short)(v+N+N-1-i);
|
||||
}
|
||||
v = (short) (N*(N-2));
|
||||
i++;
|
||||
for (int j = N-1; j >= 0; v -= N+N, j-=2) {
|
||||
b[p++] = (short)(v+N+N-1-i);
|
||||
b[p++] = (short)(v+i);
|
||||
}
|
||||
}
|
||||
verticeIdx = buildBuffer(b);
|
||||
|
||||
String extensions = gl.glGetString(GL10.GL_EXTENSIONS);
|
||||
useVBO = extensions.indexOf("vertex_buffer_object") != -1;
|
||||
Calculator.log("VBOs support: " + useVBO + " version " + gl.glGetString(GL10.GL_VERSION));
|
||||
|
||||
if (useVBO) {
|
||||
int[] out = new int[3];
|
||||
gl.glGenBuffers(3, out, 0);
|
||||
vertexVbo = out[0];
|
||||
colorVbo = out[1];
|
||||
vertexElementVbo = out[2];
|
||||
}
|
||||
}
|
||||
|
||||
private static FloatBuffer buildBuffer(float[] b) {
|
||||
ByteBuffer bb = ByteBuffer.allocateDirect(b.length << 2);
|
||||
bb.order(ByteOrder.nativeOrder());
|
||||
FloatBuffer sb = bb.asFloatBuffer();
|
||||
sb.put(b);
|
||||
sb.position(0);
|
||||
return sb;
|
||||
}
|
||||
|
||||
private static ShortBuffer buildBuffer(short[] b) {
|
||||
ByteBuffer bb = ByteBuffer.allocateDirect(b.length << 1);
|
||||
bb.order(ByteOrder.nativeOrder());
|
||||
ShortBuffer sb = bb.asShortBuffer();
|
||||
sb.put(b);
|
||||
sb.position(0);
|
||||
return sb;
|
||||
}
|
||||
|
||||
private static ByteBuffer buildBuffer(byte[] b) {
|
||||
ByteBuffer bb = ByteBuffer.allocateDirect(b.length << 1);
|
||||
bb.order(ByteOrder.nativeOrder());
|
||||
bb.put(b);
|
||||
bb.position(0);
|
||||
return bb;
|
||||
}
|
||||
|
||||
public void update(GL11 gl, Function f, float zoom) {
|
||||
final int NTICK = Calculator.useHighQuality3d ? 5 : 0;
|
||||
final float size = 4*zoom;
|
||||
final float minX = -size, maxX = size, minY = -size, maxY = size;
|
||||
|
||||
Calculator.log("update VBOs " + vertexVbo + ' ' + colorVbo + ' ' + vertexElementVbo);
|
||||
nVertex = N*N+6+8 + NTICK*6;
|
||||
int nFloats = nVertex * 3;
|
||||
float vertices[] = new float[nFloats];
|
||||
byte colors[] = new byte[nVertex << 2];
|
||||
if (f != null) {
|
||||
Calculator.log("Graph3d update");
|
||||
float sizeX = maxX - minX;
|
||||
float sizeY = maxY - minY;
|
||||
float stepX = sizeX / (N-1);
|
||||
float stepY = sizeY / (N-1);
|
||||
int pos = 0;
|
||||
double sum = 0;
|
||||
float y = minY;
|
||||
float x = minX - stepX;
|
||||
int nRealPoints = 0;
|
||||
for (int i = 0; i < N; i++, y+=stepY) {
|
||||
float xinc = (i & 1) == 0 ? stepX : -stepX;
|
||||
x += xinc;
|
||||
for (int j = 0; j < N; ++j, x+=xinc, pos+=3) {
|
||||
float z = (float) f.eval(x, y);
|
||||
vertices[pos] = x;
|
||||
vertices[pos+1] = y;
|
||||
vertices[pos+2] = z;
|
||||
if (z == z) { // not NAN
|
||||
sum += z * z;
|
||||
++nRealPoints;
|
||||
}
|
||||
}
|
||||
}
|
||||
float maxAbs = (float) Math.sqrt(sum / nRealPoints);
|
||||
maxAbs *= .9f;
|
||||
maxAbs = Math.min(maxAbs, 15);
|
||||
maxAbs = Math.max(maxAbs, .001f);
|
||||
|
||||
final int limitColor = N*N*4;
|
||||
for (int i = 0, j = 2; i < limitColor; i+=4, j+=3) {
|
||||
float z = vertices[j];
|
||||
if (z == z) {
|
||||
final float a = z / maxAbs;
|
||||
final float abs = a < 0 ? -a : a;
|
||||
colors[i] = floatToByte(a);
|
||||
colors[i+1] = floatToByte(1-abs*.3f);
|
||||
colors[i+2] = floatToByte(-a);
|
||||
colors[i+3] = (byte) 255;
|
||||
} else {
|
||||
vertices[j] = 0;
|
||||
z = 0;
|
||||
colors[i] = 0;
|
||||
colors[i+1] = 0;
|
||||
colors[i+2] = 0;
|
||||
colors[i+3] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
int base = N*N*3;
|
||||
int colorBase = N*N*4;
|
||||
int p = base;
|
||||
final int baseSize = 2;
|
||||
for (int i = -baseSize; i <= baseSize; i+=2*baseSize) {
|
||||
vertices[p] = i; vertices[p+1] = -baseSize; vertices[p+2] = 0;
|
||||
p += 3;
|
||||
vertices[p] = i; vertices[p+1] = baseSize; vertices[p+2] = 0;
|
||||
p += 3;
|
||||
vertices[p] = -baseSize; vertices[p+1] = i; vertices[p+2] = 0;
|
||||
p += 3;
|
||||
vertices[p] = baseSize; vertices[p+1] = i; vertices[p+2] = 0;
|
||||
p += 3;
|
||||
}
|
||||
for (int i = colorBase; i < colorBase+8*4; i += 4) {
|
||||
colors[i] = 0;
|
||||
colors[i+1] = 0;
|
||||
colors[i+2] = (byte) 255;
|
||||
colors[i+3] = (byte) 255;
|
||||
}
|
||||
base += 8*3;
|
||||
colorBase += 8*4;
|
||||
|
||||
final float unit = 2;
|
||||
final float axis[] = {
|
||||
0, 0, 0,
|
||||
unit, 0, 0,
|
||||
0, 0, 0,
|
||||
0, unit, 0,
|
||||
0, 0, 0,
|
||||
0, 0, unit,
|
||||
};
|
||||
System.arraycopy(axis, 0, vertices, base, 6*3);
|
||||
for (int i = colorBase; i < colorBase+6*4; i+=4) {
|
||||
colors[i] = (byte) 255;
|
||||
colors[i+1] = (byte) 255;
|
||||
colors[i+2] = (byte) 255;
|
||||
colors[i+3] = (byte) 255;
|
||||
}
|
||||
base += 6*3;
|
||||
colorBase += 6*4;
|
||||
|
||||
p = base;
|
||||
final float tick = .03f;
|
||||
final float offset = .01f;
|
||||
for (int i = 1; i <= NTICK; ++i) {
|
||||
vertices[p] = i-tick;
|
||||
vertices[p+1] = -offset;
|
||||
vertices[p+2] = -offset;
|
||||
|
||||
vertices[p+3] = i+tick;
|
||||
vertices[p+4] = offset;
|
||||
vertices[p+5] = offset;
|
||||
p += 6;
|
||||
|
||||
vertices[p] = -offset;
|
||||
vertices[p+1] = i-tick;
|
||||
vertices[p+2] = -offset;
|
||||
|
||||
vertices[p+3] = offset;
|
||||
vertices[p+4] = i+tick;
|
||||
vertices[p+5] = offset;
|
||||
p += 6;
|
||||
|
||||
vertices[p] = -offset;
|
||||
vertices[p+1] = -offset;
|
||||
vertices[p+2] = i-tick;
|
||||
|
||||
vertices[p+3] = offset;
|
||||
vertices[p+4] = offset;
|
||||
vertices[p+5] = i+tick;
|
||||
p += 6;
|
||||
|
||||
}
|
||||
for (int i = colorBase+NTICK*6*4-1; i >= colorBase; --i) {
|
||||
colors[i] = (byte) 255;
|
||||
}
|
||||
|
||||
vertexBuf = buildBuffer(vertices);
|
||||
colorBuf = buildBuffer(colors);
|
||||
|
||||
if (useVBO) {
|
||||
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vertexVbo);
|
||||
gl.glBufferData(GL11.GL_ARRAY_BUFFER, vertexBuf.capacity()*4, vertexBuf, GL11.GL_STATIC_DRAW);
|
||||
vertexBuf = null;
|
||||
|
||||
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, colorVbo);
|
||||
gl.glBufferData(GL11.GL_ARRAY_BUFFER, colorBuf.capacity(), colorBuf, GL11.GL_STATIC_DRAW);
|
||||
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0);
|
||||
colorBuf = null;
|
||||
|
||||
gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, vertexElementVbo);
|
||||
gl.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, verticeIdx.capacity()*2, verticeIdx, GL11.GL_STATIC_DRAW);
|
||||
gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private byte floatToByte(float v) {
|
||||
return (byte) (v <= 0 ? 0 : v >= 1 ? 255 : (int)(v*255));
|
||||
}
|
||||
|
||||
public void draw(GL11 gl) {
|
||||
if (useVBO) {
|
||||
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vertexVbo);
|
||||
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, 0);
|
||||
|
||||
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, colorVbo);
|
||||
gl.glColorPointer(4, GL10.GL_UNSIGNED_BYTE, 0, 0);
|
||||
|
||||
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0);
|
||||
// gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, N*N);
|
||||
|
||||
gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, vertexElementVbo);
|
||||
gl.glDrawElements(GL10.GL_LINE_STRIP, N*N, GL10.GL_UNSIGNED_SHORT, 0);
|
||||
gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
} else {
|
||||
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuf);
|
||||
gl.glColorPointer(4, GL10.GL_UNSIGNED_BYTE, 0, colorBuf);
|
||||
gl.glDrawElements(GL10.GL_LINE_STRIP, N*N, GL10.GL_UNSIGNED_SHORT, verticeIdx);
|
||||
}
|
||||
final int N2 = N*N;
|
||||
gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, N2);
|
||||
gl.glDrawArrays(GL10.GL_LINES, N2, nVertex - N2);
|
||||
}
|
||||
}
|
249
android-app/src/main/java/arity/calculator/Graph3dView.java
Executable file
249
android-app/src/main/java/arity/calculator/Graph3dView.java
Executable file
@ -0,0 +1,249 @@
|
||||
// Copyright (C) 2009 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
import android.content.Context;
|
||||
import android.opengl.Matrix;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.ZoomButtonsController;
|
||||
import org.javia.arity.Function;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
|
||||
public class Graph3dView extends GLView implements
|
||||
GraphView,
|
||||
ZoomButtonsController.OnZoomListener,
|
||||
TouchHandler.TouchHandlerInterface {
|
||||
|
||||
private float lastTouchX, lastTouchY;
|
||||
private TouchHandler touchHandler;
|
||||
private ZoomButtonsController zoomController = new ZoomButtonsController(this);
|
||||
private float zoomLevel = 1, targetZoom, zoomStep = 0, currentZoom;
|
||||
private FPS fps = new FPS();
|
||||
private Graph3d graph;
|
||||
|
||||
public Graph3dView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public Graph3dView(Context context) {
|
||||
super(context);
|
||||
touchHandler = new TouchHandler(this);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
startLooping();
|
||||
zoomController.setOnZoomListener(this);
|
||||
|
||||
Matrix.setIdentityM(matrix1, 0);
|
||||
Matrix.rotateM(matrix1, 0, -75, 1, 0, 0);
|
||||
}
|
||||
|
||||
public void onVisibilityChanged(boolean visible) {
|
||||
}
|
||||
|
||||
public void onZoom(boolean zoomIn) {
|
||||
boolean changed = false;
|
||||
if (zoomIn) {
|
||||
if (canZoomIn(zoomLevel)) {
|
||||
targetZoom = zoomLevel * .625f;
|
||||
zoomStep = -zoomLevel / 40;
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
if (canZoomOut(zoomLevel)) {
|
||||
targetZoom = zoomLevel * 1.6f;
|
||||
zoomStep = zoomLevel / 20;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
zoomController.setZoomInEnabled(canZoomIn(targetZoom));
|
||||
zoomController.setZoomOutEnabled(canZoomOut(targetZoom));
|
||||
if (!shouldRotate()) {
|
||||
setRotation(0, 0);
|
||||
}
|
||||
startLooping();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void glDraw() {
|
||||
if ((zoomStep < 0 && zoomLevel > targetZoom) ||
|
||||
(zoomStep > 0 && zoomLevel < targetZoom)) {
|
||||
zoomLevel += zoomStep;
|
||||
} else if (zoomStep != 0) {
|
||||
zoomStep = 0;
|
||||
zoomLevel = targetZoom;
|
||||
isDirty = true;
|
||||
if (!shouldRotate()) {
|
||||
stopLooping();
|
||||
}
|
||||
}
|
||||
super.glDraw();
|
||||
}
|
||||
|
||||
private boolean canZoomIn(float zoom) {
|
||||
return zoom > .2f;
|
||||
}
|
||||
|
||||
private boolean canZoomOut(float zoom) {
|
||||
return zoom < 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
zoomController.setVisible(false);
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
public void onTouchDown(float x, float y) {
|
||||
zoomController.setVisible(true);
|
||||
stopLooping();
|
||||
lastTouchX = x;
|
||||
lastTouchY = y;
|
||||
}
|
||||
|
||||
public void onTouchMove(float x, float y) {
|
||||
float deltaX = x - lastTouchX;
|
||||
float deltaY = y - lastTouchY;
|
||||
if (deltaX > 1 || deltaX < -1 || deltaY > 1 || deltaY < -1) {
|
||||
setRotation(deltaX, deltaY);
|
||||
glDraw();
|
||||
lastTouchX = x;
|
||||
lastTouchY = y;
|
||||
}
|
||||
}
|
||||
|
||||
public void onTouchUp(float x, float y) {
|
||||
float vx = touchHandler.velocityTracker.getXVelocity();
|
||||
float vy = touchHandler.velocityTracker.getYVelocity();
|
||||
// Calculator.log("velocity " + vx + ' ' + vy);
|
||||
setRotation(vx / 100, vy / 100);
|
||||
if (shouldRotate()) {
|
||||
startLooping();
|
||||
}
|
||||
}
|
||||
|
||||
public void onTouchZoomDown(float x1, float y1, float x2, float y2) {
|
||||
|
||||
}
|
||||
|
||||
public void onTouchZoomMove(float x1, float y1, float x2, float y2) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
return touchHandler != null ? touchHandler.onTouchEvent(event) : super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
// ----
|
||||
|
||||
private float[] matrix1 = new float[16], matrix2 = new float[16], matrix3 = new float[16];
|
||||
private float angleX, angleY;
|
||||
private boolean isDirty;
|
||||
private Function function;
|
||||
private static final float DISTANCE = 15f;
|
||||
|
||||
void setRotation(float x, float y) {
|
||||
angleX = x;
|
||||
angleY = y;
|
||||
}
|
||||
|
||||
boolean shouldRotate() {
|
||||
final float limit = .5f;
|
||||
return angleX < -limit || angleX > limit || angleY < -limit || angleY > limit;
|
||||
}
|
||||
|
||||
public void setFunction(Function f) {
|
||||
function = f;
|
||||
zoomLevel = 1;
|
||||
isDirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceCreated(GL10 gl, int width, int height) {
|
||||
gl.glDisable(GL10.GL_DITHER);
|
||||
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
|
||||
gl.glClearColor(0, 0, 0, 1);
|
||||
gl.glShadeModel(Calculator.useHighQuality3d ? GL10.GL_SMOOTH : GL10.GL_FLAT);
|
||||
gl.glDisable(GL10.GL_LIGHTING);
|
||||
graph = new Graph3d((GL11) gl);
|
||||
isDirty = true;
|
||||
angleX = .5f;
|
||||
angleY = 0;
|
||||
|
||||
gl.glViewport(0, 0, width, height);
|
||||
initFrustum(gl, DISTANCE * zoomLevel);
|
||||
currentZoom = zoomLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawFrame(GL10 gl10) {
|
||||
GL11 gl = (GL11) gl10;
|
||||
if (currentZoom != zoomLevel) {
|
||||
initFrustum(gl, DISTANCE * zoomLevel);
|
||||
currentZoom = zoomLevel;
|
||||
}
|
||||
if (isDirty) {
|
||||
graph.update(gl, function, zoomLevel);
|
||||
isDirty = false;
|
||||
}
|
||||
|
||||
if (fps.incFrame()) {
|
||||
Calculator.log("f/s " + fps.getValue());
|
||||
}
|
||||
|
||||
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
|
||||
gl.glMatrixMode(GL10.GL_MODELVIEW);
|
||||
gl.glLoadIdentity();
|
||||
gl.glTranslatef(0, 0, -DISTANCE * zoomLevel);
|
||||
|
||||
Matrix.setIdentityM(matrix2, 0);
|
||||
float ax = Math.abs(angleX);
|
||||
float ay = Math.abs(angleY);
|
||||
if (ay * 3 < ax) {
|
||||
Matrix.rotateM(matrix2, 0, angleX, 0, 1, 0);
|
||||
} else if (ax * 3 < ay) {
|
||||
Matrix.rotateM(matrix2, 0, angleY, 1, 0, 0);
|
||||
} else {
|
||||
if (ax > ay) {
|
||||
Matrix.rotateM(matrix2, 0, angleX, 0, 1, 0);
|
||||
Matrix.rotateM(matrix2, 0, angleY, 1, 0, 0);
|
||||
} else {
|
||||
Matrix.rotateM(matrix2, 0, angleY, 1, 0, 0);
|
||||
Matrix.rotateM(matrix2, 0, angleX, 0, 1, 0);
|
||||
}
|
||||
}
|
||||
Matrix.multiplyMM(matrix3, 0, matrix2, 0, matrix1, 0);
|
||||
gl.glMultMatrixf(matrix3, 0);
|
||||
System.arraycopy(matrix3, 0, matrix1, 0, 16);
|
||||
graph.draw(gl);
|
||||
}
|
||||
|
||||
private void initFrustum(GL10 gl, float distance) {
|
||||
gl.glMatrixMode(GL10.GL_PROJECTION);
|
||||
gl.glLoadIdentity();
|
||||
float near = distance * (1 / 3f);
|
||||
float far = distance * 3f;
|
||||
float dimen = near / 5f;
|
||||
float h = dimen * height / width;
|
||||
gl.glFrustumf(-dimen, dimen, -h, h, near, far);
|
||||
gl.glMatrixMode(GL10.GL_MODELVIEW);
|
||||
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
|
||||
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
|
||||
}
|
||||
|
||||
private void printMatrix(float[] m, String name) {
|
||||
StringBuffer b = new StringBuffer();
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
b.append(m[i]).append(' ');
|
||||
}
|
||||
Calculator.log(name + ' ' + b.toString());
|
||||
}
|
||||
}
|
30
android-app/src/main/java/arity/calculator/GraphView.java
Executable file
30
android-app/src/main/java/arity/calculator/GraphView.java
Executable file
@ -0,0 +1,30 @@
|
||||
// Copyright (C) 2009-2010 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
import org.javia.arity.Function;
|
||||
|
||||
public interface GraphView {
|
||||
|
||||
static final String SCREENSHOT_DIR = "/screenshots";
|
||||
|
||||
public void setFunction(Function f);
|
||||
|
||||
public void onPause();
|
||||
public void onResume();
|
||||
|
||||
public String captureScreenshot();
|
||||
|
||||
void setId(int id);
|
||||
|
||||
/*
|
||||
**********************************************************************
|
||||
*
|
||||
* CUSTOMIZATION
|
||||
*
|
||||
**********************************************************************
|
||||
*/
|
||||
|
||||
/* void setBgColor(int color);
|
||||
void setAxisColor(int color);*/
|
||||
}
|
16
android-app/src/main/java/arity/calculator/Help.java
Executable file
16
android-app/src/main/java/arity/calculator/Help.java
Executable file
@ -0,0 +1,16 @@
|
||||
// Copyright (C) 2009 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.webkit.WebView;
|
||||
|
||||
public class Help extends Activity {
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
WebView view = new WebView(this);
|
||||
setContentView(view);
|
||||
view.loadUrl("file:///android_asset/help.html");
|
||||
}
|
||||
}
|
112
android-app/src/main/java/arity/calculator/History.java
Executable file
112
android-app/src/main/java/arity/calculator/History.java
Executable file
@ -0,0 +1,112 @@
|
||||
// Copyright (C) 2009 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
import android.content.Context;
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
class History extends FileHandler {
|
||||
private static final int SIZE_LIMIT = 30;
|
||||
ArrayList<HistoryEntry> entries = new ArrayList<HistoryEntry>();
|
||||
int pos;
|
||||
HistoryEntry aboveTop = new HistoryEntry("", "");
|
||||
|
||||
|
||||
History(Context context) {
|
||||
super(context, "history", 1);
|
||||
load();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
entries.clear();
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
int size() {
|
||||
return entries.size();
|
||||
}
|
||||
|
||||
void doRead(DataInputStream is) throws IOException {
|
||||
aboveTop = new HistoryEntry(is);
|
||||
int loadSize = is.readInt();
|
||||
for (int i = 0; i < loadSize; ++i) {
|
||||
entries.add(new HistoryEntry(is));
|
||||
}
|
||||
pos = entries.size();
|
||||
}
|
||||
|
||||
void doWrite(DataOutputStream os) throws IOException {
|
||||
aboveTop.save(os);
|
||||
os.writeInt(entries.size());
|
||||
for (HistoryEntry entry : entries) {
|
||||
entry.save(os);
|
||||
}
|
||||
}
|
||||
|
||||
private HistoryEntry currentEntry() {
|
||||
if (pos < entries.size()) {
|
||||
return entries.get(pos);
|
||||
} else {
|
||||
return aboveTop;
|
||||
}
|
||||
}
|
||||
|
||||
int getListPos() {
|
||||
return entries.size() - 1 - pos;
|
||||
}
|
||||
|
||||
boolean onEnter(String text, String result) {
|
||||
if (result == null) {
|
||||
result = "";
|
||||
}
|
||||
currentEntry().onEnter();
|
||||
pos = entries.size();
|
||||
if (text.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
if (entries.size() > 0) {
|
||||
HistoryEntry top = entries.get(entries.size()-1);
|
||||
if (text.equals(top.line) && result.equals(top.result)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (entries.size() > SIZE_LIMIT) {
|
||||
entries.remove(0);
|
||||
}
|
||||
entries.add(new HistoryEntry(text, result));
|
||||
pos = entries.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
void moveToPos(int listPos, String text) {
|
||||
currentEntry().editLine = text;
|
||||
pos = entries.size() - listPos - 1;
|
||||
}
|
||||
|
||||
void updateEdited(String text) {
|
||||
currentEntry().editLine = text;
|
||||
}
|
||||
|
||||
boolean moveUp(String text) {
|
||||
updateEdited(text);
|
||||
if (pos >= entries.size()) {
|
||||
return false;
|
||||
}
|
||||
++pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean moveDown(String text) {
|
||||
updateEdited(text);
|
||||
if (pos <= 0) {
|
||||
return false;
|
||||
}
|
||||
--pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
String getText() {
|
||||
return currentEntry().editLine;
|
||||
}
|
||||
}
|
34
android-app/src/main/java/arity/calculator/HistoryEntry.java
Executable file
34
android-app/src/main/java/arity/calculator/HistoryEntry.java
Executable file
@ -0,0 +1,34 @@
|
||||
// Copyright (C) 2009 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
class HistoryEntry {
|
||||
String line, editLine, result;
|
||||
|
||||
HistoryEntry(DataInputStream is) throws IOException {
|
||||
line = is.readUTF();
|
||||
editLine = is.readUTF();
|
||||
if (editLine.length() == 0) {
|
||||
editLine = line;
|
||||
}
|
||||
result = is.readUTF();
|
||||
}
|
||||
|
||||
HistoryEntry(String text, String result) {
|
||||
line = text;
|
||||
editLine = text;
|
||||
this.result = result == null ? "" : result;
|
||||
}
|
||||
|
||||
void save(DataOutputStream os) throws IOException {
|
||||
os.writeUTF(line);
|
||||
os.writeUTF(editLine.equals(line) ? "" : editLine);
|
||||
os.writeUTF(result);
|
||||
}
|
||||
|
||||
void onEnter() {
|
||||
editLine = line;
|
||||
}
|
||||
}
|
21
android-app/src/main/java/arity/calculator/MotionEventWrap.java
Executable file
21
android-app/src/main/java/arity/calculator/MotionEventWrap.java
Executable file
@ -0,0 +1,21 @@
|
||||
// Copyright (C) 2010 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
class MotionEventWrap {
|
||||
private static final boolean IS_API_5 = Util.SDK_VERSION >= 5;
|
||||
|
||||
static int getPointerCount(MotionEvent event) {
|
||||
return IS_API_5 ? MotionEventWrapNew.getPointerCount(event) : 1;
|
||||
}
|
||||
|
||||
static float getX(MotionEvent event, int idx) {
|
||||
return IS_API_5 ? MotionEventWrapNew.getX(event, idx) : 0;
|
||||
}
|
||||
|
||||
static float getY(MotionEvent event, int idx) {
|
||||
return IS_API_5 ? MotionEventWrapNew.getX(event, idx) : 0;
|
||||
}
|
||||
}
|
19
android-app/src/main/java/arity/calculator/MotionEventWrapNew.java
Executable file
19
android-app/src/main/java/arity/calculator/MotionEventWrapNew.java
Executable file
@ -0,0 +1,19 @@
|
||||
// Copyright (C) 2010 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
class MotionEventWrapNew {
|
||||
static int getPointerCount(MotionEvent event) {
|
||||
return event.getPointerCount();
|
||||
}
|
||||
|
||||
static float getX(MotionEvent event, int idx) {
|
||||
return event.getX(idx);
|
||||
}
|
||||
|
||||
static float getY(MotionEvent event, int idx) {
|
||||
return event.getY(idx);
|
||||
}
|
||||
}
|
69
android-app/src/main/java/arity/calculator/ShowGraph.java
Executable file
69
android-app/src/main/java/arity/calculator/ShowGraph.java
Executable file
@ -0,0 +1,69 @@
|
||||
// Copyright (C) 2009 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import org.javia.arity.Function;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ShowGraph extends Activity {
|
||||
|
||||
private GraphView view;
|
||||
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
ArrayList<Function> funcs = Calculator.graphedFunction;
|
||||
if (funcs == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
int size = funcs.size();
|
||||
if (size == 1) {
|
||||
Function f = funcs.get(0);
|
||||
view = f.arity() == 1 ? new Graph2dView(this) : new Graph3dView(this);
|
||||
view.setFunction(f);
|
||||
} else {
|
||||
view = new Graph2dView(this);
|
||||
((Graph2dView) view).setFunctions(funcs);
|
||||
}
|
||||
setContentView((View) view);
|
||||
}
|
||||
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
view.onPause();
|
||||
}
|
||||
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
view.onResume();
|
||||
}
|
||||
|
||||
/* public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
(new MenuInflater(this)).inflate(R.menu.graph, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
super.onOptionsItemSelected(item);
|
||||
switch (item.getItemId()) {
|
||||
case R.id.capture_screenshot:
|
||||
String fileName = view.captureScreenshot();
|
||||
if (fileName != null) {
|
||||
Toast.makeText(this, "screenshot saved as \n" + fileName, Toast.LENGTH_LONG).show();
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setDataAndType(Uri.fromFile(new File(fileName)), "image/png");
|
||||
startActivity(i);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}*/
|
||||
}
|
79
android-app/src/main/java/arity/calculator/TouchHandler.java
Executable file
79
android-app/src/main/java/arity/calculator/TouchHandler.java
Executable file
@ -0,0 +1,79 @@
|
||||
// Copyright (C) 2009-2010 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
|
||||
class TouchHandler {
|
||||
static interface TouchHandlerInterface {
|
||||
void onTouchDown(float x, float y);
|
||||
void onTouchMove(float x, float y);
|
||||
void onTouchUp(float x, float y);
|
||||
void onTouchZoomDown(float x1, float y1, float x2, float y2);
|
||||
void onTouchZoomMove(float x1, float y1, float x2, float y2);
|
||||
}
|
||||
|
||||
VelocityTracker velocityTracker = VelocityTracker.obtain();
|
||||
|
||||
private boolean isAfterZoom;
|
||||
private TouchHandlerInterface listener;
|
||||
|
||||
TouchHandler(TouchHandlerInterface listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
// Calculator.log("touch " + event + ' ' + event.getPointerCount() + event.getPointerId(0));
|
||||
|
||||
int fullAction = event.getAction();
|
||||
int action = fullAction & MotionEvent.ACTION_MASK;
|
||||
int pointer = (fullAction & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
|
||||
float x = event.getX();
|
||||
float y = event.getY();
|
||||
int nPoints = MotionEventWrap.getPointerCount(event);
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
isAfterZoom = false;
|
||||
velocityTracker.clear();
|
||||
velocityTracker.addMovement(event);
|
||||
listener.onTouchDown(x, y);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (nPoints == 1) {
|
||||
if (isAfterZoom) {
|
||||
velocityTracker.clear();
|
||||
listener.onTouchDown(x, y);
|
||||
isAfterZoom = false;
|
||||
}
|
||||
velocityTracker.addMovement(event);
|
||||
listener.onTouchMove(x, y);
|
||||
} else if (nPoints == 2) {
|
||||
listener.onTouchZoomMove(x, y, MotionEventWrap.getX(event, 1), MotionEventWrap.getY(event, 1));
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
velocityTracker.addMovement(event);
|
||||
velocityTracker.computeCurrentVelocity(1000);
|
||||
listener.onTouchUp(x, y);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
if (nPoints == 2) {
|
||||
listener.onTouchZoomDown(x, y, MotionEventWrap.getX(event, 1), MotionEventWrap.getY(event, 1));
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
if (nPoints == 2) {
|
||||
isAfterZoom = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
70
android-app/src/main/java/arity/calculator/Util.java
Executable file
70
android-app/src/main/java/arity/calculator/Util.java
Executable file
@ -0,0 +1,70 @@
|
||||
// Copyright (C) 2009-2010 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Environment;
|
||||
import android.os.Build;
|
||||
|
||||
import java.nio.ShortBuffer;
|
||||
import java.io.*;
|
||||
|
||||
class Util {
|
||||
public static final int SDK_VERSION = getSdkVersion();
|
||||
|
||||
private static int getSdkVersion() {
|
||||
try {
|
||||
return Integer.parseInt(Build.VERSION.SDK);
|
||||
} catch (NumberFormatException e) {
|
||||
Calculator.log("invalid SDK " + Build.VERSION.SDK);
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
static String saveBitmap(Bitmap bitmap, String dir, String baseName) {
|
||||
try {
|
||||
File sdcard = Environment.getExternalStorageDirectory();
|
||||
File pictureDir = new File(sdcard, dir);
|
||||
pictureDir.mkdirs();
|
||||
File f = null;
|
||||
for (int i = 1; i < 200; ++i) {
|
||||
String name = baseName + i + ".png";
|
||||
f = new File(pictureDir, name);
|
||||
if (!f.exists()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!f.exists()) {
|
||||
String name = f.getAbsolutePath();
|
||||
FileOutputStream fos = new FileOutputStream(name);
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
|
||||
fos.flush();
|
||||
fos.close();
|
||||
return name;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Calculator.log("exception saving screenshot: " + e);
|
||||
} finally {
|
||||
/*
|
||||
if (fos != null) {
|
||||
fos.close();
|
||||
}
|
||||
*/
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static void bitmapBGRtoRGB(Bitmap bitmap, int width, int height) {
|
||||
int size = width * height;
|
||||
short data[] = new short[size];
|
||||
ShortBuffer buf = ShortBuffer.wrap(data);
|
||||
bitmap.copyPixelsToBuffer(buf);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
//BGR-565 to RGB-565
|
||||
short v = data[i];
|
||||
data[i] = (short) (((v&0x1f) << 11) | (v&0x7e0) | ((v&0xf800) >> 11));
|
||||
}
|
||||
buf.rewind();
|
||||
bitmap.copyPixelsFromBuffer(buf);
|
||||
}
|
||||
}
|
53
android-app/src/main/java/arity/calculator/ZoomTracker.java
Executable file
53
android-app/src/main/java/arity/calculator/ZoomTracker.java
Executable file
@ -0,0 +1,53 @@
|
||||
// Copyright (C) 2010 Mihai Preda
|
||||
|
||||
package arity.calculator;
|
||||
|
||||
class ZoomTracker {
|
||||
private float sx1, sy1, sx2, sy2;
|
||||
private float initialDist;
|
||||
private float initialValue;
|
||||
|
||||
float value;
|
||||
float moveX, moveY;
|
||||
|
||||
void start(float value, float x1, float y1, float x2, float y2) {
|
||||
sx1 = x1;
|
||||
sy1 = y1;
|
||||
sx2 = x2;
|
||||
sy2 = y2;
|
||||
initialDist = distance(x1, y1, x2, y2);
|
||||
initialValue = value;
|
||||
}
|
||||
|
||||
boolean update(float x1, float y1, float x2, float y2) {
|
||||
final float LIMIT = 1.5f;
|
||||
if (Math.abs(x1 - sx1) < LIMIT && Math.abs(y1 - sy1) < LIMIT &&
|
||||
Math.abs(x2 - sx2) < LIMIT && Math.abs(y2 - sy2) < LIMIT) {
|
||||
return false;
|
||||
}
|
||||
moveX = common(x1, sx1, x2, sx2);
|
||||
moveY = common(y1, sy1, y2, sy2);
|
||||
float dist = distance(x1, y1, x2, y2);
|
||||
value = initialDist / dist * initialValue;
|
||||
sx1 = x1;
|
||||
sx2 = x2;
|
||||
sy1 = y1;
|
||||
sy2 = y2;
|
||||
return true;
|
||||
}
|
||||
|
||||
private float distance(float x1, float y1, float x2, float y2) {
|
||||
final float dx = x1-x2;
|
||||
final float dy = y1-y2;
|
||||
// return (float) Math.sqrt(dx*dx+dy*dy);
|
||||
return Math.max(dx*dx, dy*dy);
|
||||
}
|
||||
|
||||
private float common(float x1, float sx1, float x2, float sx2) {
|
||||
float dx1 = x1 - sx1;
|
||||
float dx2 = x2 - sx2;
|
||||
return (dx1 < 0 && dx2 < 0) ? Math.max(dx1, dx2) :
|
||||
(dx1 > 0 && dx2 > 0) ? Math.min(dx1, dx2):
|
||||
0;
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
public class AndroidCalculatorLogger implements CalculatorLogger {
|
||||
|
||||
@NotNull
|
||||
private static final String TAG = AndroidCalculatorLogger.class.getSimpleName();
|
||||
private static final String TAG = "Calculatorpp";
|
||||
|
||||
@Override
|
||||
public void debug(@Nullable String tag, @NotNull String message) {
|
||||
@ -21,7 +21,7 @@ public class AndroidCalculatorLogger implements CalculatorLogger {
|
||||
|
||||
@NotNull
|
||||
private String getTag(@Nullable String tag) {
|
||||
return tag != null ? tag : TAG;
|
||||
return tag != null ? TAG + "/" + tag : TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -25,6 +25,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.AndroidUtils;
|
||||
import org.solovyev.android.calculator.about.CalculatorFragmentType;
|
||||
import org.solovyev.android.calculator.about.CalculatorReleaseNotesFragment;
|
||||
import org.solovyev.android.calculator.plot.CalculatorPlotActivity;
|
||||
import org.solovyev.android.fragments.FragmentUtils;
|
||||
import org.solovyev.android.prefs.Preference;
|
||||
import org.solovyev.common.equals.EqualsTool;
|
||||
@ -63,7 +64,7 @@ public class CalculatorActivity extends SherlockFragmentActivity implements Shar
|
||||
activityHelper.addTab(this, CalculatorFragmentType.variables, null, R.id.main_second_pane);
|
||||
activityHelper.addTab(this, CalculatorFragmentType.functions, null, R.id.main_second_pane);
|
||||
activityHelper.addTab(this, CalculatorFragmentType.operators, null, R.id.main_second_pane);
|
||||
activityHelper.addTab(this, CalculatorFragmentType.plotter, null, R.id.main_second_pane);
|
||||
activityHelper.addTab(this, CalculatorPlotActivity.getPlotterFragmentType(), null, R.id.main_second_pane);
|
||||
activityHelper.addTab(this, CalculatorFragmentType.faq, null, R.id.main_second_pane);
|
||||
} else {
|
||||
getSupportActionBar().hide();
|
||||
|
@ -23,6 +23,7 @@ import org.solovyev.android.calculator.help.CalculatorHelpActivity;
|
||||
import org.solovyev.android.calculator.history.CalculatorHistoryActivity;
|
||||
import org.solovyev.android.calculator.math.edit.*;
|
||||
import org.solovyev.android.calculator.matrix.CalculatorMatrixActivity;
|
||||
import org.solovyev.android.calculator.plot.AbstractCalculatorPlotFragment;
|
||||
import org.solovyev.android.calculator.plot.CalculatorPlotActivity;
|
||||
import org.solovyev.android.calculator.plot.CalculatorPlotFragment;
|
||||
import org.solovyev.android.calculator.plot.PlotInput;
|
||||
@ -99,10 +100,14 @@ public final class CalculatorActivityLauncher implements CalculatorEventListener
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
public static void plotGraph(@NotNull final Context context, @NotNull Generic generic, @NotNull Constant constant){
|
||||
public static void plotGraph(@NotNull final Context context,
|
||||
@NotNull Generic generic,
|
||||
@NotNull Constant xVariable,
|
||||
@Nullable Constant yVariable){
|
||||
final Intent intent = new Intent();
|
||||
intent.putExtra(ChartFactory.TITLE, context.getString(R.string.c_graph));
|
||||
intent.putExtra(CalculatorPlotFragment.INPUT, new CalculatorPlotFragment.Input(generic.toString(), constant.getName()));
|
||||
final AbstractCalculatorPlotFragment.Input input = new CalculatorPlotFragment.Input(generic.toString(), xVariable.getName(), yVariable == null ? null : yVariable.getName());
|
||||
intent.putExtra(CalculatorPlotFragment.INPUT, input);
|
||||
intent.setClass(context, CalculatorPlotActivity.class);
|
||||
AndroidUtils2.addFlags(intent, false, context);
|
||||
context.startActivity(intent);
|
||||
@ -214,7 +219,7 @@ public final class CalculatorActivityLauncher implements CalculatorEventListener
|
||||
App.getInstance().getUiThreadExecutor().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plotGraph(context, plotInput.getFunction(), plotInput.getConstant());
|
||||
plotGraph(context, plotInput.getFunction(), plotInput.getXVariable(), plotInput.getYVariable());
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
@ -3,6 +3,7 @@ package org.solovyev.android.calculator;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import net.robotmedia.billing.BillingController;
|
||||
@ -65,8 +66,12 @@ public class CalculatorApplication extends android.app.Application implements Sh
|
||||
**********************************************************************
|
||||
*/
|
||||
|
||||
@NotNull
|
||||
private final List<CalculatorEventListener> listeners = new ArrayList<CalculatorEventListener>();
|
||||
|
||||
@NotNull
|
||||
protected final Handler uiHandler = new Handler();
|
||||
|
||||
/*
|
||||
**********************************************************************
|
||||
*
|
||||
@ -186,6 +191,11 @@ public class CalculatorApplication extends android.app.Application implements Sh
|
||||
return new CalculatorFragmentHelperImpl(layoutId, titleResId, listenersOnCreate);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Handler getUiHandler() {
|
||||
return uiHandler;
|
||||
}
|
||||
|
||||
/*
|
||||
**********************************************************************
|
||||
*
|
||||
|
@ -13,6 +13,7 @@ import org.solovyev.android.calculator.math.edit.CalculatorFunctionsFragment;
|
||||
import org.solovyev.android.calculator.math.edit.CalculatorOperatorsFragment;
|
||||
import org.solovyev.android.calculator.math.edit.CalculatorVarsFragment;
|
||||
import org.solovyev.android.calculator.matrix.CalculatorMatrixEditFragment;
|
||||
import org.solovyev.android.calculator.plot.CalculatorArityPlotFragment;
|
||||
import org.solovyev.android.calculator.plot.CalculatorPlotFragment;
|
||||
|
||||
/**
|
||||
@ -31,6 +32,7 @@ public enum CalculatorFragmentType {
|
||||
functions(CalculatorFunctionsFragment.class, R.layout.math_entities_fragment, R.string.c_functions),
|
||||
operators(CalculatorOperatorsFragment.class, R.layout.math_entities_fragment, R.string.c_operators),
|
||||
plotter(CalculatorPlotFragment.class, R.layout.plot_fragment, R.string.c_graph),
|
||||
plotter_2(CalculatorArityPlotFragment.class, R.layout.plot_fragment, R.string.c_graph),
|
||||
about(CalculatorAboutFragment.class, R.layout.about_fragment, R.string.c_about),
|
||||
faq(CalculatorHelpFaqFragment.class, R.layout.help_faq_fragment, R.string.c_faq),
|
||||
hints(CalculatorHelpHintsFragment.class, R.layout.help_hints_fragment, R.string.c_hints),
|
||||
|
@ -0,0 +1,525 @@
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuInflater;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
import jscl.math.Expression;
|
||||
import jscl.math.Generic;
|
||||
import jscl.math.function.Constant;
|
||||
import jscl.text.ParseException;
|
||||
import org.achartengine.renderer.XYMultipleSeriesRenderer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.*;
|
||||
import org.solovyev.android.menu.ActivityMenu;
|
||||
import org.solovyev.android.menu.IdentifiableMenuItem;
|
||||
import org.solovyev.android.menu.ListActivityMenu;
|
||||
import org.solovyev.android.sherlock.menu.SherlockMenuHelper;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/30/12
|
||||
* Time: 3:09 PM
|
||||
*/
|
||||
public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment implements CalculatorEventListener {
|
||||
|
||||
/*
|
||||
**********************************************************************
|
||||
*
|
||||
* CONSTANTS
|
||||
*
|
||||
**********************************************************************
|
||||
*/
|
||||
|
||||
protected static final String TAG = "CalculatorPlotFragment";
|
||||
|
||||
public static final String INPUT = "plotter_input";
|
||||
|
||||
protected static final String PLOT_BOUNDARIES = "plot_boundaries";
|
||||
|
||||
private static final int DEFAULT_MIN_NUMBER = -10;
|
||||
|
||||
private static final int DEFAULT_MAX_NUMBER = 10;
|
||||
|
||||
/*
|
||||
**********************************************************************
|
||||
*
|
||||
* FIELDS
|
||||
*
|
||||
**********************************************************************
|
||||
*/
|
||||
|
||||
@Nullable
|
||||
private Input input;
|
||||
|
||||
private int bgColor;
|
||||
|
||||
// thread for applying UI changes
|
||||
@NotNull
|
||||
private final Handler uiHandler = new Handler();
|
||||
|
||||
@NotNull
|
||||
private PreparedInput preparedInput;
|
||||
|
||||
@NotNull
|
||||
private ActivityMenu<Menu, MenuItem> fragmentMenu = ListActivityMenu.fromResource(R.menu.plot_menu, PlotMenu.class, SherlockMenuHelper.getInstance());
|
||||
|
||||
// thread which calculated data for graph view
|
||||
@NotNull
|
||||
private final Executor plotExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
@NotNull
|
||||
private final CalculatorEventHolder lastEventHolder = new CalculatorEventHolder(CalculatorUtils.createFirstEventDataId());
|
||||
|
||||
|
||||
public AbstractCalculatorPlotFragment() {
|
||||
super(CalculatorApplication.getInstance().createFragmentHelper(R.layout.plot_fragment, R.string.c_graph, false));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final Bundle arguments = getArguments();
|
||||
|
||||
if (arguments != null) {
|
||||
input = (CalculatorPlotFragment.Input) arguments.getSerializable(INPUT);
|
||||
}
|
||||
|
||||
if (input == null) {
|
||||
this.bgColor = getResources().getColor(R.color.cpp_pane_background);
|
||||
} else {
|
||||
this.bgColor = getResources().getColor(android.R.color.transparent);
|
||||
}
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (input == null) {
|
||||
this.preparedInput = prepareInputFromDisplay(Locator.getInstance().getDisplay().getViewState(), savedInstanceState);
|
||||
} else {
|
||||
this.preparedInput = prepareInput(input, true, savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle out) {
|
||||
super.onSaveInstanceState(out);
|
||||
|
||||
final PlotBoundaries plotBoundaries = getPlotBoundaries();
|
||||
if (plotBoundaries != null) {
|
||||
out.putSerializable(PLOT_BOUNDARIES, plotBoundaries);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected abstract PlotBoundaries getPlotBoundaries();
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
createChart(preparedInput);
|
||||
createGraphicalView(getView(), preparedInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCalculatorEvent(@NotNull CalculatorEventData calculatorEventData, @NotNull CalculatorEventType calculatorEventType, @Nullable final Object data) {
|
||||
if (calculatorEventType.isOfType(CalculatorEventType.display_state_changed)) {
|
||||
PreparedInput preparedInput = getPreparedInput();
|
||||
if (!preparedInput.isFromInputArgs()) {
|
||||
|
||||
final CalculatorEventHolder.Result result = this.lastEventHolder.apply(calculatorEventData);
|
||||
if (result.isNewAfter()) {
|
||||
preparedInput = prepareInputFromDisplay(((CalculatorDisplayChangeEventData) data).getNewValue(), null);
|
||||
this.preparedInput = preparedInput;
|
||||
|
||||
|
||||
final PreparedInput finalPreparedInput = preparedInput;
|
||||
getUiHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
if (!finalPreparedInput.isError()) {
|
||||
createChart(finalPreparedInput);
|
||||
|
||||
final View view = getView();
|
||||
if (view != null) {
|
||||
createGraphicalView(view, finalPreparedInput);
|
||||
}
|
||||
} else {
|
||||
onError();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void onError();
|
||||
|
||||
protected abstract void createGraphicalView(@NotNull View view, @NotNull PreparedInput preparedInput);
|
||||
|
||||
protected abstract void createChart(@NotNull PreparedInput preparedInput);
|
||||
|
||||
|
||||
protected double getMaxValue(@Nullable PlotBoundaries plotBoundaries) {
|
||||
return plotBoundaries == null ? DEFAULT_MAX_NUMBER : plotBoundaries.getXMax();
|
||||
}
|
||||
|
||||
protected double getMinValue(@Nullable PlotBoundaries plotBoundaries) {
|
||||
return plotBoundaries == null ? DEFAULT_MIN_NUMBER : plotBoundaries.getXMin();
|
||||
}
|
||||
|
||||
/*
|
||||
**********************************************************************
|
||||
*
|
||||
* GETTERS
|
||||
*
|
||||
**********************************************************************
|
||||
*/
|
||||
|
||||
@NotNull
|
||||
public Handler getUiHandler() {
|
||||
return uiHandler;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PreparedInput getPreparedInput() {
|
||||
return preparedInput;
|
||||
}
|
||||
|
||||
public int getBgColor() {
|
||||
return bgColor;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Executor getPlotExecutor() {
|
||||
return plotExecutor;
|
||||
}
|
||||
|
||||
/*
|
||||
**********************************************************************
|
||||
*
|
||||
* MENU
|
||||
*
|
||||
**********************************************************************
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
|
||||
final FragmentActivity activity = this.getActivity();
|
||||
if (activity != null) {
|
||||
fragmentMenu.onCreateOptionsMenu(activity, menu);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
|
||||
final FragmentActivity activity = this.getActivity();
|
||||
if (activity != null) {
|
||||
fragmentMenu.onPrepareOptionsMenu(activity, menu);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
return super.onOptionsItemSelected(item) || fragmentMenu.onOptionsItemSelected(this.getActivity(), item);
|
||||
}
|
||||
|
||||
/*
|
||||
**********************************************************************
|
||||
*
|
||||
* STATIC
|
||||
*
|
||||
**********************************************************************
|
||||
*/
|
||||
|
||||
@NotNull
|
||||
protected static PreparedInput prepareInputFromDisplay(@NotNull CalculatorDisplayViewState displayState, @Nullable Bundle savedInstanceState) {
|
||||
try {
|
||||
if (displayState.isValid() && displayState.getResult() != null) {
|
||||
final Generic expression = displayState.getResult();
|
||||
if (CalculatorUtils.isPlotPossible(expression, displayState.getOperation())) {
|
||||
final List<Constant> variables = new ArrayList<Constant>(CalculatorUtils.getNotSystemConstants(expression));
|
||||
final Constant xVariable = variables.get(0);
|
||||
|
||||
final Constant yVariable;
|
||||
if ( variables.size() > 1 ) {
|
||||
yVariable = variables.get(1);
|
||||
} else {
|
||||
yVariable = null;
|
||||
}
|
||||
|
||||
final Input input = new Input(expression.toString(), xVariable.getName(), yVariable == null ? null : yVariable.getName());
|
||||
return prepareInput(input, false, savedInstanceState);
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, e.getLocalizedMessage(), e);
|
||||
}
|
||||
|
||||
return PreparedInput.newErrorInstance(false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PreparedInput prepareInput(@NotNull Input input, boolean fromInputArgs, @Nullable Bundle savedInstanceState) {
|
||||
PreparedInput result;
|
||||
|
||||
try {
|
||||
final PreparedExpression preparedExpression = ToJsclTextProcessor.getInstance().process(input.getExpression());
|
||||
final Generic expression = Expression.valueOf(preparedExpression.getExpression());
|
||||
final Constant xVar = new Constant(input.getXVariableName());
|
||||
|
||||
final Constant yVar;
|
||||
if (input.getYVariableName() != null) {
|
||||
yVar = new Constant(input.getYVariableName());
|
||||
} else {
|
||||
yVar = null;
|
||||
}
|
||||
|
||||
PlotBoundaries plotBoundaries = null;
|
||||
if (savedInstanceState != null) {
|
||||
plotBoundaries = (PlotBoundaries) savedInstanceState.getSerializable(PLOT_BOUNDARIES);
|
||||
}
|
||||
|
||||
if ( plotBoundaries == null ) {
|
||||
plotBoundaries = PlotBoundaries.newDefaultInstance();
|
||||
}
|
||||
|
||||
result = PreparedInput.newInstance(input, expression, xVar, yVar, fromInputArgs, plotBoundaries);
|
||||
} catch (ParseException e) {
|
||||
result = PreparedInput.newErrorInstance(fromInputArgs);
|
||||
Locator.getInstance().getNotifier().showMessage(e);
|
||||
} catch (CalculatorParseException e) {
|
||||
result = PreparedInput.newErrorInstance(fromInputArgs);
|
||||
Locator.getInstance().getNotifier().showMessage(e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static enum PlotMenu implements IdentifiableMenuItem<MenuItem> {
|
||||
|
||||
preferences(R.id.menu_plot_settings) {
|
||||
@Override
|
||||
public void onClick(@NotNull MenuItem data, @NotNull Context context) {
|
||||
context.startActivity(new Intent(context, CalculatorPlotPreferenceActivity.class));
|
||||
}
|
||||
};
|
||||
|
||||
private final int itemId;
|
||||
|
||||
private PlotMenu(int itemId) {
|
||||
this.itemId = itemId;
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Integer getItemId() {
|
||||
return itemId;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class PlotBoundaries implements Serializable {
|
||||
|
||||
private double xMin;
|
||||
private double xMax;
|
||||
private double yMin;
|
||||
private double yMax;
|
||||
|
||||
public PlotBoundaries() {
|
||||
}
|
||||
|
||||
public PlotBoundaries(@NotNull XYMultipleSeriesRenderer renderer) {
|
||||
this.xMin = renderer.getXAxisMin();
|
||||
this.yMin = renderer.getYAxisMin();
|
||||
this.xMax = renderer.getXAxisMax();
|
||||
this.yMax = renderer.getYAxisMax();
|
||||
}
|
||||
|
||||
public double getXMin() {
|
||||
return xMin;
|
||||
}
|
||||
|
||||
public double getXMax() {
|
||||
return xMax;
|
||||
}
|
||||
|
||||
public double getYMin() {
|
||||
return yMin;
|
||||
}
|
||||
|
||||
public double getYMax() {
|
||||
return yMax;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PlotBoundaries{" +
|
||||
"yMax=" + yMax +
|
||||
", yMin=" + yMin +
|
||||
", xMax=" + xMax +
|
||||
", xMin=" + xMin +
|
||||
'}';
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static PlotBoundaries newDefaultInstance() {
|
||||
PlotBoundaries plotBoundaries = new PlotBoundaries();
|
||||
plotBoundaries.xMin = DEFAULT_MIN_NUMBER;
|
||||
plotBoundaries.yMin = DEFAULT_MIN_NUMBER;
|
||||
plotBoundaries.xMax = DEFAULT_MAX_NUMBER;
|
||||
plotBoundaries.yMax = DEFAULT_MAX_NUMBER;
|
||||
return plotBoundaries;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PreparedInput {
|
||||
|
||||
@Nullable
|
||||
private Input input;
|
||||
|
||||
@Nullable
|
||||
private Generic expression;
|
||||
|
||||
@Nullable
|
||||
private Constant xVariable;
|
||||
|
||||
@Nullable
|
||||
private Constant yVariable;
|
||||
|
||||
private boolean fromInputArgs;
|
||||
|
||||
@NotNull
|
||||
private PlotBoundaries plotBoundaries = PlotBoundaries.newDefaultInstance();
|
||||
|
||||
private PreparedInput() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static PreparedInput newInstance(@NotNull Input input,
|
||||
@NotNull Generic expression,
|
||||
@NotNull Constant xVariable,
|
||||
@Nullable Constant yVariable,
|
||||
boolean fromInputArgs,
|
||||
@NotNull PlotBoundaries plotBoundaries) {
|
||||
final PreparedInput result = new PreparedInput();
|
||||
|
||||
result.input = input;
|
||||
result.expression = expression;
|
||||
result.xVariable = xVariable;
|
||||
result.yVariable = yVariable;
|
||||
result.fromInputArgs = fromInputArgs;
|
||||
result.plotBoundaries = plotBoundaries;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static PreparedInput newErrorInstance(boolean fromInputArgs) {
|
||||
final PreparedInput result = new PreparedInput();
|
||||
|
||||
result.input = null;
|
||||
result.expression = null;
|
||||
result.xVariable = null;
|
||||
result.yVariable = null;
|
||||
result.fromInputArgs = fromInputArgs;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean isFromInputArgs() {
|
||||
return fromInputArgs;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Input getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Generic getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PlotBoundaries getPlotBoundaries() {
|
||||
return plotBoundaries;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Constant getXVariable() {
|
||||
return xVariable;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Constant getYVariable() {
|
||||
return yVariable;
|
||||
}
|
||||
|
||||
public boolean isError() {
|
||||
return input == null || expression == null || xVariable == null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Input implements Serializable {
|
||||
|
||||
@NotNull
|
||||
private String expression;
|
||||
|
||||
@NotNull
|
||||
private String xVariableName;
|
||||
|
||||
@Nullable
|
||||
private String yVariableName;
|
||||
|
||||
public Input(@NotNull String expression,
|
||||
@NotNull String xVariableName,
|
||||
@Nullable String yVariableName) {
|
||||
this.expression = expression;
|
||||
this.xVariableName = xVariableName;
|
||||
this.yVariableName = yVariableName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getXVariableName() {
|
||||
return xVariableName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getYVariableName() {
|
||||
return yVariableName;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import arity.calculator.Graph2dView;
|
||||
import arity.calculator.Graph3dView;
|
||||
import arity.calculator.GraphView;
|
||||
import jscl.math.Generic;
|
||||
import jscl.math.function.Constant;
|
||||
import org.javia.arity.Complex;
|
||||
import org.javia.arity.Function;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/30/12
|
||||
* Time: 4:43 PM
|
||||
*/
|
||||
public class CalculatorArityPlotFragment extends AbstractCalculatorPlotFragment {
|
||||
|
||||
@Nullable
|
||||
private GraphView graphView;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected PlotBoundaries getPlotBoundaries() {
|
||||
if ( graphView != null ) {
|
||||
// todo serso: return plot boundaries
|
||||
return null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createGraphicalView(@NotNull View root, @NotNull PreparedInput preparedInput) {
|
||||
// remove old
|
||||
final ViewGroup graphContainer = (ViewGroup) root.findViewById(R.id.main_fragment_layout);
|
||||
|
||||
if (graphView instanceof View) {
|
||||
graphContainer.removeView((View) graphView);
|
||||
}
|
||||
|
||||
if (!preparedInput.isError()) {
|
||||
final Generic expression = preparedInput.getExpression();
|
||||
final Constant xVariable = preparedInput.getXVariable();
|
||||
final Constant yVariable = preparedInput.getYVariable();
|
||||
|
||||
final int arity = yVariable == null ? 1 : 2;
|
||||
|
||||
final List<Function> functions = new ArrayList<Function>();
|
||||
functions.add(new Function() {
|
||||
@Override
|
||||
public int arity() {
|
||||
return arity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double eval(double x) {
|
||||
return PlotUtils.calculatorExpression(expression, xVariable, x).realPart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double eval(double x, double y) {
|
||||
return PlotUtils.calculatorExpression(expression, xVariable, x, yVariable, y).realPart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Complex eval(Complex x) {
|
||||
jscl.math.numeric.Complex result = PlotUtils.calculatorExpression(expression, xVariable, x.re);
|
||||
return new Complex(result.realPart(), result.imaginaryPart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Complex eval(Complex x, Complex y) {
|
||||
jscl.math.numeric.Complex result = PlotUtils.calculatorExpression(expression, xVariable, x.re, yVariable, y.re);
|
||||
return new Complex(result.realPart(), result.imaginaryPart());
|
||||
}
|
||||
});
|
||||
|
||||
if (functions.size() == 1) {
|
||||
final Function f = functions.get(0);
|
||||
graphView = f.arity() == 1 ? new Graph2dView(getActivity()) : new Graph3dView(getActivity());
|
||||
graphView.setFunction(f);
|
||||
} else {
|
||||
graphView = new Graph2dView(this.getActivity());
|
||||
((Graph2dView) graphView).setFunctions(functions);
|
||||
}
|
||||
|
||||
graphContainer.addView((View) graphView);
|
||||
} else {
|
||||
onError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createChart(@NotNull PreparedInput preparedInput) {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (this.graphView != null) {
|
||||
this.graphView.onResume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onError() {
|
||||
final View root = getView();
|
||||
if (root != null && graphView instanceof View) {
|
||||
final ViewGroup graphContainer = (ViewGroup) root.findViewById(R.id.main_fragment_layout);
|
||||
graphContainer.removeView((View) graphView);
|
||||
}
|
||||
this.graphView = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (this.graphView != null) {
|
||||
this.graphView.onPause();
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package org.solovyev.android.calculator.plot;
|
||||
import android.app.ActionBar;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.CalculatorFragmentActivity;
|
||||
import org.solovyev.android.calculator.R;
|
||||
@ -29,6 +30,11 @@ public class CalculatorPlotActivity extends CalculatorFragmentActivity {
|
||||
}
|
||||
|
||||
getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
|
||||
getActivityHelper().setFragment(this, CalculatorFragmentType.plotter, arguments, R.id.main_layout);
|
||||
getActivityHelper().setFragment(this, getPlotterFragmentType(), arguments, R.id.main_layout);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static CalculatorFragmentType getPlotterFragmentType() {
|
||||
return CalculatorFragmentType.plotter_2;
|
||||
}
|
||||
}
|
||||
|
@ -6,23 +6,12 @@
|
||||
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuInflater;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
import jscl.math.Expression;
|
||||
import jscl.math.Generic;
|
||||
import jscl.math.function.Constant;
|
||||
import jscl.text.ParseException;
|
||||
import org.achartengine.chart.XYChart;
|
||||
import org.achartengine.model.XYMultipleSeriesDataset;
|
||||
import org.achartengine.model.XYSeries;
|
||||
@ -32,47 +21,16 @@ import org.achartengine.tools.ZoomEvent;
|
||||
import org.achartengine.tools.ZoomListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.CalculatorApplication;
|
||||
import org.solovyev.android.calculator.CalculatorDisplayChangeEventData;
|
||||
import org.solovyev.android.calculator.CalculatorDisplayViewState;
|
||||
import org.solovyev.android.calculator.CalculatorEventData;
|
||||
import org.solovyev.android.calculator.CalculatorEventHolder;
|
||||
import org.solovyev.android.calculator.CalculatorEventListener;
|
||||
import org.solovyev.android.calculator.CalculatorEventType;
|
||||
import org.solovyev.android.calculator.CalculatorFragment;
|
||||
import org.solovyev.android.calculator.CalculatorParseException;
|
||||
import org.solovyev.android.calculator.CalculatorPreferences;
|
||||
import org.solovyev.android.calculator.CalculatorUtils;
|
||||
import org.solovyev.android.calculator.Locator;
|
||||
import org.solovyev.android.calculator.PreparedExpression;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.ToJsclTextProcessor;
|
||||
import org.solovyev.android.menu.ActivityMenu;
|
||||
import org.solovyev.android.menu.IdentifiableMenuItem;
|
||||
import org.solovyev.android.menu.ListActivityMenu;
|
||||
import org.solovyev.android.sherlock.menu.SherlockMenuHelper;
|
||||
import org.solovyev.common.MutableObject;
|
||||
import org.solovyev.common.collections.CollectionsUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/1/11
|
||||
* Time: 12:40 AM
|
||||
*/
|
||||
public class CalculatorPlotFragment extends CalculatorFragment implements CalculatorEventListener {
|
||||
|
||||
private static final String TAG = CalculatorPlotFragment.class.getSimpleName();
|
||||
|
||||
private static final int DEFAULT_MIN_NUMBER = -10;
|
||||
|
||||
private static final int DEFAULT_MAX_NUMBER = 10;
|
||||
|
||||
public static final String INPUT = "plotter_input";
|
||||
private static final String PLOT_BOUNDARIES = "plot_boundaries";
|
||||
public class CalculatorPlotFragment extends AbstractCalculatorPlotFragment {
|
||||
|
||||
public static final long EVAL_DELAY_MILLIS = 200;
|
||||
|
||||
@ -85,98 +43,7 @@ public class CalculatorPlotFragment extends CalculatorFragment implements Calcul
|
||||
@Nullable
|
||||
private MyGraphicalView graphicalView;
|
||||
|
||||
// thread which calculated data for graph view
|
||||
@NotNull
|
||||
private final Executor plotExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
// thread for applying UI changes
|
||||
@NotNull
|
||||
private final Handler uiHandler = new Handler();
|
||||
|
||||
@NotNull
|
||||
private PreparedInput preparedInput;
|
||||
|
||||
@Nullable
|
||||
private Input input;
|
||||
|
||||
@NotNull
|
||||
private final CalculatorEventHolder lastEventHolder = new CalculatorEventHolder(CalculatorUtils.createFirstEventDataId());
|
||||
|
||||
private int bgColor;
|
||||
|
||||
@NotNull
|
||||
private ActivityMenu<Menu, MenuItem> fragmentMenu = ListActivityMenu.fromResource(R.menu.plot_menu, PlotMenu.class, SherlockMenuHelper.getInstance());
|
||||
|
||||
public CalculatorPlotFragment() {
|
||||
super(CalculatorApplication.getInstance().createFragmentHelper(R.layout.plot_fragment, R.string.c_graph, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final Bundle arguments = getArguments();
|
||||
|
||||
if (arguments != null) {
|
||||
input = (Input) arguments.getSerializable(INPUT);
|
||||
}
|
||||
|
||||
if (input == null) {
|
||||
this.bgColor = getResources().getColor(R.color.cpp_pane_background);
|
||||
} else {
|
||||
this.bgColor = getResources().getColor(android.R.color.transparent);
|
||||
}
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PreparedInput prepareInputFromDisplay(@NotNull CalculatorDisplayViewState displayState, @Nullable Bundle savedInstanceState) {
|
||||
try {
|
||||
if (displayState.isValid() && displayState.getResult() != null) {
|
||||
final Generic expression = displayState.getResult();
|
||||
if (CalculatorUtils.isPlotPossible(expression, displayState.getOperation())) {
|
||||
final Constant constant = CollectionsUtils.getFirstCollectionElement(CalculatorUtils.getNotSystemConstants(expression));
|
||||
|
||||
final Input input = new Input(expression.toString(), constant.getName());
|
||||
return prepareInput(input, false, savedInstanceState);
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, e.getLocalizedMessage(), e);
|
||||
}
|
||||
|
||||
return PreparedInput.newErrorInstance(false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PreparedInput prepareInput(@NotNull Input input, boolean fromInputArgs, @Nullable Bundle savedInstanceState) {
|
||||
PreparedInput result;
|
||||
|
||||
try {
|
||||
final PreparedExpression preparedExpression = ToJsclTextProcessor.getInstance().process(input.getExpression());
|
||||
final Generic expression = Expression.valueOf(preparedExpression.getExpression());
|
||||
final Constant variable = new Constant(input.getVariableName());
|
||||
|
||||
PlotBoundaries plotBoundaries = null;
|
||||
if ( savedInstanceState != null ) {
|
||||
plotBoundaries = (PlotBoundaries)savedInstanceState.getSerializable(PLOT_BOUNDARIES);
|
||||
}
|
||||
|
||||
result = PreparedInput.newInstance(input, expression, variable, fromInputArgs, plotBoundaries);
|
||||
} catch (ParseException e) {
|
||||
result = PreparedInput.newErrorInstance(fromInputArgs);
|
||||
Locator.getInstance().getNotifier().showMessage(e);
|
||||
} catch (CalculatorParseException e) {
|
||||
result = PreparedInput.newErrorInstance(fromInputArgs);
|
||||
Locator.getInstance().getNotifier().showMessage(e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void createChart() {
|
||||
if (!preparedInput.isError()) {
|
||||
protected void createChart(@NotNull PreparedInput preparedInput) {
|
||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this.getActivity());
|
||||
final Boolean interpolate = CalculatorPreferences.Graph.interpolate.getPreference(preferences);
|
||||
final GraphLineColor realLineColor = CalculatorPreferences.Graph.lineColorReal.getPreference(preferences);
|
||||
@ -184,54 +51,34 @@ public class CalculatorPlotFragment extends CalculatorFragment implements Calcul
|
||||
|
||||
//noinspection ConstantConditions
|
||||
try {
|
||||
this.chart = PlotUtils.prepareChart(getMinValue(null), getMaxValue(null), preparedInput.getExpression(), preparedInput.getVariable(), bgColor, interpolate, realLineColor.getColor(), imagLineColor.getColor());
|
||||
this.chart = PlotUtils.prepareChart(getMinValue(null), getMaxValue(null), preparedInput.getExpression(), preparedInput.getXVariable(), getBgColor(), interpolate, realLineColor.getColor(), imagLineColor.getColor());
|
||||
} catch (ArithmeticException e) {
|
||||
PlotUtils.handleArithmeticException(e, CalculatorPlotFragment.this);
|
||||
}
|
||||
} else {
|
||||
onError();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (input == null) {
|
||||
this.preparedInput = prepareInputFromDisplay(Locator.getInstance().getDisplay().getViewState(), savedInstanceState);
|
||||
} else {
|
||||
this.preparedInput = prepareInput(input, true, savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle out) {
|
||||
super.onSaveInstanceState(out);
|
||||
|
||||
protected PlotBoundaries getPlotBoundaries() {
|
||||
if (chart != null) {
|
||||
out.putSerializable(PLOT_BOUNDARIES, new PlotBoundaries(chart.getRenderer()));
|
||||
return new PlotBoundaries(chart.getRenderer());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
createChart();
|
||||
createGraphicalView(getView(), this.preparedInput.getPlotBoundaries());
|
||||
}
|
||||
|
||||
private void createGraphicalView(@NotNull View root, @Nullable PlotBoundaries plotBoundaries) {
|
||||
protected void createGraphicalView(@NotNull View root, @Nullable PreparedInput preparedInput) {
|
||||
final ViewGroup graphContainer = (ViewGroup) root.findViewById(R.id.main_fragment_layout);
|
||||
|
||||
if (graphicalView != null) {
|
||||
graphContainer.removeView(graphicalView);
|
||||
}
|
||||
|
||||
if (!preparedInput.isError()) {
|
||||
if (!getPreparedInput().isError()) {
|
||||
final XYChart chart = this.chart;
|
||||
assert chart != null;
|
||||
|
||||
final PlotBoundaries plotBoundaries = preparedInput.getPlotBoundaries();
|
||||
double minValue = getMinValue(plotBoundaries);
|
||||
double maxValue = getMaxValue(plotBoundaries);
|
||||
|
||||
@ -249,20 +96,20 @@ public class CalculatorPlotFragment extends CalculatorFragment implements Calcul
|
||||
maxY = Math.max(maxY, series.getMaxY());
|
||||
}
|
||||
|
||||
if (plotBoundaries == null) {
|
||||
if (preparedInput == null) {
|
||||
chart.getRenderer().setXAxisMin(Math.max(minX, minValue));
|
||||
chart.getRenderer().setYAxisMin(Math.max(minY, minValue));
|
||||
chart.getRenderer().setXAxisMax(Math.min(maxX, maxValue));
|
||||
chart.getRenderer().setYAxisMax(Math.min(maxY, maxValue));
|
||||
} else {
|
||||
chart.getRenderer().setXAxisMin(plotBoundaries.xMin);
|
||||
chart.getRenderer().setYAxisMin(plotBoundaries.yMin);
|
||||
chart.getRenderer().setXAxisMax(plotBoundaries.xMax);
|
||||
chart.getRenderer().setYAxisMax(plotBoundaries.yMax);
|
||||
chart.getRenderer().setXAxisMin(plotBoundaries.getXMin());
|
||||
chart.getRenderer().setYAxisMin(plotBoundaries.getYMin());
|
||||
chart.getRenderer().setXAxisMax(plotBoundaries.getXMax());
|
||||
chart.getRenderer().setYAxisMax(plotBoundaries.getYMax());
|
||||
}
|
||||
|
||||
graphicalView = new MyGraphicalView(this.getActivity(), chart);
|
||||
graphicalView.setBackgroundColor(this.bgColor);
|
||||
graphicalView.setBackgroundColor(this.getBgColor());
|
||||
|
||||
graphicalView.addZoomListener(new ZoomListener() {
|
||||
@Override
|
||||
@ -292,14 +139,6 @@ public class CalculatorPlotFragment extends CalculatorFragment implements Calcul
|
||||
|
||||
}
|
||||
|
||||
private double getMaxValue(@Nullable PlotBoundaries plotBoundaries) {
|
||||
return plotBoundaries == null ? DEFAULT_MAX_NUMBER : plotBoundaries.xMax;
|
||||
}
|
||||
|
||||
private double getMinValue(@Nullable PlotBoundaries plotBoundaries) {
|
||||
return plotBoundaries == null ? DEFAULT_MIN_NUMBER : plotBoundaries.xMin;
|
||||
}
|
||||
|
||||
|
||||
private void updateDataSets(@NotNull final XYChart chart) {
|
||||
updateDataSets(chart, EVAL_DELAY_MILLIS);
|
||||
@ -309,10 +148,13 @@ public class CalculatorPlotFragment extends CalculatorFragment implements Calcul
|
||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this.getActivity());
|
||||
final GraphLineColor imagLineColor = CalculatorPreferences.Graph.lineColorImag.getPreference(preferences);
|
||||
|
||||
final Generic expression = preparedInput.getExpression();
|
||||
final Constant variable = preparedInput.getVariable();
|
||||
final PreparedInput preparedInput = getPreparedInput();
|
||||
|
||||
if (expression != null && variable != null) {
|
||||
final Generic expression = preparedInput.getExpression();
|
||||
final Constant variable = preparedInput.getXVariable();
|
||||
final MyGraphicalView graphicalView = this.graphicalView;
|
||||
|
||||
if (expression != null && variable != null && graphicalView != null) {
|
||||
pendingOperation.setObject(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -321,7 +163,7 @@ public class CalculatorPlotFragment extends CalculatorFragment implements Calcul
|
||||
//lock all operations with history
|
||||
if (pendingOperation.getObject() == this) {
|
||||
|
||||
plotExecutor.execute(new Runnable() {
|
||||
getPlotExecutor().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final XYMultipleSeriesRenderer dr = chart.getRenderer();
|
||||
@ -349,7 +191,7 @@ public class CalculatorPlotFragment extends CalculatorFragment implements Calcul
|
||||
PlotUtils.handleArithmeticException(e, CalculatorPlotFragment.this);
|
||||
}
|
||||
|
||||
uiHandler.post(new Runnable() {
|
||||
getUiHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
graphicalView.repaint();
|
||||
@ -366,37 +208,12 @@ public class CalculatorPlotFragment extends CalculatorFragment implements Calcul
|
||||
}
|
||||
|
||||
|
||||
uiHandler.postDelayed(pendingOperation.getObject(), millisToWait);
|
||||
getUiHandler().postDelayed(pendingOperation.getObject(), millisToWait);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final MutableObject<Runnable> pendingOperation = new MutableObject<Runnable>();
|
||||
|
||||
@Override
|
||||
public void onCalculatorEvent(@NotNull CalculatorEventData calculatorEventData, @NotNull CalculatorEventType calculatorEventType, @Nullable final Object data) {
|
||||
if (calculatorEventType.isOfType(CalculatorEventType.display_state_changed)) {
|
||||
if (!preparedInput.isFromInputArgs()) {
|
||||
|
||||
final CalculatorEventHolder.Result result = this.lastEventHolder.apply(calculatorEventData);
|
||||
if (result.isNewAfter()) {
|
||||
this.preparedInput = prepareInputFromDisplay(((CalculatorDisplayChangeEventData) data).getNewValue(), null);
|
||||
createChart();
|
||||
|
||||
uiHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final View view = getView();
|
||||
if (view != null) {
|
||||
createGraphicalView(view, preparedInput.getPlotBoundaries());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* public void zoomInClickHandler(@NotNull View v) {
|
||||
this.graphicalView.zoomIn();
|
||||
@ -406,196 +223,8 @@ public class CalculatorPlotFragment extends CalculatorFragment implements Calcul
|
||||
this.graphicalView.zoomOut();
|
||||
}*/
|
||||
|
||||
/*
|
||||
**********************************************************************
|
||||
*
|
||||
* MENU
|
||||
*
|
||||
**********************************************************************
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
|
||||
final FragmentActivity activity = this.getActivity();
|
||||
if (activity != null) {
|
||||
fragmentMenu.onCreateOptionsMenu(activity, menu);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
|
||||
final FragmentActivity activity = this.getActivity();
|
||||
if (activity != null) {
|
||||
fragmentMenu.onPrepareOptionsMenu(activity, menu);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
return super.onOptionsItemSelected(item) || fragmentMenu.onOptionsItemSelected(this.getActivity(), item);
|
||||
}
|
||||
|
||||
public void onError() {
|
||||
this.chart = null;
|
||||
}
|
||||
|
||||
/*
|
||||
**********************************************************************
|
||||
*
|
||||
* STATIC
|
||||
*
|
||||
**********************************************************************
|
||||
*/
|
||||
|
||||
private static enum PlotMenu implements IdentifiableMenuItem<MenuItem> {
|
||||
|
||||
preferences(R.id.menu_plot_settings) {
|
||||
@Override
|
||||
public void onClick(@NotNull MenuItem data, @NotNull Context context) {
|
||||
context.startActivity(new Intent(context, CalculatorPlotPreferenceActivity.class));
|
||||
}
|
||||
};
|
||||
|
||||
private final int itemId;
|
||||
|
||||
private PlotMenu(int itemId) {
|
||||
this.itemId = itemId;
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Integer getItemId() {
|
||||
return itemId;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class PlotBoundaries implements Serializable {
|
||||
|
||||
private double xMin;
|
||||
private double xMax;
|
||||
private double yMin;
|
||||
private double yMax;
|
||||
|
||||
public PlotBoundaries() {
|
||||
}
|
||||
|
||||
public PlotBoundaries(@NotNull XYMultipleSeriesRenderer renderer) {
|
||||
this.xMin = renderer.getXAxisMin();
|
||||
this.yMin = renderer.getYAxisMin();
|
||||
this.xMax = renderer.getXAxisMax();
|
||||
this.yMax = renderer.getYAxisMax();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PlotBoundaries{" +
|
||||
"yMax=" + yMax +
|
||||
", yMin=" + yMin +
|
||||
", xMax=" + xMax +
|
||||
", xMin=" + xMin +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static class PreparedInput {
|
||||
|
||||
@Nullable
|
||||
private Input input;
|
||||
|
||||
@Nullable
|
||||
private Generic expression;
|
||||
|
||||
@Nullable
|
||||
private Constant variable;
|
||||
|
||||
private boolean fromInputArgs;
|
||||
|
||||
@Nullable
|
||||
private PlotBoundaries plotBoundaries = null;
|
||||
|
||||
private PreparedInput() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static PreparedInput newInstance(@NotNull Input input, @NotNull Generic expression, @NotNull Constant variable, boolean fromInputArgs, @Nullable PlotBoundaries plotBoundaries) {
|
||||
final PreparedInput result = new PreparedInput();
|
||||
|
||||
result.input = input;
|
||||
result.expression = expression;
|
||||
result.variable = variable;
|
||||
result.fromInputArgs = fromInputArgs;
|
||||
result.plotBoundaries = plotBoundaries;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static PreparedInput newErrorInstance(boolean fromInputArgs) {
|
||||
final PreparedInput result = new PreparedInput();
|
||||
|
||||
result.input = null;
|
||||
result.expression = null;
|
||||
result.variable = null;
|
||||
result.fromInputArgs = fromInputArgs;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean isFromInputArgs() {
|
||||
return fromInputArgs;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Input getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Generic getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PlotBoundaries getPlotBoundaries() {
|
||||
return plotBoundaries;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Constant getVariable() {
|
||||
return variable;
|
||||
}
|
||||
|
||||
public boolean isError() {
|
||||
return input == null || expression == null || variable == null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Input implements Serializable {
|
||||
|
||||
@NotNull
|
||||
private String expression;
|
||||
|
||||
@NotNull
|
||||
private String variableName;
|
||||
|
||||
public Input(@NotNull String expression, @NotNull String variableName) {
|
||||
this.expression = expression;
|
||||
this.variableName = variableName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getVariableName() {
|
||||
return variableName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -202,7 +202,7 @@ public final class PlotUtils {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
static void handleArithmeticException(@NotNull ArithmeticException e, @NotNull CalculatorPlotFragment calculatorPlotFragment) {
|
||||
static void handleArithmeticException(@NotNull ArithmeticException e, @NotNull AbstractCalculatorPlotFragment calculatorPlotFragment) {
|
||||
String message = e.getLocalizedMessage();
|
||||
if (StringUtils.isEmpty(message)) {
|
||||
message = e.getMessage();
|
||||
@ -371,9 +371,20 @@ public final class PlotUtils {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Complex calculatorExpression(@NotNull Generic expression, @NotNull Constant variable, double x) {
|
||||
public static Complex calculatorExpression(@NotNull Generic expression, @NotNull Constant xVar, double x) {
|
||||
try {
|
||||
return unwrap(expression.substitute(variable, Expression.valueOf(x)).numeric());
|
||||
return unwrap(expression.substitute(xVar, Expression.valueOf(x)).numeric());
|
||||
} catch (RuntimeException e) {
|
||||
return NaN;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Complex calculatorExpression(@NotNull Generic expression, @NotNull Constant xVar, double x, @NotNull Constant yVar, double y) {
|
||||
try {
|
||||
Generic tmp = expression.substitute(xVar, Expression.valueOf(x));
|
||||
tmp = tmp.substitute(yVar, Expression.valueOf(y));
|
||||
return unwrap(tmp.numeric());
|
||||
} catch (RuntimeException e) {
|
||||
return NaN;
|
||||
}
|
||||
|
@ -45,7 +45,8 @@ public final class CalculatorUtils {
|
||||
boolean result = false;
|
||||
|
||||
if (operation == JsclOperation.simplify) {
|
||||
if (getNotSystemConstants(expression).size() == 1) {
|
||||
int size = getNotSystemConstants(expression).size();
|
||||
if (size == 1 || size == 2) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user