package org.geogebra.common.kernel.geos; import org.geogebra.common.awt.GColor; import org.geogebra.common.euclidian.DrawableND; import org.geogebra.common.euclidian.EuclidianViewInterfaceCommon; import org.geogebra.common.euclidian.draw.DrawInputBox; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.algos.AlgoPointInRegion; import org.geogebra.common.kernel.algos.AlgoPointOnPath; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.ExpressionNodeConstants.StringType; import org.geogebra.common.kernel.arithmetic.FunctionalNVar; import org.geogebra.common.kernel.commands.EvalInfo; import org.geogebra.common.kernel.kernelND.GeoElementND; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.main.MyError; import org.geogebra.common.plugin.GeoClass; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.AsyncOperation; import org.geogebra.common.util.StringUtil; import org.geogebra.common.util.TextObject; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.lang.Unicode; /** * Input box for user input * * @author Michael * */ public class GeoInputBox extends GeoButton { private static int defaultLength = 20; private int length; private int printDecimals = -1; private int printFigures = -1; private boolean useSignificantFigures = false; private StringTemplate tpl = StringTemplate.defaultTemplate; /** * Creates new text field * * @param c * construction */ public GeoInputBox(Construction c) { super(c); length = defaultLength; } /** * @param cons * construction * @param labelOffsetX * x offset * @param labelOffsetY * y offset */ public GeoInputBox(Construction cons, int labelOffsetX, int labelOffsetY) { this(cons); this.labelOffsetX = labelOffsetX; this.labelOffsetY = labelOffsetY; } @Override public boolean isChangeable() { return true; } @Override public GeoClass getGeoClassType() { return GeoClass.TEXTFIELD; } @Override public boolean isTextField() { return true; } /** * @param geo * new linked geo */ public void setLinkedGeo(GeoElementND geo) { linkedGeo = geo; text = geo.getValueForInputBar(); // remove quotes from start and end if (text.length() > 0 && text.charAt(0) == '"') { text = text.substring(1); } if (text.length() > 0 && text.charAt(text.length() - 1) == '"') { text = text.substring(0, text.length() - 1); } } /** * Returns the linked geo * * @return linked geo */ public GeoElementND getLinkedGeo() { return linkedGeo; } private GeoElementND linkedGeo = null; private String text = null; @Override public String toValueString(StringTemplate tpl1) { if (linkedGeo == null) { return ""; } return text; } /** * Set the text * * @param newText * new text value */ public void setText(String newText) { text = newText; } /** * Get the text (used for scripting) * * @return the text */ public String getText() { return text; } @Override public boolean isGeoInputBox() { return true; } /** * Sets length of the input box * * @param len * new length */ public void setLength(int len) { length = len; this.updateVisualStyle(GProperty.LENGTH); } /** * @return length of the input box */ public int getLength() { return length; } @Override protected void getXMLtags(StringBuilder sb) { super.getXMLtags(sb); if (linkedGeo != null) { sb.append("\t<linkedGeo exp=\""); StringUtil.encodeXML(sb, linkedGeo.getLabel(StringTemplate.xmlTemplate)); sb.append("\""); sb.append("/>\n"); // print decimals if (printDecimals >= 0 && !useSignificantFigures) { sb.append("\t<decimals val=\""); sb.append(printDecimals); sb.append("\"/>\n"); } // print significant figures if (printFigures >= 0 && useSignificantFigures) { sb.append("\t<significantfigures val=\""); sb.append(printFigures); sb.append("\"/>\n"); } } if (getLength() != defaultLength) { sb.append("\t<length val=\""); sb.append(getLength()); sb.append("\""); sb.append("/>\n"); } } @Override public GeoElement copy() { return new GeoInputBox(cons, labelOffsetX, labelOffsetY); } /** * @param inputText * new value for linkedGeo */ public void updateLinkedGeo(String inputText) { String defineText = inputText; boolean imaginaryAdded = false; if (linkedGeo.isGeoLine()) { // not y= // and not Line[A,B] if ((defineText.indexOf('=') == -1) && (defineText.indexOf('[') == -1)) { // x + 1 changed to // y = x + 1 defineText = "y=" + defineText; } String prefix = linkedGeo.getLabel(tpl) + ":"; // need a: in front of // X = (-0.69, 0) + \lambda (1, -2) if (!defineText.startsWith(prefix)) { defineText = prefix + defineText; } } else if (linkedGeo.isGeoText()) { defineText = "\"" + defineText + "\""; } else if (linkedGeo.isGeoPoint()) { if (((GeoPointND) linkedGeo).getMode() == Kernel.COORD_COMPLEX) { // z=2 doesn't work for complex numbers (parses to // GeoNumeric) defineText = defineText + "+0" + Unicode.IMAGINARY; imaginaryAdded = true; } } else if (linkedGeo instanceof FunctionalNVar) { // string like f(x,y)=x^2 // or f(\theta) = \theta defineText = linkedGeo.getLabel(tpl) + "(" + ((FunctionalNVar) linkedGeo).getVarString(tpl) + ")=" + defineText; } if ("".equals(defineText.trim())) { return; } double num = Double.NaN; ExpressionNode parsed = null; if (linkedGeo.isGeoNumeric()) { try { parsed = kernel.getParser() .parseExpression(inputText); } catch (Throwable e) { // TODO Auto-generated catch block e.printStackTrace(); } } // for a simple number, round it to the textfield setting (if set) if (parsed != null && parsed.isConstant() && !linkedGeo.isGeoAngle() && (printDecimals > -1 || printFigures > -1)) { try { // can be a calculation eg 1/2+3 // so use full GeoGebra parser num = kernel.getAlgebraProcessor().evaluateToDouble(inputText, false, null); defineText = kernel.format(num, tpl); } catch (Exception e) { // user has entered eg 33+ // do nothing e.printStackTrace(); } } try { if (linkedGeo instanceof GeoNumeric && linkedGeo.isIndependent() && parsed != null && parsed.isConstant()) { // can be a calculation eg 1/2+3 // so use full GeoGebra parser Log.debug("Simple update"); kernel.getAlgebraProcessor().evaluateToDouble(defineText, false, (GeoNumeric) linkedGeo); // setValue -> avoid slider range changing linkedGeo.updateRepaint(); } else { final boolean imaginary = imaginaryAdded; EvalInfo info = new EvalInfo(!cons.isSuppressLabelsActive(), linkedGeo.isIndependent()); kernel.getAlgebraProcessor() .changeGeoElementNoExceptionHandling(linkedGeo, defineText, info, true, new AsyncOperation<GeoElementND>() { @Override public void callback(GeoElementND obj) { if (imaginary) { ExpressionNode def = obj .getDefinition(); if (def != null && def.getOperation() == Operation.PLUS && def.getRight() .toString( StringTemplate.defaultTemplate) .equals("0" + Unicode.IMAGINARY)) { obj.setDefinition( def.getLeftTree()); setLinkedGeo(obj); obj.updateRepaint(); return; } } setLinkedGeo(obj); } }, kernel.getApplication().getErrorHandler()); return; } } catch (MyError e1) { kernel.getApplication().showError(e1); return; } catch (Exception e1) { Log.error(e1.getMessage()); kernel.getApplication().showError(kernel.getApplication() .getLocalization().getError("InvalidInput")); return; } this.setLinkedGeo(linkedGeo); } /** * Called by a Drawable for this object when it is updated * * @param textFieldToUpdate * the Drawable's text field */ public void updateText(TextObject textFieldToUpdate) { if (linkedGeo != null) { String linkedText; if (linkedGeo.isGeoText()) { linkedText = ((GeoText) linkedGeo).getTextString(); } else if (linkedGeo.getParentAlgorithm() instanceof AlgoPointOnPath || linkedGeo .getParentAlgorithm() instanceof AlgoPointInRegion) { linkedText = linkedGeo.toValueString(tpl); } else { // want just a number for eg a=3 but we want variables for eg // y=m x + c boolean substituteNos = linkedGeo.isGeoNumeric() && linkedGeo.isIndependent(); linkedText = linkedGeo.getFormulaString(tpl, substituteNos); } if (linkedText == null) { linkedText = ""; } if (linkedGeo.isGeoText() && (linkedText.indexOf("\n") > -1)) { // replace linefeed with \\n while (linkedText.indexOf("\n") > -1) { linkedText = linkedText.replaceAll("\n", "\\\\\\\\n"); } } if (!textFieldToUpdate.getText().equals(linkedText)) { // avoid // redraw // error textFieldToUpdate.setText(linkedText); } } else { textFieldToUpdate.setText(text); } setText(textFieldToUpdate.getText()); } /** * Called by a Drawable when its text object is updated * * @param textFieldToUpdate * the Drawable's text field */ public void textObjectUpdated(TextObject textFieldToUpdate) { if (linkedGeo != null) { updateLinkedGeo(textFieldToUpdate.getText()); updateText(textFieldToUpdate); } else { setText(textFieldToUpdate.getText()); } } /** * Called by a Drawable when the input is submitted (e.g. by pressing ENTER) */ public void textSubmitted() { runClickScripts(getText()); } private void updateTemplate() { if (useSignificantFigures() && printFigures > -1) { tpl = StringTemplate.printFigures(StringType.GEOGEBRA, printFigures, false); } else if (!useSignificantFigures && printDecimals > -1) { tpl = StringTemplate.printDecimals(StringType.GEOGEBRA, printDecimals, false); } else { tpl = StringTemplate.get(StringType.GEOGEBRA); } } @Override public int getPrintDecimals() { return printDecimals; } @Override public int getPrintFigures() { return printFigures; } @Override public void setPrintDecimals(int printDecimals, boolean update) { this.printDecimals = printDecimals; printFigures = -1; useSignificantFigures = false; updateTemplate(); } @Override public void setPrintFigures(int printFigures, boolean update) { this.printFigures = printFigures; printDecimals = -1; useSignificantFigures = true; updateTemplate(); } @Override public boolean useSignificantFigures() { return useSignificantFigures; } @Override public void setBackgroundColor(final GColor bgCol) { if (bgCol == null) { // transparent bgColor = null; return; } // default in case alpha = 0 (not allowed for Input Boxes) int red = 255, green = 255, blue = 255; // fix for files saved with alpha = 0 if (bgCol.getAlpha() != 0) { red = bgCol.getRed(); green = bgCol.getGreen(); blue = bgCol.getBlue(); } bgColor = GColor.newColor(red, green, blue); } @Override public int getTotalWidth(EuclidianViewInterfaceCommon ev) { DrawableND draw = ev.getDrawableFor(this); if (draw instanceof DrawInputBox) { return ((DrawInputBox) draw).getTotalSize().getWidth(); } return getWidth(); } @Override public int getTotalHeight(EuclidianViewInterfaceCommon ev) { DrawableND draw = ev.getDrawableFor(this); if (draw instanceof DrawInputBox) { return ((DrawInputBox) draw).getTotalSize().getHeight(); } return getHeight(); } }