package org.geogebra.common.kernel;
import org.geogebra.common.gui.inputfield.InputHelper;
import org.geogebra.common.kernel.arithmetic.ValidExpression;
import org.geogebra.common.kernel.commands.EvalInfo;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoFunction;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.main.MyError;
import org.geogebra.common.main.error.ErrorHandler;
import org.geogebra.common.main.error.ErrorHelper;
import org.geogebra.common.util.StringUtil;
import org.geogebra.common.util.debug.Log;
/**
* Periodically tries evaluating current input and creates preview
*
* @author Mathieu + Zbynek
*/
public class ScheduledPreviewFromInputBar implements Runnable {
private static final int DEFAULT_MAX_LENGTH = 1000;
/**
*
*/
private final Kernel kernel;
/**
* @param kernel
* kernel
*/
ScheduledPreviewFromInputBar(Kernel kernel) {
this.kernel = kernel;
}
private String input = "";
private String validInput = "";
private ErrorHandler validation;
private int maxLength = DEFAULT_MAX_LENGTH;
private void setInput(String str, ErrorHandler validation) {
this.input = str;
this.validation = validation;
if (StringUtil.emptyTrim(str)) {
validInput = "";
maxLength = DEFAULT_MAX_LENGTH;
return;
}
if (str.length() > maxLength) {
validInput = null;
Log.debug("Timeout at length " + maxLength);
return;
}
long start = System.currentTimeMillis();
try {
ValidExpression ve = this.kernel.getAlgebraProcessor()
.getValidExpressionNoExceptionHandling(input);
if (ve != null) {
validInput = input;
}
} catch (MyError t) {
ErrorHelper.handleError(t, null, kernel.getLocalization(),
validation);
} catch (Exception e) {
ErrorHelper.handleException(e, kernel.getApplication(), validation);
} catch (Error e) {
ErrorHelper.handleException(new Exception(e),
kernel.getApplication(), validation);
}
if (System.currentTimeMillis() > start + 200) {
maxLength = str.length();
validInput = null;
} else {
maxLength = DEFAULT_MAX_LENGTH;
}
}
/**
* @param fallback
* what to return if no input is valid
* @return last valid value of input
*/
public String getInput(String fallback) {
if (fallback == null || fallback.length() == 0) {
return "";
}
String ret = validInput;
validInput = null;
if (ret == null || ret.length() == 0) {
return fallback;
}
return ret;
}
private GeoElement[] previewGeos;
private String[] sliders;
@Override
public void run() {
cleanOldSliders();
if (input.length() == 0) {
if (validation != null) {
validation.resetError();
}
this.kernel.notifyUpdatePreviewFromInputBar(null);
return;
}
if (StringUtil.empty(validInput)) {
if (validation != null) {
// timeout -- assume OK as we don't know if it's wrong
validation.showError(maxLength != DEFAULT_MAX_LENGTH ? null
: kernel.getLocalization().getError("InvalidInput"));
}
this.kernel.notifyUpdatePreviewFromInputBar(null);
return;
}
EvalInfo info = new EvalInfo(false, true).withScripting(false)
.withCAS(false);
Log.debug("preview for: " + validInput);
boolean silentModeOld = this.kernel.isSilentMode();
previewGeos = null;
Long start = System.currentTimeMillis();
try {
this.kernel.setSilentMode(true);
ValidExpression ve = this.kernel.getAlgebraProcessor()
.getValidExpressionNoExceptionHandling(validInput);
if (!isCASeval(ve)) {
GeoElement existingGeo = this.kernel.lookupLabel(ve.getLabel());
if (existingGeo == null) {
GeoElementND[] inputGeos = this.kernel.getAlgebraProcessor()
.processAlgebraCommandNoExceptionHandling(ve, false,
validation, null, info.withSliders(false));
previewGeos = null;
if (inputGeos != null) {
InputHelper.updateProperties(previewGeos, kernel
.getApplication().getActiveEuclidianView(), 0);
int unlabeled = 0;
for (GeoElementND geo : inputGeos) {
if (geo instanceof GeoFunction) {
boolean b = ((GeoFunction) geo)
.validate(ve.getLabel() == null, false);
if (!b) {
geo.setUndefined();
}
}
if (!geo.isLabelSet()) {
geo.setSelectionAllowed(false);
unlabeled++;
}
}
previewGeos = new GeoElement[unlabeled];
int i = 0;
for (GeoElementND geo : inputGeos) {
if (!geo.isLabelSet()) {
previewGeos[i++] = geo.toGeoElement();
}
}
}
this.kernel.notifyUpdatePreviewFromInputBar(previewGeos);
} else {
Log.debug("existing geo: " + existingGeo);
kernel.notifyUpdatePreviewFromInputBar(null);
if (validation != null && validInput.equals(input)) {
validation.resetError();
}
}
} else {
Log.debug("cas cell ");
kernel.notifyUpdatePreviewFromInputBar(null);
}
if (validation != null && previewGeos != null
&& validInput.equals(input)) {
validation.resetError();
}
this.kernel.setSilentMode(silentModeOld);
} catch (Throwable ee) {
Log.debug("-- invalid input" + ee + ":" + validInput);
this.kernel.setSilentMode(true);
this.kernel.notifyUpdatePreviewFromInputBar(null);
this.kernel.setSilentMode(silentModeOld);
}
if (System.currentTimeMillis() > start + 200) {
maxLength = validInput.length();
validInput = null;
}
}
private static boolean isCASeval(ValidExpression ve) {
String label = ve.getLabel();
if (label != null && label.startsWith("$")) {
Integer row = -1;
try {
row = Integer.parseInt(label.substring(1)) - 1;
} catch (Exception e) {
// spreadsheet reference
}
return row > 0;
}
return false;
}
private void cleanOldSliders() {
if (sliders != null) {
for (int i = 0; i < sliders.length; i++) {
GeoElement slider = kernel.lookupLabel(sliders[i].trim());
slider.setFixed(false);
slider.remove();
}
kernel.notifyRepaint();
sliders = null;
}
}
/**
* try to create/update preview for input typed
*
* @param newInput
* current algebra input
* @param validate
* validation callback
*/
public void updatePreviewFromInputBar(String newInput,
ErrorHandler validate) {
if (this.input.equals(newInput)) {
Log.debug("no update needed (same input)");
return;
}
setInput(newInput, validate);
kernel.getApplication().schedulePreview(this);
}
/**
* preview is not recalculated if input has not changed since last
* calculation
*
* @param newInput
* input
* @return GeoElement[] preview for this input
*/
public GeoElement[] getPreview(String newInput) {
if (this.input.equals(newInput)) {
Log.debug("no update needed (same input)");
return previewGeos;
}
// create new preview immediately
kernel.getApplication().cancelPreview();
setInput(newInput, validation);
run();
return previewGeos;
}
/**
* @param string
* slider names
*/
public void addSliders(String string) {
cleanOldSliders();
sliders = string.split(",");
}
/**
* @return whether last input parses OK
*/
public boolean isValid() {
if (validInput == null && input != null) {
setInput(input, validation);
}
return input != null && input.equals(validInput);
}
/**
* Clears preview.
*/
public void clear() {
input = "";
validInput = "";
previewGeos = null;
setInput("", null);
}
}