new plotter

This commit is contained in:
Sergey Solovyev 2012-12-31 00:37:14 +04:00
parent 3dab118a1b
commit 5f7ee1e64e
35 changed files with 3447 additions and 460 deletions

View File

@ -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

View File

@ -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;
}
}

Binary file not shown.

View File

@ -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>

View 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);
}
}

View 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);
}
}

View 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();
}
}

View 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);
}
}

View 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;
}
}

View 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;
}

View 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);
}
}

View 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;
}
}

View 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);
}
}

View 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());
}
}

View 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);*/
}

View 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");
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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);
}
}

View 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;
}*/
}

View 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;
}
}

View 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);
}
}

View 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;
}
}

View File

@ -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

View File

@ -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();

View File

@ -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;

View File

@ -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;
}
/*
**********************************************************************
*

View File

@ -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),

View File

@ -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;
}
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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;
}
}