WarpPI/core/src/main/java/it/cavallium/warppi/gui/screens/MathInputScreen.java

712 lines
23 KiB
Java

package it.cavallium.warppi.gui.screens;
import java.io.IOException;
import java.util.HashMap;
import it.cavallium.warppi.gui.RenderContext;
import it.cavallium.warppi.gui.ScreenContext;
import org.apache.commons.lang3.SerializationUtils;
import it.cavallium.warppi.WarpPI;
import it.cavallium.warppi.Platform.ConsoleUtils;
import it.cavallium.warppi.device.input.Keyboard;
import it.cavallium.warppi.event.Key;
import it.cavallium.warppi.event.KeyPressedEvent;
import it.cavallium.warppi.event.KeyReleasedEvent;
import it.cavallium.warppi.gui.HistoryBehavior;
import it.cavallium.warppi.gui.expression.InputContext;
import it.cavallium.warppi.gui.expression.blocks.Block;
import it.cavallium.warppi.gui.expression.blocks.BlockContainer;
import it.cavallium.warppi.gui.expression.containers.InputContainer;
import it.cavallium.warppi.gui.expression.containers.NormalInputContainer;
import it.cavallium.warppi.gui.expression.containers.NormalOutputContainer;
import it.cavallium.warppi.gui.expression.containers.OutputContainer;
import it.cavallium.warppi.gui.graphicengine.BinaryFont;
import it.cavallium.warppi.gui.graphicengine.Renderer;
import it.cavallium.warppi.math.AngleMode;
import it.cavallium.warppi.math.Function;
import it.cavallium.warppi.math.FunctionDynamic;
import it.cavallium.warppi.math.FunctionOperator;
import it.cavallium.warppi.math.FunctionSingle;
import it.cavallium.warppi.math.MathContext;
import it.cavallium.warppi.math.MathematicalSymbols;
import it.cavallium.warppi.math.functions.Expression;
import it.cavallium.warppi.math.functions.Number;
import it.cavallium.warppi.math.functions.Variable;
import it.cavallium.warppi.math.functions.Variable.V_TYPE;
import it.cavallium.warppi.math.functions.Variable.VariableValue;
import it.cavallium.warppi.math.parser.MathParser;
import it.cavallium.warppi.math.solver.MathSolver;
import it.cavallium.warppi.util.Error;
import it.cavallium.warppi.util.Errors;
import it.cavallium.warppi.util.Utils;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
public class MathInputScreen extends Screen {
private static final BinaryFont fontBig = Utils.getFont(false);
private final boolean isCloned;
public MathContext calc;
public InputContext ic;
public InputContainer userInput;
public OutputContainer result;
public int errorLevel = 0; // 0 = nessuno, 1 = risultato, 2 = tutto
private boolean computingResult = false;
private Thread computingThread;
private int computingAnimationIndex = 0;
private double computingAnimationElapsedTime = 0;
private double computingElapsedTime = 0;
private boolean computingBreakTipVisible = false;
boolean mustRefresh = true;
@SuppressWarnings("unused")
private int currentStep = 0;
public MathInputScreen() {
super();
historyBehavior = HistoryBehavior.NORMAL;
isCloned = false;
}
/**
* Create a copy of this element
*/
private MathInputScreen(MathInputScreen old) {
this.calc = new MathContext(old.calc);
this.historyBehavior = old.historyBehavior;
this.created = old.created;
this.currentStep = old.currentStep;
this.d = old.d;
this.errorLevel = old.errorLevel;
this.ic = new InputContext(old.ic);
this.initialized = old.initialized;
this.mustRefresh = old.mustRefresh;
this.result = new NormalOutputContainer(old.result);
this.userInput = new NormalInputContainer(old.userInput, this.ic);
this.isCloned = true;
}
@Override
public void created() throws InterruptedException {
ic = new InputContext();
calc = new MathContext();
calc.init();
}
@Override
public void initialized() throws InterruptedException {
/* Fine caricamento */
}
@Override
public void graphicInitialized(ScreenContext ctx) throws InterruptedException {
/* Fine caricamento */
try {
if (!BlockContainer.isInitialized()) {
BlockContainer.initializeFonts(ctx.getGraphicEngine().loadFont("norm"), ctx.getGraphicEngine().loadFont("smal"));
}
} catch (final IOException e) {
e.printStackTrace();
WarpPI.getPlatform().exit(1);
}
if (!isCloned) {
userInput = new NormalInputContainer(ic);
result = new NormalOutputContainer();
}
}
@Override
public void beforeRender(final ScreenContext ctx, final float dt) {
if (WarpPI.INSTANCE.getHardwareDevice().getDisplayManager().error == null) {
ctx.getGraphicEngine().getRenderer().glClearColor(0xFFc5c2af);
} else {
ctx.getGraphicEngine().getRenderer().glClearColor(0xFFDC3C32);
}
if (userInput != null)
if (userInput.beforeRender(dt)) {
mustRefresh = true;
}
if (computingResult) {
computingElapsedTime += dt;
computingAnimationElapsedTime += dt;
if (computingAnimationElapsedTime > 0.1) {
computingAnimationElapsedTime -= 0.1;
computingAnimationIndex = (computingAnimationIndex + 1) % 16;
mustRefresh = true;
}
if (computingElapsedTime > 5) {
computingBreakTipVisible = true;
}
} else {
computingElapsedTime = 0;
computingAnimationElapsedTime = 0;
computingAnimationIndex = 0;
computingBreakTipVisible = false;
}
}
@Override
public void render(RenderContext ctx) {
final Renderer renderer = ctx.getRenderer();
MathInputScreen.fontBig.use(WarpPI.INSTANCE.getHardwareDevice().getDisplayManager().display);
final int textColor = 0xFF000000;
final int padding = 4;
renderer.glColor(textColor);
userInput.draw(WarpPI.INSTANCE.getHardwareDevice().getDisplayManager().display, renderer, padding, padding);
if (computingResult) {
renderer.glColor3f(1, 1, 1);
final int leftX = 208;
final int leftY = 16;
final int size = 32;
final int posY = computingAnimationIndex % 2;
final int posX = (computingAnimationIndex - posY) / 2;
renderer.glFillRect(ctx.getWidth() - size - 4, ctx.getHeight() - size - 4, size, size, leftX + size * posX, leftY + size * posY, size, size);
if (computingBreakTipVisible) {
Utils.getFont(false).use(WarpPI.INSTANCE.getHardwareDevice().getDisplayManager().display);
renderer.glColor3f(0.75f, 0, 0);
renderer.glDrawStringRight(ctx.getWidth() - 4 - size - 4, ctx.getHeight() - size / 2 - renderer.getCurrentFont().getCharacterHeight() / 2 - 4, "Press (=) to stop");
}
} else if (!result.isContentEmpty()) {
result.draw(WarpPI.INSTANCE.getHardwareDevice().getDisplayManager().display, renderer, ctx.getWidth() - result.getWidth() - 2, ctx.getHeight() - result.getHeight() - 2);
}
}
@Override
public void renderTopmost(RenderContext ctx) {
final Renderer renderer = ctx.getRenderer();
renderer.glColor3f(1, 1, 1);
final int pos = 2;
final int spacersNumb = 1;
int skinN = 0;
if (calc.exactMode) {
skinN = 22;
} else {
skinN = 21;
}
WarpPI.INSTANCE.getHardwareDevice().getDisplayManager().guiSkin.use(WarpPI.INSTANCE.getHardwareDevice().getDisplayManager().display);
renderer.glFillRect(2 + 18 * pos + 2 * spacersNumb, 2, 16, 16, 16 * skinN, 16 * 0, 16, 16);
}
@Override
public boolean mustBeRefreshed() {
if (mustRefresh) {
mustRefresh = false;
return true;
} else {
return false;
}
}
@Override
public boolean onKeyPressed(final KeyPressedEvent k) {
WarpPI.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_DEBUG_MIN, "MathInputScreen", "Pressed key " + k.getKey().toString());
try {
switch (k.getKey()) {
case OK:
userInput.toggleExtra();
mustRefresh = true;
return true;
case HISTORY_BACK:
if (userInput.isExtraOpened()) {
userInput.closeExtra();
currentStep = 0;
mustRefresh = true;
return true;
}
case BACK:
if (userInput.isExtraOpened()) {
userInput.closeExtra();
mustRefresh = true;
return true;
}
default:
if (userInput.isExtraOpened() && userInput.getExtraKeyboardEventListener().onKeyPressed(k)) {
currentStep = 0;
return true;
} else {
final boolean step = k.getKey() == Key.STEP;
switch (k.getKey()) {
case STEP:
currentStep++;
return simplify(true, false);
case SIMPLIFY:
return simplify(false, true);
case NUM0:
typeChar('0');
return true;
case NUM1:
typeChar('1');
return true;
case NUM2:
typeChar('2');
return true;
case NUM3:
typeChar('3');
return true;
case NUM4:
typeChar('4');
return true;
case NUM5:
typeChar('5');
return true;
case NUM6:
typeChar('6');
return true;
case NUM7:
typeChar('7');
return true;
case NUM8:
typeChar('8');
return true;
case NUM9:
typeChar('9');
return true;
case PLUS:
typeChar('+');
return true;
case MINUS:
typeChar('-');
return true;
case PLUS_MINUS:
typeChar('±');
return true;
case MULTIPLY:
typeChar('*');
return true;
case DIVIDE:
typeChar('/');
return true;
case PARENTHESIS_OPEN:
typeChar('(');
return true;
case PARENTHESIS_CLOSE:
typeChar(')');
return true;
case DOT:
typeChar('.');
return true;
case EQUAL:
typeChar('=');
return true;
case SQRT:
typeChar('â’¶');
return true;
case ROOT:
typeChar('√');
return true;
case POWER_OF_2:
typeChar(MathematicalSymbols.POWER_OF_TWO);
return true;
case POWER_OF_x:
typeChar(MathematicalSymbols.POWER);
return true;
case PI:
typeChar(MathematicalSymbols.PI);
return true;
case EULER_NUMBER:
typeChar(MathematicalSymbols.EULER_NUMBER);
return true;
case LETTER_X:
typeChar(MathematicalSymbols.variables[23]);
return true;
case LETTER_Y:
typeChar(MathematicalSymbols.variables[24]);
return true;
case SINE:
typeChar(MathematicalSymbols.SINE);
return true;
case COSINE:
typeChar(MathematicalSymbols.COSINE);
return true;
case TANGENT:
typeChar(MathematicalSymbols.TANGENT);
return true;
case ARCSINE:
typeChar(MathematicalSymbols.ARC_SINE);
return true;
case ARCCOSINE:
typeChar(MathematicalSymbols.ARC_COSINE);
return true;
case ARCTANGENT:
typeChar(MathematicalSymbols.ARC_TANGENT);
return true;
case LOGARITHM:
typeChar(MathematicalSymbols.LOGARITHM);
return true;
case DELETE:
userInput.del();
currentStep = 0;
mustRefresh = true;
return true;
case LEFT:
userInput.moveLeft();
mustRefresh = true;
return true;
case RIGHT:
userInput.moveRight();
mustRefresh = true;
return true;
case RESET:
userInput.clear();
result.clear();
currentStep = 0;
if (WarpPI.INSTANCE.getHardwareDevice().getDisplayManager().error != null) {
WarpPI.getPlatform().getConsoleUtils().out().println(1, "Resetting after error...");
WarpPI.INSTANCE.getHardwareDevice().getDisplayManager().error = null;
}
return true;
case SURD_MODE:
calc.exactMode = !calc.exactMode;
if (!result.isContentEmpty()) {
result.clear();
currentStep = 0;
simplify(false, false);
}
return true;
case debug1:
WarpPI.INSTANCE.getHardwareDevice().getDisplayManager().setScreen(new EmptyScreen());
return true;
case HISTORY_BACK:
// if (Engine.INSTANCE.getHardwareDevice().getDisplayManager().canGoBack()) {
// if (currentExpression != null && currentExpression.length() > 0 & Engine.INSTANCE.getHardwareDevice().getDisplayManager().sessions[Engine.INSTANCE.getHardwareDevice().getDisplayManager().currentSession + 1] instanceof MathInputScreen) {
// newExpression = currentExpression;
// try {
// interpreta(true);
// } catch (final Error e) {}
// }
// }
return false;
case HISTORY_FORWARD:
// if (Engine.INSTANCE.getHardwareDevice().getDisplayManager().canGoForward()) {
// if (currentExpression != null && currentExpression.length() > 0 & Engine.INSTANCE.getHardwareDevice().getDisplayManager().sessions[Engine.INSTANCE.getHardwareDevice().getDisplayManager().currentSession - 1] instanceof MathInputScreen) {
// newExpression = currentExpression;
// try {
// interpreta(true);
// } catch (final Error e) {}
// }
// }
return false;
case debug_DEG:
if (calc.angleMode.equals(AngleMode.DEG) == false) {
calc.angleMode = AngleMode.DEG;
currentStep = 0;
return true;
}
return false;
case debug_RAD:
if (calc.angleMode.equals(AngleMode.RAD) == false) {
calc.angleMode = AngleMode.RAD;
currentStep = 0;
return true;
}
return false;
case debug_GRA:
if (calc.angleMode.equals(AngleMode.GRA) == false) {
calc.angleMode = AngleMode.GRA;
currentStep = 0;
return true;
}
return false;
case DRG_CYCLE:
if (calc.angleMode.equals(AngleMode.DEG) == true) {
calc.angleMode = AngleMode.RAD;
} else if (calc.angleMode.equals(AngleMode.RAD) == true) {
calc.angleMode = AngleMode.GRA;
} else {
calc.angleMode = AngleMode.DEG;
}
currentStep = 0;
return true;
default:
return false;
}
}
}
} catch (final Exception ex) {
ex.printStackTrace();
return true;
}
}
@SuppressWarnings("unchecked")
private void swapInputScreen() {
MathInputScreen mis = new MathInputScreen(this);
WarpPI.INSTANCE.getHardwareDevice().getDisplayManager().setScreen(mis);
}
@SuppressWarnings("unused")
@Deprecated
private ObjectArrayList<Function> solveExpression(final ObjectArrayList<Function> f22) {
return null;
/*
try {
try {
return calc.solveExpression(f22);
} catch (final Exception ex) {
if (Utils.debugOn) {
ex.printStackTrace();
}
throw new Error(Errors.SYNTAX_ERROR);
}
} catch (final Error e) {
final StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
d.errorStackTrace = sw.toString().toUpperCase().replace("\t", " ").replace("\r", "").split("\n");
Engine.INSTANCE.getHardwareDevice().getDisplayManager().error = e.id.toString();
System.err.println(e.id);
}
return null;
*/
}
@Deprecated
protected void step() {
/*
try {
try {
showVariablesDialog();
ObjectArrayList<Function> results = new ObjectArrayList<>();
final ObjectArrayList<Function> partialResults = new ObjectArrayList<>();
for (final Function f : calc.f2) {
if (f instanceof Equation) {
Engine.INSTANCE.getHardwareDevice().getDisplayManager().setScreen(new SolveEquationScreen(this));
} else {
results.add(f);
for (final Function itm : results) {
if (itm.isSimplified() == false) {
final List<Function> dt = itm.simplify();
partialResults.addAll(dt);
} else {
partialResults.add(itm);
}
}
results = new ObjectArrayList<>(partialResults);
partialResults.clear();
}
}
if (results.size() == 0) {
calc.resultsCount = 0;
} else {
calc.resultsCount = results.size();
Collections.reverse(results);
// add elements to al, including duplicates
final Set<Function> hs = new LinkedHashSet<>();
hs.addAll(results);
results.clear();
results.addAll(hs);
calc.f2 = results;
}
Utils.out.println(1, calc.f2.toString());
} catch (final Exception ex) {
if (Utils.debugOn) {
ex.printStackTrace();
}
throw new Error(Errors.SYNTAX_ERROR);
}
} catch (final Error e) {
final StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
d.errorStackTrace = sw.toString().toUpperCase().replace("\t", " ").replace("\r", "").split("\n");
Engine.INSTANCE.getHardwareDevice().getDisplayManager().error = e.id.toString();
System.err.println(e.id);
}
*/
}
protected boolean simplify(final boolean step, final boolean swapScreen) {
if (!step) {
currentStep = 0;
}
if (WarpPI.INSTANCE.getHardwareDevice().getDisplayManager().error != null) {
//TODO: make the error management a global API rather than being relegated to this screen.
WarpPI.getPlatform().getConsoleUtils().out().println(1, "Resetting after error...");
WarpPI.INSTANCE.getHardwareDevice().getDisplayManager().error = null;
calc.f = null;
calc.f2 = null;
calc.resultsCount = 0;
return true;
} else if (!computingResult) {
computingResult = true;
computingThread = new Thread(() -> {
try {
try {
if (!userInput.isAlreadyParsed() && !userInput.isEmpty()) {
final Expression expr = MathParser.parseInput(calc, userInput);
if (calc.f == null | calc.f2 == null) {
calc.f = new ObjectArrayList<>();
calc.f2 = new ObjectArrayList<>();
} else {
calc.f.clear();
calc.f2.clear();
}
calc.f.add(expr);
WarpPI.getPlatform().getConsoleUtils().out().println(2, "INPUT: " + expr);
final MathSolver ms = new MathSolver(expr);
final ObjectArrayList<ObjectArrayList<Function>> resultSteps = ms.solveAllSteps();
resultSteps.add(0, Utils.newArrayList(expr));
int stepNumber = 0;
for (ObjectArrayList<Function> resultStep : resultSteps) {
stepNumber++;
WarpPI.getPlatform().getConsoleUtils().out().println(0, "STEP " + stepNumber);
for (Function function : resultStep) {
WarpPI.getPlatform().getConsoleUtils().out().println(0, " :: " + function.toString());
}
}
final ObjectArrayList<Function> resultExpressions = resultSteps.get(resultSteps.size() - 1);
for (final Function rr : resultExpressions) {
WarpPI.getPlatform().getConsoleUtils().out().println(0, "RESULT: " + rr.toString());
}
final ObjectArrayList<ObjectArrayList<Block>> resultBlocks = MathParser.parseOutput(calc, resultExpressions);
result.setContentAsMultipleGroups(resultBlocks);
// showVariablesDialog(() -> {
// currentExpression = newExpression;
// simplify();
// });
if (swapScreen == true) {
this.swapInputScreen();
}
}
} catch (final InterruptedException ex) {
WarpPI.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_DEBUG_MIN, "Computing thread stopped.");
} catch (final Exception ex) {
if (WarpPI.getPlatform().getSettings().isDebugEnabled()) {
ex.printStackTrace();
}
throw new Error(Errors.SYNTAX_ERROR);
}
} catch (final Error e) {
d.errorStackTrace = WarpPI.getPlatform().stacktraceToString(e);
WarpPI.INSTANCE.getHardwareDevice().getDisplayManager().error = e.id.toString();
System.err.println(e.id);
}
computingResult = false;
});
WarpPI.getPlatform().setThreadName(computingThread, "Computing Thread");
WarpPI.getPlatform().setThreadDaemon(computingThread);
computingThread.setPriority(Thread.NORM_PRIORITY + 3);
computingThread.start();
return true;
} else {
if (computingThread != null) {
computingThread.interrupt();
computingResult = false;
return true;
}
return false;
}
}
@SuppressWarnings("unused")
@Deprecated
private void changeEquationScreen() {
throw new UnsupportedOperationException();
//
// if (!userInput.isEmpty()) {
// final MathInputScreen cloned = clone();
// cloned.userInput.setCaretPosition(cloned.userInput.getCaretMaxPosition()-1);
// Engine.INSTANCE.getHardwareDevice().getDisplayManager().replaceScreen(cloned);
// initialized = false;
// Engine.INSTANCE.getHardwareDevice().getDisplayManager().setScreen(this);
//
// }
}
public void typeChar(final char chr) {
userInput.typeChar(chr);
mustRefresh = true;
}
@Override
public boolean onKeyReleased(final KeyReleasedEvent k) {
if (k.getKey() == Key.OK) {
return true;
} else if (userInput.isExtraOpened() && userInput.getExtraKeyboardEventListener().onKeyReleased(k)) {
return true;
} else {
switch (k.getKey()) {
default:
return false;
}
}
}
public void showVariablesDialog() {
showVariablesDialog(null);
}
public void showVariablesDialog(final Runnable runnable) {
final Thread ct = new Thread(() -> {
final ObjectArrayList<Function> knownVarsInFunctions = getKnownVariables(calc.f.toArray(new Function[calc.f.size()]));
for (final VariableValue f : calc.variablesValues) {
if (knownVarsInFunctions.contains(f.v)) {
knownVarsInFunctions.remove(f.v);
}
}
boolean cancelled = false;
for (final Function f : knownVarsInFunctions) {
final ChooseVariableValueScreen cvs = new ChooseVariableValueScreen(this, new VariableValue((Variable) f, new Number(calc, 0)));
WarpPI.INSTANCE.getHardwareDevice().getDisplayManager().setScreen(cvs);
try {
WarpPI.INSTANCE.getHardwareDevice().getDisplayManager().screenChange.acquire();
} catch (final InterruptedException e) {}
if (cvs.resultNumberValue == null) {
cancelled = true;
break;
} else {
final int is = calc.variablesValues.size();
for (int i = 0; i < is; i++) {
if (calc.variablesValues.get(i).v == f) {
calc.variablesValues.remove(i);
}
}
calc.variablesValues.add(new VariableValue((Variable) f, (Number) cvs.resultNumberValue));
}
}
if (!cancelled) {
if (runnable != null) {
runnable.run();
}
}
});
WarpPI.getPlatform().setThreadName(ct, "Variables user-input queue thread");
ct.setPriority(Thread.MIN_PRIORITY);
WarpPI.getPlatform().setThreadDaemon(ct);
ct.start();
}
private ObjectArrayList<Function> getKnownVariables(final Function[] fncs) {
final ObjectArrayList<Function> res = new ObjectArrayList<>();
for (final Function f : fncs) {
if (f instanceof FunctionOperator) {
res.addAll(getKnownVariables(new Function[] { ((FunctionOperator) f).getParameter1(), ((FunctionOperator) f).getParameter2() }));
} else if (f instanceof FunctionDynamic) {
res.addAll(getKnownVariables(((FunctionDynamic) f).getParameters()));
} else if (f instanceof FunctionSingle) {
res.addAll(getKnownVariables(new Function[] { ((FunctionSingle) f).getParameter() }));
} else if (f instanceof Variable) {
if (((Variable) f).getType() == Variable.V_TYPE.CONSTANT) {
if (!res.contains(f)) {
res.add(f);
}
}
}
}
return res;
}
@Override
public String getSessionTitle() {
return "Calculator";
}
}