package org.geogebra.common.kernel.geos;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;
import org.geogebra.common.awt.GColor;
import org.geogebra.common.awt.GFont;
import org.geogebra.common.cas.GeoGebraCAS;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoSurfaceCartesian3D;
import org.geogebra.common.kernel.AlgoCasCellInterface;
import org.geogebra.common.kernel.CASException;
import org.geogebra.common.kernel.CircularDefinitionException;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.VarString;
import org.geogebra.common.kernel.algos.AlgoElement;
import org.geogebra.common.kernel.algos.DrawInformationAlgo;
import org.geogebra.common.kernel.arithmetic.AssignmentType;
import org.geogebra.common.kernel.arithmetic.Command;
import org.geogebra.common.kernel.arithmetic.Equation;
import org.geogebra.common.kernel.arithmetic.ExpressionNode;
import org.geogebra.common.kernel.arithmetic.ExpressionNodeConstants;
import org.geogebra.common.kernel.arithmetic.ExpressionNodeConstants.StringType;
import org.geogebra.common.kernel.arithmetic.ExpressionValue;
import org.geogebra.common.kernel.arithmetic.Function;
import org.geogebra.common.kernel.arithmetic.FunctionExpander;
import org.geogebra.common.kernel.arithmetic.FunctionNVar;
import org.geogebra.common.kernel.arithmetic.FunctionVariable;
import org.geogebra.common.kernel.arithmetic.Functional;
import org.geogebra.common.kernel.arithmetic.FunctionalNVar;
import org.geogebra.common.kernel.arithmetic.GeoSurfaceReplacer;
import org.geogebra.common.kernel.arithmetic.Inspecting;
import org.geogebra.common.kernel.arithmetic.Inspecting.CommandFinder;
import org.geogebra.common.kernel.arithmetic.Inspecting.IneqFinder;
import org.geogebra.common.kernel.arithmetic.MyArbitraryConstant;
import org.geogebra.common.kernel.arithmetic.MyList;
import org.geogebra.common.kernel.arithmetic.MyVecNDNode;
import org.geogebra.common.kernel.arithmetic.MyVecNode;
import org.geogebra.common.kernel.arithmetic.Traversing;
import org.geogebra.common.kernel.arithmetic.Traversing.ArbconstReplacer;
import org.geogebra.common.kernel.arithmetic.Traversing.CommandCollector;
import org.geogebra.common.kernel.arithmetic.Traversing.CommandRemover;
import org.geogebra.common.kernel.arithmetic.Traversing.CommandReplacer;
import org.geogebra.common.kernel.arithmetic.Traversing.DummyVariableCollector;
import org.geogebra.common.kernel.arithmetic.Traversing.GeoDummyReplacer;
import org.geogebra.common.kernel.arithmetic.ValidExpression;
import org.geogebra.common.kernel.arithmetic.ValueType;
import org.geogebra.common.kernel.arithmetic3D.MyVec3DNode;
import org.geogebra.common.kernel.commands.EvalInfo;
import org.geogebra.common.kernel.implicit.GeoImplicit;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.kernel.kernelND.GeoSurfaceCartesianND;
import org.geogebra.common.main.MyError;
import org.geogebra.common.plugin.GeoClass;
import org.geogebra.common.plugin.Operation;
import org.geogebra.common.plugin.script.GgbScript;
import org.geogebra.common.util.StringUtil;
import org.geogebra.common.util.debug.Log;
import org.geogebra.common.util.lang.Unicode;
/**
* Cell pair of input and output strings used in the CAS view. This needs to be
* a GeoElement in order to handle dependencies between cells and other
* GeoElements together with AlgoSymbolic.
*
* @author Markus Hohenwarter
*/
public class GeoCasCell extends GeoElement
implements VarString, TextProperties {
private AssignmentType assignmentType = AssignmentType.NONE;
private boolean keepInputUsed;
/**
* @param assignmentType
* the {@link AssignmentType} to set
*/
public void setAssignmentType(AssignmentType assignmentType) {
this.assignmentType = assignmentType;
}
/**
* @return the current {@link AssignmentType}
*/
public AssignmentType getAssignmentType() {
return assignmentType;
}
/**
* @return whether KeepInput command is part of this expression
*/
public boolean isKeepInputUsed() {
return keepInputUsed;
}
/**
* Symbol for static reference
*/
public static final char ROW_REFERENCE_STATIC = '#';
/**
* Symbol for dynamic reference
*/
public static final char ROW_REFERENCE_DYNAMIC = '$';
/**
* Assignment variable used when plotting with marble
*/
private static final String PLOT_VAR = "GgbmpvarPlot";
private ValidExpression inputVE, evalVE, outputVE;
private String input, prefix, postfix, error, latex, latexInput;
private String localizedInput;
private String currentLocaleStr;
private boolean suppressOutput = false;
// input variables of this cell
private TreeSet<String> invars, functionvars;
// defined input GeoElements of this cell
private TreeSet<GeoElement> inGeos;
private boolean isCircularDefinition;
// twin geo, e.g. GeoCasCell m := 8 creates GeoNumeric m = 8
private GeoElement twinGeo;
private boolean firstComputeOutput;
private boolean ignoreTwinGeoUpdate;
private String assignmentVar;
private boolean includesRowReferences;
private boolean includesNumericCommand;
private boolean useGeoGebraFallback;
private String evalCmd, evalComment;
private int row = -1; // for CAS view, set by Construction
// use this cell as text field
private boolean useAsText;
// for the future, is only holding font infos
private GeoText commentText;
private boolean nativeOutput;
private ArrayList<Vector<String>> substList;
private boolean nSolveCmdNeeded = false;
/**
* Creates new CAS cell
*
* @param c
* construction
*/
public GeoCasCell(final Construction c) {
super(c);
input = "";
localizedInput = "";
setInputVE(null);
outputVE = null;
prefix = "";
evalVE = null;
postfix = "";
evalCmd = "";
evalComment = "";
useAsText = false;
commentText = new GeoText(c, "");
twinGeo = null;
// setGeoText(commentText);
substList = new ArrayList<Vector<String>>();
}
/**
* Sets this GeoCasCell to the current state of geo which also needs to be a
* GeoCasCell. Note that twinGeo is kept null.
*/
@Override
public void set(final GeoElementND geo) {
// some dead code removed in r20927
}
/**
* Returns the input of this row. Command names are localized when
* kernel.isPrintLocalizedCommandNames() is true, otherwise internal command
* names are used.
*
* @param tpl
* string template
* @return input string
*/
public String getInput(final StringTemplate tpl) {
if (tpl.isPrintLocalizedCommandNames()) {
// input with localized command names
if (currentLocaleStr == null
|| !currentLocaleStr.equals(getLoc().getLocaleStr())) {
updateLocalizedInput(tpl, input);
}
return localizedInput;
}
// input with internal command names
return input;
}
/**
* Returns the output of this row.
*
* @param tpl
* string template
* @return output string
*/
public String getOutput(StringTemplate tpl) {
if (error != null) {
if (tpl.isPrintLocalizedCommandNames()) {
return getLoc().getError(error);
}
return error;
}
if (outputVE == null) {
return "";
}
return outputVE.toAssignmentString(tpl, getAssignmentType());
}
/**
* Returns the output of this row without any definitions. where getOutput
* returns g: x+y=1, this returns only x+y=1
*
* @param tpl
* string template
* @return output string
*/
public String getOutputRHS(StringTemplate tpl) {
if (error != null) {
if (tpl.isPrintLocalizedCommandNames()) {
return getLoc().getError(error);
}
return error;
}
if (outputVE == null) {
return "";
}
return outputVE.toString(tpl);
}
/**
* @return prefix
*/
public String getPrefix() {
return prefix;
}
/**
* Returns the evaluation text (between prefix and postfix) of this row
* using internal command names. This method is important to process this
* row using GeoGebraCAS. XML template is used because we need both maximal
* precision and internal commands
*
* @return the evaluation text
*/
public String getEvalText() {
if (evalVE == null) {
return "";
}
return evalVE.toString(StringTemplate.xmlTemplate);
}
/**
* Returns the evaluation expression (between prefix and postfix) of this
* row. This method is important to process this row using GeoGebraCAS.
*
* @return the evaluation expression
*/
public ValidExpression getEvalVE() {
return evalVE;
}
/**
* @return input expression
*/
public ValidExpression getInputVE() {
return inputVE;
}
/**
* @return postfix
*/
public String getPostfix() {
return postfix;
}
/**
* @return LaTeX representation of output
*/
public String getLaTeXOutput() {
if (useAsText) {
return "\\text{" + this.commentText.getTextString() + "}";
}
if (isError()) {
return "";
} else if (latex == null) {
if (outputVE != null) {
StringBuilder sb = new StringBuilder("\\mathbf{");
// create LaTeX string
if (nativeOutput || !(outputVE instanceof ExpressionNode)) {
// #5119 use same rounding as in Algebra, but avoid 3.14 ->
// pi hack
sb.append(outputVE.toAssignmentLaTeXString(
includesNumericCommand()
? StringTemplate.numericLatex
: StringTemplate.latexTemplateCAS,
getAssignmentType()));
} else {
GeoElement geo = ((GeoElement) ((ExpressionNode) outputVE)
.getLeft());
appendLaTeXOutputGeo(sb, geo);
}
sb.append("}");
latex = sb.toString();
// TODO Uncomment once support for latex line breaking is
// implemented.
// kernel.setInsertLineBreaks(oldLineBreaks);
}
}
return latex;
}
private void appendLaTeXOutputGeo(StringBuilder sb, GeoElement geo) {
if (isAssignmentVariableDefined()) {
sb.append(getAssignmentLHS(StringTemplate.latexTemplateCAS));
if (geo instanceof GeoFunction
|| geo instanceof GeoSurfaceCartesianND) {
sb.append('(');
sb.append(((VarString) geo)
.getVarString(StringTemplate.latexTemplateCAS));
sb.append(')');
}
switch (getAssignmentType()) {
case DEFAULT:
sb.append(outputVE.getAssignmentOperator().trim());
break;
case DELAYED:
sb.append(outputVE.getDelayedAssignmentOperator().trim());
break;
case NONE:
break;
}
}
if (!(geo instanceof GeoLocus)) {
sb.append(geo.toValueString(StringTemplate.latexTemplateCAS));
} else {
// as GeoLocuses can not be converted to value strings
sb.append(geo.algoParent
.getDefinition(StringTemplate.latexTemplateCAS));
}
}
/**
* @return whether this cell is used as comment
*/
public boolean isUseAsText() {
return useAsText;
}
/**
* @param val
* true to use this cell as comment only
*/
public void setUseAsText(final boolean val) {
useAsText = val;
// TODO: by expanding the GeoText functionality, this could become a
// problem
if (!val) {
this.input = this.commentText.getTextString();
} else {
this.commentText.setTextString(input);
}
suppressOutput = useAsText;
// recalc row height
update();
}
/**
* @param val
* true if we should set evalCmd to NSolve
*/
public void setNSolveCmdNeeded(boolean val) {
this.nSolveCmdNeeded = val;
}
/**
* @return nSolveCmdNeeded
*/
public boolean getNSolveCmdNeeded() {
return this.nSolveCmdNeeded;
}
/**
* @param ft
* font
*/
public void setFont(GFont ft) {
setFontSizeMultiplier((double) ft.getSize()
/ (double) kernel.getApplication().getFontSize());
setFontStyle(ft.getStyle());
}
/**
* @param style
* font style
*/
@Override
public void setFontStyle(int style) {
commentText.setFontStyle(style);
}
/**
* @return font color
*/
public GColor getFontColor() {
return this.getObjectColor();
}
/**
* @param c
* font color
*/
public void setFontColor(GColor c) {
this.setObjColor(c);
}
/**
* @return font style
*/
@Override
public int getFontStyle() {
return commentText.getFontStyle();
}
/**
* @param d
* font size multiplier
*/
@Override
public void setFontSizeMultiplier(double d) {
commentText.setFontSizeMultiplier(d);
}
/**
* @return font size
*/
@Override
public double getFontSizeMultiplier() {
return commentText.getFontSizeMultiplier();
}
/**
* @param gt
* comment text
*/
public void setGeoText(GeoText gt) {
if (gt != null) {
commentText = gt;
// setInput(gt.toString());
}
}
/**
* @return comment text
*/
public GeoText getGeoText() {
return commentText;
}
/**
* @return whether input and output are empty
*/
public boolean isEmpty() {
return isInputEmpty() && isOutputEmpty();
}
/**
* @return whether input is empty
*/
public boolean isInputEmpty() {
return getInputVE() == null;
}
/**
* @return whether output is empty
*/
public boolean isOutputEmpty() {
return outputVE == null && error == null;
}
/**
* @return true if output is not empty and can be shown
*/
public boolean showOutput() {
return !isOutputEmpty() && !suppressOutput();
}
private boolean suppressOutput() {
return suppressOutput && !isError();
}
/**
* Returns if this GeoCasCell has a twinGeo or not
*
* @return if this GeoCasCell has a twinGeo or not
*/
public boolean hasTwinGeo() {
return twinGeo != null;
}
/**
* Sets the input of this row using the current casTwinGeo.
*
* @param force
* force update (needed if twin geo is a slider)
* @param dragging
* whether this was triggered by drag
*/
public void setInputFromTwinGeo(boolean force, boolean dragging) {
if (ignoreTwinGeoUpdate && !force) {
return;
}
if (twinGeo != null && twinGeo.isIndependent()
&& twinGeo.isLabelSet()) {
// Update ASSIGNMENT of twin geo
// e.g. m = 8 changed in GeoGebra should set cell to m := 8
String assignmentStr = twinGeo
.toCasAssignment(StringTemplate.defaultTemplate);
if (suppressOutput) {
assignmentStr = assignmentStr + ";";
}
String evalCmd1 = evalCmd;
if (setInput(assignmentStr)) {
if ("Numeric".equals(evalCmd1)) {
setProcessingInformation("",
"Numeric["
+ evalVE.toString(
StringTemplate.defaultTemplate)
+ "]",
"");
}
setEvalCommand(evalCmd1);
// GGB-1249 don't update the cell if dragging
if (!dragging) {
computeOutput(false, false);
}
update();
}
}
}
/**
* Sets the input of this row.
*
* @param inValue
* input value
* @return success
*/
public boolean setInput(String inValue) {
return setInput(inValue, false);
}
/**
* Sets the input of this row.
*
* @param inValue
* input value
* @param internalInput
* true if the input is in internal format, otherwise false (i.e.
* user input)
* @return success
*/
public boolean setInput(String inValue, boolean internalInput) {
String inNotNull = inValue != null ? inValue : "";
// if the cell is used as comment, treat it as empty
if (useAsText) {
suppressOutput = true;
setInputVE(null);
this.commentText.setTextString(inNotNull);
} else { // parse input into valid expression
suppressOutput = inNotNull.endsWith(";");
// with nSolve command do not update inputVE
// only the input string
if (!nSolveCmdNeeded) {
setInputVE(parseGeoGebraCASInputAndResolveDummyVars(inNotNull));
}
}
latexInput = null;
input = inNotNull; // remember exact user input
prefix = "";
evalVE = getInputVE();
postfix = "";
setEvalCommand("");
setEvalComment("");
setError(null);
// update input and output variables
updateInputVariables(getInputVE());
// input should have internal command names
if (!internalInput) {
internalizeInput();
}
// for efficiency: input with localized command names
updateLocalizedInput(StringTemplate.defaultTemplate, input);
// make sure computeOutput() knows that input has changed
firstComputeOutput = true;
if (!isEmpty()) {
// make sure we put this casCell into the construction set
cons.addToGeoSetWithCasCells(this);
}
return true;
}
private void updateLocalizedInput(final StringTemplate tpl,
final String input1) {
// for efficiency: localized input with local command names
currentLocaleStr = getLoc().getLocaleStr();
localizedInput = localizeInput(input1, tpl);
}
/**
* Sets row number for CAS view. This method should only be called by
* {@link Construction#updateCasCellRows()}
*
* @param row
* row number
*/
final public void setRowNumber(final int row) {
this.row = row;
}
/***
* Returns position of the given GeoCasCell object (free or dependent) in
* the construction list. This is the row number used in the CAS view.
*
* @return row number of casCell for CAS view or -1 if casCell is not in
* construction list
*/
final public int getRowNumber() {
return row;
}
/**
* Updates row references strings in input by setting input =
* inputVE.toString()
*/
public void updateInputStringWithRowReferences() {
updateInputStringWithRowReferences(false);
}
/**
* Updates input strings row references
*
* @param force
* true if update variable names also
*/
public void updateInputStringWithRowReferences(boolean force) {
if (!includesRowReferences && !force) {
return;
}
// inputVE will print the correct label, e.g. $4 for
// the row reference
input = getInputVE().toAssignmentString(StringTemplate.noLocalDefault,
getAssignmentType());
// TODO this always translates input.
updateLocalizedInput(StringTemplate.defaultTemplate,
getInputVE().toAssignmentString(StringTemplate.defaultTemplate,
getAssignmentType()));
if (suppressOutput) { // append ; if output is suppressed
input = input + ";";
localizedInput = localizedInput + ";";
}
}
/**
* Sets how this row should be evaluated. Note that the input is NOT changed
* by this method, so you need to call setInput() first. Make sure that
* input = prefix + eval without wrapper command + postfix.
*
* @param prefix
* beginning part that should NOT be evaluated, e.g. "25a +"
* @param evaluate
* part of the input that needs to be evaluated, e.g.
* "Expand[(a+b)^2]"
* @param postfix
* end part that should NOT be evaluated, e.g. " + "5 (c+d)"
*/
public void setProcessingInformation(final String prefix,
final String evaluate, final String postfix) {
String eval = evaluate;
// needed for TRAC-3081
if (eval.contains("CLIPBOARDmagicSTRING")) {
eval = eval.replaceAll("CLIPBOARDmagicSTRING", "");
}
String postfix1 = postfix;
String prefix1 = prefix;
setEvalCommand("");
setEvalComment("");
if (prefix1 == null) {
prefix1 = "";
}
if (postfix1 == null) {
postfix1 = "";
}
// stop if input is assignment
if (isAssignmentVariableDefined()) {
eval = prefix1 + eval + postfix1;
prefix1 = "";
postfix1 = "";
}
// commented since this causes mode changes to evaluate to be ignored
// when the input remains the same.
// see ticket #1620
//
// nothing to do
// if ("".equals(prefix) && "".equals(postfix) &&
// localizedInput.equals(eval))
// return;
// parse eval text into valid expression
evalVE = parseGeoGebraCASInputAndResolveDummyVars(eval);
if (inputVE != null && inputVE.getLabel() != null && evalVE != null) {
evalVE.setLabel(inputVE.getLabel());
}
if (evalVE != null) {
evalVE = resolveInputReferences(evalVE, inGeos);
if (evalVE.isTopLevelCommand()) {
// extract command from eval
setEvalCommand(evalVE.getTopLevelCommand().getName());
}
this.prefix = prefix1;
this.postfix = postfix1;
} else {
evalVE = getInputVE();
this.prefix = "";
this.postfix = "";
}
}
// private boolean hasPrefixOrPostfix() {
// return prefix.length() > 0 && postfix.length() > 0;
// }
/**
* Checks if newInput is structurally equal to the current input String.
*
* a+b/c is equal to a+(b/c), but not to (a+b)/c
*
* @param newInput
* new input
* @return whether newInput and current input have same stucture
*/
public boolean isStructurallyEqualToLocalizedInput(final String newInput) {
if (localizedInput != null && localizedInput.equals(newInput)) {
return true;
}
if (!kernel.getGeoGebraCAS().isStructurallyEqual(getInputVE(), newInput,
getKernel())) {
setError("CAS.SelectionStructureError");
return false;
}
return true;
}
/**
* Parses the given expression and resolves variables as GeoDummy objects.
* The result is returned as a ValidExpression.
*/
private ValidExpression parseGeoGebraCASInputAndResolveDummyVars(
final String inValue) {
try {
return (kernel.getGeoGebraCAS()).getCASparser()
.parseGeoGebraCASInputAndResolveDummyVars(inValue,
getKernel(), this);
} catch (CASException c) {
setError(getLoc().getError(c.getKey()));
return null;
} catch (Throwable e) {
return null;
}
}
/**
* Updates the set of input variables and array of input GeoElements. For
* example, the input "b := a + 5" has the input variable "a"
*/
private void updateInputVariables(final ValidExpression ve) {
// clear var sets
clearInVars();
if (ve == null || useAsText) {
return;
}
// get all command names
HashSet<Command> commands = new HashSet<Command>();
ve.traverse(CommandCollector.getCollector(commands));
if (commands.isEmpty()) {
commands = null;
} else {
for (Command cmd : commands) {
String cmdName = cmd.getName();
// Numeric used
includesNumericCommand = includesNumericCommand
|| ("Numeric".equals(cmdName)
&& cmd.getArgumentNumber() > 1)
|| "ScientificText".equals(cmdName);
// if command not known to CAS
if (!kernel.getGeoGebraCAS().isCommandAvailable(cmd)) {
if (kernel.lookupCasCellLabel(cmdName) != null
|| kernel.lookupLabel(cmdName) != null) {
// treat command name as defined user function name
getInVars().add(cmdName);
} else if (kernel.getAlgebraProcessor()
.isCommandAvailable(cmdName)) {
// command is known to GeoGebra: use possible fallback
useGeoGebraFallback = true;
} else {
// treat command name as undefined user function name
getInVars().add(cmdName);
}
}
}
}
useGeoGebraFallback = useGeoGebraFallback
|| ve.inspect(Inspecting.textFinder);
// get all used GeoElement variables
// check for function
boolean isFunction = ve instanceof FunctionNVar;
// get input vars. Do this *before* we set the assignment variable to
// avoid name clash,
// see #2599
// f(x)=FitPoly[...] has no x on RHS, but we need it
if (ve instanceof FunctionNVar) {
for (FunctionVariable fv : ((FunctionNVar) ve)
.getFunctionVariables()) {
getFunctionVars()
.add(fv.toString(StringTemplate.defaultTemplate));
}
}
HashSet<GeoElement> geoVars = ve.getVariables();
if (geoVars != null) {
for (GeoElement geo : geoVars) {
String var = geo.getLabel(StringTemplate.defaultTemplate);
if (isFunction && ((FunctionNVar) ve).isFunctionVariable(var)) {
// function variable, e.g. k in f(k) := k^2 + 3
getFunctionVars().add(var);
} else {
// input variable, e.g. b in a + 3 b
getInVars().add(var);
cons.getCASdummies().addAll(invars);
}
}
}
switch (getAssignmentType()) {
case NONE:
setAssignmentVar(null);
break;
// do that only if the expression is an assignment
case DEFAULT:
// outvar of assignment b := a + 5 is "b"
setAssignmentVar(ve.getLabel());
break;
case DELAYED:
setAssignmentVar(ve.getLabel());
break;
}
if (ve.getLabel() != null && getFunctionVars().isEmpty()) {
String var = getFunctionVariable(ve, getKernel());
if (var != null) {
getFunctionVars().add(var);
}
}
// create Array of defined input GeoElements
inGeos = updateInputGeoElements(invars);
// replace GeoDummyVariable objects in inputVE by the found inGeos
// This is important for row references and renaming of inGeos to work
setInputVE(resolveInputReferences(getInputVE(), inGeos));
// check for circular definition
isCircularDefinition = false;
if (inGeos != null) {
for (GeoElement inGeo : inGeos) {
if (inGeo.isChildOf(this) || this.equals(inGeo)) {
isCircularDefinition = true;
setError("CircularDefinition");
}
}
}
}
private static String getFunctionVariable(final ValidExpression ve,
Kernel kernel) {
if (!ve.isTopLevelCommand()) {
return null;
}
Command cmd = ve.getTopLevelCommand();
if ("Derivative".equals(cmd.getName())) {
if (cmd.getArgumentNumber() > 1) {
if (!cmd.getArgument(1).isLeaf() || !(cmd.getArgument(1)
.getLeft() instanceof GeoDummyVariable)) {
return null;
}
return ((GeoElement) cmd.getArgument(1).getLeft())
.toString(StringTemplate.defaultTemplate);// StringTemplate.defaultTemplate);
}
Iterator<GeoElement> it = cmd.getArgument(0).getVariables()
.iterator();
while (it.hasNext()) {
GeoElement em = it.next();
if (kernel.lookupLabel(
em.toString(StringTemplate.defaultTemplate)) == null) {
if (em instanceof VarString) {
return ((VarString) em)
.getVarString(StringTemplate.defaultTemplate);
}
}
}
}
return null;
}
/**
* Sets input to use internal command names and translatedInput to use
* localized command names. As a side effect, all command names are added as
* input variables as they could be function names.
*/
private void internalizeInput() {
// local commands -> internal commands
input = GgbScript.localizedScript2Script(kernel.getApplication(),
input);
}
/**
* Returns the input using command names in the current language.
*/
private String localizeInput(final String input1,
final StringTemplate tpl) {
// replace all internal command names in input by local command names
if (tpl.isPrintLocalizedCommandNames()) {
// internal commands -> local commands
return GgbScript.script2LocalizedScript(kernel.getApplication(),
input1);
}
// keep internal commands
return input1;
}
// make sure we don't enter setAssignmentVar from itself
private boolean ignoreSetAssignment = false;
/**
* Set assignment var of this cell. For example "b := a^2 + 3" has
* assignment var "b".
*
* @param var
*/
private void setAssignmentVar(final String var) {
if (ignoreSetAssignment) {
return;
}
if (assignmentVar != null && assignmentVar.equals(var)) {
return;
}
if (assignmentVar != null) {
// remove old label from construction
cons.removeCasCellLabel(assignmentVar);
}
if (var == null) {
assignmentVar = null;
// make sure we are using an unused label
} else if (cons.isFreeLabel(var)) {
// check for invalid assignment variables like $, $$, $1, $2, ...,
// $1$, $2$, ... which are dynamic references
if (!LabelManager.validVar(var)) {
setError("CAS.VariableIsDynamicReference");
}
assignmentVar = var;
}
// needed for GGB-450
else if (cons.isFileLoading() && inputVE.getLabel().equals(var)) {
if (!LabelManager.validVar(var)) {
setError("CAS.VariableIsDynamicReference");
}
assignmentVar = var;
} else {
changeAssignmentVar(var, getPointVectorDefault(var));
}
// store label of this CAS cell in Construction
if (assignmentVar != null) {
if (twinGeo != null) {
ignoreSetAssignment = true;
twinGeo.rename(assignmentVar);
}
updateDependentCellInput();
cons.putCasCellLabel(this, assignmentVar);
} else {
// remove twinGeo if we had one
setTwinGeo(null);
}
ignoreSetAssignment = false;
}
/**
* Replace old assignment var in input, e.g. "m := 8" becomes "a := 8"
*
* @param oldLabel
* @param newLabel
*/
private void changeAssignmentVar(final String oldLabel,
final String newLabel) {
if (newLabel.equals(oldLabel)) {
return;
}
getInputVE().setLabel(newLabel);
if (oldLabel != null) {
input = input.replaceFirst(oldLabel, newLabel);
if (latexInput != null && latexInput.indexOf(oldLabel) >= 0) {
latexInput = latexInput.replaceFirst(oldLabel, newLabel);
} else {
latexInput = null;
}
localizedInput = localizedInput.replaceFirst(oldLabel, newLabel);
}
assignmentVar = newLabel;
}
private TreeSet<String> getInVars() {
if (invars == null) {
invars = new TreeSet<String>();
}
return invars;
}
private TreeSet<String> getFunctionVars() {
if (functionvars == null) {
functionvars = new TreeSet<String>();
}
return functionvars;
}
private void clearInVars() {
invars = null;
functionvars = null;
includesRowReferences = false;
includesNumericCommand = false;
useGeoGebraFallback = false;
}
/**
* Returns the n-th input variable (in alphabetical order).
*
* @param n
* index
* @return n-th input variable
*/
public String getInVar(int n) {
if (invars == null) {
return null;
}
Iterator<String> it = invars.iterator();
int pos = 0;
while (it.hasNext()) {
String var = it.next();
if (pos == n) {
return var;
}
pos++;
}
return null;
}
/**
* Returns all GeoElement input variables including GeoCasCell objects and
* row references in construction order.
*
* @return input GeoElements including GeoCasCell objects
*/
public TreeSet<GeoElement> getGeoElementVariables() {
if (inGeos == null) {
inGeos = updateInputGeoElements(invars);
}
return inGeos;
}
private TreeSet<GeoElement> updateInputGeoElements(
final TreeSet<String> inputVars) {
if (inputVars == null || inputVars.isEmpty()) {
return null;
}
// list to collect geo variables
TreeSet<GeoElement> geoVars = new TreeSet<GeoElement>();
// go through all variables
for (String varLabel : inputVars) {
// lookup GeoCasCell first
GeoElement geo = kernel.lookupCasCellLabel(varLabel);
if (geo == null) {
// try row reference lookup
// $ for previous row
if (varLabel.equals(
ExpressionNodeConstants.CAS_ROW_REFERENCE_PREFIX)) {
geo = row > 0 ? cons.getCasCell(row - 1)
: cons.getLastCasCell();
} else {
try {
geo = kernel.lookupCasRowReference(varLabel);
} catch (CASException ex) {
this.setError(ex.getKey());
return null;
}
}
if (geo != null) {
includesRowReferences = true;
}
}
if (geo == null) {
// now lookup other GeoElements
geo = kernel.lookupLabel(varLabel);
if (geo != null && geo.getCorrespondingCasCell() != null) {
// this is a twin geo of a CAS cell
// input will be set from CAS
geo = geo.getCorrespondingCasCell();
}
}
if (geo != null) {
// add found GeoElement to variable list
geoVars.add(geo);
}
}
if (geoVars.size() == 0) {
return null;
}
return geoVars;
}
/**
* Replaces GeoDummyVariable objects in inputVE by the found inGeos. This is
* important for row references and renaming of inGeos to work.
*/
private ValidExpression resolveInputReferences(final ValidExpression ve,
final TreeSet<GeoElement> inputGeos) {
if (ve == null) {
return ve;
}
AssignmentType assign = getAssignmentType();
ValidExpression ret;
// make sure we have an expression node
ExpressionNode node;
if (ve.isTopLevelCommand() && getFunctionVars().iterator().hasNext()) {
Log.warn("wrong function syntax");
String[] labels = ve.getLabels();
if (ve instanceof ExpressionNode) {
node = (ExpressionNode) ve;
} else {
node = new ExpressionNode(kernel, ve);
}
ret = new Function(node, new FunctionVariable(kernel,
getFunctionVars().iterator().next()));
ret.setLabels(labels);
} else if (ve instanceof FunctionNVar) {
node = ((FunctionNVar) ve).getExpression();
ret = ve; // make sure we return the Function
} else if (ve instanceof ExpressionNode) {
node = (ExpressionNode) ve;
ret = ve; // return the original ExpressionNode
} else {
node = new ExpressionNode(kernel, ve);
node.setLabel(ve.getLabel());
ret = node; // return a new ExpressionNode
}
// replace GeoDummyVariable occurances for each geo
if (inputGeos != null) {
for (GeoElement inGeo : inputGeos) {
// replacement uses default template
GeoDummyReplacer ge = GeoDummyReplacer.getReplacer(
inGeo.getLabel(StringTemplate.defaultTemplate), inGeo,
false);
node.traverse(ge);
if (!ge.didReplacement()) {
// try $ row reference
ge = GeoDummyReplacer.getReplacer(
ExpressionNodeConstants.CAS_ROW_REFERENCE_PREFIX,
inGeo, false);
node.traverse(ge);
}
}
}
// handle GeoGebra Fallback
if (useGeoGebraFallback) {
if (!includesOnlyDefinedVariables(true)) {
useGeoGebraFallback = false;
}
}
setAssignmentType(assign);
return ret;
}
/**
* Replaces GeoDummyVariable objects in outputVE by the function inGeos.
* This is important for row references and renaming of inGeos to work.
*/
private static void resolveFunctionVariableReferences(
final ValidExpression outputVE) {
if (!(outputVE instanceof FunctionNVar)) {
return;
}
FunctionNVar fun = (FunctionNVar) outputVE;
// replace function variables in tree
for (FunctionVariable fVar : fun.getFunctionVariables()) {
// look for GeoDummyVariable objects with name of function variable
// and replace them
fun.getExpression().replaceVariables(fVar.getSetVarString(), fVar);
}
}
/**
* Replaces GeoDummyVariable objects in outputVE by GeoElements from kernel
* that are not GeoCasCells.
*/
private void resolveGeoElementReferences(final ValidExpression outVE) {
if (invars == null || !(outVE instanceof FunctionNVar)) {
return;
}
FunctionNVar fun = (FunctionNVar) outVE;
// replace function variables in tree
for (String varLabel : invars) {
GeoElement geo = kernel.lookupLabel(varLabel);
if (geo != null) {
// look for GeoDummyVariable objects with name of function
// variable and replace them
GeoDummyReplacer ge = GeoDummyReplacer.getReplacer(varLabel,
geo, false);
fun.getExpression().traverse(ge);
}
}
}
/**
* Returns whether this object only depends on named GeoElements defined in
* the kernel.
*
* @return whether this object only depends on named GeoElements
*/
final public boolean includesOnlyDefinedVariables() {
return includesOnlyDefinedVariables(false);
}
/**
* Same as previous function, except ignoring the undefined variables x and
* y to provide definition of functions like: f: x+y=1
*
* @param ignoreUndefinedXY
* true to ignore x,y
* @return whether this object only depends on named GeoElements
*/
final public boolean includesOnlyDefinedVariables(
final boolean ignoreUndefinedXY) {
if (invars == null) {
return true;
}
for (String varLabel : invars) {
if (!(ignoreUndefinedXY
&& ("x".equals(varLabel) || "y".equals(varLabel)))) {
// definitions
// of
// funktions
// like
// f:
// x+y =
// 1
// //TODO:
// find
// a
// better
// way
if (kernel.lookupLabel(varLabel) == null) {
return false;
}
}
}
return true;
}
/**
* Returns whether var is an input variable of this cell. For example, "b"
* is an input variable of "c := a + b"
*
* @param var
* variable name
* @return whether var is an input variable of this cell
*/
final public boolean isInputVariable(final String var) {
return invars != null && invars.contains(var);
}
/**
* Returns whether var is a function variable of this cell. For example, "y"
* is a function variable of "f(y) := 2y + b"
*
* @param var
* variable name
* @return whether var is a function variable of this cell
*/
final public boolean isFunctionVariable(final String var) {
return functionvars != null && functionvars.contains(var);
}
/**
* Returns the function variable string if input is a function or null
* otherwise. For example, "m" is a function variable of "f(m) := 2m + b"
*
* @return function variable string
*/
final public String getFunctionVariable() {
if (functionvars != null && !functionvars.isEmpty()) {
return functionvars.first();
}
return null;
}
/**
* Returns whether this cell includes row references like $2.
*
* @return whether this cell includes row references like $2.
*/
final public boolean includesRowReferences() {
return includesRowReferences;
}
/**
* Returns whether this cell includes any Numeric[] commands.
*
* @return whether this cell includes any Numeric[] commands.
*/
final public boolean includesNumericCommand() {
return includesNumericCommand;
}
/**
* Returns the assignment variable of this cell. For example, "c" is the
* assignment variable of "c := a + b"
*
* @return may be null
*/
final public String getAssignmentVariable() {
return assignmentVar;
}
/**
* @return true if assignment variable is defined
*/
final public boolean isAssignmentVariableDefined() {
return assignmentVar != null;
}
/**
* @param cmd
* command
*/
final public void setEvalCommand(final String cmd) {
if ("Evaluate".equals(cmd)) {
evalCmd = "";
setKeepInputUsed(false);
return;
}
if ("Substitute".equals(cmd)) {
updateInputVariables(evalVE);
}
evalCmd = cmd == null ? "" : cmd;
// includesNumericCommand = includesNumericCommand || evalCmd != null
// && "Numeric".equals(evalCmd);
setKeepInputUsed(
evalCmd != null && evalCmd.toLowerCase().equals("keepinput"));
}
/**
* @param keepInputUsed
* true if KeepInput was used
*/
public void setKeepInputUsed(final boolean keepInputUsed) {
this.keepInputUsed = keepInputUsed;
}
/**
* @param comment
* comment
*/
final public void setEvalComment(final String comment) {
if (comment != null) {
if (!"".equals(comment)) {
setSubstList(getSubstListFromSubstComment(comment));
}
if (evalComment != null && !"".equals(evalComment)
&& "".equals(comment)) {
setSubstList(getSubstListFromSubstComment(evalComment));
}
evalComment = comment;
}
}
/**
* @param output
* output string (from CAS)
* @param prependLabel
* whether f(x):= must be prepended to output before evaluation
*/
public void setOutput(final String output, boolean prependLabel) {
error = null;
clearStrings();
// when input is a function declaration, output also needs to become a
// function
// so we need to add f(x,y) := if it is missing
boolean isFunctionDeclaration = isAssignmentVariableDefined()
&& functionvars != null && !functionvars.isEmpty();
// note: MPReduce returns "f" for a function definition "f(x) := x^2"
// && !output.startsWith(assignmentVar);
if (nativeOutput) {
String res = output;
if (isFunctionDeclaration && prependLabel) {
// removing y from expressions y = x! and
outputVE = (ValidExpression) parseGeoGebraCASInputAndResolveDummyVars(
res).traverse(Traversing.FunctionCreator.getCreator());
StringBuilder sb = new StringBuilder();
sb.append(getInputVE().getLabelForAssignment());
switch (getAssignmentType()) {
case DEFAULT:
sb.append(getInputVE().getAssignmentOperator());
break;
case DELAYED:
sb.append(getInputVE().getDelayedAssignmentOperator());
break;
case NONE:
break;
}
// #5119 make sure internally the result does not depend on
// rounding
sb.append(outputVE.toString(StringTemplate.numericDefault));
res = sb.toString();
}
// parse output into valid expression
ValidExpression parsed = parseGeoGebraCASInputAndResolveDummyVars(
res);
if ((evalCmd != null && "NSolve".equals(evalCmd))
|| (inputVE != null && inputVE.getTopLevelCommand() != null
&& inputVE.getTopLevelCommand().getName()
.equals("NSolve"))) {
parsed = removeComplexResults(parsed);
}
outputVE = parsed == null ? null
: (ValidExpression) parsed
.traverse(Traversing.GgbVectRemover.getInstance());
// needed for GGB-810
// replace geoDummys with constants
if (arbconst != null) {
ArrayList<GeoNumeric> constList = arbconst.getConstList();
if (!constList.isEmpty()) {
for (GeoNumeric geoNum : constList) {
geoNum.setSendValueToCas(true);
GeoDummyReplacer replacer = GeoDummyReplacer
.getReplacer(geoNum.getLabelSimple(), geoNum,
false);
outputVE.traverse(replacer);
}
}
}
if (outputVE != null) {
CommandReplacer cr = CommandReplacer.getReplacer(kernel, true);
outputVE.traverse(cr);
if (inputVE != null) {
if (inputVE.isTopLevelCommand("Vector")) {
ExpressionNode wrapped = outputVE.wrap();
wrapped.setForceVector();
outputVE = wrapped;
}
}
} else {
setError("CAS.GeneralErrorMessage");
}
}
if (isFunctionDeclaration) {
// replace GeoDummyVariable objects in outputVE by the function
// variables
resolveFunctionVariableReferences(outputVE);
// replace GeoDummyVariable objects in outputVE by GeoElements from
// kernel
resolveGeoElementReferences(outputVE);
} else if (isAssignmentVariableDefined()) {
outputVE.setLabel(assignmentVar);
if (Character.isLowerCase(assignmentVar.charAt(0))) {
ExpressionValue ve = outputVE.unwrap();
if (ve instanceof MyVecNode) {
MyVecNode node = (MyVecNode) ve;
node.setCASVector();
} else if (ve instanceof MyVec3DNode) {
MyVec3DNode node3d = (MyVec3DNode) ve;
node3d.setCASVector();
}
}
}
}
private ValidExpression removeComplexResults(ValidExpression ve) {
if (ve instanceof ExpressionNode
&& ((ExpressionNode) ve).getLeft() instanceof MyList
&& ((ExpressionNode) ve).getRight() == null) {
ArrayList<ExpressionValue> results = new ArrayList<ExpressionValue>();
for (int i = 0; i < ((MyList) ((ExpressionNode) ve).getLeft())
.getLength(); i++) {
boolean isComplex = ((MyList) ((ExpressionNode) ve).getLeft())
.getListElement(i)
.inspect(Inspecting.ComplexChecker.INSTANCE);
if (!isComplex) {
results.add(((MyList) ((ExpressionNode) ve).getLeft())
.getListElement(i));
}
}
MyList filteredResultList = new MyList(kernel, results.size());
if (!results.isEmpty()) {
for (ExpressionValue ev : results) {
filteredResultList.addListElement(ev);
}
}
return new ExpressionNode(kernel, filteredResultList);
}
return ve;
}
/**
* Updates the given GeoElement using the given casExpression.
*
* @param allowFunction
* whether we can use eg x as function (false: x is just a dummy)
*/
public void updateTwinGeo(boolean allowFunction) {
ignoreTwinGeoUpdate = true;
if (firstComputeOutput && twinGeo == null) {
// create twin geo
createTwinGeo(allowFunction);
} else {
// input did not change: just do a simple update
simpleUpdateTwinGeo(allowFunction);
}
ignoreTwinGeoUpdate = false;
}
/**
* Creates a twinGeo using the current output
*/
private void createTwinGeo(boolean allowFunction) {
if (isError()) {
return;
}
boolean isLine = false;
// case we have 3DLine
if (inputVE != null && inputVE.getTopLevelCommand() != null
&& inputVE.getTopLevelCommand().getName().equals("Line")
&& outputVE instanceof Equation
&& ((Equation) outputVE).getLHS().getLeft()
.toString(StringTemplate.defaultTemplate).equals("X")
&& ((Equation) outputVE).getRHS().getLeft()
.evaluatesTo3DVector()) {
isLine = true;
}
if (!isAssignmentVariableDefined()) {
return;
}
if (isNative() && (getInputVE() instanceof Function)
&& (outputVE instanceof ExpressionNode)) {
String[] labels = outputVE.getLabels();
outputVE = new Function((ExpressionNode) outputVE,
((Function) getInputVE()).getFunctionVariable());
outputVE.setLabels(labels);
} else if (isNative() && (getInputVE() instanceof FunctionNVar)
&& (outputVE instanceof ExpressionNode)) {
String[] labels = outputVE.getLabels();
outputVE = new FunctionNVar((ExpressionNode) outputVE,
((FunctionNVar) getInputVE()).getFunctionVariables());
outputVE.setLabels(labels);
}
// check that assignment variable is not a reserved name in GeoGebra
if (!isLine && kernel.getApplication().getParserFunctions()
.isReserved(assignmentVar)) {
return;
}
// needed for GGB-450
GeoElement geo = null;
// if file is loading check for already existent
// twingeo with assignmentVar label
if (cons.isFileLoading() && assignmentVar != null) {
geo = kernel.lookupLabel(assignmentVar);
}
// try to create twin geo for assignment, e.g. m := c + 3
ArbconstReplacer repl = ArbconstReplacer.getReplacer(arbconst);
arbconst.reset();
outputVE.traverse(repl);
setEquationMode();
GeoElement newTwinGeo = null;
if (isLine && outputVE instanceof Equation) {
try {
boolean old = kernel.getConstruction().isSuppressLabelsActive();
kernel.getConstruction().setSuppressLabelCreation(true);
GeoElement[] line = kernel.getAlgebraProcessor()
.doProcessValidExpression(outputVE,
new EvalInfo(false));
kernel.getConstruction().setSuppressLabelCreation(old);
newTwinGeo = line[0];
} catch (MyError e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CircularDefinitionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
HashSet<FunctionVariable> fVarSet = new HashSet<FunctionVariable>();
if (isFunctionProducingCommand()) {
((ExpressionNode) outputVE).setForceFunction();
TreeSet<String> varSet = new TreeSet<String>(
new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
evalVE.traverse(
Traversing.DummyVariableCollector.getCollector(varSet));
Iterator<String> it = varSet.iterator();
// collect function variables
while (it.hasNext() && varSet.size() != 1) {
String curFVar = it.next();
if ("y".equals(curFVar)) {
FunctionVariable fv = new FunctionVariable(kernel,
curFVar);
fVarSet.add(fv);
}
}
}
newTwinGeo = silentEvalInGeoGebra(outputVE, allowFunction);
// twingeo exists
// change newTwinGeo
if (geo != null) {
newTwinGeo = geo;
}
// update newTwinGeo as multivariable function
if (isFunctionProducingCommand() && !fVarSet.isEmpty()
&& newTwinGeo instanceof GeoFunction) {
FunctionVariable[] funcVars = ((GeoFunction) newTwinGeo)
.getFunctionVariables();
FunctionVariable[] newFuncVars = new FunctionVariable[funcVars.length
+ fVarSet.size()];
Iterator<FunctionVariable> it = fVarSet.iterator();
while (it.hasNext()) {
FunctionVariable curFV = it.next();
int i;
for (i = 0; i < funcVars.length; i++) {
newFuncVars[i] = funcVars[i];
}
newFuncVars[i] = curFV;
i++;
}
FunctionNVar newFNV = new FunctionNVar(
((GeoFunction) newTwinGeo).getFunctionExpression(),
newFuncVars);
newTwinGeo = new GeoFunctionNVar(cons, newFNV);
}
if (uniformListCommand() && newTwinGeo instanceof GeoList) {
makePlotable((GeoList) newTwinGeo);
}
}
if (outputVE.unwrap() instanceof GeoElement
&& ((GeoElement) outputVE.unwrap())
.getDrawAlgorithm() instanceof DrawInformationAlgo) {
newTwinGeo.setDrawAlgorithm(
(DrawInformationAlgo) ((GeoElement) outputVE.unwrap())
.getDrawAlgorithm());
}
if (newTwinGeo != null && !dependsOnDummy(newTwinGeo)) {
setTwinGeo(newTwinGeo);
if (twinGeo instanceof GeoImplicit) {
((GeoImplicit) twinGeo).setInputForm();
}
if (newTwinGeo instanceof GeoNumeric) {
newTwinGeo.setLabelVisible(true);
}
}
}
private boolean uniformListCommand() {
if (inputVE == null) {
return false;
}
return inputVE.isTopLevelCommand("Sequence")
|| inputVE.isTopLevelCommand("Zip")
|| inputVE.isTopLevelCommand("KeepIf")
|| inputVE.isTopLevelCommand("IterationList");
}
private static void makePlotable(GeoList list) {
if (list.size() < 2) {
return;
}
boolean hasFunction = false;
for (int i = 0; i < list.size() && !hasFunction; i++) {
if (list.get(i).isGeoFunction()) {
hasFunction = true;
}
}
if (hasFunction) {
for (int i = 0; i < list.size(); i++) {
if (list.get(i).isGeoNumeric()) {
list.setListElement(i,
((GeoNumeric) list.get(i)).getGeoFunction());
}
}
}
}
/**
* @return whether the output should be considered a function even if it is
* just a number eg. plotting LeftSide[7=x] should produce f(x)=7
*/
private boolean isFunctionProducingCommand() {
if (evalVE == null || evalVE.getTopLevelCommand() == null) {
return false;
}
String name = evalVE.getTopLevelCommand().getName();
if ("LeftSide".equals(name) || "RightSide".equals(name)) {
return true;
}
return false;
}
private void setEquationMode() {
if (this.inputVE != null && this.inputVE.unwrap() instanceof Equation
&& this.inputVE.inspect(new Inspecting() {
@Override
public boolean check(ExpressionValue v) {
return (v instanceof FunctionVariable
|| v instanceof GeoDummyVariable)
&& "z".equals(v.toString(
StringTemplate.defaultTemplate));
}
})) {
if (outputVE.unwrap() instanceof Equation) {
((Equation) outputVE.unwrap()).setForcePlane();
}
}
}
/**
* Sets the label of twinGeo.
*
* @return whether label was set
*/
public boolean setLabelOfTwinGeo() {
if (twinGeo == null || twinGeo.isLabelSet()
|| !isAssignmentVariableDefined()) {
return false;
}
// allow GeoElement to get same label as CAS cell, so we temporarily
// remove the label
// but keep it in the underlying CAS
cons.removeCasCellLabel(assignmentVar);
// set Label of twinGeo
twinGeo.setLabel(assignmentVar);
// set back CAS cell label
cons.putCasCellLabel(this, assignmentVar);
if (cons.isFileLoading()) {
updateConstructionDependencies();
}
return true;
}
// method to switch geoDummys with geoNumerics in outputVE and twinGeo
// needed for undo
private void updateConstructionDependencies() {
if (this.getInputVE() != null && this.getInputVE() instanceof Function
&& ((Function) this.getInputVE()).getFunctionExpression()
.getTopLevelCommand() != null
&& (((Function) this.getInputVE()).getFunctionExpression()
.getTopLevelCommand().getName().equals("Integral")
|| ((Function) this.getInputVE())
.getFunctionExpression().getTopLevelCommand()
.getName().equals("SolveODE"))) {
MyArbitraryConstant myArbConst = cons.getArbitraryConsTable()
.get(this.row);
if (this.arbconst.getConstList().isEmpty() && myArbConst != null) {
ArrayList<GeoNumeric> constList = myArbConst.getConstList();
if (!constList.isEmpty()) {
for (GeoNumeric geoNum : constList) {
cons.addToConstructionList(geoNum, false);
cons.putLabel(geoNum);
this.arbconst.getConstList().add(geoNum);
GeoDummyReplacer replacer = GeoDummyReplacer
.getReplacer(
geoNum.getLabelSimple(), geoNum, false);
if (outputVE != null) {
outputVE.traverse(replacer);
}
if (twinGeo instanceof GeoFunction
&& ((GeoFunction) twinGeo).getFunction() != null
&& ((GeoFunction) twinGeo)
.getFunctionExpression() != null) {
((GeoFunction) twinGeo).getFunctionExpression()
.traverse(replacer);
}
}
}
}
}
}
/**
* Sets twinGeo using current output
*/
private void simpleUpdateTwinGeo(boolean allowFunction) {
if (twinGeo == null) {
return;
} else if (isError()) {
twinGeo.setUndefined();
return;
}
ArbconstReplacer repl = ArbconstReplacer.getReplacer(arbconst);
arbconst.reset();
outputVE.traverse(repl);
setEquationMode();
// silent evaluation of output in GeoGebra
GeoElement lastOutputEvaluationGeo = silentEvalInGeoGebra(outputVE,
allowFunction);
// Log.debug(lastOutputEvaluationGeo);
if (lastOutputEvaluationGeo != null
&& !dependsOnDummy(lastOutputEvaluationGeo)) {
try {
if (Test.canSet(twinGeo, lastOutputEvaluationGeo)) {
if (lastOutputEvaluationGeo instanceof GeoNumeric
&& twinGeo instanceof GeoNumeric) {
((GeoNumeric) twinGeo)
.extendMinMax(lastOutputEvaluationGeo);
}
if (twinGeo instanceof GeoSurfaceCartesian3D
&& lastOutputEvaluationGeo instanceof GeoSurfaceCartesian3D) {
// when we replace twinGeo, dependent geos are also
// deleted from cons
twinGeo.doRemove();
notifyRemove();
twinGeo = lastOutputEvaluationGeo;
cons.addToConstructionList(twinGeo, true);
cons.putLabel(twinGeo);
twinGeo.notifyAdd();
twinGeo.setCorrespondingCasCell(this);
// add to construction casCell and parentAlgo
if (this.getParentAlgorithm() != null) {
cons.addToConstructionList(
this.getParentAlgorithm(), true);
}
cons.addToGeoSetWithCasCells(this);
if (assignmentVar == null) {
assignmentVar = twinGeo
.getLabel(StringTemplate.defaultTemplate);
}
}
// switch twinGeo with new evaluation
// needed for undo->function wasn't draggable
else {
if (uniformListCommand() && lastOutputEvaluationGeo instanceof GeoList) {
makePlotable((GeoList) lastOutputEvaluationGeo);
}
// if both geos are the same type we can use set safely
twinGeo.set(lastOutputEvaluationGeo);
// update constants references
if (lastOutputEvaluationGeo instanceof GeoFunction) {
ExpressionNode expr = ((FunctionalNVar) lastOutputEvaluationGeo)
.getFunctionExpression();
expr.inspect(new ArbconstAlgoFixer());
}
}
} else if (!lastOutputEvaluationGeo.isDefined()) {
// newly created GeoElement is undefined, we can set our
// twin geo undefined
twinGeo.setUndefined();
} else {
// different types:
// needed for TRAC-2635
// list wanted but we get line from giac
if (inputVE != null && inputVE.isTopLevelCommand("Tangent")
&& twinGeo instanceof GeoList
&& !(lastOutputEvaluationGeo instanceof GeoList)
&& (((Command) ((ExpressionNode) inputVE).getLeft())
.getArgumentNumber() == 2)) {
ExpressionNode[] args = ((Command) ((ExpressionNode) inputVE)
.getLeft()).getArguments();
// Tangent[Point, Conic]
if (args[0].getLeft() instanceof GeoPoint
&& args[1].getLeft() instanceof GeoConic) {
((GeoList) twinGeo).clear();
((GeoList) twinGeo).add(lastOutputEvaluationGeo);
}
} else {
twinGeo = lastOutputEvaluationGeo;
cons.replace(twinGeo, lastOutputEvaluationGeo);
}
}
if (outputVE.unwrap() instanceof GeoElement
&& ((GeoElement) outputVE.unwrap())
.getDrawAlgorithm() instanceof DrawInformationAlgo) {
twinGeo.setDrawAlgorithm(
(DrawInformationAlgo) ((GeoElement) outputVE
.unwrap()).getDrawAlgorithm());
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
// r2835: if the evaluation of outputVE returns null we have no twin
// geo, we remove the old one and return
// We only hide the geo from XML to avoid parsing eg 0=0 on next
// file load
twinGeo.setUndefined();
twinGeo.setAlgebraVisible(false);
cons.removeFromConstructionList(twinGeo);
return;
}
if (isIndependent()) {
twinGeo.update();
} else {
// AlgoDependentCasCell calls one more update; important to skip
// this because of spreadsheet trace
twinGeo.updateGeo(false);
}
}
@Override
public void updateCascade() {
update();
// Log.debug("updating"+getLabel(StringTemplate.defaultTemplate));
if (twinGeo != null && !dependsOnDummy(twinGeo)) {
ignoreTwinGeoUpdate = true;
twinGeo.update();
ignoreTwinGeoUpdate = false;
updateAlgoUpdateSetWith(twinGeo);
} else if (algoUpdateSet != null) {
// update all algorithms in the algorithm set of this GeoElement
algoUpdateSet.updateAll();
}
}
@Override
public void update(boolean drag) {
clearStrings();
super.update(drag);
}
/**
* Evaluates ValidExpression in GeoGebra and returns one GeoElement or null.
*
* @param ve
* @return result GeoElement or null
*/
private GeoElement silentEvalInGeoGebra(final ValidExpression ve,
boolean allowFunction) {
if (!nativeOutput && outputVE.isExpressionNode()
&& ((ExpressionNode) outputVE)
.getLeft() instanceof GeoElement) {
GeoElement ret = (GeoElement) ((ExpressionNode) outputVE).getLeft();
return ret;
}
boolean wasFunction = outputVE instanceof FunctionNVar;
boolean wasCurve = twinGeo == null || twinGeo.isParametric();
// replace variables x and y with a FunctionVariable object
FunctionVariable fvX = new FunctionVariable(kernel, "x");
Traversing variableReplacer = Traversing.VariableReplacer
.getReplacer("x", fvX, kernel);
ve.traverse(variableReplacer);
FunctionVariable fvY = new FunctionVariable(kernel, "y");
variableReplacer = Traversing.VariableReplacer.getReplacer("y", fvY,
kernel);
ve.traverse(variableReplacer);
if (kernel.getApplication().is3D()) {
FunctionVariable fvZ = new FunctionVariable(kernel, "z");
variableReplacer = Traversing.VariableReplacer.getReplacer("z", fvZ,
kernel);
ve.traverse(variableReplacer);
}
boolean oldValue = kernel.isSilentMode();
kernel.setSilentMode(true);
try {
// evaluate in GeoGebra
ExpressionNode copy = ve.deepCopy(kernel).wrap();
copy.setLabel(ve.getLabel());
kernel.getAlgebraProcessor().setDisableGcd(true);
GeoElement[] ggbEval = kernel.getAlgebraProcessor()
.doProcessValidExpression(copy,
new EvalInfo(false).withSimplifying(false));
if (ggbEval != null) {
if (!allowFunction && (ggbEval[0] instanceof FunctionalNVar)
&& !wasFunction) {
return null;
}
if (!allowFunction && (ggbEval[0].isParametric() && !wasCurve)) {
return null;
}
return ggbEval[0];
}
return null;
} catch (Throwable e) {
Log.error("GeoCasCell.silentEvalInGeoGebra: " + ve + "\n\terror: "
+ e.getMessage());
return null;
} finally {
kernel.setSilentMode(oldValue);
}
}
/**
* Computes the output of this CAS cell based on its current input settings.
* Note that this will also change a corresponding twinGeo.
*/
final public void computeOutput() {
// do not compute output if this cell is used as a text cell
if (!useAsText) {
// input VE is noll sometimes, ie if Solve is used on a=b+c,b
if (getEvalVE() == null) {
return;
}
computeOutput(getAssignmentType() != AssignmentType.DELAYED, false);
}
}
private MyArbitraryConstant arbconst = new MyArbitraryConstant(this);
private ValidExpression expandedEvalVE;
/**
* @return whether top level command is Substitute
*/
public boolean isSubstitute() {
Command cmd = evalVE.getTopLevelCommand();
return cmd != null && "Substitute".equals(cmd.getName());
}
/**
* Computes the output of this CAS cell based on its current input settings.
*
* @param doTwinGeoUpdate
* whether twin geo should be updated or not
*/
private void computeOutput(final boolean doTwinGeoUpdate,
final boolean allowFunction) {
// check for circular definition before we do anything
if (isCircularDefinition) {
setError("CircularDefinition");
if (doTwinGeoUpdate) {
updateTwinGeo(allowFunction);
}
return;
}
if (input.contains("Surface")) {
useGeoGebraFallback = true;
}
String result = null;
boolean success = false;
CASException ce = null;
nativeOutput = true;
if (inputVE != null && getAssignmentType() == AssignmentType.DELAYED) {
result = inputVE.wrap().toString(StringTemplate.numericNoLocal);
success = result != null;
} else if (!useGeoGebraFallback) {
// CAS EVALUATION
try {
if (evalVE == null) {
throw new CASException("Invalid input (evalVE is null)");
}
boolean isSubstitute = isSubstitute();
// wrap in Evaluate if it's an expression rather than a command
// needed for Giac (for simplifying x+x to 2x)
evalVE = wrapEvaluate(evalVE,
isSubstitute && !isKeepInputUsed());
// wrap in PointList if the top level command is Solutions
// and the assignment variable is defined
if (isAssignmentVariableDefined()) {
adjustPointList(true);
}
expandedEvalVE = pointList ? wrapPointList(evalVE) : evalVE;
if (expandedEvalVE.isTopLevelCommand()
&& !expandedEvalVE.isTopLevelCommand("Evaluate")
&& ((Command) expandedEvalVE
.unwrap()).getArgumentNumber() != 1
&& ((Command) expandedEvalVE
.unwrap()).getArgument(0) != null) {
ExpressionNode node = ((Command) expandedEvalVE.unwrap())
.getArgument(0);
if (!(node.getLeft() instanceof GeoSurfaceCartesian3D)
&& !(node.getRight() instanceof MyList)) {
// needed for GGB-494
// replace GeoSurfaceCartesian3D geos with MyVect3D with
// expressions of surface
expandedEvalVE = (ValidExpression) expandedEvalVE
.traverse(GeoSurfaceReplacer.getInstance());
}
}
if (!expandedEvalVE.isTopLevelCommand("Delete")
&& !this.getNSolveCmdNeeded()) {
FunctionExpander fex = FunctionExpander.getCollector();
expandedEvalVE = (ValidExpression) expandedEvalVE.wrap()
.getCopy(kernel).traverse(fex);
expandedEvalVE = processSolveCommand(expandedEvalVE);
// needed for GGB-955
expandedEvalVE = processSolutionCommand(expandedEvalVE);
}
// make work NSolve with cell input
if (expandedEvalVE.isTopLevelCommand("NSolve")
&& ((Command) expandedEvalVE.unwrap()).getArgument(0)
.getLeft() instanceof GeoCasCell) {
ExpressionNode inputVEofGeoCasCell = (ExpressionNode) ((GeoCasCell) ((Command) expandedEvalVE
.unwrap())
.getArgument(0).getLeft()).getInputVE();
((Command) expandedEvalVE.unwrap()).setArgument(0,
inputVEofGeoCasCell);
}
// hack needed for GGB-494
// Solve command with list of equs and list of vars
if (expandedEvalVE instanceof ExpressionNode
&& ((ExpressionNode) expandedEvalVE)
.getLeft() instanceof Command
&& "Solve"
.equals(((Command) ((ExpressionNode) expandedEvalVE)
.getLeft()).getName())
&& ((Command) ((ExpressionNode) expandedEvalVE)
.getLeft()).getArgumentNumber() == 2) {
// get list of equations
ExpressionValue equListV = ((Command) ((ExpressionNode) expandedEvalVE)
.getLeft()).getArgument(0).unwrap();
if (equListV instanceof MyList) {
MyList equList = (MyList) equListV;
// "x" geoDummy instead of functionVariable
GeoDummyVariable x = new GeoDummyVariable(cons, "x");
// "y" geoDummy instead of functionVariable
GeoDummyVariable y = new GeoDummyVariable(cons, "y");
for (int i = 0; i < equList.size(); i++) {
if (equList
.getListElement(i) instanceof ExpressionNode
&& equList.getListElement(i)
.unwrap() instanceof Equation) {
// set Equation in list of equs instead of
// ExpressionNode that contains Equation
equList.setListElement(i,
equList.getListElement(i).unwrap());
// Equation contains "x" functionVariable
// replace with simple GeoDummyVariable
equList.getListElement(i)
.traverse(GeoDummyReplacer
.getReplacer("x", x, true));
// Equation contains "y" functionVariable
// replace with simple GeoDummyVariable
equList.getListElement(i)
.traverse(GeoDummyReplacer
.getReplacer("y", y, true));
}
}
}
}
// we need the row number of this row
// to store the arbitrary constant in construction
cons.updateCasCellRows();
if (!cons.getArbitraryConsTable().isEmpty()) {
// get abritraryConstant for this cell from construction
MyArbitraryConstant myArbconst = cons
.getArbitraryConsTable().get(this.row);
// case we found an arbconst
if (myArbconst != null && arbconst.getPosition() == 0) {
// replace it
arbconst = myArbconst;
// replace geoCasCell for arbitrary constant
if (arbconst.getCasCell() != this) {
arbconst.setCasCell(this);
}
// hack needed for web with file loading
if (cons.isFileLoading()) {
ArrayList<GeoNumeric> constList = arbconst
.getConstList();
// switch geoNumerics created by xml reading
// with geoNumerics created by cas evaluation
if (constList != null && !constList.isEmpty()) {
for (GeoNumeric geoNum : constList) {
GeoElement geo = cons.lookupLabel(
geoNum.getLabelSimple());
if (geo instanceof GeoNumeric) {
((GeoNumeric) geo)
.setIsDependentConst(true);
cons.removeLabel(geo);
cons.addToConstructionList(geoNum,
true);
cons.putLabel(geoNum);
}
}
}
}
}
}
// compute the result using CAS
result = kernel.getGeoGebraCAS().evaluateGeoGebraCAS(
expandedEvalVE, arbconst, StringTemplate.numericNoLocal,
this, kernel);
// if we had constants in expression
// store arbconst in construction
if (arbconst.getPosition() != 0) {
cons.getArbitraryConsTable().put(this.row, arbconst);
}
// switch back the variable exchanges in result to command
// SolveODE
ArrayList<String> varSwaps = ((GeoGebraCAS) (kernel
.getGeoGebraCAS())).getVarSwaps();
if (!varSwaps.isEmpty()) {
for (String currStr : varSwaps) {
String[] swap = currStr.split("->");
result = result.replaceAll(swap[1], swap[0]);
}
((GeoGebraCAS) (kernel.getGeoGebraCAS())).getVarSwaps()
.clear();
}
// if KeepInput was used, return the input, except for the
// Substitute command
if (!isSubstitute && inputVE != null && isKeepInputUsed()) {
result = inputVE.wrap()
.toString(StringTemplate.numericNoLocal);
}
success = result != null;
} catch (CASException e) {
Log.error("GeoCasCell.computeOutput(), CAS eval: " + evalVE
+ "\n\terror: " + e.getMessage());
success = false;
ce = e;
} catch (Exception e) {
Log.error("GeoCasCell.computeOutput(), CAS eval: " + evalVE
+ "\n\t " + e);
e.printStackTrace();
success = false;
ce = new CASException(e);
}
}
// GEOGEBRA FALLBACK
else {
// EVALUATE evalVE in GeoGebra
boolean oldValue = kernel.isSilentMode();
kernel.setSilentMode(true);
try {
// process inputExp in GeoGebra *without* assignment (we need to
// avoid redefinition)
GeoElementND[] geos = kernel.getAlgebraProcessor()
.processAlgebraCommandNoExceptionsOrErrors(
// we remove Numeric commands, since we are
// using GeoGebra here
evalVE.deepCopy(kernel)
.traverse(Traversing.CommandRemover
.getRemover("Numeric"))
.toString(StringTemplate.maxPrecision),
false);
// GeoElement evalGeo = silentEvalInGeoGebra(evalVE);
if (geos != null) {
if (geos.length == 0 && evalVE.isTopLevelCommand()
&& isScriptingCommand(
evalVE.getTopLevelCommand().getName())) {
geos = new GeoElement[] { new GeoBoolean(cons, true) };
}
success = true;
/*
* Relation does not return any output, so it does not make
* sense to read off geos.
*/
if (!evalVE.isTopLevelCommand("Relation")) {
result = geos[0]
.toValueString(StringTemplate.numericNoLocal);
AlgoElement parentAlgo = geos[0].getParentAlgorithm();
if (parentAlgo != null) {
parentAlgo.remove();
// make sure fallback algos are synced with CAS, but
// not
// printed in XML (#2688)
parentAlgo.setPrintedInXML(false);
}
outputVE = new ExpressionNode(kernel, geos[0]);
// geos[0].addCasAlgoUser();
}
nativeOutput = false;
}
} catch (Throwable th2) {
th2.printStackTrace();
System.err.println("GeoCasCell.computeOutput(), GeoGebra eval: "
+ evalVE + "\n error: " + th2.getMessage());
success = false;
} finally {
kernel.setSilentMode(oldValue);
}
}
// set Output
finalizeComputation(success, result, ce, doTwinGeoUpdate,
allowFunction);
}
// replace in Solutions[{h(s)=g(t)},{s,t}] vector nodes with equations
private ValidExpression processSolutionCommand(ValidExpression ve) {
if (ve.isTopLevelCommand("Solutions")) {
Command cmd = (Command) ve.unwrap();
if (cmd.getArgumentNumber() == 2) {
ExpressionNode arg1 = cmd.getArgument(0);
if (arg1.getLeft() instanceof MyList
&& ((MyList) arg1.getLeft()).getListDepth() == 1
&& ((MyList) arg1.getLeft())
.getListElement(0) instanceof Equation) {
expandEquation(cmd, ((Equation) ((MyList) arg1.getLeft())
.getListElement(0)));
} else if (arg1.unwrap() instanceof Equation) {
expandEquation(cmd, (Equation) arg1.unwrap());
}
}
}
return ve;
}
private void expandEquation(Command cmd, Equation eqn) {
ExpressionNode lhs = eqn.getLHS();
ExpressionNode rhs = eqn.getRHS();
if (lhs.getLeft() instanceof MyVecNode
&& rhs.getLeft() instanceof MyVecNode) {
ExpressionValue xLHS = ((MyVecNode) lhs.getLeft()).getX();
ExpressionValue xRHS = ((MyVecNode) rhs.getLeft()).getX();
Equation xEqu = new Equation(kernel, xLHS, xRHS);
ExpressionValue yLHS = ((MyVecNode) lhs.getLeft()).getY();
ExpressionValue yRHS = ((MyVecNode) rhs.getLeft()).getY();
Equation yEqu = new Equation(kernel, yLHS, yRHS);
MyList myList = new MyList(kernel, 2);
myList.addListElement(new ExpressionNode(kernel, xEqu));
myList.addListElement(new ExpressionNode(kernel, yEqu));
ExpressionNode arg1 = new ExpressionNode(kernel, myList);
cmd.setArgument(0, arg1);
}
}
private static boolean isScriptingCommand(String name) {
return "Delete".equals(name) || "StartAnimation".equals(name)
|| (name != null && name.startsWith("Set"))
|| (name != null && name.startsWith("Show"));
}
/**
* Wraps an expression in PointList command and copies the assignment
*
* @param arg
* expression to be wrapped
* @return point list command
*/
private ValidExpression wrapPointList(ValidExpression arg) {
Command c = new Command(kernel, "PointList", false);
c.addArgument(arg.wrap());
ExpressionNode expr = c.wrap();
expr.setLabel(arg.getLabel());
return expr;
}
/*
* wrap eg x+x as Evaluate[x+x] so that it's simplified
*/
private ValidExpression wrapEvaluate(ValidExpression arg,
boolean forceWrapping) {
// don't want to wrap eg Integral[(x+1)^100] otherwise it will be
// expanded
if (arg.unwrap() instanceof Command && !forceWrapping) {
return arg;
}
// don't wrap if f'(x) is on top level (it is the same as
// Derivative[f(x)])
// but DO wrap f'(x+1) or f'(3) as it may simplify
if (arg.unwrap() instanceof ExpressionNode) {
ExpressionNode en = (ExpressionNode) arg.unwrap();
if (en.getOperation() == Operation.EQUAL_BOOLEAN) {
return arg;
}
if ((en.getOperation().equals(Operation.FUNCTION)
|| en.getOperation().equals(Operation.FUNCTION_NVAR))
&& en.getLeft() instanceof ExpressionNode) {
ExpressionNode en2 = (ExpressionNode) en.getLeft();
if (en2.getOperation().equals(Operation.DERIVATIVE)
&& en.getRight().unwrap() instanceof GeoDummyVariable) {
return arg;
}
}
}
ExpressionValue argUnwrapped = arg.unwrap();
// wrap in ExpressionNode if necessary
ExpressionNode en;
if (arg.isExpressionNode()) {
en = (ExpressionNode) arg;
} else if (argUnwrapped.isExpressionNode()) {
en = (ExpressionNode) argUnwrapped;
} else {
// eg f(x):=x+x
// eg {x+x,y+y}
// eg x+x=y+y
en = new ExpressionNode(kernel, arg.unwrap(),
Operation.NO_OPERATION, null);
}
// Log.debug(en);
// Log.debug("WRAPPING");
Command c = new Command(kernel, "Evaluate", false);
c.addArgument(en);
ExpressionNode expr = c.wrap();
expr.setLabel(arg.getLabel());
return expr;
}
private ValidExpression processSolveCommand(ValidExpression ve) {
if ((!(ve.unwrap() instanceof Command))) {
return ve;
}
if (((Command) ve.unwrap()).getName().equals("Numeric")) {
((Command) ve.unwrap()).setArgument(0,
processSolveCommand(((Command) ve.unwrap()).getArgument(0))
.wrap());
return ve;
}
if (!((Command) ve.unwrap()).getName().equals("Solve")) {
return ve;
}
Command cmd = (Command) ve.unwrap();
// Hack: collapse X=(a,b), X=(a+b,a-b+1) into one equation
MyList arg = cmd.getArgument(0).unwrap() instanceof MyList
? (MyList) cmd.getArgument(0).unwrap() : null;
if (arg != null && arg.size() == 2) {
String lhs1 = lhs(arg.getListElement(0), "@0");
String lhs2 = lhs(arg.getListElement(1), "@1");
if (lhs1.equals(lhs2)) {
String test = null;
try {
test = kernel.getParser().parseLabel(lhs1);
} catch (Throwable t) {
// not a label
}
if (test != null && !((Equation) arg.getListElement(0).unwrap())
.getRHS().evaluatesToNumber(true)) {
Equation merge = new Equation(kernel,
((Equation) arg.getListElement(0).unwrap())
.getRHS(),
((Equation) arg.getListElement(1).unwrap())
.getRHS());
cmd.setArgument(0, merge.wrap());
}
}
}
if (cmd.getArgumentNumber() >= 2) {
if (cmd.getArgument(1).unwrap() instanceof MyList) {
/* Modify solve in the following way: */
/* Solve[expr, {var}] -> Solve[expr, var] */
/* Ticket #697 */
MyList argList = (MyList) cmd.getArgument(1).unwrap();
if (argList.size() == 1) {
cmd.setArgument(1, argList.getItem(0).wrap());
}
}
return cmd.wrap();
}
if (cmd.getArgumentNumber() == 0) {
return cmd.wrap();
}
ExpressionNode en = cmd.getArgument(0);
/*
* Solve command has one argument which is an expression | equation |
* list
*/
/*
* We extract all the variables, order them, giving x y and z a priority
*/
/*
* Return the first n of them, where n is the number of
* equation/expression in the first parameter
*/
/* Ticket #3563 */
Set<String> set = new TreeSet<String>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
if (o1.equals(o2)) {
return 0;
}
if ("x".equals(o1)) {
return -1;
}
if ("x".equals(o2)) {
return 1;
}
if ("y".equals(o1)) {
return -1;
}
if ("y".equals(o2)) {
return 1;
}
if ("z".equals(o1)) {
return -1;
}
if ("z".equals(o2)) {
return 1;
}
return o1.compareTo(o2);
}
});
cmd.getArgument(0).traverse(DummyVariableCollector.getCollector(set));
int n = en.unwrap() instanceof MyList
? ((MyList) en.unwrap()).getLength() : 1;
// for equation (t,t) = (2s-1,3s+3)
// make sure that we allow the correct number of variables
// needed for #5332
if (en.unwrap() instanceof Equation) {
// 2DVector -> allow 2 variables
if (((Equation) en.unwrap()).getLHS()
.evaluatesToNonComplex2DVector()
&& ((Equation) en.unwrap()).getRHS()
.evaluatesToNonComplex2DVector()) {
n = 2;
}
// 3DVector -> allow 3 variables
if (((Equation) en.unwrap()).getLHS().evaluatesTo3DVector()
&& ((Equation) en.unwrap()).getRHS()
.evaluatesTo3DVector()) {
n = 3;
}
}
MyList variables = new MyList(kernel, n);
int i = 0;
Iterator<String> ite = set.iterator();
if (n == 1) {
if (ite.hasNext()) {
cmd.addArgument(new GeoDummyVariable(cons, ite.next()).wrap());
}
} else {
while (i < n && ite.hasNext()) {
variables
.addListElement(new GeoDummyVariable(cons, ite.next()));
i++;
}
if (variables.size() > 0) {
cmd.addArgument(variables.wrap());
}
}
return cmd.wrap();
}
private static String lhs(ExpressionValue arg, String fallback) {
return arg.unwrap() instanceof Equation ? ((Equation) arg.unwrap())
.getLHS().toString(StringTemplate.defaultTemplate) : fallback;
}
private void finalizeComputation(final boolean success, final String result,
final CASException ce, final boolean doTwinGeoUpdate,
boolean allowFunction) {
if (success) {
if ((prefix.length() == 0 && postfix.length() == 0)
// ignore selection with keep input
|| (keepInputUsed)) {
setOutput(result, true);
} else {
// make sure that evaluation is put into parentheses
StringBuilder sb = new StringBuilder();
sb.append(prefix);
sb.append(" (");
sb.append(result);
sb.append(") ");
sb.append(postfix);
setOutput(sb.toString(), true);
}
} else {
if (ce == null) {
setError("CAS.GeneralErrorMessage");
} else {
setError(ce.getKey());
}
}
// update twinGeo
if (doTwinGeoUpdate) {
updateTwinGeo(allowFunction);
}
if (outputVE != null && (!doTwinGeoUpdate || twinGeo == null)
&& !getAssignmentType().equals(AssignmentType.DELAYED)) {
ArbconstReplacer repl = ArbconstReplacer.getReplacer(arbconst);
arbconst.reset();
// Bugfix for ticket: 2468
// if outputVE is only a constant -> insert branch otherwise
// traverse did not work correct
outputVE.traverse(repl);
}
// set back firstComputeOutput, see setInput()
firstComputeOutput = false;
// invalidate latex
clearStrings();
}
/**
* @param error
* error message
*/
public void setError(final String error) {
this.error = error;
clearStrings();
outputVE = null;
}
/**
* @return true if this displays error
*/
public boolean isError() {
return error != null;
}
/**
* @return true if this displays circular definition error
*/
public boolean isCircularDefinition() {
return isCircularDefinition;
}
/**
* Appends <cascell caslabel="m"> XML tag to StringBuilder.
*/
@Override
protected void getElementOpenTagXML(StringBuilder sb) {
sb.append("<cascell");
if (assignmentVar != null) {
sb.append(" caslabel=\"");
StringUtil.encodeXML(sb, assignmentVar);
sb.append("\" ");
}
sb.append(">\n");
}
/**
* Appends </cascell> XML tag to StringBuilder.
*/
@Override
protected void getElementCloseTagXML(StringBuilder sb) {
sb.append("</cascell>\n");
}
/**
* Appends <cellPair> XML tag to StringBuilder.
*/
@Override
protected void getXMLtags(StringBuilder sb) {
// StringBuilder sb = new StringBuilder();
sb.append("\t<cellPair>\n");
// useAsText
if (useAsText) {
sb.append("\t\t<useAsText>\n");
getFontXML(sb);
sb.append("\t\t</useAsText>\n");
}
// inputCell
if (!isInputEmpty() || useAsText
|| (input != null && input.length() > 0)) {
sb.append("\t\t<inputCell>\n");
getInputExpressionXML(sb);
sb.append("\t\t</inputCell>\n");
}
// outputCell
if (!isOutputEmpty()) {
sb.append("\t\t<outputCell>\n");
getOutputExpressionXML(sb);
sb.append("\t\t</outputCell>\n");
}
sb.append("\t</cellPair>\n");
// return sb.toString();
}
private void getOutputExpressionXML(StringBuilder sb) {
sb.append("\t\t\t<expression value=\"");
StringUtil.encodeXML(sb, getOutput(StringTemplate.xmlTemplate));
sb.append("\"");
if (isError()) {
sb.append(" error=\"true\"");
}
if (isNative()) {
sb.append(" native=\"true\"");
}
if (!"".equals(evalCmd)) {
sb.append(" evalCommand=\"");
StringUtil.encodeXML(sb, evalCmd);
sb.append("\" ");
}
if (!"".equals(evalComment)) {
sb.append(" evalComment=\"");
StringUtil.encodeXML(sb, evalComment);
sb.append("\" ");
}
sb.append("/>\n");
}
private void getInputExpressionXML(StringBuilder sb) {
sb.append("\t\t\t<expression value=\"");
if (useAsText) {
StringUtil.encodeXML(sb, commentText.getTextString());
sb.append("\" ");
} else {
StringUtil.encodeXML(sb, input);
sb.append("\" ");
if (evalVE != getInputVE()) {
if (!"".equals(prefix)) {
sb.append(" prefix=\"");
StringUtil.encodeXML(sb, prefix);
sb.append("\" ");
}
sb.append(" eval=\"");
StringUtil.encodeXML(sb, getEvalText());
sb.append("\" ");
if (!"".equals(postfix)) {
sb.append(" postfix=\"");
StringUtil.encodeXML(sb, postfix);
sb.append("\" ");
}
sb.append("evalCmd=\"");
StringUtil.encodeXML(sb, evalCmd);
sb.append("\"");
}
if (pointList) {
sb.append(" pointList=\"true\"");
}
}
sb.append("/>\n");
}
private void getFontXML(StringBuilder sb) {
sb.append("\t\t\t<FontStyle value=\"");
sb.append(getFontStyle());
sb.append("\" ");
sb.append("/>\n");
sb.append("\t\t\t<FontSizeM value=\"");
sb.append(getFontSizeMultiplier());
sb.append("\" ");
sb.append("/>\n");
sb.append("\t\t\t<FontColor r=\"");
sb.append(getFontColor().getRed());
sb.append("\" b=\"");
sb.append(getFontColor().getBlue());
sb.append("\" g=\"");
sb.append(getFontColor().getGreen());
sb.append("\"/>\n");
}
@Override
public GeoClass getGeoClassType() {
return GeoClass.CAS_CELL;
}
@Override
public GeoElement copy() {
GeoCasCell casCell = new GeoCasCell(cons);
casCell.set(this);
return casCell;
}
@Override
public boolean isDefined() {
return !isError();
}
@Override
public void setUndefined() {
setError("CAS.GeneralErrorMessage");
if (twinGeo != null) {
twinGeo.setUndefined();
}
}
@Override
public String toValueString(final StringTemplate tpl) {
return outputVE != null ? outputVE.toValueString(tpl) : toString(tpl);
}
@Override
public boolean showInAlgebraView() {
return false;
}
@Override
protected boolean showInEuclidianView() {
return false;
}
@Override
public boolean isEqual(final GeoElementND geo) {
return geo == this;
}
/**
* Returns assignment variable, e.g. "a" for "a := 5" or row reference, e.g.
* "$5$". Note that kernel.getCASPrintForm() is taken into account, e.g. row
* references return the output of this cell (instead of the label) for the
* underlying CAS.
*/
@Override
public String getLabel(StringTemplate tpl) {
// standard case: assignment
if (assignmentVar != null) {
return tpl.printVariableName(assignmentVar);
}
// row reference like $5
StringBuilder sb = new StringBuilder();
switch (tpl.getStringType()) {
// send output to underlying CAS
case GIAC:
sb.append(" (");
sb.append(outputVE == null ? "?" : outputVE.toString(tpl));
sb.append(") ");
break;
default:
// standard case: return current row, e.g. $5
if (row >= 0) {
if (tpl.hasType(StringType.LATEX)) {
sb.append("\\$");
} else {
sb.append(ExpressionNodeConstants.CAS_ROW_REFERENCE_PREFIX);
}
sb.append(row + 1);
}
break;
}
return sb.toString();
}
/**
* This might appear when we use KeepInput and display the result => we want
* to show symbolic version
*/
@Override
public String toString(final StringTemplate tpl) {
return getLabel(tpl);
}
@Override
public String getAlgebraDescriptionDefault() {
if (isDefined()) {
return getOutput(StringTemplate.defaultTemplate);
}
final StringBuilder sbAlgebraDesc = new StringBuilder();
sbAlgebraDesc.append(label);
sbAlgebraDesc.append(' ');
sbAlgebraDesc.append(getLoc().getPlain("Undefined"));
return sbAlgebraDesc.toString();
}
@Override
public boolean isGeoCasCell() {
return true;
}
@Override
public void doRemove() {
if (assignmentVar != null) {
// remove variable name from Construction
cons.removeCasCellLabel(assignmentVar);
assignmentVar = null;
}
super.doRemove();
cons.removeFromGeoSetWithCasCells(this);
setTwinGeo(null);
if (this.isInConstructionList()) {
cons.updateCasCells();
}
}
/**
* @param newTwinGeo
* new twin GeoElement
*/
private void setTwinGeo(final GeoElement newTwinGeo) {
Log.debug(newTwinGeo);
if (newTwinGeo == null && twinGeo != null) {
GeoElement oldTwinGeo = twinGeo;
twinGeo = null;
oldTwinGeo.setCorrespondingCasCell(null);
oldTwinGeo.doRemove();
}
twinGeo = newTwinGeo;
if (twinGeo == null) {
return;
}
twinGeo.setCorrespondingCasCell(this);
twinGeo.setParentAlgorithm(getParentAlgorithm());
if (twinGeo.isGeoNumeric() && inputVE != null
&& (inputVE.isTopLevelCommand("Integral")
|| inputVE.isTopLevelCommand("IntegralBetween"))) {
((GeoNumeric) twinGeo).setDrawable(true, false);
}
if (dependsOnDummy(twinGeo)) {
twinGeo.setUndefined();
twinGeo.setAlgebraVisible(false);
} else {
twinGeo.setAlgebraVisible(true);
}
}
private static boolean dependsOnDummy(final GeoElement geo) {
if (geo instanceof GeoDummyVariable) {
GeoElement subst = ((GeoDummyVariable) geo)
.getElementWithSameName();
if (subst != null && (!subst.sendValueToCas ||
// needed for GGB-810
// skip constants
(subst.getLabelSimple() != null
&& subst.getLabelSimple().startsWith("c_")))) {
return false;
} else if (subst == null
&& ((GeoDummyVariable) geo).getVarName() != null
&& ((GeoDummyVariable) geo).getVarName().startsWith("c_")) {
return false;
}
return true;
}
if (geo.isGeoList()) {
for (int i = 0; i < ((GeoList) geo).size(); i++) {
if (dependsOnDummy(((GeoList) geo).get(i))) {
return true;
}
}
}
AlgoElement algo = geo.getParentAlgorithm();
if (algo == null || geo.getParentAlgorithm() == null) {
return false;
}
for (int i = 0; i < algo.getInput().length; i++) {
if (dependsOnDummy(algo.getInput()[i])) {
return true;
}
}
return false;
}
/**
* @return twin element
*/
public GeoElement getTwinGeo() {
return twinGeo;
}
/**
* Adds algorithm to update set of this GeoCasCell and also to the update
* set of an independent twinGeo.
*/
@Override
public boolean addToUpdateSets(final AlgoElement algorithm) {
final boolean added = super.addToUpdateSets(algorithm);
if (twinGeo != null && twinGeo.isIndependent()) {
twinGeo.addToUpdateSets(algorithm);
}
return added;
}
/**
* s algorithm from update set of this GeoCasCell and also from the update
* set of an independent twinGeo.
*/
@Override
public boolean removeFromUpdateSets(final AlgoElement algorithm) {
final boolean removed = super.removeFromUpdateSets(algorithm);
if (twinGeo != null && twinGeo.isIndependent()) {
twinGeo.removeFromUpdateSets(algorithm);
}
return removed;
}
/**
* @return output value as valid expression
*/
public ValidExpression getOutputValidExpression() {
return outputVE;
}
// public void setIgnoreTwinGeoUpdate(boolean ignoreTwinGeoUpdate) {
// this.ignoreTwinGeoUpdate = ignoreTwinGeoUpdate;
// }
@Override
public boolean isLaTeXDrawableGeo() {
return true;
}
@Override
public String getVarString(final StringTemplate tpl) {
if (getInputVE() instanceof FunctionNVar) {
return ((FunctionNVar) getInputVE()).getVarString(tpl);
}
return "";
}
/**
* @return function variables in list
*/
public MyList getFunctionVariableList() {
if (getInputVE() instanceof FunctionNVar) {
MyList ml = new MyList(kernel);
for (FunctionVariable fv : ((FunctionNVar) getInputVE())
.getFunctionVariables()) {
ml.addListElement(fv);
}
return ml;
}
return null;
}
/**
* @return function variables of input function
*/
public FunctionVariable[] getFunctionVariables() {
if (getInputVE() instanceof FunctionNVar) {
return ((FunctionNVar) getInputVE()).getFunctionVariables();
}
return new FunctionVariable[0];
}
private void setInputVE(ValidExpression inputVE) {
this.inputVE = inputVE;
}
@Override
public GColor getAlgebraColor() {
if (twinGeo == null) {
return GColor.BLACK;
}
return twinGeo.getAlgebraColor();
}
/**
* @param b
* whether this cell was stored as native in XML
*/
public void setNative(final boolean b) {
nativeOutput = b;
}
/**
* @return whether output was computed without using GeoGebra fallback
*/
public boolean isNative() {
return nativeOutput;
}
/**
* toggles the euclidianVisibility of the twinGeo, if there is no twinGeo
* toggleTwinGeoEuclidianVisible tries to create one and set the visibility
* to true
*/
public void toggleTwinGeoEuclidianVisible() {
boolean visible;
if (hasTwinGeo()) {
visible = !twinGeo.isEuclidianVisible()
&& twinGeo.isEuclidianShowable();
} else {
// creates a new twinGeo, if not possible return
if (outputVE == null || !plot()) {
return;
}
visible = hasTwinGeo() && twinGeo.isEuclidianShowable();
}
if (hasTwinGeo()) {
twinGeo.setEuclidianVisible(visible);
twinGeo.updateVisualStyle(GProperty.VISIBLE);
}
kernel.getApplication().storeUndoInfo();
kernel.notifyRepaint();
}
private boolean pointList;
private String tooltip;
/**
* Assigns result to a variable if possible
*
* @return false if it is not possible to plot this GeoCasCell true if there
* is already a twinGeo, or a new twinGeo was created successfully
*/
public boolean plot() {
if (getEvalVE() == null || "".equals(input)) {
return false;
} else if (hasTwinGeo()) { // there is already a twinGeo, this means
// this cell is plotable,
return true;
}
String oldEvalComment = evalComment;
ValidExpression oldEvalVE = evalVE;
ValidExpression oldInputVE = getInputVE();
String oldAssignmentVar = assignmentVar;
AssignmentType oldOVEAssignmentType = getAssignmentType();
assignmentVar = getPlotVar();
adjustPointList(false);
this.firstComputeOutput = true;
this.computeOutput(true, true);
if (twinGeo != null && !dependsOnDummy(twinGeo)) {
twinGeo.setLabel(null);
}
if (twinGeo != null && twinGeo.getLabelSimple() != null
&& twinGeo.isEuclidianShowable()) {
String twinGeoLabelSimple = twinGeo.getLabelSimple();
changeAssignmentVar(assignmentVar, twinGeoLabelSimple);
// we use EvalVE here as it's more transparent to push the command
// to the input
// except Evaluate and KeepInput
ValidExpression ex = getEvalVE().deepCopy(kernel);
CommandRemover remover;
if (input.startsWith("Numeric[")) {
remover = CommandRemover.getRemover("KeepInput", "Evaluate");
} else {
remover = CommandRemover.getRemover("KeepInput", "Evaluate",
"Numeric");
}
ex.traverse(remover);
setAssignmentType(AssignmentType.DEFAULT);
if (twinGeo instanceof GeoSurfaceCartesianND) {
StringBuilder sb = new StringBuilder();
sb.append(twinGeoLabelSimple);
sb.append("(");
sb.append(((GeoSurfaceCartesianND) twinGeo)
.getVarString(StringTemplate.defaultTemplate));
sb.append(")");
ex.setLabel(sb.toString());
} else {
ex.setLabel(twinGeo
.getAssignmentLHS(StringTemplate.defaultTemplate));
}
if (twinGeo instanceof GeoFunction) {
ex.traverse(Traversing.FunctionCreator.getCreator());
}
setAssignmentType(AssignmentType.DEFAULT);
getEvalVE().setLabel(
twinGeo.getAssignmentLHS(StringTemplate.defaultTemplate));
boolean wasKeepInputUsed = isKeepInputUsed();
boolean wasNumericUsed = "Numeric".equals(evalCmd);
setInput(ex.toAssignmentString(StringTemplate.numericDefault,
AssignmentType.DEFAULT));
if (wasKeepInputUsed) {
setKeepInputUsed(true);
setEvalCommand("KeepInput");
} else if (wasNumericUsed) {
setProcessingInformation("",
"Numeric[" + inputVE
.toString(StringTemplate.defaultTemplate) + "]",
"");
setEvalCommand("Numeric");
}
computeOutput(false, false);
this.update();
clearStrings();
cons.addToConstructionList(twinGeo, true);
// notify only construction protocol
// needed for GGB-810
kernel.notifyConstructionProtocol(twinGeo);
} else {
// Log.debug("Fail" + oldEvalComment);
if (twinGeo != null && twinGeo.getLabelSimple() != null) {
twinGeo.doRemove();
}
// plot failed, undo assignment
assignmentVar = oldAssignmentVar;
setAssignmentType(oldOVEAssignmentType);
this.firstComputeOutput = true;
evalComment = oldEvalComment;
evalVE = oldEvalVE;
// needed for GGB-525
// reevaluate Solve, Solutions etc without requiring pointList
pointList = false;
setInputVE(oldInputVE);
this.computeOutput(true, false);
return false;
}
return true;
}
private boolean inequalityInEvalVE() {
if (expandedEvalVE == null) {
return false;
}
return expandedEvalVE.inspect(IneqFinder.INSTANCE);
}
private void clearStrings() {
tooltip = null;
latex = null;
}
private String getPlotVar() {
boolean isCasVector = false;
if (outputVE == null) {
return PLOT_VAR;
}
ExpressionValue unwrapped = outputVE.unwrap();
if (unwrapped == null) {
return PLOT_VAR;
}
if (unwrapped instanceof MyVecNDNode) {
isCasVector = unwrapped.evaluatesToVectorNotPoint();
}
if (isCasVector) {
return PLOT_VAR.toLowerCase();
}
return PLOT_VAR;
}
/**
* @param pointList2
* whether evalVE needs to be wrapped in PointList when
* evaluating
*/
public void setPointList(boolean pointList2) {
pointList = pointList2;
}
@Override
public boolean hasCoords() {
return outputVE != null && outputVE.hasCoords();
}
private int SCREEN_WIDTH = 80;
private int preferredRowNumber = -1;
@Override
public String getTooltipText(final boolean colored,
final boolean alwaysOn) {
if (isError()) {
return getLoc().getError(error);
}
if (tooltip == null && outputVE != null) {
tooltip = getOutput(StringTemplate.defaultTemplate);
tooltip = tooltip.replace("gGbSuM(", "\u03a3(");
tooltip = tooltip.replace("gGbInTeGrAl(", "\u222b(");
if (tooltip.length() > SCREEN_WIDTH && tooltip.indexOf('{') > -1) {
int listStart = tooltip.indexOf('{');
StringBuilder sb = new StringBuilder(tooltip.length() + 20);
sb.append(tooltip.substring(0, listStart + 1));
int currLine = 0;
for (int i = listStart + 1; i < tooltip.length(); i++) {
if (tooltip.charAt(i) == ',') {
int nextComma = tooltip.indexOf(',', i + 1);
if (nextComma == -1) {
nextComma = tooltip.length() - 1;
}
if (currLine + (nextComma - i) > SCREEN_WIDTH) {
sb.append(",\n");
currLine = 0;
i++;
}
}
currLine++;
sb.append(tooltip.charAt(i));
}
tooltip = sb.toString();
}
tooltip = GeoElement.indicesToHTML(tooltip, true);
}
return tooltip;
}
/**
* @return information about eval command for display in the cell
*/
public String getCommandAndComment() {
if (!this.showOutput()) {
return "";
}
StringBuilder evalCmdLocal = new StringBuilder();
if (pointList) {
evalCmdLocal.append(getLoc().getCommand("PointList"));
} else if ("".equals(evalCmd)) {
return getOutputPrefix();
} else if ("Numeric".equals(evalCmd)) {
return Unicode.CAS_OUTPUT_NUMERIC;
} else if ("KeepInput".equals(evalCmd)) {
return Unicode.CAS_OUTPUT_KEEPINPUT;
} else {
evalCmdLocal.append(getLoc().getCommand(evalCmd));
}
if (input.startsWith(evalCmdLocal.toString()) || (localizedInput != null
&& localizedInput.startsWith(evalCmdLocal.toString()))) {
// don't show command if it is already at beginning of input
return getOutputPrefix();
}
// eval comment (e.g. "x=5, y=8")
if (evalComment.length() > 0) {
if (evalCmdLocal.length() != 0) {
evalCmdLocal.append(", ");
}
evalCmdLocal.append(evalComment);
}
evalCmdLocal.append(":");
return evalCmdLocal.toString();
}
private String getOutputPrefix() {
if (kernel.getLocalization().rightToLeftReadingOrder) {
return Unicode.CAS_OUTPUT_PREFIX_RTL;
}
return Unicode.CAS_OUTPUT_PREFIX;
}
/**
* @return whether this cell depends on variables or was created using a
* command
*/
public boolean hasVariablesOrCommands() {
if (getGeoElementVariables() != null) {
return true;
}
return inputVE != null && inputVE.inspect(CommandFinder.INSTANCE);
}
/**
* Sets pointList variable to the right value
*
* @param onlySolutions
* true if set point list only for Solutions NSolutions and
* CSolutions
*/
public void adjustPointList(boolean onlySolutions) {
if (evalVE.isTopLevelCommand()
&& (getPlotVar().equals(assignmentVar))) {
String cmd = evalVE.getTopLevelCommand().getName();
if (!inequalityInEvalVE() && (("Solutions".equals(cmd)
|| "CSolutions".equals(cmd) || "NSolutions".equals(cmd))
|| (!onlySolutions && ("Solve".equals(cmd)
|| "CSolve".equals(cmd) || "NSolve".equals(cmd)
|| "Root".equals(cmd)
|| "ComplexRoot".equals(cmd))))) {
// if we got evalVE by clicking Solve button, inputVE might just
// contain the equations
// we want the command in input as well
if (!pointList) {
inputVE = evalVE;
}
pointList = true;
}
}
}
private void updateDependentCellInput() {
List<AlgoElement> algos = getAlgorithmList();
if (algos != null) {
for (AlgoElement algo : algos) {
if (algo instanceof AlgoCasCellInterface) {
AlgoCasCellInterface algoCell = (AlgoCasCellInterface) algo;
algoCell.getCasCell()
.updateInputStringWithRowReferences(true);
}
}
}
}
@Override
public int getPrintDecimals() {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getPrintFigures() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void setPrintDecimals(int printDecimals, boolean update) {
// TODO Auto-generated method stub
}
@Override
public void setPrintFigures(int printFigures, boolean update) {
// TODO Auto-generated method stub
}
@Override
public boolean isSerifFont() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setSerifFont(boolean serifFont) {
// TODO Auto-generated method stub
}
@Override
public boolean useSignificantFigures() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean justFontSize() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isLaTeXTextCommand() {
// TODO Auto-generated method stub
return false;
}
@Override
final public HitType getLastHitType() {
return HitType.ON_FILLING;
}
/**
* @param tpl
* string template (might be MathQuill or JLM)
* @return input in LaTeX form
*/
public String getLaTeXInput(StringTemplate tpl) {
if (useAsText) {
return "\\text{" + this.commentText.getTextString() + "}";
}
return latexInput == null
? (inputVE == null ? input
: inputVE.toAssignmentString(tpl, getAssignmentType()))
: latexInput;
}
/**
* Stores input only, no processing
*
* @param latexInput
* LaTeX form of input
*/
public void setLaTeXInput(String latexInput) {
this.latexInput = latexInput;
}
/**
* Returns input, wrapped in used command if necessary
*/
@Override
public String getDefinitionDescription(StringTemplate tpl) {
if (evalVE.unwrap() instanceof Command
&& "Evaluate".equals(((Command) evalVE.unwrap()).getName())) {
return ((Command) evalVE.unwrap()).getArgument(0).toString(tpl);
}
return evalVE.toString(tpl);
}
@Override
public ValueType getValueType() {
// TODO Auto-generated method stub
if (outputVE != null) {
return outputVE.getValueType();
}
return inputVE != null ? inputVE.getValueType() : ValueType.UNKNOWN;
}
/**
* transforms evalComment into set of substitutions in case of Substitution
* command
*
* @return set of substitutions
*/
private ArrayList<Vector<String>> getSubstListFromSubstComment(
String evalCommentStr) {
substList = new ArrayList<Vector<String>>();
String[] splitComment = evalCommentStr.split(",");
for (int i = 0; i < splitComment.length; i++) {
String[] currSubstPair = splitComment[i].split("=");
Vector<String> substRow = new Vector<String>(2);
substRow.add(currSubstPair[0]);
substRow.add(currSubstPair[1]);
substList.add(substRow);
}
return substList;
}
/**
* @return substitution list
*/
public ArrayList<Vector<String>> getSubstList() {
return substList;
}
/**
* @param list
* substitution list for this cell
*/
public void setSubstList(ArrayList<Vector<String>> list) {
this.substList = list;
}
/**
* @param order
* derivative order
* @param fast
* true to avoid CAS computation
* @return derivative
*/
public ExpressionValue getGeoDerivative(int order, boolean fast) {
return getTwinGeo() == null ? null
: ((Functional) getTwinGeo()).getGeoDerivative(order, fast);
}
@Override
public double evaluateDouble() {
if (twinGeo != null) {
return twinGeo.evaluateDouble();
}
return super.evaluateDouble();
}
/**
* @param tpl
* template
* @param output
* whether to substitute variables
* @return input or output
*/
public String getOutputOrInput(StringTemplate tpl, boolean output) {
if (!output) {
return getOutputValidExpression().toAssignmentString(
getFormulaString(tpl, output), getAssignmentType());
}
return getOutput(tpl);
}
/**
* Reset row number (to avoid double deletion) and save it to preference
*/
public void resetRowNumber() {
if (getRowNumber() >= 0) {
preferredRowNumber = getRowNumber();
}
setRowNumber(-1);
}
/**
* Reload the current row number from saved preference
*/
public void reloadRowNumber() {
setRowNumber(preferredRowNumber);
preferredRowNumber = -1;
}
}