/*
GeoGebra - Dynamic Mathematics for Everyone
http://www.geogebra.org
This file is part of GeoGebra.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation.
*/
package org.geogebra.common.kernel.geos;
import java.util.ArrayList;
import java.util.Comparator;
import org.geogebra.common.awt.GFont;
import org.geogebra.common.awt.GRectangle2D;
import org.geogebra.common.euclidian.EuclidianConstants;
import org.geogebra.common.euclidian.EuclidianViewInterfaceCommon;
import org.geogebra.common.factories.AwtFactory;
import org.geogebra.common.kernel.CircularDefinitionException;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Locateable;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.algos.AlgoDependentText;
import org.geogebra.common.kernel.algos.AlgoElement;
import org.geogebra.common.kernel.algos.AlgoSequence;
import org.geogebra.common.kernel.algos.AlgoTextCorner;
import org.geogebra.common.kernel.arithmetic.ExpressionNode;
import org.geogebra.common.kernel.arithmetic.ExpressionNodeConstants.StringType;
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.ValueType;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.main.App;
import org.geogebra.common.plugin.EuclidianStyleConstants;
import org.geogebra.common.plugin.GeoClass;
import org.geogebra.common.util.NormalizerMinimal;
import org.geogebra.common.util.StringUtil;
import org.geogebra.common.util.debug.Log;
import org.geogebra.common.util.lang.Unicode;
/**
* Geometrical element for holding text
*
*/
public class GeoText extends GeoElement
implements Locateable, AbsoluteScreenLocateable, TextValue,
TextProperties, SpreadsheetTraceable, HasSymbolicMode {
private String str;
private GeoPointND startPoint; // location of Text on screen
private boolean isLaTeX;
// corners of the text Michael Borcherds 2007-11-26, see AlgoTextCorner
private GRectangle2D boundingBox;
private boolean needsUpdatedBoundingBox = false;
// font options
private boolean serifFont;
private int fontStyle;
// private int fontSize = 0; // must be zero, as that is the value NOT saved
// to
// // XML
// changed to a multiplier from ggb42
private double fontSizeD = 1;
private int printDecimals = -1;
private int printFigures = -1;
private boolean useSignificantFigures = false;
/** index of exra small modifier */
final public static int FONTSIZE_EXTRA_SMALL = 0;
/** index of very small modifier */
final public static int FONTSIZE_VERY_SMALL = 1;
/** index of small modifier */
final public static int FONTSIZE_SMALL = 2;
/** index of medium modifier */
final public static int FONTSIZE_MEDIUM = 3;
/** index of large modifier */
final public static int FONTSIZE_LARGE = 4;
/** index of very large modifier */
final public static int FONTSIZE_VERY_LARGE = 5;
/** index of exra large modifier */
final public static int FONTSIZE_EXTRA_LARGE = 6;
// for absolute screen location
private boolean hasAbsoluteScreenLocation = false;
/**
* Creates new text
*
* @param c
* construction
*/
public GeoText(Construction c) {
super(c);
// moved from GeoElement's constructor
// must be called from the subclass, see
// http://benpryor.com/blog/2008/01/02/dont-call-subclass-methods-from-a-superclass-constructor/
setConstructionDefaults(); // init visual settings
// don't show in algebra view
// setAlgebraVisible(false);
}
/**
* Creates new GeoText
*
* @param c
* construction
* @param value
* text
*/
public GeoText(Construction c, String value) {
this(c);
setTextString(value);
}
/**
* Copy constructor
*
* @param text
* text to copy
*/
public GeoText(GeoText text) {
this(text.cons);
set(text);
}
@Override
public GeoElement copy() {
return new GeoText(this);
}
@Override
public void set(GeoElementND geo) {
if (!geo.isGeoText()) {
return;
}
GeoText gt = (GeoText) geo;
// macro output: don't set start point
// but update to desired number format
if (cons != geo.getConstruction() && isAlgoMacroOutput()) {
if (!useSignificantFigures) {
gt.setPrintDecimals(printDecimals > -1 ? printDecimals
: kernel.getPrintDecimals(), true);
} else {
gt.setPrintFigures(printFigures > -1 ? printFigures
: kernel.getPrintFigures(), true);
}
str = gt.str;
isLaTeX = gt.isLaTeX;
updateTemplate();
return;
}
str = gt.str;
isLaTeX = gt.isLaTeX;
// needed for Corner[Element[text
boundingBox = gt.getBoundingBox();
try {
if (gt.startPoint != null) {
if (gt.hasAbsoluteLocation()) {
if (this.startPoint != null && this.hasAbsoluteLocation()) {
// just use the value
this.startPoint.set(gt.startPoint);
} else {
// create new location point
setStartPoint(gt.startPoint.copy());
}
} else {
// take existing location point
setStartPoint(gt.startPoint);
}
}
} catch (CircularDefinitionException e) {
Log.debug("set GeoText: CircularDefinitionException");
}
updateTemplate();
}
@Override
public void setVisualStyle(GeoElement geo) {
super.setVisualStyle(geo);
if (!geo.isGeoText()) {
return;
}
GeoText text = (GeoText) geo;
serifFont = text.serifFont;
fontStyle = text.fontStyle;
fontSizeD = text.fontSizeD;
printDecimals = text.printDecimals;
printFigures = text.printFigures;
useSignificantFigures = text.useSignificantFigures;
updateTemplate();
}
/**
* Sets the text contained in this object
*
* @param text2
* text
*/
final public void setTextString(String text2) {
String text = text2 == null ? "" : text2;
// Michael Borcherds 2008-05-11
// remove trailing linefeeds (FreeHEP EMF export doesn't like them)
while (text.length() > 1 && text.charAt(text.length() - 1) == '\n') {
text = text.substring(0, text.length() - 1);
}
if (isLaTeX) {
// TODO: check greek letters of latex string
str = StringUtil.toLaTeXString(text, false);
} else {
// replace "\\n" with a proper newline
// for eg Text["Hello\\nWorld",(1,1)]
str = text.replaceAll("\\\\\\\\n", "\n");
}
}
/**
* Returns the string wrapped in this text
*
* @return the string wrapped in this text
*/
@Override
final public String getTextString() {
return str;
}
/**
* Sets the startpoint without performing any checks. This is needed for
* macros.
*/
@Override
public void initStartPoint(GeoPointND p, int number) {
startPoint = p;
}
@Override
public void setStartPoint(GeoPointND p, int number)
throws CircularDefinitionException {
setStartPoint(p);
}
@Override
public void removeStartPoint(GeoPointND p) {
if (startPoint == p) {
try {
setStartPoint(null);
} catch (Exception e) {
// cannot happen
}
}
}
@Override
public void setStartPoint(GeoPointND p) throws CircularDefinitionException {
// don't allow this if it's eg Text["hello",(2,3)]
if (alwaysFixed)
{
return;
// macro output uses initStartPoint() only
// if (isAlgoMacroOutput()) return;
}
// check for circular definition
if (isParentOf(p)) {
throw new CircularDefinitionException();
}
// remove old dependencies
if (startPoint != null) {
startPoint.getLocateableList().unregisterLocateable(this);
}
// set new location
if (p == null) {
if (startPoint != null) {
startPoint = startPoint.copy();
} else {
startPoint = null;
}
labelOffsetX = 0;
labelOffsetY = 0;
} else {
startPoint = p;
// add new dependencies
startPoint.getLocateableList().registerLocateable(this);
// absolute screen position should be deactivated
setAbsoluteScreenLocActive(false);
}
}
@Override
public void doRemove() {
super.doRemove();
// tell startPoint
if (startPoint != null) {
startPoint.getLocateableList().unregisterLocateable(this);
}
}
@Override
public GeoPointND getStartPoint() {
return startPoint;
}
@Override
public GeoPointND[] getStartPoints() {
if (startPoint == null) {
return null;
}
GeoPointND[] ret = new GeoPointND[1];
ret[0] = startPoint;
return ret;
}
@Override
public boolean hasAbsoluteLocation() {
return startPoint == null || startPoint.isAbsoluteStartPoint();
}
@Override
public void setWaitForStartPoint() {
// this can be ignored for a text
// as the position of its startpoint
// is irrelevant for the rest of the construction
}
@Override
public void update(boolean drag) {
super.update(drag);
if (getLabelSimple() != null
&& getLabelSimple().startsWith("altText")) {
kernel.getApplication().setAltText();
}
// if (needsUpdatedBoundingBox) {
// kernel.notifyUpdate(this);
// }
}
/**
* always returns true
*/
@Override
public boolean isDefined() {
return str != null && (startPoint == null || startPoint.isDefined());
}
/**
* doesn't do anything
*/
@Override
public void setUndefined() {
str = null;
}
@Override
public String toValueString(StringTemplate tpl1) {
// http://www.geogebra.org/forum/viewtopic.php?f=8&t=26139
return str == null ? "" : str;
}
/**
* Returns quoted text value string.
*/
@Override
public String toOutputValueString(StringTemplate tpl1) {
StringType printForm = tpl1.getStringType();
sbToString.setLength(0);
if (printForm.equals(StringType.LATEX)) {
if (!StringUtil.containsLaTeX(str)) {
sbToString.append("\\text{");
}
sbToString.append(Unicode.OPEN_DOUBLE_QUOTE);
} else {
sbToString.append('\"');
}
if (str != null) {
sbToString.append(str);
}
if (printForm.equals(StringType.LATEX)) {
sbToString.append(Unicode.CLOSE_DOUBLE_QUOTE);
if (!StringUtil.containsLaTeX(str)) {
sbToString.append("}");
}
} else {
sbToString.append('\"');
}
return sbToString.toString();
}
@Override
public String toString(StringTemplate tpl1) {
sbToString.setLength(0);
if (isLabelSet()) {
sbToString.append(label);
sbToString.append(" = ");
}
sbToString.append('\"');
if (str != null) {
sbToString.append(tpl1.escapeString(str));
}
sbToString.append('\"');
return sbToString.toString();
}
private StringBuilder sbToString = new StringBuilder(80);
@Override
public boolean showInAlgebraView() {
return true;
}
@Override
protected boolean showInEuclidianView() {
return isDefined();
}
@Override
public int getRelatedModeID() {
return EuclidianConstants.MODE_TEXT;
}
@Override
public GeoClass getGeoClassType() {
return GeoClass.TEXT;
}
@Override
public boolean isMoveable() {
if (alwaysFixed) {
return false;
}
return !isLocked();
}
/**
* used for eg Text["text",(1,2)] to stop it being editable
*/
public boolean isTextCommand = false;
/**
*
* @param isCommand
* new value of isTextCommand
*/
public void setIsTextCommand(boolean isCommand) {
this.isTextCommand = isCommand;
}
@Override
public boolean isTextCommand() {
// check for eg If[ a==1 , "hello", "bye"] first
if ((getParentAlgorithm() != null)
&& !(getParentAlgorithm() instanceof AlgoDependentText)) {
return true;
}
return isTextCommand;
}
/**
* @return true if this text was produced by algo with LaTeX output
*/
@Override
public boolean isLaTeXTextCommand() {
if (!isTextCommand || getParentAlgorithm() == null) {
return false;
}
return getParentAlgorithm().isLaTeXTextCommand();
}
@Override
public void setAlgoMacroOutput(boolean isAlgoMacroOutput) {
super.setAlgoMacroOutput(true);
setIsTextCommand(true);
}
/**
*/
boolean alwaysFixed = false;
private StringTemplate tpl = StringTemplate.defaultTemplate;
private GeoText linkedText;
/**
* For Text[Text[a]] the inner text must use template of the outer
*
* @param text
* descendant whose string template may be used
*/
public void addTextDescendant(GeoText text) {
if (isLabelSet()) {
return;
}
linkedText = text;
}
/**
*
* @param alwaysFixed
* flag to prevent movement of Text["whee",(1,2)]
*/
public void setAlwaysFixed(boolean alwaysFixed) {
this.alwaysFixed = alwaysFixed;
}
@Override
public boolean isFixable() {
// workaround for Text["text",(1,2)]
if (alwaysFixed) {
return false;
}
return true;
}
@Override
public boolean isNumberValue() {
return false;
}
@Override
public boolean evaluatesToText() {
return true;
}
@Override
public boolean isGeoText() {
return true;
}
@Override
public MyStringBuffer getText() {
if (str != null) {
return new MyStringBuffer(kernel, str);
}
return new MyStringBuffer(kernel, "");
}
/**
* save object in XML format
*/
@Override
public final void getXML(boolean getListenersToo, StringBuilder sb) {
// an independent text needs to add
// its expression itself
// e.g. text0 = "Circle"
if (isIndependent() && getDefaultGeoType() < 0) {
sb.append("<expression");
sb.append(" label=\"");
StringUtil.encodeXML(sb, label);
sb.append("\" exp=\"");
StringUtil.encodeXML(sb,
toOutputValueString(StringTemplate.xmlTemplate));
// expression
sb.append("\"/>\n");
}
sb.append("<element");
sb.append(" type=\"text\"");
sb.append(" label=\"");
StringUtil.encodeXML(sb, label);
if (getDefaultGeoType() >= 0) {
sb.append("\" default=\"");
sb.append(getDefaultGeoType());
}
sb.append("\">\n");
if (isSymbolicMode()) {
sb.append("\t<symbolic val=\"true\" />\n");
}
getXMLtags(sb);
if (getListenersToo) {
getListenerTagsXML(sb);
}
sb.append("</element>\n");
}
/**
* returns all class-specific xml tags for getXML
*/
@Override
protected void getXMLtags(StringBuilder sb) {
getXMLvisualTags(sb, false);
getXMLfixedTag(sb);
if (isLaTeX) {
sb.append("\t<isLaTeX val=\"true\"/>\n");
}
appendFontTag(sb, serifFont, fontSizeD, fontStyle, isLaTeX,
kernel.getApplication());
// 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");
}
getBreakpointXML(sb);
getAuxiliaryXML(sb);
// store location of text (and possible labelOffset)
sb.append(getXMLlocation());
getScriptTags(sb);
}
/**
* Returns startPoint of this text in XML notation.
*/
private String getXMLlocation() {
StringBuilder sb = new StringBuilder();
if (hasAbsoluteScreenLocation) {
sb.append("\t<absoluteScreenLocation x=\"");
sb.append(labelOffsetX);
sb.append("\" y=\"");
sb.append(labelOffsetY);
sb.append("\"/>\n");
} else {
// location of text
if (startPoint != null) {
sb.append(startPoint.getStartPointXML());
if (labelOffsetX != 0 || labelOffsetY != 0) {
sb.append("\t<labelOffset");
sb.append(" x=\"");
sb.append(labelOffsetX);
sb.append("\" y=\"");
sb.append(labelOffsetY);
sb.append("\"/>\n");
}
}
}
return sb.toString();
}
@Override
public void setAllVisualPropertiesExceptEuclidianVisible(GeoElement geo,
boolean keepAdvanced) {
super.setAllVisualPropertiesExceptEuclidianVisible(geo, keepAdvanced);
// start point of text
if (geo instanceof GeoText) {
GeoText text = (GeoText) geo;
setSameLocation(text);
setLaTeX(text.isLaTeX, true);
}
}
private void setSameLocation(GeoText text) {
if (text.hasAbsoluteScreenLocation) {
setAbsoluteScreenLocActive(true);
setAbsoluteScreenLoc(text.getAbsoluteScreenLocX(),
text.getAbsoluteScreenLocY());
} else {
if (text.startPoint != null) {
try {
setStartPoint(text.startPoint);
} catch (Exception e) {
// Circular definition, do nothing
}
}
}
}
/**
* Returns true for LaTeX texts
*
* @return true for LaTeX texts
*/
public boolean isLaTeX() {
return isLaTeX;
}
/**
* Changes type of this object to math rendering type (LaTeX)
*
* @param b
* true for math rendering
* @param updateParentAlgo
* when true, parent is recomputed
*/
public void setLaTeX(boolean b, boolean updateParentAlgo) {
if (b == isLaTeX) {
return;
}
isLaTeX = b;
updateTemplate();
// update parent algorithm if it's not a sequence
if (updateParentAlgo) {
updateParent();
}
}
private void updateParent() {
AlgoElement parent = getParentAlgorithm();
if (parent != null && !(parent instanceof AlgoSequence)) {
parent.update();
}
}
@Override
public void setAbsoluteScreenLoc(int x, int y) {
labelOffsetX = x;
labelOffsetY = y;
if (!hasScreenLocation() && (x != 0 && y != 0)) {
setScreenLocation(x, y);
}
}
@Override
public int getAbsoluteScreenLocX() {
return labelOffsetX;
}
@Override
public int getAbsoluteScreenLocY() {
return labelOffsetY;
}
@Override
public double getRealWorldLocX() {
if (startPoint == null) {
return 0;
}
return startPoint.getInhomCoords().getX();
}
@Override
public double getRealWorldLocY() {
if (startPoint == null) {
return 0;
}
return startPoint.getInhomCoords().getY();
}
@Override
public void setRealWorldLoc(double x, double y) {
GeoPointND locPoint = getStartPoint();
if (locPoint == null) {
locPoint = new GeoPoint(cons);
try {
setStartPoint(locPoint);
} catch (Exception e) {
// circular definition, do nothing
}
}
locPoint.setCoords(x, y, 1.0);
labelOffsetX = 0;
labelOffsetY = 0;
}
@Override
public void setAbsoluteScreenLocActive(boolean flag) {
if (flag == hasAbsoluteScreenLocation) {
return;
}
hasAbsoluteScreenLocation = flag;
if (flag) {
// remove startpoint
if (startPoint != null) {
startPoint.getLocateableList().unregisterLocateable(this);
startPoint = null;
}
} else {
labelOffsetX = 0;
labelOffsetY = 0;
}
}
@Override
public boolean isAbsoluteScreenLocActive() {
return hasAbsoluteScreenLocation;
}
@Override
public boolean isAbsoluteScreenLocateable() {
return true;
}
// public int getFontSize() {
// return fontSize;
// }
@Override
public double getFontSizeMultiplier() {
return fontSizeD;
}
/**
*
* @param index
* index of size in the settings
* @return additive size modifier
*/
public static double getRelativeFontSize(int index) {
switch (index) {
case FONTSIZE_EXTRA_SMALL: // extra small
return 0.5;
case FONTSIZE_VERY_SMALL: // very small
return 0.7;
case FONTSIZE_SMALL: // small
return 1;
default:
case FONTSIZE_MEDIUM: // medium
return 1.4;
case FONTSIZE_LARGE: // large
return 2;
case FONTSIZE_VERY_LARGE: // very large
return 4;
case FONTSIZE_EXTRA_LARGE: // extra large
return 8;
}
}
/**
*
* @param d
* font size modifier
* @return corresponding index
*/
public static int getFontSizeIndex(double d) {
if (d <= 0.5) {
return FONTSIZE_EXTRA_SMALL;
}
if (d <= 0.8) {
return FONTSIZE_VERY_SMALL;
}
if (d <= 1) {
return FONTSIZE_SMALL;
}
if (d <= 1.5) {
return FONTSIZE_MEDIUM;
}
if (d <= 2) {
return FONTSIZE_LARGE;
}
if (d <= 4) {
return FONTSIZE_VERY_LARGE;
}
return FONTSIZE_EXTRA_LARGE;
}
// public void setFontSize(int size) {
// fontSize = size;
// }
@Override
public void setFontSizeMultiplier(double d) {
fontSizeD = d;
}
@Override
public int getFontStyle() {
return fontStyle;
}
@Override
public void setFontStyle(int fontStyle) {
this.fontStyle = fontStyle;
// needed for eg \sqrt in latex
if ((fontStyle & GFont.BOLD) != 0) {
setLineThickness(
EuclidianStyleConstants.DEFAULT_LINE_THICKNESS * 2);
} else {
setLineThickness(EuclidianStyleConstants.DEFAULT_LINE_THICKNESS);
}
}
@Override
final public int getPrintDecimals() {
return printDecimals;
}
@Override
final public int getPrintFigures() {
return printFigures;
}
@Override
public void setPrintDecimals(int printDecimals, boolean update) {
AlgoElement algo = getParentAlgorithm();
if (algo != null && update) {
this.printDecimals = printDecimals;
printFigures = -1;
useSignificantFigures = false;
updateTemplate();
updateTemplateAlgos(algo);
}
}
@Override
public void setPrintFigures(int printFigures, boolean update) {
AlgoElement algo = getParentAlgorithm();
if (algo != null && update) {
this.printFigures = printFigures;
printDecimals = -1;
useSignificantFigures = true;
updateTemplate();
updateTemplateAlgos(algo);
}
}
private void updateTemplateAlgos(AlgoElement algo) {
if (algo == null) {
return;
}
for (int i = 0; i < algo.getInput().length; i++) {
if (algo.getInput()[i].isGeoText()) {
updateTemplateAlgos(algo.getInput()[i].getParentAlgorithm());
}
}
algo.update();
}
@Override
public boolean useSignificantFigures() {
return useSignificantFigures;
}
@Override
public boolean isSerifFont() {
return serifFont;
}
@Override
public void setSerifFont(boolean serifFont) {
this.serifFont = serifFont;
}
/**
* @param result
* point for storing result
* @param n
* index of corner (1 for lower left, then anticlockwise)
*/
public void calculateCornerPoint(GeoPoint result, int n) {
// adapted from GeoImage by Michael Borcherds 2007-11-26
if (hasAbsoluteScreenLocation || boundingBox == null) {
result.setUndefined();
return;
}
switch (n) {
case 4: // top left
result.setCoords(boundingBox.getX(), boundingBox.getY(), 1.0);
break;
case 3: // top right
result.setCoords(boundingBox.getX() + boundingBox.getWidth(),
boundingBox.getY(), 1.0);
break;
case 2: // bottom right
result.setCoords(boundingBox.getX() + boundingBox.getWidth(),
boundingBox.getY() + boundingBox.getHeight(), 1.0);
break;
case 1: // bottom left
result.setCoords(boundingBox.getX(),
boundingBox.getY() + boundingBox.getHeight(), 1.0);
break;
default:
result.setUndefined();
}
}
/**
* @return Bounding box of this text
*/
public GRectangle2D getBoundingBox() {
return boundingBox;
}
/**
* @param x
* x coord
* @param y
* y coord
* @param w
* width
* @param h
* height
*/
public void setBoundingBox(double x, double y, double w, double h) {
boolean firstTime = boundingBox == null;
if (firstTime) {
boundingBox = AwtFactory.getPrototype().newRectangle2D();
}
boundingBox.setRect(x, y, w, h);
}
/**
* @return tue if bounding box is not correct anymore
*/
public final boolean isNeedsUpdatedBoundingBox() {
return needsUpdatedBoundingBox;
}
/**
* @param needsUpdatedBoundingBox
* true to make sure this object upates itself
*/
public final void setNeedsUpdatedBoundingBox(
boolean needsUpdatedBoundingBox) {
this.needsUpdatedBoundingBox = needsUpdatedBoundingBox;
}
// Michael Borcherds 2008-04-30
@Override
final public boolean isEqual(GeoElementND geo) {
// return false if it's a different type
if (str == null) {
return false;
}
if (geo.isGeoText()) {
return str.equals(((GeoText) geo).str);
}
return false;
}
@Override
public void setZero() {
str = "";
}
/**
* Returns a comparator for GeoText objects. If equal, doesn't return zero
* (otherwise TreeSet deletes duplicates)
*
* @return comparator
*/
public static Comparator<GeoText> getComparator() {
if (comparator == null) {
comparator = new Comparator<GeoText>() {
@Override
public int compare(GeoText itemA, GeoText itemB) {
NormalizerMinimal noramlizer = itemA.getKernel()
.getApplication().getNormalizer();
// remove accents etc
String strA = noramlizer.transform(itemA.getTextString());
String strB = noramlizer.transform(itemB.getTextString());
// do comparison without accents etc
int comp = strA.compareTo(strB);
if (comp == 0) {
// try compare with accents
comp = itemA.getTextString()
.compareTo(itemB.getTextString());
}
if (comp == 0) {
// if we return 0 for equal strings, the TreeSet deletes
// the equal one
return itemA.getConstructionIndex() > itemB
.getConstructionIndex() ? -1 : 1;
}
return comp;
}
};
}
return comparator;
}
private static Comparator<GeoText> comparator;
private void updateTemplate() {
StringType type = isLaTeX ? StringType.LATEX : StringType.GEOGEBRA;
if (useSignificantFigures() && printFigures > -1) {
tpl = StringTemplate.printFigures(type, printFigures, false);
} else if (!useSignificantFigures && printDecimals > -1) {
tpl = StringTemplate.printDecimals(type, printDecimals, false);
} else {
tpl = StringTemplate.get(type);
}
tpl = tpl.deriveWithFractions(this.symbolicMode);
}
@Override
public boolean isAlwaysFixed() {
return alwaysFixed;
}
@Override
public boolean justFontSize() {
return false;
}
@Override
public boolean isRedefineable() {
return true;
}
@Override
public boolean isLaTeXDrawableGeo() {
return isLaTeX() || (getTextString() != null
&& getTextString().indexOf('_') != -1);
}
@Override
public boolean hasDrawable3D() {
return true;
}
@Override
public boolean hasBackgroundColor() {
return true;
}
/**
* String template; contains both string type and precision
*
* @return template
*/
public StringTemplate getStringTemplate() {
if (linkedText == null) {
return tpl;
}
return linkedText.getStringTemplate();
}
private static enum SpreadsheetTraceableCase {
SPREADSHEET_TRACEABLE_NOT_TESTED, SPREADSHEET_TRACEABLE_TRUE, SPREADSHEET_TRACEABLE_FALSE
}
private SpreadsheetTraceableCase spreadsheetTraceableCase = SpreadsheetTraceableCase.SPREADSHEET_TRACEABLE_NOT_TESTED;
private ExpressionValue spreadsheetTraceableValue;
private ExpressionNode spreadsheetTraceableLeftTree;
/**
* set objects for trace to spreadsheet
*
* @param leftTree
* tree for column heading
* @param value
* value to trace
*/
public void setSpreadsheetTraceable(ExpressionNode leftTree,
ExpressionValue value) {
this.spreadsheetTraceableLeftTree = leftTree;
this.spreadsheetTraceableValue = value;
}
/**
* init case for spreadsheet traceable case
*/
public void initSpreadsheetTraceableCase() {
spreadsheetTraceableCase = SpreadsheetTraceableCase.SPREADSHEET_TRACEABLE_NOT_TESTED;
}
@Override
public boolean isSpreadsheetTraceable() {
// App.printStacktrace("\n"+this+"\n"+spreadsheetTraceableCase);
switch (spreadsheetTraceableCase) {
case SPREADSHEET_TRACEABLE_TRUE:
return true;
case SPREADSHEET_TRACEABLE_FALSE:
return false;
case SPREADSHEET_TRACEABLE_NOT_TESTED:
AlgoElement algo = getParentAlgorithm();
if (algo != null && (algo instanceof AlgoDependentText)) {
((AlgoDependentText) algo).setSpreadsheetTraceableText();
if (spreadsheetTraceableLeftTree != null) {
spreadsheetTraceableCase = SpreadsheetTraceableCase.SPREADSHEET_TRACEABLE_TRUE;
// if no traceable value, only copy possible
if (spreadsheetTraceableValue == null) {
traceModes = TraceModesEnum.ONLY_COPY;
} else {
traceModes = TraceModesEnum.ONE_VALUE_OR_COPY;
}
return true;
}
}
// spreadsheetTraceableCase =
// SpreadsheetTraceableCase.SPREADSHEET_TRACEABLE_FALSE;
// return false;
spreadsheetTraceableCase = SpreadsheetTraceableCase.SPREADSHEET_TRACEABLE_TRUE;
traceModes = TraceModesEnum.ONLY_COPY;
return true;
default:
return false;
}
}
@Override
public void updateColumnHeadingsForTraceValues() {
resetSpreadsheetColumnHeadings();
GeoText text = getColumnHeadingText(spreadsheetTraceableLeftTree);
text.setLaTeX(this.isLaTeX, false);
spreadsheetColumnHeadings.add(text);
}
@Override
public void addToSpreadsheetTraceList(
ArrayList<GeoNumeric> spreadsheetTraceList) {
GeoNumeric numeric = new GeoNumeric(cons,
spreadsheetTraceableValue.evaluateDouble());
spreadsheetTraceList.add(numeric);
}
private TraceModesEnum traceModes;
private boolean symbolicMode;
private int totalHeight;
private int totalWidth;
@Override
public TraceModesEnum getTraceModes() {
return traceModes;
}
/**
* @param sb
* string builder for appending the tag
* @param serifFont
* serif flag
* @param fontSizeD
* font size
* @param fontStyle
* font style
* @param isLaTeX
* latex flag
* @param app
* application
*/
public static void appendFontTag(StringBuilder sb, boolean serifFont,
double fontSizeD, int fontStyle, boolean isLaTeX, App app) {
// font settings
if (serifFont || fontSizeD != 1 || fontStyle != 0 || isLaTeX) {
sb.append("\t<font serif=\"");
sb.append(serifFont);
// multiplier
sb.append("\" sizeM=\"");
sb.append(fontSizeD);
// work out an estimate (can't guarantee exact)
double oldFontSize = app.getFontSize() * fontSizeD
- app.getFontSize();
if (oldFontSize > 0) {
oldFontSize = Math.ceil(oldFontSize);
} else {
oldFontSize = Math.floor(oldFontSize);
}
// still write this (for ggb40 compatibility)
sb.append("\" size=\"");
sb.append((int) oldFontSize);
sb.append("\" style=\"");
sb.append(fontStyle);
sb.append("\"/>\n");
}
}
@Override
public boolean isPinnable() {
return true;
}
@Override
public void updateLocation() {
updateGeo(false);
kernel.notifyUpdateLocation(this);
}
@Override
public void updateVisualStyle(GProperty prop) {
super.updateVisualStyle(prop);
if (prop == GProperty.FONT) {
ArrayList<AlgoElement> algosTextCorner = new ArrayList<AlgoElement>();
for (AlgoElement algo : getAlgorithmList()) {
if (algo instanceof AlgoTextCorner) {
algosTextCorner.add(algo);
}
}
AlgoElement.updateCascadeAlgos(algosTextCorner);
}
}
@Override
final public HitType getLastHitType() {
return HitType.ON_FILLING;
}
@Override
protected boolean isVisibleInView3DNotSet() {
if (isVisibleInView(App.VIEW_EUCLIDIAN) && !hasAbsoluteLocation()) {
// visible: we set it
visibleInView3D = VisibleInView.TRUE;
return true;
}
// not visible: we set it
visibleInView3D = VisibleInView.FALSE;
return false;
}
/**
* may need to be added to 3D view after creation
*/
public void checkVisibleIn3DViewNeeded() {
if (isVisibleInView(App.VIEW_EUCLIDIAN)) {
// we need to force visibility in 3D view and views
// for plane
addViews3D();
if (kernel.getApplication().isEuclidianView3Dinited()) {
kernel.getApplication().getEuclidianView3D().add(this);
}
setVisibleInViewForPlane(true);
kernel.getApplication().addToViewsForPlane(this);
}
}
@Override
public ValueType getValueType() {
return ValueType.TEXT;
}
@Override
public boolean needToShowBothRowsInAV() {
return false;
}
@Override
public void setSymbolicMode(boolean mode, boolean updateParent) {
if (mode != this.symbolicMode) {
this.symbolicMode = mode;
updateTemplate();
if (updateParent) {
updateParent();
}
}
}
@Override
public boolean isSymbolicMode() {
return this.symbolicMode;
}
/**
* Sets the total width of the geo.
*
* @param width
* to set.
*/
public void setTotalWidth(int width) {
totalWidth = width;
}
/**
* Sets the total height of the geo.
*
* @param height
* to set.
*/
public void setTotalHeight(int height) {
totalHeight = height;
}
public int getTotalWidth(EuclidianViewInterfaceCommon ev) {
return totalWidth;
}
public int getTotalHeight(EuclidianViewInterfaceCommon ev) {
return totalHeight;
}
}