package org.geogebra.common.gui.dialog;
import org.geogebra.common.awt.GColor;
import org.geogebra.common.awt.GFont;
import org.geogebra.common.euclidian.EuclidianView;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.ConstructionDefaults;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.algos.AlgoDependentText;
import org.geogebra.common.kernel.arithmetic.ExpressionNode;
import org.geogebra.common.kernel.arithmetic.ExpressionValue;
import org.geogebra.common.kernel.arithmetic.MyStringBuffer;
import org.geogebra.common.kernel.arithmetic.TextValue;
import org.geogebra.common.kernel.arithmetic.ValidExpression;
import org.geogebra.common.kernel.commands.EvalInfo;
import org.geogebra.common.kernel.geos.GeoText;
import org.geogebra.common.kernel.parser.ParseException;
import org.geogebra.common.kernel.parser.TokenMgrError;
import org.geogebra.common.main.App;
import org.geogebra.common.main.MyError;
/**
*
* Abstract class for displaying a preview of a GeoText while editing. The class
* requires a GUI panel that encloses an instance of EuclidianView. The preview
* is drawn as a GeoText element in this EuclidianView.
*
* The class maintains two hidden geos (previewGeoIndependent and
* previewGeoDependent) that are used to preview the two possible types of
* GeoText, independent and dependent.
*
*
* @author G. Sturr
*
*/
public abstract class TextPreviewer {
protected EuclidianView ev;
protected Kernel kernel;
private App app;
private GeoText previewGeoIndependent, previewGeoDependent;
private AlgoDependentText textAlgo;
private final Construction cons;
private boolean isIndependent;
/**
* @param kernel
* Kernel
*/
public TextPreviewer(Kernel kernel) {
this.kernel = kernel;
this.cons = kernel.getConstruction();
this.ev = getEuclidianView();
this.setApp(kernel.getApplication());
// set EV display properties
removeEVMouseListeners();
ev.setAllowShowMouseCoords(false);
ev.setAxesCornerCoordsVisible(false);
ev.updateFonts();
ev.updateSize();
}
protected abstract EuclidianView getEuclidianView();
protected abstract void removeEVMouseListeners();
/**
* Updates the preferred size of this panel to match the estimated size of
* the given preview geo. This forces the enclosing scrollpane to show
* scrollbars when the size of the preview geo grows larger than the
* scrollpane viewport.
*
* Note: The preview geo uses absolute screen coords, so we can't easily get
* the bounding box dimensions and must use dummy containers to estimate
* these dimensions.
*
* @param previewGeo
*/
protected abstract void updateViewportSize(GeoText previewGeo);
public void updateFonts() {
ev.updateFonts();
}
/**
* Removes the preview geos
*/
public void removePreviewGeoText() {
if (previewGeoIndependent != null) {
ev.remove(previewGeoIndependent);
previewGeoIndependent.remove();
previewGeoIndependent = null;
}
if (previewGeoDependent != null) {
ev.remove(previewGeoDependent);
previewGeoDependent.remove();
previewGeoDependent = null;
textAlgo.remove();
}
ev.repaint();
}
/**
* Updates the preview geos and creates new geos if needed. Changes are
* determined by the inputValue string and the visual style of the
* targetGeo.
*
* @param targetGeo
* geo being edited
* @param inputValue
* input text
* @param isLaTeXset
* whether user set it to LaTeX
* @param mayDetectLaTeX
* whether we may change the LaTeX property
* @return whether this is latex
*/
public boolean updatePreviewText(GeoText targetGeo, String inputValue,
boolean isLaTeXset, boolean mayDetectLaTeX) {
boolean isLaTeX = isLaTeXset;
if (mayDetectLaTeX && !isLaTeXset) {
isLaTeX = isLaTeX || guessLaTeX(inputValue);
}
// Application.printStacktrace("inputValue: " + inputValue);
// initialize variables
ValidExpression exp = null;
StringTemplate tpl = targetGeo == null ? StringTemplate.defaultTemplate
: targetGeo.getStringTemplate();
ExpressionValue eval = null;
boolean hasParseError = false;
boolean showErrorMessage = false;
isIndependent = false;
// create previewGeoIndependent
if (previewGeoIndependent == null) {
previewGeoIndependent = new GeoText(kernel.getConstruction());
previewGeoIndependent.setFontSizeMultiplier(1.0);
previewGeoIndependent.addView(ev.getViewID());
ev.add(previewGeoIndependent);
}
// prepare the input string for processing
// String formattedInput = formatInputValue(inputValue);
// parse the input text
try {
exp = kernel.getParser().parseGeoGebraExpression(inputValue);
}
catch (ParseException e) {
isIndependent = true;
hasParseError = true;
if (inputValue.length() > 0) {
showErrorMessage = true;
}
} catch (MyError e) {
isIndependent = true;
hasParseError = true; // odd numbers of quotes give parse errors
showErrorMessage = true;
} catch (TokenMgrError e) {
isIndependent = true;
hasParseError = true; // odd numbers of quotes give parse errors
showErrorMessage = true;
}
// resolve variables and evaluate the expression
if (!(hasParseError)) {
try {
exp.resolveVariables(new EvalInfo(false));
isIndependent = exp.isConstant();
eval = exp.evaluate(tpl);
}
catch (Error e) {
isIndependent = true;
showErrorMessage = true;
// Log.debug("resolve error:" + e.getCause());
} catch (Exception e) {
showErrorMessage = true;
isIndependent = true;
// Log.debug("resolve exception");
}
}
// ====================================
// update the preview Geo
// case1: independent text based on string only, including error
// messages
if (isIndependent) {
// set the text string for the geo
String text = "";
if (showErrorMessage) {
text = ev.getApplication().getLocalization()
.getError("InvalidInput");
} else if (eval != null) {
MyStringBuffer eval2 = ((TextValue) eval).getText();
text = eval2.toValueString(tpl);
}
previewGeoIndependent.setTextString(text);
// update the display style
updateVisualProperties(previewGeoIndependent, targetGeo, isLaTeX,
showErrorMessage);
}
// case 2: dependent GeoText, needs AlgoDependentText
else {
if (previewGeoDependent != null) {
ev.remove(previewGeoDependent);
previewGeoDependent.remove();
textAlgo.remove();
}
// if eg FormulaText["\sqrt{x}"] is entered, it should be displayed
// in the preview as LaTeX
// NB FormulaText[a] is displayed as-is
// FormulaText[a]+"" needs to have LaTeX box manually checked
if (exp.evaluate(tpl).isGeoElement()
&& ((GeoText) (exp.evaluate(tpl))).isLaTeXTextCommand()) {
isLaTeX = true;
}
// eg just an x in the "empty box"
// (otherwise leads to NPE so
// cons.removeFromConstructionList(textAlgo); doesn't get called
if (((ExpressionNode) exp).getGeoElementVariables() == null) {
// can't make an AlgoDependentText
return isLaTeX;
}
// create new previewGeoDependent
textAlgo = new AlgoDependentText(cons, (ExpressionNode) exp, false);
cons.removeFromConstructionList(textAlgo);
previewGeoDependent = textAlgo.getGeoText();
previewGeoDependent.addView(ev.getViewID());
ev.add(previewGeoDependent);
// set the display style
updateVisualProperties(previewGeoDependent, targetGeo, isLaTeX,
showErrorMessage);
// needed to reflect change of significant digits
textAlgo.update();
}
// hide/show the preview geos
previewGeoIndependent.setEuclidianVisible(isIndependent);
previewGeoIndependent.updateRepaint();
ev.update(previewGeoIndependent);
if (previewGeoDependent != null) {
previewGeoDependent.setEuclidianVisible(!isIndependent);
previewGeoDependent.updateRepaint();
ev.update(previewGeoDependent);
}
// update the panel size to match the geo
if (previewGeoIndependent.isEuclidianVisible()) {
updateViewportSize(previewGeoIndependent);
}
if ((previewGeoDependent != null)
&& previewGeoDependent.isEuclidianVisible()) {
updateViewportSize(previewGeoDependent);
}
ev.repaintView();
return isLaTeX;
}
private static boolean guessLaTeX(String textString) {
return textString != null
&& (textString.contains("\\") || textString.contains("^"));
}
/**
* Sets the visual properties of a preview geo
*/
private void updateVisualProperties(GeoText geo, GeoText targetGeo,
boolean isLaTeX, boolean isErrorMessage) {
// set error message style
if (isErrorMessage) {
geo.setVisualStyle(cons.getConstructionDefaults()
.getDefaultGeo(ConstructionDefaults.DEFAULT_TEXT));
geo.setObjColor(GColor.RED);
geo.setBackgroundColor(GColor.WHITE);
// geo.setFontSize(app.geGFontSize());
geo.setFontStyle(GFont.ITALIC);
geo.setLaTeX(false, true);
}
// set text style
else {
if (targetGeo != null) {
geo.setVisualStyle(targetGeo);
} else {
if (isLaTeX) {
geo.setSerifFont(true);
}
geo.setObjColor(GColor.BLACK);
}
geo.setLaTeX(isLaTeX, true);
}
// set geo position in upper left corner (it might need changing after
// isLaTeX change)
locateTextGeo(geo);
// Log.debug("preview text geo loc:" + geo.getAbsoluteScreenLocX() +
// " , "
// + geo.getAbsoluteScreenLocY());
}
/**
* Positions the preview geo in the upper left corner of the panel two
* settings are needed to account for differences in the way LaTeX and
* standard text is drawn
*/
private static void locateTextGeo(GeoText geo) {
int xInset = 4;
int yInset = (int) (geo.isLaTeX() ? 4
: 18 + 12 * (geo.getFontSizeMultiplier() - 1));
geo.setAbsoluteScreenLocActive(true);
geo.setAbsoluteScreenLoc(xInset, yInset);
}
protected App getApp() {
return app;
}
protected void setApp(App app) {
this.app = app;
}
}