/* 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. */ /* * GeoElement.java * * Created on 30. August 2001, 17:10 */ package org.geogebra.common.kernel.geos; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Stack; import java.util.TreeSet; import org.geogebra.common.awt.GColor; import org.geogebra.common.awt.GPoint; import org.geogebra.common.awt.MyImage; import org.geogebra.common.euclidian.DrawableND; import org.geogebra.common.euclidian.EuclidianView; import org.geogebra.common.euclidian.EuclidianViewInterfaceSlim; import org.geogebra.common.factories.FormatFactory; import org.geogebra.common.factories.LaTeXFactory; import org.geogebra.common.gui.dialog.options.model.AxisModel.IAxisModelListener; import org.geogebra.common.kernel.AnimationManager; import org.geogebra.common.kernel.CircularDefinitionException; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.ConstructionDefaults; import org.geogebra.common.kernel.GTemplate; import org.geogebra.common.kernel.GraphAlgo; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.Locateable; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.algos.AlgoAttachCopyToView; import org.geogebra.common.kernel.algos.AlgoBarChart; import org.geogebra.common.kernel.algos.AlgoCirclePointRadiusInterface; import org.geogebra.common.kernel.algos.AlgoDependentText; import org.geogebra.common.kernel.algos.AlgoDynamicCoordinatesInterface; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.algos.AlgoIntegralODE; import org.geogebra.common.kernel.algos.AlgoJoinPointsSegment; import org.geogebra.common.kernel.algos.AlgoMacroInterface; import org.geogebra.common.kernel.algos.AlgoName; import org.geogebra.common.kernel.algos.AlgoTranslate; import org.geogebra.common.kernel.algos.AlgoVectorPoint; import org.geogebra.common.kernel.algos.AlgorithmSet; import org.geogebra.common.kernel.algos.ConstructionElement; import org.geogebra.common.kernel.algos.DrawInformationAlgo; import org.geogebra.common.kernel.algos.TableAlgo; 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.FunctionVariable; import org.geogebra.common.kernel.arithmetic.Inspecting; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.kernel.arithmetic.NumberValue; import org.geogebra.common.kernel.arithmetic.TextValue; import org.geogebra.common.kernel.arithmetic.Traversing; import org.geogebra.common.kernel.arithmetic.ValueType; import org.geogebra.common.kernel.commands.Commands; 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.kernel.kernelND.GeoVectorND; import org.geogebra.common.main.App; import org.geogebra.common.main.Feature; import org.geogebra.common.main.MyError; import org.geogebra.common.plugin.EuclidianStyleConstants; import org.geogebra.common.plugin.Event; import org.geogebra.common.plugin.EventType; import org.geogebra.common.plugin.GeoClass; import org.geogebra.common.plugin.Operation; import org.geogebra.common.plugin.ScriptManager; import org.geogebra.common.plugin.script.JsScript; import org.geogebra.common.plugin.script.Script; import org.geogebra.common.util.ExtendedBoolean; import org.geogebra.common.util.IndexHTMLBuilder; import org.geogebra.common.util.LaTeXCache; import org.geogebra.common.util.MyMath; import org.geogebra.common.util.NumberFormatAdapter; import org.geogebra.common.util.SpreadsheetTraceSettings; import org.geogebra.common.util.StringUtil; import org.geogebra.common.util.debug.GeoGebraProfiler; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.lang.Language; import org.geogebra.common.util.lang.Unicode; import com.google.j2objc.annotations.Weak; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * * @author Markus * @version 2011-12-02 */ public abstract class GeoElement extends ConstructionElement implements GeoElementND { /** * Column headings for spreadsheet trace */ protected ArrayList<GeoText> spreadsheetColumnHeadings = null; /** min decimals or significant figures to use in editing string */ public static final int MIN_EDITING_PRINT_PRECISION = 5; // maximum label offset distance private static final int MAX_LABEL_OFFSET = 80; // private static int geoElementID = Integer.MIN_VALUE; /** * Default point labels */ private static final char[] pointLabels = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'Z' }; private static final char[] functionLabels = { 'f', 'g', 'h', 'p', 'q', 'r', 's', 't' }; private static final char[] lineLabels = { 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'a', 'b', 'c', 'd', 'e' }; private static final char[] vectorLabels = { 'u', 'v', 'w', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't' }; private static final char[] conicLabels = { 'c', 'd', 'e', 'f', 'g', 'h', 'k', 'p', 'q', 'r', 's', 't' }; private static final char[] lowerCaseLabels = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w' }; private static final char[] integerLabels = { 'n', 'i', 'j', 'k', 'l', 'm', }; private static final char[] arabic = { '\u0623', '\u0628', '\u062a', '\u062b', '\u062c', '\u062d', '\u062e', '\u062f', '\u0630', '\u0631', '\u0632', '\u0633', '\u0634', '\u0635', '\u0636', '\u0637', '\u0638', '\u0639', '\u063a', '\u0641', '\u0642', '\u0643', '\u0644', '\u0645', '\u0646', '\u0647', // needs this too // '\u0640' (see // later on) '\u0648', '\u064a' }; private static final char[] yiddish = { '\u05D0', '\u05D1', '\u05D2', '\u05D3', '\u05D4', '\u05D5', '\u05D6', '\u05D7', '\u05D8', '\u05DB', '\u05DC', '\u05DE', '\u05E0', '\u05E1', '\u05E2', '\u05E4', '\u05E6', '\u05E7', '\u05E8', '\u05E9', '\u05EA' }; /** label mode: name */ public static final int LABEL_NAME = 0; /** label mode: name + value */ public static final int LABEL_NAME_VALUE = 1; /** label mode: value */ public static final int LABEL_VALUE = 2; /** label mode: caption */ public static final int LABEL_CAPTION = 3; // Michael Borcherds 2008-02-18 /** label mode: default */ public static final int LABEL_DEFAULT = 4; /** label mode: default, name */ public static final int LABEL_DEFAULT_NAME = 5; /** label mode: default, name + value */ public static final int LABEL_DEFAULT_NAME_VALUE = 6; /** label mode: default, value */ public static final int LABEL_DEFAULT_VALUE = 7; /** label mode: default, caption */ public static final int LABEL_DEFAULT_CAPTION = 8; /** tooltip mode: iff AV showing */ public static final int TOOLTIP_ALGEBRAVIEW_SHOWING = 0; /** tooltip mode: always on */ public static final int TOOLTIP_ON = 1; /** tooltip mode: always off */ public static final int TOOLTIP_OFF = 2; /** tooltip mode: caption, always on */ public static final int TOOLTIP_CAPTION = 3; /** tooltip mode: next spreadsheet cell, always on */ public static final int TOOLTIP_NEXTCELL = 4; private int tooltipMode = TOOLTIP_ALGEBRAVIEW_SHOWING; /** should only be used directly in subclasses */ protected String label; private String realLabel; // for macro constructions, see setRealLabel() for // details private String oldLabel; // see doRenameLabel private String caption; // accessible via getRawCaption /** true if label is wanted, but not set */ private boolean labelWanted = false; /** tue if label is set */ private boolean labelSet = false; private boolean localVarLabelSet = false; private boolean euclidianVisible = true; private boolean forceEuclidianVisible = false; private boolean algebraVisible = true; private boolean labelVisible = true; private boolean isConsProtBreakpoint; // in construction protocol private boolean isAlgoMacroOutput; // is an output object of a macro // construction /** fixed (cannot be moved or deleted) */ protected boolean fixed = false; /** label, value, caption, label+value */ public int labelMode = LABEL_DEFAULT; /** cartesian, polar or complex */ public int toStringMode = Kernel.COORD_CARTESIAN; /** default (foreground) color */ protected GColor objColor = GColor.BLACK; /** background color */ protected GColor bgColor = null; // none by default /** color when selected */ protected GColor selColor = objColor; /** color for fill */ protected GColor fillColor = objColor; private int layer = 0; private NumberValue animationIncrement; private GeoNumberValue animationSpeedObj; private GeoCasCell correspondingCasCell; // used by GeoCasCell private boolean animating = false; /** maximal animation speed */ final public static double MAX_ANIMATION_SPEED = 100; /** animation type: oscillating */ final public static int ANIMATION_OSCILLATING = 0; /** animation type: increasing */ final public static int ANIMATION_INCREASING = 1; /** animation type: decreasing */ final public static int ANIMATION_DECREASING = 2; /** animation type: increasing once */ final public static int ANIMATION_INCREASING_ONCE = 3; private int animationType = ANIMATION_OSCILLATING; private int animationDirection = 1; /** transparency */ protected double alphaValue = 0.0f; /** angle of hatching */ protected int hatchingAngle = 45; // in degrees /** distance of hatching */ protected int hatchingDistance = 10; private boolean inverseFill = false; private String fillSymbol = null; /** The original location if any */ private ScreenLocation screenLocation = null; // ================================= // G.Sturr new fill options /** * substitute for imageFileName and image - Arpad Fekete; // 2011-12-01 */ protected GeoElementGraphicsAdapter graphicsadapter; /** * Fill types of elements * * @author Giulliano Bellucci */ public enum FillType { /** * Simple fill (color+opacity) * * need to be in menu order here * * the integer is used in the XML so can't be changed */ STANDARD(0, false), /** * Hatched fill */ HATCH(1, true), /** * Crosshatched fill */ CROSSHATCHED(2, true), /** * Chessboard fill, upright or diagonal */ CHESSBOARD(3, true), /** * Dotted fill */ DOTTED(4, true), /** * Honeycomb fill */ HONEYCOMB(5, true), /** * Brick fill */ BRICK(6, true), /** * Weaving fill */ WEAVING(9, true), /** * Unicode symbols fill */ SYMBOLS(7, true), /** * Image background */ IMAGE(8, false); private int value; private boolean hatch; /** * @return value for XML */ public int getValue() { return value; } private FillType(int value, boolean hatch) { this.value = value; this.hatch = hatch; } /** * @return whether this is hatch or something else (image, standard) */ public boolean isHatch() { return hatch; } } /** fill type */ protected FillType fillType = FillType.STANDARD; // ================================= /** color space: RGB */ final public static int COLORSPACE_RGB = 0; /** color space: HSB */ final public static int COLORSPACE_HSB = 1; /** color space: HSL */ final public static int COLORSPACE_HSL = 2; private int colorSpace = COLORSPACE_RGB; private List<Integer> viewFlags = null; /** * @return used color space (GeoElement.COLORSPACE_*) */ @Override public int getColorSpace() { return colorSpace; } /** * @param colorSpace * color space (GeoElement.COLORSPACE_*) */ @Override public void setColorSpace(final int colorSpace) { this.colorSpace = colorSpace; } private int defaultGeoType = -1; /** * @return index for ConstructionDefaults or -1 if not default geo */ public int getDefaultGeoType() { return defaultGeoType; } /** * * @return true if a default geo */ public boolean isDefaultGeo() { return defaultGeoType != -1; } /** * @param defaultGT * index for ConstructionDefaults */ public void setDefaultGeoType(final int defaultGT) { defaultGeoType = defaultGT; } /** offset for label in EV */ public int labelOffsetX = 0; /** offset for label in EV */ public int labelOffsetY = 0; private boolean auxiliaryObject = false; private boolean selectionAllowed = true; // on change: see setVisualValues() // spreadsheet specific properties private GPoint spreadsheetCoords, oldSpreadsheetCoords; // number of AlgoCellRange using this cell: don't allow renaming when // greater 0 private int cellRangeUsers = 0; /** condition to show object */ protected GeoBoolean condShowObject; /** whether we should send value to CAS (for false we send the name) */ protected boolean sendValueToCas = true; // function to determine color private GeoList colFunction; // { GeoNumeric red, GeoNumeric Green, // GeoNumeric Blue } private boolean useVisualDefaults = true; /** true if color is set */ protected boolean isColorSet = false; /** true if geo is highlited */ protected boolean highlighted = false; private boolean selected = false; private String strAlgebraDescription, strAlgebraDescTextOrHTML, strAlgebraDescriptionHTML, strLabelTextOrHTML; /** LaTeX string for LaTeX export */ protected String strLaTeX; private boolean strAlgebraDescriptionNeedsUpdate = true; private boolean strAlgebraDescTextOrHTMLneedsUpdate = true; private boolean strAlgebraDescriptionHTMLneedsUpdate = true; private boolean strLabelTextOrHTMLUpdate = true; /** true if strLaTex is out of sync */ protected boolean strLaTeXneedsUpdate = true; // line thickness and line type: s /** * note: line thickness in Drawable is calculated as lineThickness / 2.0f */ private int lineThickness = EuclidianStyleConstants.DEFAULT_LINE_THICKNESS; /** line type (full, dashed, ...) see EuclidianStyleConstants.LINE_TYPE */ public int lineType = EuclidianStyleConstants.DEFAULT_LINE_TYPE; /** line type for hidden parts (for 3D) */ public int lineTypeHidden = EuclidianStyleConstants.DEFAULT_LINE_TYPE_HIDDEN; /** line opacity */ protected int lineOpacity = 255; /** decoration type */ private int decorationType = DECORATION_NONE; // DECORATION /** Decoration type: no decoration */ public static final int DECORATION_NONE = 0; // segment decorations /** Decoration type: one tick */ public static final int DECORATION_SEGMENT_ONE_TICK = 1; /** Decoration type: two ticks */ public static final int DECORATION_SEGMENT_TWO_TICKS = 2; /** Decoration type: three ticks */ public static final int DECORATION_SEGMENT_THREE_TICKS = 3; // Michael Borcherds 2007-10-06 /** Decoration type: one arow */ public static final int DECORATION_SEGMENT_ONE_ARROW = 4; /** Decoration type: two arrows */ public static final int DECORATION_SEGMENT_TWO_ARROWS = 5; /** Decoration type: three arrows */ public static final int DECORATION_SEGMENT_THREE_ARROWS = 6; // Michael Borcherds 2007-10-06 // angle decorations /** Decoration type for angles: two arcs */ public static final int DECORATION_ANGLE_TWO_ARCS = 1; /** Decoration type for angles: three arcs */ public static final int DECORATION_ANGLE_THREE_ARCS = 2; /** Decoration type for angles: one tick */ public static final int DECORATION_ANGLE_ONE_TICK = 3; /** Decoration type for angles: two ticks */ public static final int DECORATION_ANGLE_TWO_TICKS = 4; /** Decoration type for angles: three ticks */ public static final int DECORATION_ANGLE_THREE_TICKS = 5; /** * Decoration type for angles: counterclockwise arrow * * @author Michael Borcherds, 2007-10-22 */ public static final int DECORATION_ANGLE_ARROW_ANTICLOCKWISE = 6; /** * Decoration type for angles: clockwise arrow * * @author Michael Borcherds, 2007-10-22 */ public static final int DECORATION_ANGLE_ARROW_CLOCKWISE = 7; /** parent algorithm */ @Weak protected AlgoElement algoParent = null; /** draw algorithm */ protected AlgoElement algoDraw = null; /** directly dependent algos */ private ArrayList<AlgoElement> algorithmList; /** set of all dependent algos sorted in topological order */ protected AlgorithmSet algoUpdateSet; /********************************************************/ /** * Creates new GeoElement for given construction * * @param c * Construction */ public GeoElement(final Construction c) { super(c); c.addUsedType(this.getGeoClassType()); graphicsadapter = kernel.getApplication() .newGeoElementGraphicsAdapter(); // this.geoID = geoCounter++; // moved to subclasses, see // http://benpryor.com/blog/2008/01/02/dont-call-subclass-methods-from-a-superclass-constructor/ // setConstructionDefaults(); // init visual settings // new elements become breakpoints if only breakpoints are shown // isConsProtBreakpoint = cons.showOnlyBreakpoints(); EuclidianViewInterfaceSlim ev; if ((kernel.getApplication() != null) && ((ev = kernel.getApplication() .getActiveEuclidianView()) != null) && (kernel.getApplication().getActiveEuclidianView() .getViewID() != App.VIEW_EUCLIDIAN)) { viewFlags = new ArrayList<Integer>(); viewFlags.add(ev.getViewID()); // if ev isn't Graphics or Graphics 2, then also add 1st 2D // euclidian view if (!(ev.isDefault2D())) { viewFlags.add(App.VIEW_EUCLIDIAN); } } } /* ****************************************************** */ /** * We may need a simple method to get the label, as in the CopyPaste class. * * @return label if it is set */ @Override public String getLabelSimple() { return label; } /** * We may need a simple method to set the label, as in the CopyPaste class. * * @param lab * the label to set */ @Override public void setLabelSimple(final String lab) { label = lab; GeoElementSpreadsheet.setBackgroundColor(this); } /** * Returns label or local variable label if set, returns output value string * otherwise * * @param tpl * string template * @return label or output value string */ @Override public String getLabel(StringTemplate tpl) { if (!tpl.isUseRealLabels() || (realLabel == null) || "".equals(realLabel)) { if (!isLabelSet() && !localVarLabelSet) { if (algoParent != null) { return algoParent.getDefinition(tpl); } if (definition != null) { return definition.toString(tpl); } return toOutputValueString(tpl); } return tpl.printVariableName(label); } return tpl.printVariableName(realLabel); } /** * @param c * geo to receive the label copy */ public void copyLabel(final GeoElement c) { setLabelSimple(c.label); } /** * Switch label mode among value, name, value+name and caption * * @param mode * LABEL_ mode */ @Override public void setLabelMode(final int mode) { if (isDefaultGeo()) { switch (mode) { case LABEL_NAME: case LABEL_NAME_VALUE: case LABEL_VALUE: case LABEL_CAPTION: // old values for default geos: set label to default labelMode = LABEL_DEFAULT; break; default: labelMode = mode; } if (labelMode != LABEL_DEFAULT) { App app = getKernel().getApplication(); if (app != null) { app.setLabelingStyleIsNotSelected(); } } } else { switch (mode) { case LABEL_NAME_VALUE: case LABEL_DEFAULT_NAME_VALUE: labelMode = LABEL_NAME_VALUE; break; case LABEL_VALUE: case LABEL_DEFAULT_VALUE: labelMode = LABEL_VALUE; break; case LABEL_CAPTION: // Michael Borcherds 2008-02-18 case LABEL_DEFAULT_CAPTION: labelMode = LABEL_CAPTION; break; case LABEL_DEFAULT: setLabelModeDefault(); break; default: labelMode = LABEL_NAME; } } } /** * Switch label mode among value, name, value+name and caption from stylebar * * @param index * stylebar index */ public void setLabelModeFromStylebar(final int index) { // set label to not visible if (index == 0) { setLabelVisible(false); if (isDefaultGeo()) { if (labelMode == LABEL_DEFAULT) { // set to default label mode setLabelModeDefault(); // shift for LABEL_DEFAULT_NAME_VALUE, etc. labelMode += 5; } // tells app that no labeling style is selected App app = getKernel().getApplication(); if (app != null) { app.setLabelingStyleIsNotSelected(); } } return; } // set label to visible and mode setLabelVisible(true); final int mode = index - 1; if (isDefaultGeo()) { // shift for LABEL_DEFAULT_NAME_VALUE, etc. labelMode = mode + 5; App app = getKernel().getApplication(); if (app != null) { app.setLabelingStyleIsNotSelected(); } } else { switch (mode) { case LABEL_NAME_VALUE: case LABEL_DEFAULT_NAME_VALUE: labelMode = LABEL_NAME_VALUE; break; case LABEL_VALUE: case LABEL_DEFAULT_VALUE: labelMode = LABEL_VALUE; break; case LABEL_CAPTION: // Michael Borcherds 2008-02-18 case LABEL_DEFAULT_CAPTION: labelMode = LABEL_CAPTION; break; case LABEL_DEFAULT: setLabelModeDefault(); break; default: labelMode = LABEL_NAME; } } } /** * set label mode to default mode */ protected void setLabelModeDefault() { labelMode = LABEL_NAME; } /** * Returns how should label look like in Euclidian view * * @return label mode (name, value, name + value, caption) */ @Override public final int getLabelMode() { return labelMode; } /** * return the label position (2D or 3D vector) * * @return the label position (2D or 3D vector) */ public Coords getLabelPosition() { return Coords.O; } /** * Returns the {@link GeoClass} * * @return GeoClass */ @Override public abstract GeoClass getGeoClassType(); /** * every subclass implements it's own copy method this is needed for * assignment copies like: a = 2.7 b = a (here copy() is needed) * * @return copy of current element */ @Override public abstract GeoElement copy(); /** * overridden in GeoList so that the list elements are copied too (needed * for tracing to spreadsheet) * * @return copy of this object */ public GeoElement deepCopyGeo() { return copy(); } /** * This method always returns a GeoElement of the SAME CLASS as this * GeoElement. Furthermore the resulting geo is in construction cons. * * @param consToCopy * construction * @return copy in given construction */ @Override public GeoElement copyInternal(final Construction consToCopy) { // default implementation: changed in some subclasses final GeoElement geoCopy = copy(); geoCopy.setConstruction(consToCopy); return geoCopy; } /** * Copies the given points array. The resulting points are part of the given * construction. * * @param cons * construction * @param points * array of points * @return copy of points in construction cons */ public static GeoPoint[] copyPoints(final Construction cons, final GeoPointND[] points) { // fix for Sequence[Polygon[Element[liste1, i], Element[liste1, i + 1], // j], i, 0, 300] if (points == null) { return null; } final GeoPoint[] pointsCopy = new GeoPoint[points.length]; for (int i = 0; i < points.length; i++) { pointsCopy[i] = (GeoPoint) ((GeoPoint) points[i]) .copyInternal(cons); pointsCopy[i].set(points[i]); } return pointsCopy; } /** * Copies the given points array. The resulting points are part of the given * construction. * * @param cons * construction * @param points * array of points * @return copy of points in construction cons */ public static GeoPointND[] copyPointsND(final Construction cons, final GeoPointND[] points) { final GeoPointND[] pointsCopy = new GeoPointND[points.length]; for (int i = 0; i < points.length; i++) { pointsCopy[i] = (GeoPointND) points[i].copyInternal(cons); ((GeoElement) pointsCopy[i]).set(points[i]); } return pointsCopy; } /** * Copies the given element using given kernel * * @param kernel1 * Kernel */ @Override public ExpressionValue deepCopy(final Kernel kernel1) { // default implementation: changed in some subclasses return copy(); } @Override public void resolveVariables(EvalInfo info) { // do nothing } /** * @return true for infinite numbers or points with infinite coords */ @Override public boolean isInfinite() { return false; } /** * every subclass implements it's own set method * * @param geo * geo to copy */ @Override public abstract void set(GeoElementND geo); /** * Returns false for undefined objects * * @return false when undefined */ @Override public abstract boolean isDefined(); /** * Makes object undefined, some objects lose their internally stored value * when this is called */ @Override public abstract void setUndefined(); @Override public abstract String toValueString(StringTemplate tpl); private EuclidianViewInterfaceSlim viewForValueString; private boolean autoColor; /** * sets a view for building the value string * * @param view * view */ public void setViewForValueString(final EuclidianViewInterfaceSlim view) { viewForValueString = view; } /** * * @return the view used for building the value string */ public EuclidianViewInterfaceSlim getViewForValueString() { return viewForValueString; } /** * * @return true if the value string can be changed regarding a view */ public boolean hasValueStringChangeableRegardingView() { return false; } /** * Returns definition or value string of this object. Automatically * increases decimals to at least 5, e.g. FractionText[4/3] -> * FractionText[1.333333333333333] * * @param useChangeable * if false, point on path is ignored * @param useOutputValueString * if true, use outputValueString rather than valueString * @return definition or value string of this object */ @Override public String getRedefineString(final boolean useChangeable, final boolean useOutputValueString) { StringTemplate tpl = StringTemplate.editTemplate; String ret = ""; final boolean isIndependent = !isPointOnPath() && useChangeable ? isChangeable() : isIndependent(); if (isIndependent && getDefinition() == null) { ret = useOutputValueString ? toOutputValueString(tpl) : toValueString(tpl); } else if (getParentAlgorithm() != null) { ret = getParentAlgorithm().getDefinition(tpl); } else if (getDefinition() != null) { ret = getDefinition().toString(tpl); } return ret; } /** * Returns the character which is used between label and definition * * @return for conics, implicit polynomials and inequalities, = otherwise */ public char getLabelDelimiter() { return '='; } /** * Returns the definition of this GeoElement for the input field, e.g. A1 = * 5, B1 = A1 + 2 * * @return definition for input field */ @Override public String getDefinitionForInputBar() { return getDefinitionForInputBar(StringTemplate.editTemplate); } /** * @return definition for LaTeX editor */ public String getDefinitionForEditor() { return getDefinitionForInputBar(StringTemplate.editorTemplate); } /** * @return definition for LaTeX editor, no label */ public String getDefinitionForEditorNoLabel() { String ret = getDefinition(StringTemplate.editorTemplate); if ("".equals(ret)) { ret = getAlgebraDescription(StringTemplate.editorTemplate); } return ret; } private String getDefinitionForInputBar(StringTemplate stringTemplate) { // for expressions like "3 = 2 A2 - A1" // getAlgebraDescription() returns "3 = 5" // so we need to use getCommandDescription() in those cases String inputBarStr = getDefinition(stringTemplate); if (!"".equals(inputBarStr)) { // check needed for eg f(x) = g(x) + h(x), f(x) = sin(x) // beware correct vars for f(t) = t + a final char delimiter = getLabelDelimiter(); if (inputBarStr.indexOf(delimiter) < 0) { inputBarStr = getAssignmentLHS(stringTemplate) + (delimiter == '=' ? " =" : delimiter) + " " + inputBarStr; } } else { inputBarStr = getAlgebraDescription(stringTemplate); } return inputBarStr; } /** * Returns the value of this GeoElement for the input field, e.g. A1 = 5, B1 * = A1 + 2 * * @return value for input field */ @Override public String getValueForInputBar() { StringTemplate tpl = StringTemplate.editTemplate; // copy into text field final String ret = toOutputValueString(tpl); return ret; } /** * Sets this object to zero (number = 0, points = (0,0), etc.) */ public void setZero() { // overriden where needed } /** * Returns a value string that is saveable in an XML file. Note: this is * needed for texts that need to be quoted in lists and as command * arguments. */ @Override public String toOutputValueString(StringTemplate tpl) { if (isLocalVariable()) { return label; } return toValueString(tpl); } /** * Set visual style from defaults */ final public void setConstructionDefaults() { setConstructionDefaults(true); } /** * Set visual style from defaults * * @param setEuclidianVisible * If eucldianVisible should be set */ final public void setConstructionDefaults(boolean setEuclidianVisible) { if (useVisualDefaults) { final ConstructionDefaults consDef = cons.getConstructionDefaults(); if (consDef != null) { consDef.setDefaultVisualStyles(this, false, setEuclidianVisible); } } } /** * * @param color * new color for this object */ @Override public void setObjColor(final GColor color) { isColorSet = !this.isDefaultGeo() || !this.isGeoNumeric(); objColor = color == null ? GColor.BLACK : color; fillColor = objColor; setAlphaValue(alphaValue); // selColor = getInverseColor(objColor); if (color != null) { selColor = GColor.newColor(color.getRed(), color.getGreen(), color.getBlue(), 100); } } /** * Returns true if color was explicitly set * * @return true if color was explicitly set */ public boolean isColorSet() { return isColorSet; } // Michael Borcherds 2008-04-02 private GColor getRGBFromList(double alpha1) { double alpha2 = alpha1; if (alpha2 > 1f) { alpha2 = 1f; } if (alpha2 < 0f) { alpha2 = 0f; } final int alpha = (int) (alpha2 * 255f); return getRGBFromList(alpha); } // Michael Borcherds 2008-04-02 private GColor getRGBFromList(int withAlpha) { int alpha = withAlpha; if (alpha > 255) { alpha = 255; } else if (alpha < 0) { alpha = 0; } // get rgb values from color list double redD = 0, greenD = 0, blueD = 0; for (int i = 0; i < 3; i++) { final GeoElement geo = colFunction.get(i); if (geo.isDefined()) { final double val = ((NumberValue) geo).getDouble(); switch (i) { default: case 0: redD = val; break; case 1: greenD = val; break; case 2: blueD = val; break; } } } // double epsilon = 0.000001; // 1 - floor(1) = 0 but we want 1. // make sure the colors are between 0 and 1 redD = (redD / 2) - Math.floor(redD / 2); greenD = (greenD / 2) - Math.floor(greenD / 2); blueD = (blueD / 2) - Math.floor(blueD / 2); // step function so // [0,1] -> [0,1] // [1,2] -> [1,0] // [2,3] -> [0,1] // [3,4] -> [1,0] // [4,5] -> [0,1] etc if (redD > 0.5) { redD = 2 * (1 - redD); } else { redD = 2 * redD; } if (greenD > 0.5) { greenD = 2 * (1 - greenD); } else { greenD = 2 * greenD; } if (blueD > 0.5) { blueD = 2 * (1 - blueD); } else { blueD = 2 * blueD; } // Application.debug("red"+redD+"green"+greenD+"blue"+blueD); // adjust color triple to alternate color spaces, default to RGB switch (this.colorSpace) { case GeoElement.COLORSPACE_HSB: final int rgb = GColor.HSBtoRGB(redD, greenD, blueD); redD = (rgb >> 16) & 0xFF; greenD = (rgb >> 8) & 0xFF; blueD = rgb & 0xFF; return GColor.newColor((int) redD, (int) greenD, (int) blueD, alpha); case GeoElement.COLORSPACE_HSL: // algorithm taken from wikipedia article: // http://en.wikipedia.org/wiki/HSL_and_HSV final double H = redD * 6; final double S = greenD; final double L = blueD; final double C = (1 - Math.abs((2 * L) - 1)) * S; final double X = C * (1 - Math.abs((H % 2) - 1)); double R1 = 0, G1 = 0, B1 = 0; if (H < 1) { R1 = C; G1 = X; B1 = 0; } else if (H < 2) { R1 = X; G1 = C; B1 = 0; } else if (H < 3) { R1 = 0; G1 = C; B1 = X; } else if (H < 4) { R1 = 0; G1 = X; B1 = C; } else if (H < 5) { R1 = X; G1 = 0; B1 = C; } else { R1 = C; G1 = 0; B1 = X; } final double m = L - (.5 * C); final GColor c = GColor.newColor((int) ((R1 + m) * 255.0), (int) ((G1 + m) * 255.0), (int) ((B1 + m) * 255.0), alpha); return c; case GeoElement.COLORSPACE_RGB: default: return GColor.newColor((int) (redD * 255.0), (int) (greenD * 255.0), (int) (blueD * 255.0), alpha); } } /** * * @return color of object for selection */ // Michael Borcherds 2008-04-02 @Override public GColor getSelColor() { if (colFunction == null) { return selColor; } return getRGBFromList(100); } /** * * @return color of fill */ // Michael Borcherds 2008-04-02 @Override public GColor getFillColor() { if (colFunction == null) { return fillColor; } return getRGBFromList(getAlphaValue()); } /** * return black if the color is white, so it can be seen * * @return color for algebra view (same as label or black) */ @Override public GColor getAlgebraColor() { return GColor.updateForWhiteBackground(objColor); } /** * * @return color of label */ // Michael Borcherds 2008-04-01 @Override public GColor getLabelColor() { return getObjectColor(); } /** * * @return color of background */ @Override public GColor getBackgroundColor() { return bgColor; } /** * * @param bgCol * new background color */ public void setBackgroundColor(final GColor bgCol) { bgColor = bgCol; } /** * * @return current color for this object */ // Michael Borcherds 2008-04-02 @Override public GColor getObjectColor() { GColor col = objColor; try { if (colFunction != null) { col = getRGBFromList(255); } } catch (final Exception e) { removeColorFunction(); } return col; } // Michael Borcherds 2008-03-01 /** * Sets layer * * @param layer2 * layer from 0 to 9 */ @Override public void setLayer(int layer2) { int newlayer = layer2; // Application.printStacktrace("layer="+layer); if (layer2 == this.layer // layer valid only for Drawable objects // DON'T check this: eg angles on file load are not yet isDrawable() // || !isDrawable() ) { return; } if (newlayer > EuclidianStyleConstants.MAX_LAYERS) { newlayer = EuclidianStyleConstants.MAX_LAYERS; } else if (newlayer < 0) { newlayer = 0; } kernel.notifyChangeLayer(this, this.layer, newlayer); this.layer = newlayer; } // Michael Borcherds 2008-02-23 /** * @return layer of this geo (0 to 9) */ @Override public int getLayer() { return layer; } /** * * @return drawing priority (lower = drawn first) */ private int typePriority() { return getGeoClassType().getPriority(isIndependent()); } /** * Compare drawing priority with another object * * @param other * the other object * @param checkLastHitType * whether hits on boundary should be preferred to hits on * filling * @return whether this should be drawn fist */ public boolean drawBefore(GeoElement other, boolean checkLastHitType) { if (this.getLayer() < other.getLayer()) { return true; } if (this.getLayer() > other.getLayer()) { return false; } if (checkLastHitType) { if (this.getLastHitType() == HitType.ON_BOUNDARY && other.getLastHitType() != HitType.ON_BOUNDARY) { return false; } if (this.getLastHitType() != HitType.ON_BOUNDARY && other.getLastHitType() == HitType.ON_BOUNDARY) { return true; } } if (this.typePriority() < other.typePriority()) { return true; } if (this.typePriority() > other.typePriority()) { return false; } if (this.getConstructionIndex() < other.getConstructionIndex()) { return true; } if (this.getConstructionIndex() > other.getConstructionIndex()) { return false; } if (this.getParentAlgorithm() instanceof AlgoMacroInterface) { return ((AlgoMacroInterface) this.getParentAlgorithm()) .drawBefore(this, other); } // Log.warn("Objects "+this+" and "+other+" have the same drawing // priority."); return true; } /** * Changes transparency of this geo * * @param alpha * new alpha value */ @Override public void setAlphaValue(final double alpha) { if ((fillColor == null) || (alpha < 0.0f) || (alpha > 1.0f)) { return; } alphaValue = alpha; fillColor = GColor.newColor(fillColor.getRed(), fillColor.getGreen(), fillColor.getBlue(), (int) (255 * alpha)); } /** * @return alpha value (transparency) * * NOTE: can be -1 for lists, see GeoList.getAlphaValue(), * GeoList.setgetAlphaValue() */ @Override public double getAlphaValue() { if ((colFunction == null) || (colFunction.size() == 3)) { return alphaValue; } final GeoElement geo = colFunction.get(3); if (geo.isDefined()) { double alpha = ((NumberValue) geo).getDouble(); // ensure between 0 and 1 alpha = (alpha / 2) - Math.floor(alpha / 2); if (alpha > 0.5) { alpha = 2 * (1 - alpha); } else { alpha = 2 * alpha; } return alpha; } return alphaValue; } /** * @return true for limited paths */ public boolean isLimitedPath() { return false; } /** * @return true for paths */ public boolean isPath() { return false; } /** * @return true for regions */ public boolean isRegion() { return false; } /** * @return true for GeoLists */ public boolean isGeoList() { return false; } /** * Sets all visual values from given GeoElement. This will also affect * tracing, label location and the location of texts for example. * * @param geo * source geo * @param keepAdvanced * true to skip copying color function and visibility condition */ @Override final public void setAllVisualProperties(final GeoElement geo, final boolean keepAdvanced) { euclidianVisible = geo.euclidianVisible; visibleInView3D = geo.visibleInView3D; setAllVisualPropertiesExceptEuclidianVisible(geo, keepAdvanced); } /** * Sets all visual values from given GeoElement, EXCEPT euclidianVisible : * needed for apply defaults on slider/angle. * * This will also affect tracing, label location and the location of texts * for example. * * @param geo * source geo * @param keepAdvanced * true to skip copying color function and visibility condition */ public void setAllVisualPropertiesExceptEuclidianVisible( final GeoElement geo, final boolean keepAdvanced) { if (keepAdvanced) { setVisualStyle(geo); } else { setAdvancedVisualStyle(geo); } algebraVisible = geo.algebraVisible; labelOffsetX = geo.labelOffsetX; labelOffsetY = geo.labelOffsetY; caption = geo.caption; inverseFill = geo.inverseFill; if (isTraceable() && geo.isTraceable()) { ((Traceable) this).setTrace(((Traceable) geo).getTrace()); } // if (isGeoPoint() && geo.isGeoPoint()) { if (getGeoClassType().equals(GeoClass.POINT) && geo.getGeoClassType().equals(GeoClass.POINT)) { ((GeoPoint) this).setSpreadsheetTrace( ((GeoPoint) geo).getSpreadsheetTrace()); } // copy color function if (!keepAdvanced) { if (geo.colFunction != null) { setColorFunction(geo.colFunction); } } // copy ShowObjectCondition, unless it generates a // CirclularDefinitionException if (!keepAdvanced) { if (geo.condShowObject != null) { try { setShowObjectCondition(geo.getShowObjectCondition()); } catch (final Exception e) { // do nothing } } } // G.Sturr 2010-6-26 if (isSpreadsheetTraceable() && geo.getSpreadsheetTrace()) { setSpreadsheetTrace(true); traceSettings = geo.traceSettings; } // END G.Sturr } /** * In future, this can be used to turn on/off whether transformed objects * have the same style as the original object * * @param geo * source geo */ @Override public final void setVisualStyleForTransformations(final GeoElement geo) { setVisualStyle(geo); setFixed(false); updateVisualStyle(GProperty.COMBINED); } /** * Just changes the basic visual styles. If the style of a geo is reset this * is required as we don't want to overwrite advanced settings in that case. * * @param geo * source geo */ @Override public void setVisualStyle(final GeoElement geo) { // label style labelVisible = geo.getLabelVisible(); setLabelMode(geo.getLabelMode()); tooltipMode = geo.getTooltipMode(); // style of equation, coordinates, ... if (getGeoClassType() == geo.getGeoClassType()) { toStringMode = geo.toStringMode; } // colors setColorVisualStyle(geo); // line thickness and line type: // note: line thickness in Drawable is calculated as lineThickness / // 2.0f setLineThickness(geo.getLineThickness()); setLineType(geo.getLineType()); setLineTypeHidden(geo.getLineTypeHidden()); setDecorationType(geo.getDecorationType()); setLineOpacity(geo.getLineOpacity()); // set whether it's an auxilliary object setAuxiliaryObject(geo.isAuxiliaryObject()); // set fixed setFixed(geo.isLocked()); // if layer is not zero (eg a new object has layer set to // ev.getMaxLayerUsed()) // we don't want to set it if (layer == 0) { setLayer(geo.getLayer()); } } /** * set color from source geo * * @param geo * source geo */ protected void setColorVisualStyle(final GeoElement geo) { if (geo.isAutoColor()) { setObjColor(cons.getConstructionDefaults().getNextColor()); } else { objColor = geo.objColor; selColor = geo.selColor; } if (geo.isFillable()) { if (geo.isAutoColor()) { fillColor = objColor; setAlphaValue(geo.getAlphaValue()); } else { fillColor = geo.fillColor; } setFillType(geo.fillType); hatchingAngle = geo.hatchingAngle; hatchingDistance = geo.hatchingDistance; graphicsadapter.setImageFileName( geo.getGraphicsAdapter().getImageFileName()); alphaValue = geo.alphaValue; } else { fillColor = geo.objColor; setAlphaValue(geo.getAlphaValue()); } bgColor = geo.bgColor; isColorSet = geo.isColorSet(); } /** * @return whether sequential color is used, makes sense only for default * geos */ public boolean isAutoColor() { return this.autoColor; } /** * @param sequential * whether sequential color is used, makes sense only for default * geos */ public void setAutoColor(boolean sequential) { this.autoColor = sequential; } /** * Also copy advanced settings of this object. * * @param geo * source geo */ public void setAdvancedVisualStyle(final GeoElement geo) { setVisualStyle(geo); // set layer setLayer(geo.getLayer()); // copy color function setColorFunction(geo.getColorFunction()); setColorSpace(geo.getColorSpace()); // copy ShowObjectCondition, unless it generates a // CirclularDefinitionException try { setShowObjectCondition(geo.getShowObjectCondition()); } catch (final Exception e) { // CircularException, we ignore it } } /** * Copy advanced properties -- cond. visibility, dynamic colors, TODO * corners Used in macros where we can't reference the objects directly * * @param geo * style source */ @Override public final void setAdvancedVisualStyleCopy(final GeoElementND geo) { // copy color function if (geo.getColorFunction() != null) { setColorFunction(geo.getColorFunction().deepCopyGeo()); setColorSpace(geo.getColorSpace()); } // copy ShowObjectCondition, unless it generates a // CirclularDefinitionException if (geo.getShowObjectCondition() != null) { try { setShowObjectCondition(geo.getShowObjectCondition().copy()); } catch (final Exception e) { // CircularException, we ignore it } } } /** * @return graphics adapter (wrapper for fill image) of this element */ public GeoElementGraphicsAdapter getGraphicsAdapter() { return graphicsadapter; } /** * @return * * private static Color getInverseColor(Color c) { float[] hsb = * Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null); * hsb[0] += 0.40; if (hsb[0] > 1) hsb[0]--; hsb[1] = 1; hsb[2] = * 0.7f; return Color.getHSBColor(hsb[0], hsb[1], hsb[2]); * * } */ /** * Moves label by updating label offset * * @param xcoord * label x-offset * @param ycoord * label y-offset */ public void setLabelOffset(int xcoord, int ycoord) { int x = xcoord; int y = ycoord; final double len = MyMath.length(x, y); if (len > MAX_LABEL_OFFSET) { final double factor = MAX_LABEL_OFFSET / len; x = (int) Math.round(factor * x); y = (int) Math.round(factor * y); } labelOffsetX = x; labelOffsetY = y; } /** * @return whether object should be visible in at least one view */ @Override final public boolean isVisible() { return isEuclidianVisible() || isAlgebraVisible(); } /** * @return whether object should be drawn in euclidian view */ @Override final public boolean isEuclidianVisible() { // used by DrawPoint to draw parts of intersection objects near the // point if (forceEuclidianVisible) { return true; } if (!showInEuclidianView()) { return false; } if (condShowObject == null) { return euclidianVisible; } return condShowObject.getBoolean(); } /** * Allows drawing this in EV * * @param visible * true to allow drawing this in EV */ @Override public void setEuclidianVisible(final boolean visible) { euclidianVisible = visible; } /** * set euclidian visibility if there is no condition to show object set * * @param visible * true to allow drawing this in EV */ @Override public void setEuclidianVisibleIfNoConditionToShowObject( final boolean visible) { if (condShowObject == null) { setEuclidianVisible(visible); } } /** * Forces drawing this in EV * * @param visible * true to force drawing this in EV */ public void forceEuclidianVisible(final boolean visible) { forceEuclidianVisible = visible; } /** * @return true if this is allowed to be drawn in EV */ @Override public boolean isSetEuclidianVisible() { return euclidianVisible; } /** * Returns whether this GeoElement is visible in the construction protocol */ @Override final public boolean isConsProtocolBreakpoint() { return isConsProtBreakpoint; } /** * @param flag * true to make this a breakpoint */ public void setConsProtocolBreakpoint(final boolean flag) { /* * // all siblings need to have same breakpoint information GeoElement * [] siblings = getSiblings(); if (siblings != null) { for (int i=0; i * < siblings.length; i++) { siblings[i].isConsProtBreakpoint = flag; } * } */ isConsProtBreakpoint = flag; } /** * Returns the children of the parent algorithm or null. * * @return the children of the parent algorithm or null. */ public GeoElement[] getSiblings() { if (algoParent != null) { return algoParent.getOutput(); } return null; } /** * @return true if this can be drawn */ @Override public boolean isDrawable() { return true; } /** * @return true if this can be filled */ @Override public boolean isFillable() { return false; } /** @return true if inverse fill is posible */ public boolean isInverseFillable() { return false; } /** @return true if tracing is posible */ @Override public boolean isTraceable() { return false; } /** @return true if this is fixed (moving forbidden, deleting OK) */ public boolean isLocked() { return fixed; } /** * @param flag * true to make this fixed */ @Override public final void setFixed(final boolean flag) { if (!flag) { fixed = flag; } else if (isFixable()) { fixed = flag; } } /** * @return true if fixed property can be set */ public boolean isFixable() { return true; // deleting objects with fixed descendents makes them // undefined // return isIndependent(); } /** * if an object has a fixed descendent, we want to set it undefined */ @Override final public void removeOrSetUndefinedIfHasFixedDescendent() { // can't delete a fixed object at all if (isProtected(EventType.REMOVE)) { return; } boolean hasFixedDescendent = false; final Set<GeoElement> tree = getAllChildren(); final Iterator<GeoElement> it = tree.iterator(); while (it.hasNext() && !hasFixedDescendent) { if (it.next().isProtected(EventType.REMOVE)) { hasFixedDescendent = true; } } if (hasFixedDescendent) { // Application.debug("hasFixedDescendent, not deleting"); setUndefined(); updateRepaint(); } else { remove(); kernel.notifyRemoveGroup(); } } /** * @return true for auxiliary objects */ @Override final public boolean isAuxiliaryObject() { return auxiliaryObject; } /** * @return true for loci, texts and images */ public boolean isAuxiliaryObjectByDefault() { return false; } /** * Used to convert various interfaces into GeoElement * * @return this */ @Override final public GeoElement toGeoElement() { return this; } /** * @param flag * true to make this auxiliary */ @Override public void setAuxiliaryObject(final boolean flag) { if (auxiliaryObject != flag) { auxiliaryObject = flag; if (isLabelSet()) { notifyUpdateAuxiliaryObject(); } } } /** * sets whether the object's label should be drawn in an EuclidianView * * @param visible * true to make label visible */ @Override public void setLabelVisible(final boolean visible) { labelVisible = visible; } /** * Returns whether the label should be shown in Euclidian view. * * @return true if label should be shown */ @Override public boolean isLabelVisible() { return labelVisible && isLabelSet(); } /** * * @return value of labelVisible */ final public boolean getLabelVisible() { return labelVisible; } /** * Returns whether the label can be shown in Euclidian view. * * @return true if label can be shown */ public boolean isLabelShowable() { return isDrawable() && !(this instanceof TextValue || isGeoImage() || isGeoButton() || isGeoLocus() || (isGeoBoolean() && !isIndependent())); } /** * Returns whether the value (e.g. equation) should be shown as part of the * label description false for eg GeoLocus, Boolean, Button, TextField * * @return true if value should be in description */ public boolean isLabelValueShowable() { return true; } /** * @return whether object should be printed in algebra view */ final public boolean isAlgebraVisible() { return algebraVisible && showInAlgebraView(); } /** * @return true if tooltip should be shown */ public boolean showToolTipText() { // return isAlgebraVisible(); switch (tooltipMode) { default: // case TOOLTIP_ALGEBRAVIEW_SHOWING: if (!(kernel.getApplication().isUsingFullGui() && kernel.getApplication().showView(App.VIEW_ALGEBRA))) { return false; } return isAlgebraVisible(); // old behaviour case TOOLTIP_OFF: return false; case TOOLTIP_ON: case TOOLTIP_CAPTION: case TOOLTIP_NEXTCELL: return true; } } /** * @param colored * true to use colors (HTML) * @param alwaysOn * true to override default behavior * @return tooltip text as HTML */ public String getTooltipText(final boolean colored, final boolean alwaysOn) { if (getParentAlgorithm() instanceof AlgoAttachCopyToView) { return ""; } StringTemplate tpl = StringTemplate.defaultTemplate; switch (tooltipMode) { default: case TOOLTIP_ALGEBRAVIEW_SHOWING: if (!alwaysOn) { if (!(kernel.getApplication().isUsingFullGui() && kernel .getApplication().showView(App.VIEW_ALGEBRA))) { return ""; } } // else fall through: case TOOLTIP_ON: getLoc().setTooltipFlag(); final String ret = getLongDescriptionHTML(colored, false); // old // behaviour getLoc().clearTooltipFlag(); return ret; case TOOLTIP_OFF: return ""; case TOOLTIP_CAPTION: return getCaption(tpl); case TOOLTIP_NEXTCELL: // tooltip is the next cell to the right // (spreadsheet objects only) String cellLabel = getLabel(tpl); final GPoint coords = GeoElementSpreadsheet .getSpreadsheetCoordsForLabel(cellLabel); if (coords == null) { return ""; } coords.x++; cellLabel = GeoElementSpreadsheet.getSpreadsheetCellName(coords.x, coords.y); if (cellLabel == null) { return ""; } final GeoElement geo = kernel.lookupLabel(cellLabel); return (geo == null) ? "" : geo.toValueString(tpl); } } /** * @return tooltip mode */ public final int getTooltipMode() { return tooltipMode; } /** * @param mode * new tooltip mode */ @Override public void setTooltipMode(final int mode) { // return isAlgebraVisible(); switch (mode) { default: tooltipMode = TOOLTIP_ALGEBRAVIEW_SHOWING; break; case TOOLTIP_OFF: case TOOLTIP_ON: case TOOLTIP_CAPTION: case TOOLTIP_NEXTCELL: tooltipMode = mode; break; } } /** * @param visible * whether this is allowed to appear in AV */ public void setAlgebraVisible(final boolean visible) { algebraVisible = visible; } /** * @return whether this is allowed to appear in AV */ public boolean isSetAlgebraVisible() { return algebraVisible; } /** * @return whether this is shown in AV */ public abstract boolean showInAlgebraView(); /** * @return whether this is shown in EV */ protected abstract boolean showInEuclidianView(); /** * @return true if this can be edited in AV directly */ public boolean isAlgebraViewEditable() { return true; } /** * @return true if showable in EV */ final public boolean isEuclidianShowable() { return showInEuclidianView(); } /** * @return true if showable in AV */ public boolean isAlgebraShowable() { return showInAlgebraView(); } /** * @param algorithm * algorithm responsible for computation of this object */ public void setParentAlgorithm(final AlgoElement algorithm) { algoParent = algorithm; } /** * @return algorithm responsible for computation of this object */ @Override final public AlgoElement getParentAlgorithm() { return algoParent; } /** * @param algorithm * algorithm responsible for drawing this */ @Override public void setDrawAlgorithm(final DrawInformationAlgo algorithm) { if (algorithm instanceof AlgoElement) { algoDraw = (AlgoElement) algorithm; } } /** * @return algorithm responsible for drawing this */ @Override final public AlgoElement getDrawAlgorithm() { if (algoDraw == null) { return algoParent; } return algoDraw; } /** * @return list of directly dependent algos */ final public ArrayList<AlgoElement> getAlgorithmList() { if (algorithmList == null) { algorithmList = new ArrayList<AlgoElement>(); } return algorithmList; } @Override public boolean isIndependent() { return (algoParent == null) && (this.getCorrespondingCasCell() == null || !this.getCorrespondingCasCell().hasVariablesOrCommands()); } /** * Returns whether this GeoElement can be changed directly. Note: for points * on lines this is different than isIndependent() * * @return whether this geo can be changed directly */ public boolean isChangeable() { return !isProtected(EventType.UPDATE) && isIndependent(); } /** * @return whether this is changeable by dragging the pointer */ public boolean isPointerChangeable() { return !isLocked() && isIndependent(); } /** * Returns whether this GeoElement is a point on a path. * * @return true for points on path */ @Override public boolean isPointOnPath() { return false; } /** * Returns whether this object may be redefined * * @return whether this object may be redefined */ public boolean isRedefineable() { return !isProtected(EventType.UPDATE) && kernel.getApplication().letRedefine() && !(this instanceof TextValue) && isAlgebraViewEditable() && (isChangeable() || // redefine changeable (independent and // not fixed) !isIndependent()); // redefine dependent object } /** * @param type * event type (UPDATE or REMOVE) * @return whether this is protected against deleting and editing */ public boolean isProtected(EventType type) { if (kernel.getApplication().has(Feature.FIXED_OBJECTS_EDITABLE)) { return isLocked() && this.getSpreadsheetCoords() != null && type == EventType.REMOVE; } return fixed; } /** * Returns whether this GeoElement can be moved in Euclidian View. Note: * this is needed for texts and points on path * * @return true for moveable objects */ @Override public boolean isMoveable() { return isPointerChangeable(); } /** * * @return true if we can move it with 6 degrees of freedom input device */ public boolean is6dofMoveable() { return false; } /** * * @param view * view * @return true if moveable in the view */ public boolean isMoveable(final EuclidianViewInterfaceSlim view) { return view.isMoveable(this); } /** * Returns whether this (dependent) GeoElement has input points that can be * moved in Euclidian View. * * @param view * view * @return whether this geo has only moveable input points */ @SuppressFBWarnings({ "SF_SWITCH_FALLTHROUGH", "missing break is deliberate" }) public boolean hasMoveableInputPoints( final EuclidianViewInterfaceSlim view) { // allow only moving of certain object types switch (getGeoClassType()) { case CONIC: case CONIC3D: // special case for Circle[A, r] if (getParentAlgorithm() instanceof AlgoCirclePointRadiusInterface) { return containsOnlyMoveableGeos(getFreeInputPoints(view)); } // fall through case CONICPART: case IMAGE: case LINE: case LINE3D: case RAY: case RAY3D: case SEGMENT: case SEGMENT3D: case TEXT: return hasOnlyFreeInputPoints(view) && containsOnlyMoveableGeos(getFreeInputPoints(view)); case POLYGON: case POLYGON3D: case POLYLINE: case POLYLINE3D: case PENSTROKE: return containsOnlyMoveableGeos(getFreeInputPoints(view)); case VECTOR: case VECTOR3D: if (hasOnlyFreeInputPoints(view) && containsOnlyMoveableGeos(getFreeInputPoints(view))) { // check if first free input point is start point of vector final ArrayList<GeoPointND> freeInputPoints = getFreeInputPoints( view); if (freeInputPoints.size() > 0) { final GeoPointND firstInputPoint = freeInputPoints.get(0); final GeoPointND startPoint = ((Locateable) this) .getStartPoint(); return (firstInputPoint == startPoint); } } break; case ANGLE: break; case ANGLE3D: break; case AXIS: break; case AXIS3D: break; case BOOLEAN: break; case BUTTON: break; case CAS_CELL: break; case CLIPPINGCUBE3D: break; case CONICSECTION: // TODO make moveable? break; case CURVE_CARTESIAN: break; case CURVE_CARTESIAN3D: break; case CURVE_POLAR: break; case DEFAULT: break; case FUNCTION: break; case FUNCTION_NVAR: break; case IMPLICIT_POLY: break; case INTERVAL: break; case LIST: break; case LOCUS: break; case NET: break; case NUMERIC: break; case PLANE3D: break; case POINT: break; case POINT3D: break; case POLYHEDRON: break; case QUADRIC: break; case QUADRIC_LIMITED: break; case QUADRIC_PART: break; case SPACE: break; case SPLINE: break; case SURFACECARTESIAN3D: break; case TEXTFIELD: break; case TURTLE: break; default: break; } return false; } /** * Returns all free parent points of this GeoElement. * * @param view * view * @return all free parent points of this GeoElement. */ public ArrayList<GeoPointND> getFreeInputPoints( final EuclidianViewInterfaceSlim view) { if (algoParent == null) { return null; } return view.getFreeInputPoints(algoParent); } /** * @param view * view * @return whether all input points are free in given view */ final public boolean hasOnlyFreeInputPoints( final EuclidianViewInterfaceSlim view) { if (algoParent == null) { return false; } // special case for edge of polygon if (algoParent instanceof AlgoJoinPointsSegment && (view.getFreeInputPoints(algoParent).size() == 2)) { return true; } return view.getFreeInputPoints(algoParent) .size() == algoParent.input.length; } private static boolean containsOnlyMoveableGeos( final ArrayList<GeoPointND> geos) { if ((geos == null) || (geos.size() == 0)) { return false; } for (int i = 0; i < geos.size(); i++) { final GeoElement geo = (GeoElement) geos.get(i); if (!geo.isMoveable()) { return false; } } return true; } /** * Returns whether this object's class implements the interface * Translateable. * * @return whether this object's class implements the interface * Translateable. */ public boolean isTranslateable() { return false; } /** * Returns whether this GeoElement can be rotated in Euclidian View. Note: * this is needed for images * * @return whether this geo can be rotated */ public boolean isRotateMoveable() { return isPointerChangeable() && (this instanceof PointRotateable); } /** * Returns whether this GeoElement has properties that can be edited in a * properties dialog. * * @return whether this element has editable properties */ final public boolean hasProperties() { // return isDrawable() || isChangeable(); return isGeoElement(); } /** * @param s * animation step */ public void setAnimationStep(final double s) { setAnimationStep(new MyDouble(kernel, s)); } /** * @param v * animation step */ public void setAnimationStep(final NumberValue v) { animationIncrement = v; } @Override public double getAnimationStep() { if (animationIncrement == null) { animationIncrement = new MyDouble(kernel, GeoNumeric.DEFAULT_SLIDER_INCREMENT); } return animationIncrement.getDouble(); } /** * @return animation step as geo */ public NumberValue getAnimationStepObject() { if (animationIncrement == null) { return null; } return animationIncrement; } /** * @return animation speed as geo */ public GeoElement getAnimationSpeedObject() { if (animationSpeedObj == null) { return null; } return animationSpeedObj.toGeoElement(); } /** * Returns the current animation speed of this slider. Note that the speed * can be negative which will change the direction of the animation. * * @return current animation speed */ public double getAnimationSpeed() { if (animationSpeedObj == null) { initAnimationSpeedObject(); } // get speed double speed = animationSpeedObj.getDouble(); if (Double.isNaN(speed)) { speed = 0; } else if (speed > MAX_ANIMATION_SPEED) { speed = MAX_ANIMATION_SPEED; } else if (speed < -MAX_ANIMATION_SPEED) { speed = -MAX_ANIMATION_SPEED; } return speed; } /** * @param speed * new speed */ public void setAnimationSpeedObject(final GeoNumberValue speed) { animationSpeedObj = speed; } /** * @param speed * new speed */ public void setAnimationSpeed(final double speed) { initAnimationSpeedObject(); final GeoElement speedObj = animationSpeedObj.toGeoElement(); if (speedObj.isGeoNumeric() && speedObj.isIndependent()) { ((GeoNumeric) speedObj).setValue(speed); } } private void initAnimationSpeedObject() { if (animationSpeedObj == null) { final GeoNumeric num = new GeoNumeric(cons); num.setValue(1); animationSpeedObj = num; } } /** * @return animation type (ANIMATION_*) */ final public int getAnimationType() { return animationType; } /** * @param type * animation type (ANIMATION_*) */ final public void setAnimationType(final int type) { switch (type) { default: case ANIMATION_INCREASING_ONCE: case ANIMATION_INCREASING: case ANIMATION_OSCILLATING: animationType = type; animationDirection = 1; break; case ANIMATION_DECREASING: animationType = type; animationDirection = -1; break; } } /** * @return +1 or -1 */ protected int getAnimationDirection() { return animationDirection; } /** * Change direction from +1 to -1 or vice versa */ protected void changeAnimationDirection() { animationDirection = -animationDirection; } /** * Sets the state of this object to animating on or off. * * @param flag * true to make this animating * * @see Animatable interface */ public synchronized void setAnimating(final boolean flag) { final boolean oldValue = animating; animating = flag && isAnimatable(); // tell animation manager if (oldValue != animating) { final AnimationManager am = kernel.getAnimatonManager(); if (animating) { am.addAnimatedGeo(this); } else { am.removeAnimatedGeo(this); } } } /** * @return true if animation is on */ final public boolean isAnimating() { return animating; } /** * over ridden by types that implement Animateable * * @return true if this can be animated */ public boolean isAnimatable() { return false; } @Override public String toLaTeXString(final boolean symbolic, StringTemplate tpl) { return getFormulaString(tpl, !symbolic); // if (symbolic) // return toString(); // else // return toDefinedValueString(); } /** * Returns a String that can be used to define geo in the currently used * CAS. For example, "f(x) := a*x^2", "a := 20", "g := 3x + 4y = 7" * * @param tpl * StringType.Giac * @return String in the format of the current CAS. */ public String toCasAssignment(final StringTemplate tpl) { if (!isLabelSet()) { return null; } final String body = toValueString(tpl); return getAssignmentLHS(tpl) + " := " + body; } /** * @param tpl * string template * @return left hand side for assignment (label or e.g. label(x)) */ public String getAssignmentLHS(StringTemplate tpl) { return getLabel(tpl); } /** * Returns a representation of geo in currently used CAS syntax. For * example, "a*x^2" * * @param tpl * string template * * @param symbolic * true to keep variable names * @return representation of this geo for CAS */ public String getCASString(StringTemplate tpl, final boolean symbolic) { return symbolic && !isIndependent() ? getDefinition(tpl) : toValueString(tpl); } /* * ******************************************************* GeoElementTable * Management Hashtable: String (label) -> GeoElement * ****************************************************** */ /** increment number of cellRange algos using this geo */ public void addCellRangeUser() { ++cellRangeUsers; } /** decrement number of cellRange algos using this geo */ public void removeCellRangeUser() { if (cellRangeUsers > 0) { --cellRangeUsers; } } /** @return true if this can be renamed */ public boolean isRenameable() { // don't allow renaming when this object is used in // cell ranges, see AlgoCellRange return cellRangeUsers == 0; } /** * renames this GeoElement to newLabel. * * @param labelNew * new label * @return true if label was changed * @throws MyError * : if new label is already in use */ @Override public boolean rename(String labelNew) { String newLabel = labelNew; if (!isRenameable()) { return false; } if (newLabel == null) { return false; } newLabel = newLabel.trim(); if (newLabel.length() == 0) { return false; } final String labelOld = label; if (newLabel.equals(labelOld)) { return false; } else if (cons.isFreeLabel(newLabel)) { setLabel(newLabel); // now we rename return true; } else { final String str[] = { "NameUsed", newLabel }; throw new MyError(getLoc(), str); } } /** * Returns whether this object's label has been set and is valid now. (this * is needed for saving: only object's with isLabelSet() == true should be * saved) * * @return true if this geo has valid label */ @Override public boolean isLabelSet() { return labelSet; } /** * Sets label of a GeoElement and updates Construction list and GeoElement * tabel (String label, GeoElement geo) in Kernel. If the old label was * null, a new free label is assigned starting with label as a prefix. If * newLabel is not already used, this object is renamed to newLabel. * Otherwise nothing is done. * * @param labelNew * new label */ @Override public final void setLabel(String labelNew) { String newLabel = labelNew; // Application.printStacktrace(newLabel); if (cons.isSuppressLabelsActive()) { if (kernel.getApplication() .has(Feature.AUTOSCROLLING_SPREADSHEET)) { if (kernel.getApplication().getGuiManager() != null && kernel.getApplication().getGuiManager() .hasSpreadsheetView()) { kernel.getApplication().getGuiManager().getSpreadsheetView() .scrollIfNeeded(this, labelNew); } } return; } // don't want any '$'s in actual labels if ((newLabel != null) && (newLabel.indexOf('$') > -1)) { newLabel = newLabel.replaceAll("\\$", ""); } labelWanted = true; // had no label: try to set it if (!isLabelSet()) { // to avoid wasting of labels, new elements must wait // until they are shown in one of the views to get a label if (isVisible()) { // newLabel is used already: rename the using geo final GeoElement geo = kernel.lookupLabel(newLabel); if (geo != null) { geo.doRenameLabel(getFreeLabel(newLabel)); } // set newLabel for this geo doSetLabel(getFreeLabel(newLabel)); } else { // remember desired label setLabelSimple(newLabel); } } // try to rename else if (isRenameable()) { if (cons.isFreeLabel(newLabel)) { // rename doRenameLabel(newLabel); } else { // removed: null pointer exception on Intersect[a,b] // System.out.println("setLabel DID NOT RENAME: " + this.label + // " to " + newLabel + ", new label is not free: " + // cons.lookupLabel(newLabel).getLongDescription()); } } } // private StringBuilder sb; // // private String removeDollars(String s) { // if (sb == null) // sb = new StringBuilder(); // sb.setLength(0); // // for (int i = 0; i < s.length(); i++) { // char c = s.charAt(i); // if (c != '$') // sb.append(c); // } // // return sb.toString(); // } /** * Sets label of a GeoElement and updates GeoElement table (label, * GeoElement). This method should only be used by MyXMLHandler. * * @param label * label */ @Override public void setLoadedLabel(final String label) { if (isLabelSet()) { // label was set before -> rename doRenameLabel(label); } else { // no label set so far -> set new label if (label.startsWith("c_") && this instanceof GeoNumeric) { cons.setCasCellUpdate(false); } doSetLabel(getFreeLabel(label)); cons.setCasCellUpdate(false); } } /** * @param caption1 * raw caption * @return true if new caption is not null */ @Override public boolean setCaption(String caption1) { String caption2 = caption1; if ((caption2 == null) || caption2.equals(label)) { this.caption = null; return false; } // workaround for unintended feature of old Input Boxes //   and   both used to work if (caption2.indexOf(" ") > -1) { caption2 = caption2.replaceAll(" ", Unicode.NBSP); caption2 = caption2.replaceAll(" ", Unicode.NBSP); } caption2 = caption2.trim(); if (caption2.isEmpty()) { this.caption = null; return true; } this.caption = caption2; return true; } /** * * @return caption as stored in geo */ public String getCaptionSimple() { return caption; } /** * Caption string (with substitutions) * * @param tpl * string template * @return caption (or label if caption is null) */ @Override public String getCaption(StringTemplate tpl) { if (caption == null) { return getLabel(tpl); } // for speed, check first for a % if (caption.indexOf('%') < 0) { return caption; } StringBuilder captionSB = new StringBuilder(); // replace %v with value and %n with name for (int i = 0; i < caption.length(); i++) { char ch = caption.charAt(i); if ((ch == '%') && (i < (caption.length() - 1))) { // get number after % i++; ch = caption.charAt(i); switch (ch) { case 'c': // (text value) of next cell to the right String cText = ""; if (label != null) { GPoint p = GeoElementSpreadsheet .spreadsheetIndices(label); if (p.x > -1 && p.y > -1) { String labelR1 = GeoElementSpreadsheet .getSpreadsheetCellName(p.x + 1, p.y); GeoElement geoR1 = kernel.lookupLabel(labelR1); if (geoR1 != null) { cText = geoR1.toValueString(tpl); } } } captionSB.append(cText); break; case 'v': captionSB.append(toValueString(tpl)); break; case 'n': captionSB.append(getLabel(tpl)); break; case 'x': if (isGeoPoint()) { captionSB.append(kernel.format( ((GeoPointND) this).getInhomCoords().getX(), tpl)); } else if (isGeoVector()) { captionSB.append(kernel.format( ((GeoVectorND) this).getInhomCoords()[0], tpl)); } else if (isGeoLine()) { captionSB.append( kernel.format(((GeoLine) this).getX(), tpl)); } else { captionSB.append("%x"); } break; case 'y': if (isGeoPoint()) { captionSB.append(kernel.format( ((GeoPointND) this).getInhomCoords().getY(), tpl)); } else if (isGeoVector()) { captionSB.append(kernel.format( ((GeoVectorND) this).getInhomCoords()[1], tpl)); } else if (isGeoLine()) { captionSB.append( kernel.format(((GeoLine) this).getY(), tpl)); } else { captionSB.append("%y"); } break; case 'z': if (isGeoPoint()) { captionSB.append(kernel.format( ((GeoPointND) this).getInhomCoords().getZ(), tpl)); } else if (isGeoVector()) { captionSB.append( ((GeoVectorND) this).getInhomCoords().length < 3 ? "0" : kernel.format( ((GeoVectorND) this) .getInhomCoords()[2], tpl)); } else if (isGeoLine()) { captionSB.append( kernel.format(((GeoLine) this).getZ(), tpl)); } else { captionSB.append("%z"); } break; default: captionSB.append('%'); captionSB.append(ch); } } else { captionSB.append(ch); } } if (captionSB.length() == 0) { // can't return empty string // eg if %c used when not a spreadsheet cell return getLabel(tpl); } return captionSB.toString(); } /** @return caption without substitution; returns "" if caption is null */ @Override public String getRawCaption() { if (caption == null) { return ""; } return caption; } /** * @param tpl * string template * @return caption string with substitution or "" if caption is null */ public String getCaptionDescription(StringTemplate tpl) { if (caption == null) { return ""; } return getCaption(tpl); } /** * Sets label of a local variable object. This method should only be used by * Construction. * * @param label * local variable name */ public void setLocalVariableLabel(final String label) { oldLabel = this.label; this.label = label; localVarLabelSet = true; } /** * Sets label of a local variable object back to its previous label. This * method should only be used by Construction. */ public void undoLocalVariableLabel() { if (oldLabel != null) { label = oldLabel; localVarLabelSet = false; } } /** * @return true for local variables */ public boolean isLocalVariable() { return localVarLabelSet; } private void doSetLabel(final String newLabel) { // needed for GGB-810 boolean addToConstr = true; if (cons.isFileLoading() && this instanceof GeoNumeric && newLabel.startsWith("c_")) { GeoElement geo = cons.lookupLabel(newLabel); // remove from construction duplicate of constant if (geo != null) { cons.removeFromConstructionList(geo); cons.removeLabel(geo); } } // hack needed for web for file loading with algebra view else { GeoElement geo = cons.lookupLabel(newLabel); // remove from construction duplicate of constant if (geo instanceof GeoNumeric && this instanceof GeoNumeric) { try { cons.replace(geo, this); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } geo = this; if (!((GeoNumeric) geo).isDependentConst()) { ((GeoNumeric) geo).setIsDependentConst(true); } addToConstr = false; } } // UPDATE KERNEL if (!isLabelSet() && isIndependent()) { // add independent object to list of all Construction Elements // dependent objects are represented by their parent algorithm if (addToConstr) { cons.addToConstructionList(this, true); } } setLabelSimple(newLabel); // set new label setLabelSet(true); labelWanted = false; // got a label, no longer wanted if (this instanceof GeoNumeric && newLabel.startsWith("c_")) { GeoNumeric geoNum = cons.lookupConstantLabel(newLabel); if (geoNum != null) { ((GeoNumeric) this).setIsDependentConst(true); } } if (addToConstr) { cons.putLabel(this); // add new table entry } algebraStringsNeedUpdate(); updateSpreadsheetCoordinates(); if (addToConstr) { notifyAdd(); } /* * if(cons.getCASdummies().contains(newLabel)){ * cons.moveInConstructionList(this, 0); * cons.getCASdummies().remove(newLabel); for(int * i=0;cons.getCasCell(i)!=null;i++){ * kernel.getAlgebraProcessor().processCasCell(cons.getCasCell(i)); } } */ } private void updateSpreadsheetCoordinates() { if (isLabelSet() && (label.length() > 0) && StringUtil.isLetter(label.charAt(0)) // starts with letter && StringUtil.isDigit(label.charAt(label.length() - 1))) // ends // with // digit { // init old and current spreadsheet coords if (spreadsheetCoords == null) { oldSpreadsheetCoords = null; spreadsheetCoords = new GPoint(); } else { if (oldSpreadsheetCoords == null) { oldSpreadsheetCoords = new GPoint(); } oldSpreadsheetCoords.setLocation(spreadsheetCoords); } // we need to also support wrapped GeoElements like // $A4 that are implemented as dependent geos (using ExpressionNode) final GPoint p = GeoElementSpreadsheet.spreadsheetIndices( getLabel(StringTemplate.defaultTemplate)); if ((p.x >= 0) && (p.y >= 0)) { spreadsheetCoords.setLocation(p.x, p.y); } else { spreadsheetCoords = null; } } else { oldSpreadsheetCoords = spreadsheetCoords; spreadsheetCoords = null; } // Application.debug("update spread sheet coords: " + this + ", " + // spreadsheetCoords + ", old: " + oldSpreadsheetCoords); } /** * Returns the spreadsheet reference name of this GeoElement using $ signs * for absolute spreadsheet reference names like A$1 or $A$1. * * @param col$ * true if col has $ * @param row$ * true if row has $ * @return spreadsheet reference name of this GeoElement with $ signs */ public String getSpreadsheetLabelWithDollars(final boolean col$, final boolean row$) { final String colName = GeoElementSpreadsheet .getSpreadsheetColumnName(spreadsheetCoords.x); final String rowName = Integer.toString(spreadsheetCoords.y + 1); final StringBuilder sb = new StringBuilder(label.length() + 2); if (col$) { sb.append('$'); } sb.append(colName); if (row$) { sb.append('$'); } sb.append(rowName); return sb.toString(); } /** * compares labels alphabetically, but spreadsheet labels are sorted nicely * eg A1, A2, A10 not A1, A10, A2 * * @param label1 * first label * @param label2 * second label * @return negative/0/positive as in {@link Comparable#compareTo(Object)} */ final public static int compareLabels(final String label1, final String label2) { if (GeoElementSpreadsheet.isSpreadsheetLabel(label1) && GeoElementSpreadsheet.isSpreadsheetLabel(label2)) { final GPoint p1 = GeoElementSpreadsheet .getSpreadsheetCoordsForLabel(label1); final GPoint p2 = GeoElementSpreadsheet .getSpreadsheetCoordsForLabel(label2); // Application.debug(label1+" "+p1.x+" "+p1.y+" "+label2+" "+p2.x+" // "+p2.y); if (p1.x != p2.x) { return p1.x - p2.x; } return p1.y - p2.y; } return label1.compareTo(label2); } /** maximal line width */ public static final int MAX_LINE_WIDTH = 13; private void doRenameLabel(final String newLabel) { if ((newLabel == null) || newLabel.equals(label)) { return; } // UPDATE KERNEL cons.removeLabel(this); // remove old table entry oldLabel = label; // remember old label (for applet to javascript // rename) label = newLabel; // set new label GeoElementSpreadsheet.setBackgroundColor(this); // rename corresponding cas cell, before the label // is in construction set if (correspondingCasCell != null) { correspondingCasCell.setInputFromTwinGeo(false, false); } cons.putLabel(this); // add new table entry algebraStringsNeedUpdate(); updateSpreadsheetCoordinates(); kernel.notifyRename(this); // tell views updateCascade(); kernel.notifyRenameUpdatesComplete(); } /** * Returns the label of this object before rename() was called. * * @return label before renaming */ @Override final public String getOldLabel() { return oldLabel; } /** * set labels for array of GeoElements with given label prefix. e.g. * labelPrefix = "F", geos.length = 2 sets geo[0].setLabel("F_1") and * geo[1].setLabel("F_2") all members in geos are assumed to be initialized. * * @param labelPrefix * prefix * @param geos * array of geos to be labeled */ public static void setLabels(final String labelPrefix, final GeoElementND[] geos) { if (geos == null) { return; } int visible = 0; int firstVisible = 0; for (int i = geos.length - 1; i >= 0; i--) { if (geos[i].isVisible()) { firstVisible = i; visible++; } } switch (visible) { case 0: // no visible geos: they all get the labelPrefix as suggestion for (int i = 0; i < geos.length; i++) { geos[i].setLabel(labelPrefix); } break; case 1: // if there is only one visible geo, don't use indices geos[firstVisible].setLabel(labelPrefix); break; default: // is this a spreadsheet label? final GPoint p = GeoElementSpreadsheet .spreadsheetIndices(labelPrefix); if ((p.x >= 0) && (p.y >= 0)) { // more than one visible geo and it's a spreadsheet cell // use D1, E1, F1, etc as names final int col = p.x; final int row = p.y; for (int i = 0; i < geos.length; i++) { geos[i].setLabel(geos[i].getFreeLabel(GeoElementSpreadsheet .getSpreadsheetCellName(col + i, row))); } } else { // more than one visible geo: use indices if we got a // prefix for (int i = 0; i < geos.length; i++) { geos[i].setLabel(geos[i].getIndexLabel(labelPrefix)); } } } } /** * set labels for array of GeoElements pairwise: geos[i].setLabel(labels[i]) * * @param labels * array of labels * @param geos * array of geos */ public static void setLabels(final String[] labels, final GeoElement[] geos) { setLabels(labels, geos, false); } /** * Sets labels for given geos * * @param labels * labels * @param geos * geos * @param indexedOnly * true for labels a_1,a_2,a_3,... */ static void setLabels(final String[] labels, final GeoElement[] geos, final boolean indexedOnly) { final int labelLen = (labels == null) ? 0 : labels.length; if ((labelLen == 1) && (labels[0] != null) && !labels[0].equals("")) { setLabels(labels[0], geos); return; } String label; for (int i = 0; i < geos.length; i++) { if (i < labelLen) { label = labels[i]; } else { label = null; } if (indexedOnly) { label = geos[i].getIndexLabel(label); } geos[i].setLabel(label); } } /** * Get a free label. Try the suggestedLabel first * * @param suggestedLabel * label to be tried first * @return free label -- either suggestedLabel or suggestedLabel_index */ @Override public String getFreeLabel(final String suggestedLabel) { if (suggestedLabel != null) { if ("x".equals(suggestedLabel) || "y".equals(suggestedLabel)) { return getDefaultLabel(false); } if (cons.isFreeLabel(suggestedLabel)) { return suggestedLabel; } else if (suggestedLabel.length() > 0) { return getIndexLabel(suggestedLabel); } } // standard case: get default label return getDefaultLabel(false); } /** * @param isInteger * flag for integers * @return default label for this geo */ public String getDefaultLabel(final boolean isInteger) { return getDefaultLabel(null, isInteger); } /** * @return deafult label for this geo (depends on type) */ @Override public String getDefaultLabel() { return getDefaultLabel(null, false); } /* * all 24 Greek UPPER CASE */ private static final char[] greekUpperCase = { '\u0391', '\u0392', '\u0393', '\u0394', '\u0395', '\u0396', '\u0397', '\u0398', '\u0399', '\u039a', '\u039b', '\u039c', '\u039d', '\u039e', '\u039f', '\u03a0', '\u03a1', '\u03a3', '\u03a4', '\u03a5', '\u03a6', '\u03a7', '\u03a8', '\u03a9' }; /* * Greek lower case pi NOT included, also sigmaf \u03c2 omitted \u03d5 in * place of \u03c6 */ private static final char[] greekLowerCaseNoPi = { '\u03b1', '\u03b2', '\u03b3', '\u03b4', '\u03b5', '\u03b6', '\u03b7', '\u03b8', '\u03b9', '\u03ba', '\u03bb', '\u03bc', '\u03bd', '\u03be', '\u03bf', '\u03c1', '\u03c3', '\u03c4', '\u03c5', '\u03d5', '\u03c7', '\u03c8', '\u03c9' }; /** * appends all upper case Greek letters to list * * @param list * list to append Greek Upper case letters to */ public static void addAddAllGreekUpperCase(ArrayList<String> list) { for (int i = 0; i < greekUpperCase.length; i++) { list.add(greekUpperCase[i] + ""); } } /** * appends all upper case Greek letters to list * * @param list * list to append Greek Upper case letters to */ public static void addAddAllGreekLowerCaseNoPi(ArrayList<String> list) { for (int i = 0; i < greekLowerCaseNoPi.length; i++) { list.add(greekLowerCaseNoPi[i] + ""); } } /** * appends all upper case Greek letters to list * * @param listener * listener to append Greek Upper case letters to */ public static void addAddAllGreekLowerCaseNoPi( IAxisModelListener listener) { for (int i = 0; i < greekLowerCaseNoPi.length; i++) { listener.addAxisLabelItem(greekLowerCaseNoPi[i] + ""); } } /** * @param chars2 * array of one-character labels for this GeoType * @param isInteger * true for integer sliders * @return default label */ protected String getDefaultLabel(char[] chars2, final boolean isInteger) { char[] chars = chars2; if (chars == null) { if (isGeoPoint() && !(this instanceof GeoTurtle)) { // Michael Borcherds 2008-02-23 // use Greek upper case for labeling points if language is Greek // (el) if (getLoc().isUsingLocalizedLabels()) { if (getLoc().languageIs(Language.Greek.locale)) { chars = greekUpperCase; } else if (getLoc().languageIs(Language.Arabic.locale)) { // Arabic / Arabic (Morocco) chars = arabic; } else if (getLoc().languageIs(Language.Yiddish.locale)) { chars = yiddish; } else { chars = pointLabels; } } else { chars = pointLabels; } final GeoPointND point = (GeoPointND) this; if (point.getMode() == Kernel.COORD_COMPLEX) { String complexLabel = "z_1"; int i = 1; while (!cons.isFreeLabel(complexLabel)) { i++; if (i < 9) { // eg z_6 complexLabel = "z_" + i; } else { // eg z_{12} complexLabel = "z_{" + i + "}"; } } return complexLabel; } } else if (isGeoFunction()) { chars = functionLabels; } else if (isGeoLine()) { // name "edge" for segments from polyhedron if (getMetasLength() == 1 && !((FromMeta) this).getMetas()[0].isGeoPolygon()) { int counter = 0; String str; final String name = getLoc().getPlainLabel("edge"); // Name.edge do { counter++; str = name + kernel.internationalizeDigits(counter + "", StringTemplate.defaultTemplate); } while (!cons.isFreeLabel(str)); return str; } chars = lineLabels; } else if (this instanceof GeoPenStroke || this instanceof GeoLocusStroke) { // needs to come before PolyLine (subclass) return defaultNumberedLabel("penStroke"); // Name.penStroke } else if (isGeoPolyLine()) { chars = lineLabels; } else if (isGeoConic()) { chars = conicLabels; } else if (isGeoVector() || evaluatesTo3DVector()) { chars = vectorLabels; } else if (isGeoAngle()) { chars = greekLowerCaseNoPi; } else if (isGeoText()) { return defaultNumberedLabel("text"); // Name.text } else if (isGeoImage()) { return defaultNumberedLabel("picture"); // Name.picture } else if (isGeoLocus()) { if (algoParent.getClassName().equals(Commands.SolveODE) || algoParent instanceof AlgoIntegralODE || algoParent .getClassName().equals(Commands.NSolveODE)) { return defaultNumberedLabel("numericalIntegral"); // Name.numericalIntegral } else if (algoParent.getClassName() .equals(Commands.SlopeField)) { return defaultNumberedLabel("slopefield"); // Name.slopefield } else if (algoParent instanceof GraphAlgo) { return defaultNumberedLabel("graph"); // Name.graph } return defaultNumberedLabel("locus"); // Name.locus } else if (isGeoInputBox()) { return defaultNumberedLabel("textfield"); // Name.textfield } else if (isGeoButton()) { return defaultNumberedLabel("button"); // Name.button } else if (isGeoTurtle()) { return defaultNumberedLabel("turtle"); // Name.turtle } else if (isGeoList()) { final GeoList list = (GeoList) this; return defaultNumberedLabel( list.isMatrix() ? "matrix" : "list"); // Name.matrix / // Name.list } else if (isInteger && isGeoNumeric()) { chars = integerLabels; } else { chars = lowerCaseLabels; } } int counter = 0, q, r; final StringBuilder sbDefaultLabel = new StringBuilder(); boolean repeat = true; while (repeat) { sbDefaultLabel.setLength(0); q = counter / chars.length; // quotient r = counter % chars.length; // remainder final char ch = chars[r]; sbDefaultLabel.append(ch); // this arabic letter is two unicode chars if (ch == '\u0647') { sbDefaultLabel.append('\u0640'); } if (q > 0) { // don't use indices // sbDefaultLabel.append(q); // q as index if (q < 10) { sbDefaultLabel.append('_'); sbDefaultLabel.append(q); } else { sbDefaultLabel.append("_{"); sbDefaultLabel.append(q); sbDefaultLabel.append('}'); } } counter++; // is label reserved repeat = !cons.isFreeLabel(sbDefaultLabel.toString(), true, true); } return sbDefaultLabel.toString(); } private String defaultNumberedLabel(final String plainKey) { int counter = 0; String str; do { counter++; str = getLoc().getPlainLabel(plainKey) + kernel.internationalizeDigits(counter + "", StringTemplate.defaultTemplate); } while (!cons.isFreeLabel(str)); return str; } /** * Returns the next free indexed label using the given prefix. * * @param prefix * e.g. "c" * @return indexed label, e.g. "c_2" */ @Override public String getIndexLabel(final String prefix) { if (prefix == null) { return getFreeLabel(null) + "_1"; } return cons.getIndexLabel(prefix); } /** * @return true for textfields (=Input Boxes) */ public boolean isGeoInputBox() { return false; } private boolean isEmptySpreadsheetCell = false; /** * @param isEmptySpreadsheetCell * empty spreadsheet cell flag */ public void setEmptySpreadsheetCell(boolean isEmptySpreadsheetCell) { this.isEmptySpreadsheetCell = isEmptySpreadsheetCell; } /** * @return empty spreadsheet cell flag */ public boolean isEmptySpreadsheetCell() { return isEmptySpreadsheetCell; } /** * Removes this object and all dependent objects from the Kernel. If this * object is not independent, it's parent algorithm is removed too. */ @Override public void remove() { // dependent object: remove parent algorithm if (algoParent != null) { algoParent.remove(this); } else { // must be done in this order because doRemove destroys the link if (correspondingCasCell != null) { correspondingCasCell.doRemove(); } doRemove(); } } /** * removes this GeoElement and all its dependents */ @Override public void doRemove() { // stop animation of this geo setAnimating(false); // remove Listeners AlgoElement algo = getParentAlgorithm(); // first remove all dependent algorithms if (algorithmList != null) { final Object[] algos = algorithmList.toArray(); for (int i = 0; i < algos.length; i++) { algo = (AlgoElement) algos[i]; algo.remove(this); } } // remove this object from List if (isIndependent()) { cons.removeFromConstructionList(this); } /* * // remove Listeners AlgoElement algo = getParentAlgorithm(); */ if (algo != null) { cons.unregisterEuclidianViewCE(algo); } if (condShowObject != null) { condShowObject.unregisterConditionListener(this); } if (colFunction != null) { colFunction.unregisterColorFunctionListener(this); } /* * // remove all dependent algorithms if (algorithmList != null) { final * Object[] algos = algorithmList.toArray(); for (int i = 0; i < * algos.length; i++) { algo = (AlgoElement) algos[i]; * algo.remove(this); cons.updateCasCellRows(); } // * cons.updateCasCellRows(); } */ // remove this object from table if (isLabelSet()) { cons.removeLabel(this); } // remove from selection if (isSelected()) { // prevent update selection if construction will replace the geo kernel.getApplication().getSelectionManager().removeSelectedGeo( this, false, !cons.isRemovingGeoToReplaceIt()); } // notify views before we change labelSet notifyRemove(); setLabelSet(false); labelWanted = false; correspondingCasCell = null; if (latexCache != null) { // remove old key from cache // JLaTeXMathCache.removeCachedTeXFormula(keyLaTeX); latexCache.remove(); } // http://dev.geogebra.org/trac/changeset/39262 // reverted as causes infinite loop when Attach/Detach tool used // to *drag* a Point onto eg a circle // if (kernel.getApplication() != null // && kernel.getApplication().getActiveEuclidianView() != null // && kernel.getApplication().getActiveEuclidianView() // .getEuclidianController() != null) { // kernel.getApplication().getActiveEuclidianView() // .getEuclidianController().clearSelections(); // } } private LaTeXCache latexCache = null; /** * @return latex cache */ @Override public LaTeXCache getLaTeXCache() { if (latexCache == null) { latexCache = LaTeXFactory.getPrototype().newLaTeXCache(); } return latexCache; } @Override final public void notifyAdd() { kernel.notifyAdd(this); // Application.debug("add " + label); // printUpdateSets(); } @Override final public void notifyRemove() { kernel.notifyRemove(this); // Application.debug("remove " + label); // printUpdateSets(); } /** * Notify kernel (and all views) about update */ final public void notifyUpdate() { kernel.notifyUpdate(this); // Application.debug("update " + label); // printUpdateSets(); } /** * Notify kernel (and all views) about update of auxiliary object */ final public void notifyUpdateAuxiliaryObject() { kernel.notifyUpdateAuxiliaryObject(this); // Application.debug("add " + label); // printUpdateSets(); } /* * private void printUpdateSets() { Iterator itList = * cons.getAllGeoElementsIterator(); while (itList.hasNext()) { GeoElement * geo = (GeoElement) itList.next(); Application.debug(geo.label + ": " + * geo.algoUpdateSet.toString()); } } */ /* * ******************************************************* AlgorithmList * Management each GeoElement has a list of dependent * algorithms****************************************************** */ /** * add algorithm to dependency list of this GeoElement * * @param algorithm * algorithm directly dependent on this */ @Override final public void addAlgorithm(final AlgoElement algorithm) { if (!(getAlgorithmList().contains(algorithm))) { algorithmList.add(algorithm); } addToUpdateSets(algorithm); } /** * Adds the given algorithm to the dependency list of this GeoElement. The * algorithm is NOT added to the updateSet of this GeoElement! I.e. when * updateCascade() is called the given algorithm will not be updated. * * @param algorithm * algo to be added */ @Override final public void addToAlgorithmListOnly(final AlgoElement algorithm) { if (!getAlgorithmList().contains(algorithm)) { algorithmList.add(algorithm); } } /** * Adds the given algorithm to the update set this GeoElement. Note: the * algorithm is NOT added to the algorithm list, i.e. the dependency graph * of the construction. * * @param algorithm * algorithm to be added */ @Override final public void addToUpdateSetOnly(final AlgoElement algorithm) { addToUpdateSets(algorithm); } /** * remove algorithm from dependency list of this GeoElement * * @param algorithm * algorithm to be removed */ @Override final public void removeAlgorithm(final AlgoElement algorithm) { if (algorithmList != null) { algorithmList.remove(algorithm); removeFromUpdateSets(algorithm); } } /** * @return set of all dependent algos in topological order */ @Override public AlgorithmSet getAlgoUpdateSet() { if (algoUpdateSet == null) { algoUpdateSet = new AlgorithmSet(); } return algoUpdateSet; } /** * add algorithm to update sets up the construction graph * * @param algorithm * algo to be added * @return true if added */ @Override public boolean addToUpdateSets(final AlgoElement algorithm) { final boolean added = getAlgoUpdateSet().add(algorithm); if (added) { // propagate up the graph if we didn't do this before if (algoParent != null) { final GeoElement[] input = algoParent .getInputForUpdateSetPropagation(); for (int i = 0; i < input.length; i++) { input[i].addToUpdateSets(algorithm); } } } return added; } /** * remove algorithm from update sets up the construction graph * * @param algorithm * algo to be removed * @return true if removed */ @Override public boolean removeFromUpdateSets(final AlgoElement algorithm) { final boolean removed = (algoUpdateSet != null) && algoUpdateSet.remove(algorithm); if (removed) { // propagate up the graph if (algoParent != null) { final GeoElement[] input = algoParent .getInputForUpdateSetPropagation(); for (int i = 0; i < input.length; i++) { input[i].removeFromUpdateSets(algorithm); } } } return removed; } /** * updates this object and notifies kernel. Note: no dependent objects are * updated. * * @see #updateRepaint() * @param dragging * whether this was triggered by drag */ public void update(boolean dragging) { updateGeo(!cons.isUpdateConstructionRunning()); kernel.notifyUpdate(this); } @Override public final void update() { update(false); } /** * Same as update(), but do not notify kernel * * @param mayUpdateCas * whether update migt be sent to CAS * @param dragging * whether this was triggered by drag */ protected final void updateGeo(boolean mayUpdateCas, boolean dragging) { if (labelWanted && !isLabelSet()) { // check if this object's label needs to be set if (isVisible()) { setLabel(label); } } if (mayUpdateCas && correspondingCasCell != null) { correspondingCasCell.setInputFromTwinGeo(false, dragging); } // texts need updates algebraStringsNeedUpdate(); } /** * @param mayUpdateCas * whether CAS may need update */ protected final void updateGeo(boolean mayUpdateCas) { updateGeo(mayUpdateCas, false); } private void algebraStringsNeedUpdate() { strAlgebraDescriptionNeedsUpdate = true; strAlgebraDescTextOrHTMLneedsUpdate = true; strAlgebraDescriptionHTMLneedsUpdate = true; strLabelTextOrHTMLUpdate = true; strLaTeXneedsUpdate = true; } /** * Updates this object and all dependent ones. Note: no repainting is done * afterwards! synchronized for animation * * @param dragging * whether this was triggered by drag */ public void updateCascade(boolean dragging) { long l = System.currentTimeMillis(); kernel.notifyBatchUpdate(); update(dragging); updateDependentObjects(); GeoGebraProfiler.addUpdateCascade(System.currentTimeMillis() - l); kernel.notifyEndBatchUpdate(); } @Override public void updateCascade() { updateCascade(false); } private void updateDependentObjects() { if ((correspondingCasCell != null) && isIndependent()) { updateAlgoUpdateSetWith(correspondingCasCell); } else if (algoUpdateSet != null) { // update all algorithms in the algorithm set of this GeoElement cons.setAlgoSetCurrentlyUpdated(algoUpdateSet); algoUpdateSet.updateAll(); cons.setAlgoSetCurrentlyUpdated(null); } } /** * Updates algoUpdateSet and secondGeo.algoUpdateSet together efficiently. * * @param secondGeo * other geo whose update set needs an update */ protected void updateAlgoUpdateSetWith(final GeoElement secondGeo) { if (algoUpdateSet == null) { if (secondGeo.algoUpdateSet == null) { // both null return; } // update second only secondGeo.algoUpdateSet.updateAll(); } else { if (secondGeo.algoUpdateSet == null) { // update first only algoUpdateSet.updateAll(); } else { // join both algoUpdateSets and update all algorithms final TreeSet<AlgoElement> tempAlgoSet = getTempSet(); tempAlgoSet.clear(); algoUpdateSet.addAllToCollection(tempAlgoSet); secondGeo.algoUpdateSet.addAllToCollection(tempAlgoSet); for (final AlgoElement algo : tempAlgoSet) { algo.update(); } } } } @Override public boolean hasAlgoUpdateSet() { return algoUpdateSet != null; } /** * If the flag updateCascadeAll is false, this algorithm updates all * GeoElements in the given ArrayList and all algorithms that depend on free * GeoElements in that list. If the flag updateCascadeAll is true, this * algorithm updates all GeoElements in the given ArrayList and all * algorithms that depend on any GeoElement in that list. This flag was * introduced because of Ticket #1383, description of that change is there * * Note: this method is more efficient than calling updateCascade() for all * individual GeoElements. * * @param geos * geos to be updated * * @param tempSet1 * a temporary set that is used to collect all algorithms that * need to be updated * * @param updateCascadeAll * true to update cascade over dependent geos as well */ final static public synchronized void updateCascade( final ArrayList<? extends GeoElementND> geos, final TreeSet<AlgoElement> tempSet1, final boolean updateCascadeAll) { // only one geo: call updateCascade() if (geos.size() == 1) { final GeoElementND ce = geos.get(0); ce.updateCascade(); return; } // build update set of all algorithms in construction element order // clear temp set tempSet1.clear(); final int size = geos.size(); for (int i = 0; i < size; i++) { final GeoElementND geo = geos.get(i); geo.update(); if ((geo.isIndependent() || geo.isPointOnPath() || updateCascadeAll) && (geo.hasAlgoUpdateSet())) { // add all dependent algos of geo to the overall algorithm // set geo.getAlgoUpdateSet().addAllToCollection(tempSet1); } } // now we have one nice algorithm set that we can update if (tempSet1.size() > 0) { final Iterator<AlgoElement> it = tempSet1.iterator(); while (it.hasNext()) { final AlgoElement algo = it.next(); algo.update(); } } } /** * Updates all objects in a cascade, but only location is updated for the * locatables in input array * * @param geos * locateables * @param cons * construction where update is done */ final static public synchronized void updateCascadeLocation( final ArrayList<Locateable> geos, Construction cons) { // build update set of all algorithms in construction element order // clear temp set final TreeSet<AlgoElement> tempSet1 = new TreeSet<AlgoElement>(); final int size = geos.size(); for (int i = 0; i < size; i++) { final Locateable geo = geos.get(i); geo.updateLocation(); if ((geo.isIndependent() || geo.isGeoText()) && (geo.hasAlgoUpdateSet())) { // add all dependent algos of geo to the overall algorithm // set geo.getAlgoUpdateSet().addAllToCollection(tempSet1); } } // remove algos currently updated AlgorithmSet algoSetCurrentlyUpdated = cons .getAlgoSetCurrentlyUpdated(); if (algoSetCurrentlyUpdated != null) { algoSetCurrentlyUpdated.removeAllFromCollection(tempSet1); } // now we have one nice algorithm set that we can update if (tempSet1.size() > 0) { final Iterator<AlgoElement> it = tempSet1.iterator(); while (it.hasNext()) { try { final AlgoElement algo = it.next(); algo.update(); } catch (Exception e) { e.printStackTrace(); } } } } /** * Updates all GeoElements in the given ArrayList and all algorithms that * depend on free GeoElements in that list. Note: this method is more * efficient than calling updateCascade() for all individual GeoElements. * * @param geos * geos to be updated * * @param tempSet2 * a temporary set that is used to collect all algorithms that * need to be updated * @param lastAlgo * stop cascade on this algo */ final static public void updateCascadeUntil(final ArrayList<?> geos, final TreeSet<AlgoElement> tempSet2, final AlgoElement lastAlgo) { // only one geo: call updateCascade() if (geos.size() == 1) { final ConstructionElement ce = (ConstructionElement) geos.get(0); if (ce.isGeoElement()) { ((GeoElement) ce).updateCascade(); } return; } // build update set of all algorithms in construction element order // clear temp set tempSet2.clear(); final int size = geos.size(); for (int i = 0; i < size; i++) { final ConstructionElement ce = (ConstructionElement) geos.get(i); if (ce.isGeoElement()) { final GeoElement geo = (GeoElement) geos.get(i); geo.update(); if ((geo.isIndependent() || geo.isPointOnPath()) && (geo.algoUpdateSet != null)) { // add all dependent algos of geo to the overall algorithm // set geo.algoUpdateSet.addAllToCollection(tempSet2); } } } // now we have one nice algorithm set that we can update if (tempSet2.size() > 0) { final Iterator<AlgoElement> it = tempSet2.iterator(); while (it.hasNext()) { final AlgoElement algo = it.next(); algo.update(); if (algo == lastAlgo) { return; } } } } /** * Updates this object and all dependent ones. Notifies kernel to repaint * views. * * @param dragging * whether this was triggered by drag */ public void updateRepaint(boolean dragging) { updateCascade(dragging); kernel.notifyRepaint(); } /** * Updates this object and all dependent ones. Notifies kernel to repaint * views. */ @Override public void updateRepaint() { updateRepaint(false); } @Override public void updateVisualStyle(GProperty prop) { // updateGeo(); kernel.notifyUpdateVisualStyle(this, prop); // updateDependentObjects(); // kernel.notifyRepaint(); } @SuppressWarnings("deprecation") @Deprecated @Override final public String toString() { return toString(StringTemplate.defaultTemplate); } /** * implementation of interface ExpressionValue */ @Override public boolean isConstant() { return false; } @Override public final boolean isLeaf() { return true; } /** * Evaluates to number (if not numeric, returns undefined MyDouble) * * @return number or undefined double */ @Override public double evaluateDouble() { if (this instanceof NumberValue) { return ((NumberValue) this).getDouble(); } return Double.NaN; } @Override final public ExpressionValue evaluate(StringTemplate tpl) { if (this instanceof GeoCasCell) { return ((GeoCasCell) this).getOutputValidExpression(); } return this; } /** * Returns just set with just one element (itself). Do not ever remove the * final flag, it will break the update mechanism. */ @Override public final HashSet<GeoElement> getVariables() { final HashSet<GeoElement> ret = new HashSet<GeoElement>(); ret.add(this); return ret; } /** * Returns all predecessors of this GeoElement that are random numbers and * don't have labels. * * @return all random numeric unlabeled predecessors */ public ArrayList<GeoNumeric> getRandomNumberPredecessorsWithoutLabels() { if (isIndependent()) { return null; } ArrayList<GeoNumeric> randNumbers = null; final TreeSet<GeoElement> pred = getAllPredecessors(); final Iterator<GeoElement> it = pred.iterator(); while (it.hasNext()) { final GeoElement geo = it.next(); if (geo.isGeoNumeric()) { final GeoNumeric num = (GeoNumeric) geo; if (num.isRandomGeo() && !num.isLabelSet()) { if (randNumbers == null) { randNumbers = new ArrayList<GeoNumeric>(); } randNumbers.add(num); } } } return randNumbers; } /** * Returns all predecessors (of type GeoElement) that this object depends * on. The predecessors are sorted topologically. * * @return all predecessors of this geo */ public TreeSet<GeoElement> getAllPredecessors() { final TreeSet<GeoElement> set = new TreeSet<GeoElement>(); addPredecessorsToSet(set, false); set.remove(this); return set; } /** * adds all predecessors of this object to the given set the set is * topologically sorted * * @param set * set of predecessors * @param onlyIndependent * whether only indpendent geos should be added */ @Override final public void addPredecessorsToSet(final TreeSet<GeoElement> set, final boolean onlyIndependent) { if (algoParent == null) { set.add(this); } else { // parent algo algoParent.addPredecessorsToSet(set, onlyIndependent); } } /** * only add predecessors that satisfy a condition * * @param set * output set * @param check * condition */ final public void addPredecessorsToSet(final TreeSet<GeoElement> set, final Inspecting check) { if (algoParent == null) { if (check.check(this)) { set.add(this); } } else { // parent algo algoParent.addPredecessorsToSet(set, check); } } /** * @return set of all predecessor that can be randomized */ public TreeSet<GeoElement> getAllRandomizablePredecessors() { final TreeSet<GeoElement> set = new TreeSet<GeoElement>(); addRandomizablePredecessorsToSet(set); return set; } /** * @param set * set of randomizable predecessors */ final public void addRandomizablePredecessorsToSet( final TreeSet<GeoElement> set) { if (isRandomizable() && !cloneInUse) { set.add(this); } if (algoParent != null) { // parent algo algoParent.addRandomizablePredecessorsToSet(set); } } /** * Returns whether geo depends on this object. * * @param geo * other geo * @return true if geo depends on this object. */ @Override final public boolean isParentOf(final GeoElementND geo) { if (algoUpdateSet != null) { final Iterator<AlgoElement> it = algoUpdateSet.getIterator(); while (it.hasNext()) { final AlgoElement algo = it.next(); for (int i = 0; i < algo.getOutputLength(); i++) { if (geo == algo.getOutput(i)) { return true; } } } } return false; } /** * Returns whether this object is parent of other geos. * * @return true if this object is parent of other geos. */ @Override final public boolean hasChildren() { return (algorithmList != null) && (algorithmList.size() > 0); } /** * Returns whether this object is dependent on geo. * * @param geo * other geo * @return true if this object is dependent on geo. */ @Override final public boolean isChildOf(final GeoElementND geo) { if ((geo == null) || isIndependent()) { return false; } return geo.isParentOf(this); } /** * Returns whether this object is dependent on other geo (or equal) * * @param geo * other geo * @return true if this object is dependent on other geo. */ final public boolean isChildOrEqual(final GeoElementND geo) { return (this == geo) || isChildOf(geo); } /** * Returns all children (of type GeoElement) that depend on this object. * * @return set of all children of this geo */ @Override final public TreeSet<GeoElement> getAllChildren() { final TreeSet<GeoElement> set = new TreeSet<GeoElement>(); if (algoUpdateSet != null) { final Iterator<AlgoElement> it = algoUpdateSet.getIterator(); while (it.hasNext()) { final AlgoElement algo = it.next(); for (int i = 0; i < algo.getOutputLength(); i++) { set.add(algo.getOutput(i)); } } } return set; } /** * implementation of abstract methods from ConstructionElement Almost never * called, do not cache */ @Override final public GeoElement[] getGeoElements() { return new GeoElement[] { this }; } @Override final public boolean isAlgoElement() { return false; } @Override final public boolean isGeoElement() { return true; } /** * @return twinGeos construction index */ final public int getAlgoDepCasCellGeoConstIndex() { return super.getConstructionIndex(); } /** * Returns construction index in current construction. For a dependent * object the construction index of its parent algorithm is returned. */ @Override final public int getConstructionIndex() { if (algoParent == null) { return super.getConstructionIndex(); } return algoParent.getConstructionIndex(); } /** * Returns the smallest possible construction index for this object in its * construction. For an independent object 0 is returned. */ @Override final public int getMinConstructionIndex() { if (algoParent == null) { return 0; } return algoParent.getMinConstructionIndex(); } /** * Returns the largest possible construction index for this object in its * construction. */ @Override final public int getMaxConstructionIndex() { if (algoParent == null) { // independent object: // index must be less than every dependent algorithm's index int min = cons.steps(); final int size = algorithmList == null ? 0 : algorithmList.size(); for (int i = 0; i < size; ++i) { final int index = (algorithmList.get(i)).getConstructionIndex(); if (index < min) { min = index; } } return min - 1; } // dependent object return algoParent.getMaxConstructionIndex(); } @Override public String getDefinitionDescription(StringTemplate tpl) { if (algoParent == null) { if (getDefinition() != null) { return getDefinition().toString(tpl); } return ""; } return algoParent.toString(tpl); } /** * @param addHTMLtag * true to wrap in <html> * @return definition description as HTML */ final public String getDescriptionHTML(final boolean addHTMLtag) { if (algoParent == null) { return ""; } return indicesToHTML( getDefinitionDescription(StringTemplate.defaultTemplate), addHTMLtag); } @Override final public String getDefinition(StringTemplate tpl) { if (algoParent != null) { return algoParent.getDefinition(tpl); } if (definition != null) { return definition.toString(tpl); } return ""; } /** * @param addHTMLtag * tue to wrap in <HTML> * @return HTML command description */ final public String getDefinitionHTML(final boolean addHTMLtag) { if (algoParent == null) { return ""; } return indicesToHTML( algoParent.getDefinition(StringTemplate.defaultTemplate), addHTMLtag); } @Override public int getRelatedModeID() { if (algoParent == null) { return -1; } return algoParent.getRelatedModeID(); } /** * Converts indices to HTML <sub> tags if necessary. * * @param text * GGB string * @param builder * indexed HTML builder */ public static void convertIndicesToHTML(final String text, IndexHTMLBuilder builder) { // check for index if (text.indexOf('_') > -1) { indicesToHTML(text, builder); return; } builder.clear(); builder.append(text); } /** * @param desc * description * @param builder * builder for indexed strings */ final public void addLabelTextOrHTML(final String desc, IndexHTMLBuilder builder) { String ret; final boolean includesEqual = desc.indexOf('=') >= 0; // check for function in desc like "f(x) = x^2" if (includesEqual && desc.startsWith(label + '(')) { ret = desc; } else { final StringBuilder sb = new StringBuilder(); sb.append(label); if (includesEqual) { sb.append(": "); } else { sb.append(" = "); } sb.append(desc); ret = sb.toString(); } // check for index convertIndicesToHTML(ret, builder); } /** * @param addHTMLtag * true to wrap in <html> * @param tpl * string template * @return HTML representation of caption */ final public String getCaptionDescriptionHTML(final boolean addHTMLtag, StringTemplate tpl) { return indicesToHTML(getCaptionDescription(tpl), addHTMLtag); } /** * @return type string for XML */ @Override final public String getXMLtypeString() { // don't use getTypeString() as it's overridden return StringUtil.toLowerCase(getGeoClassType().xmlName); } /** * Returns type string of GeoElement. * * @return type string without "Geo" prefix in most cases, overridden in eg * GeoPoint, GeoPolygon */ public String getTypeString() { return getGeoClassType().name; } /** * overridden in GeoConicND * * @return object type */ public String getTypeStringForAlgebraView() { return getTypeString(); } /** * @return localized type string */ public String translatedTypeString() { return getLoc().getPlain(getTypeString()); } /** * @return localized type string for Algebra View */ public String translatedTypeStringForAlgebraView() { // Log.debug(getTypeStringForAlgebraView()); // Log.debug(app.getPlain(getTypeStringForAlgebraView())); return getLoc().getPlain(getTypeStringForAlgebraView()); } /** * @return Type, label and definition information about this GeoElement (for * tooltips and error messages) */ @Override final public String getLongDescription() { if (algoParent == null) { return getNameDescription(); } final StringBuilder sbLongDesc = new StringBuilder(); sbLongDesc.append(getNameDescription()); // add dependency information sbLongDesc.append(": "); sbLongDesc.append(algoParent.toString(StringTemplate.defaultTemplate)); return sbLongDesc.toString(); } /** * returns Type, label and definition information about this GeoElement as * html string. (for tooltips and error messages) * * @param colored * true to allow colors * @param addHTMLtag * true to wrap in <html> * @return description (type + label + definition) */ final public String getLongDescriptionHTML(final boolean colored, final boolean addHTMLtag) { if ((algoParent == null) || this instanceof TextValue || this instanceof GeoPenStroke) { return getNameDescriptionHTML(colored, addHTMLtag); } final StringBuilder sbLongDescHTML = new StringBuilder(); final String formatedLabel = getLabel(StringTemplate.defaultTemplate); final String typeString = translatedTypeString(); // html string if (addHTMLtag) { sbLongDescHTML.append("<html>"); } final boolean reverseOrder = getLoc() .isReverseNameDescriptionLanguage(); if (!reverseOrder) { // standard order: "point A" sbLongDescHTML.append(typeString); sbLongDescHTML.append(' '); } if (colored) { final GColor colorAdapter = GColor.newColor( getAlgebraColor().getRed(), getAlgebraColor().getGreen(), getAlgebraColor().getBlue()); sbLongDescHTML.append("<b><font color=\"#"); sbLongDescHTML.append(StringUtil.toHexString(colorAdapter)); sbLongDescHTML.append("\">"); } sbLongDescHTML.append(indicesToHTML(formatedLabel, false)); if (colored) { sbLongDescHTML.append("</font></b>"); } if (reverseOrder) { // reverse order: "A point" sbLongDescHTML.append(' '); // For Hungarian, the standard is to lowercase the type. // I don't know if this is OK for Basque as well. -- Zoltan sbLongDescHTML.append(typeString.toLowerCase()); } // add dependency information if (algoParent != null) { // Guy Hed, 25.8.2008 // In order to present the text correctly in Hebrew and Arabic: final boolean rightToLeft = getLoc().isRightToLeftReadingOrder(); if (rightToLeft) { // sbLongDescHTML.append("\u200e\u200f: \u200e"); sbLongDescHTML.append(Unicode.LeftToRightMark); sbLongDescHTML.append(Unicode.RightToLeftMark); sbLongDescHTML.append(": "); sbLongDescHTML.append(Unicode.LeftToRightMark); } else { sbLongDescHTML.append(": "); } sbLongDescHTML.append(indicesToHTML( algoParent.toString(StringTemplate.defaultTemplate), false)); if (rightToLeft) { // sbLongDescHTML.append("\u200e"); sbLongDescHTML.append(Unicode.LeftToRightMark); } } if (addHTMLtag) { sbLongDescHTML.append("</html>"); } return sbLongDescHTML.toString(); } /** * Colored label of the GeoElement. * * @return the colored label */ final public String getColoredLabel() { String formatedLabel = getLabel(StringTemplate.defaultTemplate); StringBuilder sb = new StringBuilder(); final GColor colorAdapter = GColor.newColor(getAlgebraColor().getRed(), getAlgebraColor().getGreen(), getAlgebraColor().getBlue()); sb.append("<b><font color=\"#"); sb.append(StringUtil.toHexString(colorAdapter)); sb.append("\">"); sb.append(indicesToHTML(formatedLabel, false)); sb.append("</font></b>"); return sb.toString(); } /** * Returns long description for all GeoElements in given array, each geo on * one line. * * @param geos * list of geos * @param colored * true to use colors * @param addHTMLtag * true to wrap in <html> ... </html> * @param alwaysOn * true to override default * @return long description for all GeoElements in given array. */ final public static String getToolTipDescriptionHTML( final ArrayList<GeoElement> geos, final boolean colored, final boolean addHTMLtag, final boolean alwaysOn) { if (geos == null) { return null; } StringBuilder sbToolTipDesc = new StringBuilder(); if (addHTMLtag) { sbToolTipDesc.append("<html>"); } int count = 0; for (int i = 0; i < geos.size(); ++i) { final GeoElement geo = geos.get(i); if (geo.showToolTipText() || alwaysOn) { count++; sbToolTipDesc.append(geo.getTooltipText(colored, alwaysOn)); if ((i + 1) < geos.size()) { sbToolTipDesc.append("<br>"); } } } if (count == 0) { return null; } if (addHTMLtag) { sbToolTipDesc.append("</html>"); } return sbToolTipDesc.toString(); } /** * Returns the label and/or value of this object for showing in * EuclidianView. This depends on the current setting of labelMode: * LABEL_NAME : only label LABEL_NAME_VALUE : label and value * * @return label, value, label+value or caption */ @Override public String getLabelDescription() { switch (labelMode) { case LABEL_NAME_VALUE: return getAlgebraDescriptionDefault(); case LABEL_VALUE: return toDefinedValueString(StringTemplate.defaultTemplate); case LABEL_CAPTION: // Michael Borcherds 2008-02-18 return getCaption(StringTemplate.defaultTemplate); default: // case LABEL_NAME: // return label; // Mathieu Blossier - 2009-06-30 return getLabel(StringTemplate.defaultTemplate); } } /** * Returns toValueString() if isDefined() ist true, else the translation of * "undefined" is returned * * @param tpl * string template * * @return either value string or ? */ final public String toDefinedValueString(StringTemplate tpl) { if (isDefined()) { return toValueString(tpl); } return "?"; } /** * Returns algebraic representation of this GeoElement as Text. If this is * not possible (because there are indices in the representation) a HTML * string is returned. Default template is used, caching is employed. * * @param builder * indexed HTML builder * * @return algebraic representation of this GeoElement as Text */ final public String getAlgebraDescriptionTextOrHTMLDefault( IndexHTMLBuilder builder) { if (strAlgebraDescTextOrHTMLneedsUpdate) { final String algDesc = getAlgebraDescriptionDefault(); // convertion to html is only needed if indices are found if (hasIndexLabel()) { indicesToHTML(algDesc, builder); strAlgebraDescTextOrHTML = builder.toString(); } else { builder.clear(); builder.append(algDesc); strAlgebraDescTextOrHTML = algDesc; } strAlgebraDescTextOrHTMLneedsUpdate = false; } else { // TODO in some cases we don't need this if (!builder.canAppendRawHtml()) { indicesToHTML(strAlgebraDescription, builder); } else { builder.clear(); builder.append(strAlgebraDescTextOrHTML); } } return strAlgebraDescTextOrHTML; } /** * @return algebra description */ final public String getAlgebraDescriptionHTMLDefault() { if (strAlgebraDescriptionHTMLneedsUpdate) { if (isGeoText()) { strAlgebraDescriptionHTML = indicesToHTML( toValueString(StringTemplate.defaultTemplate), false); } else { strAlgebraDescriptionHTML = indicesToHTML( getAlgebraDescriptionDefault(), false); } strAlgebraDescriptionHTMLneedsUpdate = false; } return strAlgebraDescriptionHTML; } /** * @return type and label of a GeoElement (for tooltips and error messages) */ final public String getLabelTextOrHTML() { return getLabelTextOrHTML(true); } /** * @param addHTMLTag * says if html tags have to be added * @return type and label of a GeoElement (for tooltips and error messages) */ final public String getLabelTextOrHTML(boolean addHTMLTag) { if (strLabelTextOrHTMLUpdate) { if (hasIndexLabel()) { strLabelTextOrHTML = indicesToHTML( getLabel(StringTemplate.defaultTemplate), addHTMLTag); } else { strLabelTextOrHTML = getLabel(StringTemplate.defaultTemplate); } } return strLabelTextOrHTML; } /** * Returns algebraic representation (e.g. coordinates, equation) of this * construction element. * * @param tpl * string template * @return algebraic representation (e.g. coordinates, equation) */ final public String getAlgebraDescription(StringTemplate tpl) { if (isDefined()) { return toString(tpl); } final StringBuilder sbAlgebraDesc = new StringBuilder(); sbAlgebraDesc.append(label); sbAlgebraDesc.append(' '); sbAlgebraDesc.append(getLoc().getPlain("Undefined")); return sbAlgebraDesc.toString(); } /** * Returns algebraic representation (e.g. coordinates, equation) of this * construction element. Default string template is used => caching can be * employed * * @return algebraic representation (e.g. coordinates, equation) */ public String getAlgebraDescriptionDefault() { if (strAlgebraDescriptionNeedsUpdate) { if (isDefined()) { strAlgebraDescription = toString( StringTemplate.defaultTemplate); kernel.getAlgebraProcessor().setDisableGcd(false); } else { final StringBuilder sbAlgebraDesc = new StringBuilder(); sbAlgebraDesc.append(label); sbAlgebraDesc.append(' '); sbAlgebraDesc.append(getLoc().getPlain("Undefined")); strAlgebraDescription = sbAlgebraDesc.toString(); } strAlgebraDescriptionNeedsUpdate = false; } return strAlgebraDescription; } /** * Returns simplified algebraic representation of this GeoElement. Used by * the regression test output creator. * * @param tpl * string template * * @return sumplifiedrepresentation for regression test */ final public String getAlgebraDescriptionRegrOut(StringTemplate tpl) { if (strAlgebraDescriptionNeedsUpdate) { if (isDefined()) { strAlgebraDescription = toStringMinimal(tpl); } else { final StringBuilder sbAlgebraDesc = new StringBuilder(); sbAlgebraDesc.append("?"); strAlgebraDescription = sbAlgebraDesc.toString(); } strAlgebraDescriptionNeedsUpdate = false; } else { strAlgebraDescription = toStringMinimal(tpl); } return strAlgebraDescription; } /** * ToString(tpl) by default, but may be overriden * * @param tpl * string template * @return string for regression output */ public String toStringMinimal(StringTemplate tpl) { return toString(tpl); } /** * @return LaTeX description */ @Override public String getLaTeXdescription() { if (strLaTeXneedsUpdate) { if (isDefined() && !isInfinite()) { strLaTeX = toLaTeXString(false, StringTemplate.latexTemplate); } else { strLaTeX = "?"; } } return strLaTeX; } /** * In RadioButtonTreeItem, we don't want to return null from * getLaTeXAlgebraDescription in case of GeoText, but return its simple * algebra description, which shall be (label="content") in theory * * @param substituteNumbers * whether to substitute variables * @param tpl * template * @param fallback * fallback text * @return LaTeX text */ public String getLaTeXAlgebraDescriptionWithFallback( final boolean substituteNumbers, StringTemplate tpl, boolean fallback) { String ret = null; if (!substituteNumbers) { ret = getDefinition(tpl); } if (ret != null && ret.length() > 0) { final char delimiter = getLabelDelimiter(); if (ret.indexOf(delimiter) < 0) { ret = getAssignmentLHS(StringTemplate.editTemplate) + (delimiter == '=' ? " =" : delimiter) + " " + ret; } return ret; } if (!isDefined() || !isGeoText()) { ret = getLaTeXAlgebraDescription(substituteNumbers, tpl); } if ((ret == null || "".equals(ret)) && isGeoText() && fallback) { ret = getAlgebraDescription(tpl); } return ret; } /** * Returns a string used to render a LaTeX form of the geo's algebra * description. * * @param substituteNumbers * true to replace variable names by values * @param tpl * string template * @return string used to render a LaTeX form of the geo's algebra * description. */ public String getLaTeXAlgebraDescription(final boolean substituteNumbers, StringTemplate tpl) { return getLaTeXAlgebraDescription(this, substituteNumbers, tpl); } private String getLaTeXAlgebraDescription(final GeoElement geo, final boolean substituteNumbers, StringTemplate tpl) { final String algebraDesc = geo.getAlgebraDescription(tpl); final StringBuilder sb = new StringBuilder(); if (geo.isGeoList() && ((GeoList) geo).getElementType().equals(GeoClass.TEXT)) { return null; } // handle undefined if (!geo.isDefined()) { // we need to keep the string simple (no \mbox) so that // isLatexNeeded may return true sb.append(label); sb.append("\\, \\text{"); sb.append(getLoc().getPlain("Undefined")); sb.append("} "); // handle non-GeoText prefixed with ":", e.g. "a: x = 3" } else if ((algebraDesc.indexOf(":") > -1) && !geo.isGeoText()) { sb.append(algebraDesc.split(":")[0] + ": \\,"); sb.append(geo.getFormulaString(tpl, substituteNumbers)); } // now handle non-GeoText prefixed with "=" else if ((algebraDesc.indexOf("=") > -1) && !geo.isGeoText()) { sb.append(algebraDesc.split("=")[0] + "\\, = \\,"); sb.append(geo.getFormulaString(tpl, substituteNumbers)); } else if (geo.isGeoVector()) { sb.append(label); sb.append("\\, = \\,"); sb.append(geo.getFormulaString(tpl, substituteNumbers)); } // handle GeoText with LaTeX else if (geo.isGeoText() && ((GeoText) geo).isLaTeX()) { sb.append(algebraDesc.split("=")[0]); sb.append("\\, = \\,"); if (geo.getParentAlgorithm() instanceof TableAlgo) { sb.append(((GeoText) geo).getTextString()); } else { String str = ((GeoText) geo).getTextString(); boolean containsLaTeX = StringUtil.containsLaTeX(str); if (!containsLaTeX) { sb.append("\\text{"); } sb.append(Unicode.OPEN_DOUBLE_QUOTE); sb.append(((GeoText) geo).getTextString()); sb.append(Unicode.CLOSE_DOUBLE_QUOTE); if (!containsLaTeX) { sb.append("}"); } } } // handle regular GeoText (and anything else we may have missed) // by returning a null string that will force non-LaTeX rendering else { return null; } return sb.toString(); } /* * final public Image getAlgebraImage(Image tempImage) { Graphics2D g2 = * (Graphics2D) g; GraphicsConfiguration gc = * app.getGraphicsConfiguration(); if (gc != null) { bgImage = * gc.createCompatibleImage(width, height); Point p = drawIndexedString(g2, * labelDesc, xLabel, yLabel); * * setSize(fontSize, p.x, fontSize + p.y); } */ /* * replaces all indices (_ and _{}) in str by <sub> tags, all and converts * all special characters in str to HTML examples: "a_1" becomes * "a<sub>1</sub>" "s_{AB}" becomes "s<sub>AB</sub>" */ /** * @param str * raw string * @param addHTMLtag * true to wrap in <html> * @return str with indices in HTML notation (<sub>) */ public static String indicesToHTML(final String str, final boolean addHTMLtag) { final IndexHTMLBuilder sbIndicesToHTML = new IndexHTMLBuilder( addHTMLtag); indicesToHTML(str, sbIndicesToHTML); return sbIndicesToHTML.toString(); } /** * @param str * string with indices * @param sbIndicesToHTML * indexed string build */ public static void indicesToHTML(final String str, IndexHTMLBuilder sbIndicesToHTML) { sbIndicesToHTML.clear(); int depth = 0; int startPos = 0; final int length = str.length(); for (int i = 0; i < length; i++) { switch (str.charAt(i)) { case '_': // write everything before _ if (i > startPos) { sbIndicesToHTML.appendHTML(str.substring(startPos, i)); } startPos = i + 1; depth++; // check if next character is a '{' (beginning of index with // several chars) if ((startPos < length) && (str.charAt(startPos) != '{')) { sbIndicesToHTML.startIndex(); sbIndicesToHTML .appendHTML(str.substring(startPos, startPos + 1)); sbIndicesToHTML.endIndex(); depth--; } else { sbIndicesToHTML.startIndex(); } i++; startPos++; break; case '}': if (depth > 0) { if (i > startPos) { sbIndicesToHTML.appendHTML(str.substring(startPos, i)); } sbIndicesToHTML.endIndex(); startPos = i + 1; depth--; } break; default: // break; } } if (startPos < length) { sbIndicesToHTML.appendHTML(str.substring(startPos)); } } /** * returns type and label of a GeoElement (for tooltips and error messages) */ @Override public String getNameDescription() { final StringBuilder sbNameDescription = new StringBuilder(); final String label1 = getLabel(StringTemplate.defaultTemplate); final String typeString = translatedTypeString(); if (getLoc().isReverseNameDescriptionLanguage()) { // reverse order: "A point" sbNameDescription.append(label1); sbNameDescription.append(' '); // For Hungarian, the standard is to lowercase the type. // I don't know if this is OK for Basque as well. -- Zoltan sbNameDescription.append(typeString.toLowerCase()); } else { // standard order: "point A" sbNameDescription.append(typeString); sbNameDescription.append(' '); sbNameDescription.append(label1); } return sbNameDescription.toString(); } /** * returns type and label of a GeoElement (for tooltips and error messages) * * @return type and label of a GeoElement */ final public String getNameDescriptionTextOrHTML() { if (hasIndexLabel()) { return getNameDescriptionHTML(false, true); } return getNameDescription(); } /** * Returns whether the label contains any indices (i.e. '_' chars). * * @return whether the label contains any indices (i.e. '_' chars). */ final public boolean hasIndexLabel() { return ((label == null) || (label.indexOf('_') > -1)); } /** * returns type and label of a GeoElement as html string (for tooltips and * error messages) * * @param colored * true to allow colors * @param addHTMLtag * true to wrap in <html> * @return type and label of a GeoElement as html string */ public String getNameDescriptionHTML(final boolean colored, final boolean addHTMLtag) { final StringBuilder sbNameDescriptionHTML = new StringBuilder(); if (addHTMLtag) { sbNameDescriptionHTML.append("<html>"); } final String label1 = getLabel(StringTemplate.defaultTemplate); final String typeString = translatedTypeString(); final boolean reverseOrder = getLoc() .isReverseNameDescriptionLanguage(); if (!reverseOrder // want "xAxis" not "Line xAxis" && !isAxis()) { // standard order: "point A" sbNameDescriptionHTML.append(typeString); sbNameDescriptionHTML.append(' '); } if (colored) { sbNameDescriptionHTML.append(" <b><font color=\"#"); sbNameDescriptionHTML .append(StringUtil.toHexString(getAlgebraColor())); sbNameDescriptionHTML.append("\">"); } sbNameDescriptionHTML.append(indicesToHTML(label1, false)); if (colored) { sbNameDescriptionHTML.append("</font></b>"); } if (reverseOrder // want "xAxis" not "Line xAxis" && !isAxis()) { // reverse order: "A point" sbNameDescriptionHTML.append(' '); // For Hungarian, the standard is to lowercase the type. // I don't know if this is OK for Basque as well. -- Zoltan sbNameDescriptionHTML.append(typeString.toLowerCase()); } if (addHTMLtag) { sbNameDescriptionHTML.append("</html>"); } return sbNameDescriptionHTML.toString(); } /** * @return whether this is GeoAxisND */ public boolean isAxis() { return false; } /** * @return XML of this geo as string */ public String getXML() { final StringBuilder sb = new StringBuilder(); getXML(false, sb); return sb.toString(); } /** * save object in xml format GeoGebra File Format */ @Override public void getXML(boolean getListenersToo, final StringBuilder sb) { // make sure numbers are not put in XML in eg Arabic // final boolean oldI8NValue = Kernel.internationalizeDigits; // Kernel.internationalizeDigits = false; if (isIndependent() && definition != null && getDefaultGeoType() < 0) { sb.append("<expression"); sb.append(" label =\""); sb.append(label); sb.append("\" exp=\""); StringUtil.encodeXML(sb, definition.toString(StringTemplate.xmlTemplate)); // expression sb.append("\""); // add type (e.g. for plane/line) if (isGeoPoint()) { sb.append(" type=\"point\""); } else if (isGeoVector()) { sb.append(" type=\"vector\""); } else if (isGeoLine()) { sb.append(" type=\"line\""); } else if (isGeoPlane()) { sb.append(" type=\"plane\""); } else if (isGeoConic()) { sb.append(" type=\"conic\""); } else if (isGeoQuadric()) { sb.append(" type=\"quadric\""); } else if (isGeoImplicitPoly()) { sb.append(" type=\"implicitpoly\""); } else if (isGeoImplicitSurface()) { sb.append(" type=\"implicitsurface\""); } sb.append("/>\n"); } getElementOpenTagXML(sb); getXMLtags(sb); getCaptionXML(sb); getExtraTagsXML(sb); if (getListenersToo) { getListenerTagsXML(sb); } getElementCloseTagXML(sb); } /** * Append object listener names to XML string builder * * @param sb * string builder */ protected void getListenerTagsXML(StringBuilder sb) { ScriptManager scriptManager = kernel.getApplication() .getScriptManager(); // updateListenerMap getListenerTagXML(sb, scriptManager.getUpdateListenerMap(), "objectUpdate"); // clickListenerMap getListenerTagXML(sb, scriptManager.getUpdateListenerMap(), "objectClick"); } private void getListenerTagXML(StringBuilder sb, HashMap<GeoElement, JsScript> map, String type) { if (map != null) { JsScript objectListener = map.get(this); if (objectListener != null) { sb.append("\t<listener type=\"" + type + "\" val=\""); sb.append(objectListener.getText()); sb.append("\"/>\n"); } } } /** * Appends open element tag <element> or <cascell> to the builder * * @param sb * string builder */ protected void getElementOpenTagXML(final StringBuilder sb) { final String type = getXMLtypeString(); sb.append("<element"); sb.append(" type=\""); sb.append(type); sb.append("\" label=\""); StringUtil.encodeXML(sb, label); if (defaultGeoType >= 0) { sb.append("\" default=\""); sb.append(defaultGeoType); } sb.append("\">\n"); } /** * Closes the element tag -- either <element> or <cascell> * * @param sb * string builder */ protected void getElementCloseTagXML(final StringBuilder sb) { sb.append("</element>\n"); } /** * Appends tags for click and update script to the builder * * @param sb * string builder */ public void getScriptTags(final StringBuilder sb) { if (scripts == null) { return; } Script clickScript = scripts[EventType.CLICK.ordinal()]; Script updateScript = scripts[EventType.UPDATE.ordinal()]; if (clickScript != null) { sb.append("\t<"); sb.append(clickScript.getXMLName()); sb.append(" val=\""); StringUtil.encodeXML(sb, clickScript.getInternalText()); sb.append("\"/>\n"); } if (updateScript != null) { sb.append("\t<"); sb.append(updateScript.getXMLName()); sb.append(" onUpdate=\""); StringUtil.encodeXML(sb, updateScript.getInternalText()); sb.append("\"/>\n"); } } /** * Appends caption XML tag to given builder * * @param sb * string builder */ final public void getCaptionXML(StringBuilder sb) { // caption text if ((caption != null) && (caption.length() > 0) && !caption.equals(label)) { sb.append("\t<caption val=\""); StringUtil.encodeXML(sb, caption); sb.append("\"/>\n"); } } /** * Append auxiliary XML tag to given builder * * @param sb * string builder */ protected final void getAuxiliaryXML(final StringBuilder sb) { if (!isAuxiliaryObjectByDefault()) { if (auxiliaryObject) { sb.append("\t<auxiliary val=\""); sb.append("true"); sb.append("\"/>\n"); } else if (getMetasLength() > 0) { // force save "not auxiliary" for // e.g. segments created by // polygon algo sb.append("\t<auxiliary val=\""); sb.append("false"); sb.append("\"/>\n"); } } else { // needed for eg GeoTexts (in Algebra View but Auxilliary by // default from ggb 4.0) if (!auxiliaryObject) { sb.append("\t<auxiliary val=\""); sb.append("false"); sb.append("\"/>\n"); } } } /** * returns all visual xml tags (like show, objColor, labelOffset, ...) * * @param sb * string builder */ protected void getXMLvisualTags(final StringBuilder sb) { getXMLvisualTags(sb, true); } /** * Appends visual tags to string builder * * @param sb * string builder * @param withLabelOffset * true to include label offsets */ protected void getXMLvisualTags(final StringBuilder sb, final boolean withLabelOffset) { final boolean isDrawable = isDrawable(); // show object and/or label in EuclidianView // don't save this for simple dependent numbers (e.g. in spreadsheet) if (isDrawable) { sb.append("\t<show"); sb.append(" object=\""); sb.append(euclidianVisible); sb.append("\""); sb.append(" label=\""); sb.append(labelVisible); sb.append("\""); // default: // showing in EV1 // hidden in EV2 int EVs = 0; if (!isVisibleInView(App.VIEW_EUCLIDIAN)) { // bit 0 is opposite to bit 1 // 0 = showing // 1 = hidden EVs += 1; // bit 0 } if (isVisibleInView(App.VIEW_EUCLIDIAN2)) { // 0 = hidden // 2 = showing EVs += 2; // bit 1 } if (hasDrawable3D()) { switch (visibleInView3D) { case TRUE: EVs += 4; break; case FALSE: EVs += 8; // we have to store it to distinguish from not set break; case NOT_SET: break; } switch (visibleInViewForPlane) { case TRUE: EVs += 16; break; case FALSE: EVs += 32; // we have to store it to distinguish from not // set break; case NOT_SET: break; } } if (EVs != 0) { sb.append(" ev=\""); sb.append(EVs); sb.append("\""); } sb.append("/>\n"); } if (getShowTrimmedIntersectionLines()) { sb.append("\t<showTrimmed val=\"true\"/>\n"); } // conditional visibility sb.append(getShowObjectConditionXML()); // if (isDrawable) removed - want to be able to color objects in // AlgebraView, Spreadsheet { appendObjectColorXML(sb); } if (bgColor != null) { sb.append("\t<bgColor"); sb.append(" r=\""); sb.append(bgColor.getRed()); sb.append("\""); sb.append(" g=\""); sb.append(bgColor.getGreen()); sb.append("\""); sb.append(" b=\""); sb.append(bgColor.getBlue()); sb.append("\""); sb.append(" alpha=\""); sb.append(bgColor.getAlpha()); sb.append("\"/>\n"); } // don't remove layer 0 information // we always need it in case an earlier element has higher layer eg 1 if (isDrawable) { sb.append("\t<layer "); sb.append("val=\"" + layer + "\""); sb.append("/>\n"); } if (isDefaultGeo()) { sb.append("\t<autocolor "); sb.append("val=\"" + isAutoColor() + "\""); sb.append("/>\n"); } if (withLabelOffset && ((labelOffsetX != 0) || (labelOffsetY != 0))) { sb.append("\t<labelOffset"); sb.append(" x=\""); sb.append(labelOffsetX); sb.append("\""); sb.append(" y=\""); sb.append(labelOffsetY); sb.append("\""); sb.append("/>\n"); } if (isDrawable()) { sb.append("\t<labelMode"); sb.append(" val=\""); sb.append(labelMode); sb.append("\""); sb.append("/>\n"); if (tooltipMode != TOOLTIP_ALGEBRAVIEW_SHOWING) { sb.append("\t<tooltipMode"); sb.append(" val=\""); sb.append(tooltipMode); sb.append("\""); sb.append("/>\n"); } } // trace on or off if (isTraceable()) { final Traceable t = (Traceable) this; if (t.getTrace()) { sb.append("\t<trace val=\"true\"/>\n"); } } // G.Sturr 2010-5-29 // Get spreadsheet trace XML from the trace manager // trace to spreadsheet if (kernel.getApplication().isUsingFullGui() && isSpreadsheetTraceable() && getSpreadsheetTrace()) { sb.append(kernel.getApplication().getTraceXML(this));// sb.append(null)? } /* * --- old version // trace to spreadsheet on or off if (isGeoPoint()) { * GeoPoint2 p = (GeoPoint2) this; if (p.getSpreadsheetTrace()) { * sb.append("\t<spreadsheetTrace val=\"true\"/>\n"); } } */ // END G.Sturr // decoration type if (decorationType != DECORATION_NONE) { sb.append("\t<decoration"); sb.append(" type=\""); sb.append(decorationType); sb.append("\"/>\n"); } } /** * @param sb * string builder */ protected void appendObjectColorXML(StringBuilder sb) { sb.append("\t<objColor"); sb.append(" r=\""); sb.append(objColor.getRed()); sb.append("\""); sb.append(" g=\""); sb.append(objColor.getGreen()); sb.append("\""); sb.append(" b=\""); sb.append(objColor.getBlue()); sb.append("\""); sb.append(" alpha=\""); // changed from alphavalue (don't want alpha="-1.0" in XML) // see GeoList sb.append(getAlphaValue()); sb.append("\""); StringTemplate tpl = StringTemplate.xmlTemplate; if ((colFunction != null) && kernel.getSaveScriptsToXML()) { sb.append(" dynamicr=\""); StringUtil.encodeXML(sb, colFunction.get(0).getLabel(tpl)); sb.append('\"'); sb.append(" dynamicg=\""); StringUtil.encodeXML(sb, colFunction.get(1).getLabel(tpl)); sb.append('\"'); sb.append(" dynamicb=\""); StringUtil.encodeXML(sb, colFunction.get(2).getLabel(tpl)); sb.append('\"'); if (colFunction.size() == 4) { sb.append(" dynamica=\""); StringUtil.encodeXML(sb, colFunction.get(3).getLabel(tpl)); sb.append('\"'); } sb.append(" colorSpace=\""); sb.append(colorSpace); sb.append('\"'); } if (isHatchingEnabled()) { sb.append(" fillType=\""); sb.append(fillType.ordinal()); sb.append("\" hatchAngle=\""); sb.append(hatchingAngle); sb.append("\" hatchDistance=\""); sb.append(hatchingDistance); sb.append("\""); } else if (fillType == FillType.IMAGE) { sb.append(" image=\""); sb.append(graphicsadapter.getImageFileName()); sb.append('\"'); } if (fillType == FillType.SYMBOLS) { sb.append(" fillSymbol=\""); sb.append(fillSymbol); sb.append('\"'); } if (inverseFill) { sb.append(" inverseFill=\"true\""); } sb.append("/>\n"); } /** * @param sb * string builder */ protected void getXMLanimationTags(final StringBuilder sb) { StringTemplate tpl = StringTemplate.xmlTemplate; // animation step width if (isPointerChangeable()) { sb.append("\t<animation"); final String animStep = animationIncrement == null ? "1" : getAnimationStepObject().getLabel(tpl); sb.append(" step=\""); StringUtil.encodeXML(sb, animStep); sb.append("\""); final String animSpeed = animationSpeedObj == null ? "1" : getAnimationSpeedObject().getLabel(tpl); sb.append(" speed=\""); StringUtil.encodeXML(sb, animSpeed); sb.append("\""); sb.append(" type=\"" + animationType + "\""); sb.append(" playing=\""); sb.append((isAnimating() ? "true" : "false")); sb.append("\""); sb.append("/>\n"); } } /** * Appends fixed tag to given builder * * @param sb * string builder */ public void getXMLfixedTag(final StringBuilder sb) {// package private // is object fixed if (fixed && isFixable()) { sb.append("\t<fixed val=\""); sb.append(fixed); sb.append("\"/>\n"); } // is selection allowed if (!selectionAllowed) { sb.append("\t<selectionAllowed val=\""); sb.append(selectionAllowed); sb.append("\"/>\n"); } } /** * Appends isShape tag to given builder * * @param sb * string builder */ public void getXMLisShapeTag(final StringBuilder sb) {// package private // was object created with shape tool sb.append("\t<isShape val=\""); sb.append(isShape()); sb.append("\"/>\n"); } /** * returns all class-specific xml tags for getXML GeoGebra File Format * * @param sb * string builder */ protected void getXMLtags(final StringBuilder sb) { // sb.append(getLineStyleXML()); getXMLvisualTags(sb); getXMLanimationTags(sb); getXMLfixedTag(sb); getXMLisShapeTag(sb); getAuxiliaryXML(sb); getBreakpointXML(sb); if (kernel.getSaveScriptsToXML()) { getScriptTags(sb); } } private void getExtraTagsXML(StringBuilder sb) { if (this.getParentAlgorithm() instanceof AlgoBarChart) { ((AlgoBarChart) this.getParentAlgorithm()).barXml(sb); } } /** * returns some class-specific xml tags for getConstructionRegrOut (default * implementation, may be overridden in certain subclasses) * * @param sb * string builder * @param tpl * string template */ public void getXMLtagsMinimal(final StringBuilder sb, StringTemplate tpl) { sb.append(toValueStringMinimal(tpl)); } /** * returns class-specific value string for getConstructionRegressionOut * (default implementation, may be overridden in certain subclasses) * * @param tpl * string template * @return value string */ protected String toValueStringMinimal(StringTemplate tpl) { return toValueString(tpl); } /** * returns the number in rounded format to 6 decimal places, in case of the * number is very close to 0, it returns the exact value * * @param number * number to be formated * @return formatted String */ protected String regrFormat(final double number) { if (Math.abs(number) < 0.000001) { final Double numberD = new Double(number); return numberD.toString(); } // this constructors uses US locale, so we don't have to worry about "," final NumberFormatAdapter df = FormatFactory.getPrototype() .getNumberFormat("#.######", 6); return df.format(number); } /** * Appends line type and line thickness as xml string to given builder. * * @param sb * string builder * @see #getXMLtags(StringBuilder) of GeoConic, GeoLine and GeoVector */ protected void getLineStyleXML(final StringBuilder sb) { if (isGeoPoint()) { return; } sb.append("\t<lineStyle"); sb.append(" thickness=\""); sb.append(lineThickness); sb.append("\""); sb.append(" type=\""); sb.append(lineType); sb.append("\""); sb.append(" typeHidden=\""); sb.append(lineTypeHidden); sb.append("\""); if (hasLineOpacity() && getLineOpacity() < 255) { sb.append(" opacity=\""); sb.append(lineOpacity); sb.append("\""); } sb.append("/>\n"); } /** * Returns line type and line thickness as xml string. * * @param sb * string builder * @see #getXMLtags(StringBuilder) of GeoConic, GeoLine and GeoVector */ public void getBreakpointXML(final StringBuilder sb) {// package private if (isConsProtBreakpoint) { sb.append("\t<breakpoint val=\""); sb.append(isConsProtBreakpoint); sb.append("\"/>\n"); } } private String getShowObjectConditionXML() { if (condShowObject != null && kernel.getSaveScriptsToXML()) { final StringBuilder sb = new StringBuilder(); sb.append("\t<condition showObject=\""); StringUtil.encodeXML(sb, condShowObject.getLabel(StringTemplate.xmlTemplate)); sb.append("\"/>\n"); return sb.toString(); } return ""; } @Override final public int getLineThickness() { return lineThickness; } /** * @return minimum line thickness (normally 1, but 0 for polygons, integrals * etc) */ public int getMinimumLineThickness() { return 1; } @Override final public int getLineType() { return lineType; } /** * @return the line type for hidden parts */ final public int getLineTypeHidden() { return lineTypeHidden; } /** * @param th * new thickness */ @Override public void setLineThickness(final int th) { lineThickness = Math.max(0, th); } /** * set line thickness and/or visibility (if th == 0) * * @param th * new thickness */ public void setLineThicknessOrVisibility(final int th) { if (isRegion()) { setLineThickness(th); } else { if (th > 0) { setEuclidianVisibleIfNoConditionToShowObject(true); setLineThickness(th); } else { setEuclidianVisibleIfNoConditionToShowObject(false); } } } /** * @param i * new type */ @Override public void setLineType(final int i) { lineType = i; } /** * @param i * line type for hidden lines */ public void setLineTypeHidden(final int i) { lineTypeHidden = i; } /** * @param type * new decoration type */ public void setDecorationType(final int type) { decorationType = type; } /** * @param type * decoration type * @param max * max for this object class */ public void setDecorationType(final int type, int max) { if (type >= max || type < 0) { decorationType = DECORATION_NONE; } else { decorationType = type; } } /** * @return true for 3D GeoElements */ @Override public boolean isGeoElement3D() { return false; } /** * @return true if can change fill type (e.g. hatching) */ public boolean hasFillType() { return isFillable(); } @Override public boolean isRegion3D() { return false; } /** * * @return true if the geo is drawable in 3D view */ public boolean hasDrawable3D() { return isGeoElement3D(); } /** * @return true for 3D geos with level of detail */ public boolean hasLevelOfDetail() { return false; } /** * @return true for angles */ @Override public boolean isGeoAngle() { return false; } /** * @return true for booleans */ public boolean isGeoBoolean() { return false; } /** * @return true for polylines */ @Override public boolean isGeoPolyLine() { return false; } /** * @return true for implicit polynomials */ public boolean isGeoImplicitPoly() { return false; } /** * @return true for implicit surfaces */ public boolean isGeoImplicitSurface() { return false; } /** * @return true for implicit curve */ public boolean isGeoImplicitCurve() { return false; } /** * @return true for conics */ @Override public boolean isGeoConic() { return false; } /** * @return true for conic arcs/sectors */ @Override public boolean isGeoConicPart() { return false; } /** * @return true for valid functions */ @Override public boolean isGeoFunction() { return false; } /** * @return true for valid multivariate functions */ public boolean isGeoFunctionNVar() { return false; } /** * @return true for boolean functions */ public boolean isGeoFunctionBoolean() { return false; } /** * @return true for conditional functions */ public boolean isGeoFunctionConditional() { return false; } /** * @return true for functionables */ @Override public boolean isGeoFunctionable() { return false; } /** * @return true for images */ @Override public boolean isGeoImage() { return false; } /** * @return true for turtles */ public boolean isGeoTurtle() { return false; } /** * @return true for lines */ @Override public boolean isGeoLine() { return false; } /** * @return true for planes */ public boolean isGeoPlane() { return false; } /** * @return true for quadrics */ public boolean isGeoQuadric() { return false; } /** * @return true for loci */ public boolean isGeoLocus() { return false; } /** * @return true for numbers */ @Override public boolean isGeoNumeric() { return false; } /** * @return true for (ND) points */ @Override public boolean isGeoPoint() { return false; } /** * @return true for CAS cells */ @Override public boolean isGeoCasCell() { return false; } /* * public boolean isGeoPoint3D() { return false; } */ /** * @return true for polygons */ @Override public boolean isGeoPolygon() { return false; } /** * @return true for polyhedrons */ @Override public boolean isGeoPolyhedron() { return false; } /** * @return true for rays */ @Override public boolean isGeoRay() { return false; } /** * @return true for segments */ @Override public boolean isGeoSegment() { return false; } /** * @return true for texts */ @Override public boolean isGeoText() { return false; } /** * @return true for vectors */ @Override public boolean isGeoVector() { return false; } /** * @return true for cartesian curves */ @Override public boolean isGeoCurveCartesian() { return false; } /** * @return true for cartesian surfaces */ public boolean isGeoSurfaceCartesian() { return false; } /** * @return true for functions evaluable in CAS */ public boolean isCasEvaluableObject() { return false; } @Override final public boolean isExpressionNode() { return false; } @Override final public boolean isVariable() { return false; } /** * @return is geo created with shape tool */ public boolean isShape() { return false; } /** * @param isShape * - true, if geo was created with shape tool */ public void setIsShape(boolean isShape) { // this.isShape = isShape; } @Override final public boolean contains(final ExpressionValue ev) { return ev == this; } /* * ** hightlighting and selecting only for internal purpouses, i.e. this is * not saved */ /** * @param flag * true to make this selected */ @Override public void setSelected(final boolean flag) { selected = flag; } /** * @param flag * true to make this highlighted */ final public void setHighlighted(final boolean flag) { highlighted = flag; } /** * @return true if highlighted or selected */ @Override final public boolean doHighlighting() { return (highlighted || selected) && (!isLocked() || isSelectionAllowed(null)); } /** * @return true if this object is selected */ final public boolean isSelected() { return selected; } @Override public boolean isNumberValue() { return false; } /** * @return true for angles */ public int getAngleDim() { return 0; } @Override public boolean evaluatesToNonComplex2DVector() { return false; } @Override public boolean evaluatesToVectorNotPoint() { return false; } @Override public boolean evaluatesToText() { return false; } @Override public boolean evaluatesToList() { return false; } /** * @return true for buttons */ @Override public boolean isGeoButton() { return false; } /** * @return true if this is using visual defaults */ public final boolean isUseVisualDefaults() { return useVisualDefaults; } /** * @param useVisualDefaults * true to use visual defaults */ public final void setUseVisualDefaults(final boolean useVisualDefaults) { this.useVisualDefaults = useVisualDefaults; } /** * @return true if this can have absoute screen location */ public boolean isAbsoluteScreenLocateable() { return false; } /** * @return condition to show this geo */ @Override final public GeoBoolean getShowObjectCondition() { return condShowObject; } /** * @param cond * new condition to show this geo * @throws CircularDefinitionException * if this == cond */ @Override public void setShowObjectCondition(final GeoBoolean cond) throws CircularDefinitionException { // check for circular definition // if (this == cond || isParentOf(cond)) // I relaxed this to allow (a parallel b) for a and b if (this == cond) { throw new CircularDefinitionException(); } // unregister old condition if (condShowObject != null) { condShowObject.unregisterConditionListener(this); } // set new condition condShowObject = cond; // register new condition if (condShowObject != null) { condShowObject.registerConditionListener(this); } } /** * Removes condition to show object, if it is equal to the given one * * @param bool * condition to show object */ final public void removeCondition(final GeoBoolean bool) { if (condShowObject == bool) { condShowObject = null; } } /** * @return dynamic color as list of numbers {R,G,B} / {H,S,L} / {H,S,B} */ @Override final public GeoList getColorFunction() { return colFunction; } /** * @param col * dynamic color as list of numbers {R,G,B} / {H,S,L} / {H,S,B} */ @Override public void setColorFunction(final GeoList col) // throws CircularDefinitionException { // Application.debug("setColorFunction"+col.getValue()); // check for circular definition (not needed) // if (this == col || isParentOf(col)) // throw new CircularDefinitionException(); // unregister old condition if (colFunction != null) { colFunction.unregisterColorFunctionListener(this); } // set new condition colFunction = col; // register new condition if (colFunction != null) { colFunction.registerColorFunctionListener(this); } } /** * Removes dynamic color from this geo */ public void removeColorFunction() { // unregister old condition if (colFunction != null) { colFunction.unregisterColorFunctionListener(this); } // Application.debug("removeColorFunction"); // if (colFunction == col) colFunction = null; } /** * Translates all GeoElement objects in geos by a vector in real world * coordinates or by (xPixel, yPixel) in screen coordinates. * * @param geosToMove * geos to be moved * @param rwTransVec * translation vector * @param endPosition * end position; may be null * @param viewDirection * direction of view * @param view * euclidian view * @return true if something was moved */ public static boolean moveObjects(ArrayList<GeoElement> geosToMove, final Coords rwTransVec, final Coords endPosition, final Coords viewDirection, EuclidianView view) { // AbstractApplication.printStacktrace("XXX"); if (moveObjectsUpdateList == null) { moveObjectsUpdateList = new ArrayList<GeoElement>(); } ArrayList<GeoElement> geos = geosToMove; final ArrayList<GeoElement> geos2 = new ArrayList<GeoElement>(); // remove duplicates, eg drag Circle[A,A] for (int i = 0; i < geos.size(); i++) { if (!geos2.contains(geos.get(i))) { geos2.add(geos.get(i)); } } geos = geos2; boolean moved = false; final int size = geos.size(); moveObjectsUpdateList.clear(); moveObjectsUpdateList.ensureCapacity(size); for (int i = 0; i < size; i++) { final GeoElement geo = geos.get(i); if (geo.isGeoList()) { moveObjectsUpdateList.add(geo); continue; } /* * Michael Borcherds check for isGeoPoint() as it makes the mouse * jump to the position of the point when dragging eg Image with one * corner, Rigid Polygon and stops grid-lock working properly but is * needed for eg dragging (a + x(A), b + x(B)) */ // AbstractApplication.debug((geo.getParentAlgorithm() == null) + " // " // + size + " " + geo.getClassName()+" // "+geo.getLabel(StringTemplate.defaultTemplate)); final Coords position = (size == 1) && (geo.getParentAlgorithm() != null) ? endPosition : null; moved = geo.moveObject(rwTransVec, position, viewDirection, moveObjectsUpdateList, view) || moved; } // take all independent input objects and build a common updateSet // then update all their algos. // (don't do updateCascade() on them individually as this could cause // multiple updates of the same algorithm) updateCascade(moveObjectsUpdateList, getTempSet(), false); return moved; } private static volatile ArrayList<GeoElement> moveObjectsUpdateList; private static volatile TreeSet<AlgoElement> tempSet; private static Comparator<AlgoElement> algoComparator = new Comparator<AlgoElement>() { @Override public int compare(AlgoElement o1, AlgoElement o2) { return o1.compareTo(o2); } }; /** * @return temporary set of algoritms */ protected static TreeSet<AlgoElement> getTempSet() { if (tempSet == null) { tempSet = new TreeSet<AlgoElement>(algoComparator); } return tempSet; } /** * @param rwTransVec * translation vector * @param endPosition * end position * @return true if successful */ protected boolean movePoint(final Coords rwTransVec, final Coords endPosition) { return false; } /** * @param rwTransVec * translation vector * @param endPosition * end position * @return true if successful */ protected boolean moveVector(final Coords rwTransVec, final Coords endPosition) { boolean movedGeo = false; final GeoVector vector = (GeoVector) this; if (endPosition != null) { vector.setCoords(endPosition.getX(), endPosition.getY(), 0); movedGeo = true; } // translate point else { double x = vector.getX() + rwTransVec.getX(); double y = vector.getY() + rwTransVec.getY(); // round to decimal fraction, e.g. 2.800000000001 to 2.8 if (Math.abs(rwTransVec.getX()) > Kernel.MIN_PRECISION) { x = Kernel.checkDecimalFraction(x); } if (Math.abs(rwTransVec.getY()) > Kernel.MIN_PRECISION) { y = Kernel.checkDecimalFraction(y); } // set translated point coords vector.setCoords(x, y, 0); movedGeo = true; } return movedGeo; } /** * Moves geo by a vector in real world coordinates. * * @return whether actual moving occurred */ private boolean moveObject(final Coords rwTransVec, final Coords endPosition, final Coords viewDirection, final ArrayList<GeoElement> updateGeos, EuclidianView view) { boolean movedGeo = false; GeoElement geo = this; // moveable geo if (isMoveable()) { // point if (isGeoPoint()) { if (getParentAlgorithm() instanceof AlgoDynamicCoordinatesInterface) { final GeoPointND p = ((AlgoDynamicCoordinatesInterface) getParentAlgorithm()) .getParentPoint(); movedGeo = p.movePoint(rwTransVec, endPosition); geo = (GeoElement) p; } else { movedGeo = movePoint(rwTransVec, endPosition); } } // vector else if (isGeoVector()) { movedGeo = moveVector(rwTransVec, endPosition); } // translateable else if (isTranslateable()) { final Translateable trans = (Translateable) this; trans.translate(rwTransVec); movedGeo = true; } // absolute position on screen else if (isAbsoluteScreenLocateable()) { final AbsoluteScreenLocateable screenLoc = (AbsoluteScreenLocateable) this; if (screenLoc.isAbsoluteScreenLocActive()) { final int vxPixel = (int) Math .round(kernel.getXscale() * rwTransVec.getX()); final int vyPixel = -(int) Math .round(kernel.getYscale() * rwTransVec.getY()); final int x = screenLoc.getAbsoluteScreenLocX() + vxPixel; final int y = screenLoc.getAbsoluteScreenLocY() + vyPixel; DrawableND drawable = view.getDrawableFor(geo); // https://play.google.com/apps/publish/?dev_acc=05873811091523087820#ErrorClusterDetailsPlace:p=org.geogebra.android&et=CRASH&lr=LAST_7_DAYS&ecn=java.lang.NullPointerException&tf=SourceFile&tc=org.geogebra.common.kernel.geos.GeoElement&tm=moveObject&nid&an&c&s=new_status_desc if (drawable != null) { drawable.move(); screenLoc.setAbsoluteScreenLoc(x, y); movedGeo = true; } } else if (isGeoNumeric()) { view.getDrawableFor(geo).move(); if (!((GeoNumeric) geo).isSliderFixed()) { // real world screen position - GeoNumeric ((GeoNumeric) geo).setRealWorldLoc( ((GeoNumeric) geo).getRealWorldLocX() + rwTransVec.getX(), ((GeoNumeric) geo).getRealWorldLocY() + rwTransVec.getY()); movedGeo = true; } } else if (isGeoText()) { // check for GeoText with unlabeled start point final GeoText movedGeoText = (GeoText) this; if (movedGeoText.hasAbsoluteLocation()) { // absolute location: change location final GeoPointND locPoint = movedGeoText .getStartPoint(); if (locPoint != null) { locPoint.translate(rwTransVec); movedGeo = true; } } } } if (movedGeo) { if (updateGeos != null) { updateGeos.add(geo); } else { geo.updateCascade(); } } } // non-moveable geo else if (isTranslateable() && getParentAlgorithm() instanceof AlgoTranslate) { AlgoElement algo = getParentAlgorithm(); GeoElement[] input = algo.getInput(); GeoElement in = input[1]; if (in.isGeoVector()) { ArrayList<GeoElement> tempMoveObjectList = kernel .getApplication().getSelectionManager() .getTempMoveGeoList(); if (in.isIndependent()) { movedGeo = in.moveVector(rwTransVec, endPosition); addParentToUpdateList(in, updateGeos, tempMoveObjectList); } else if (in.getParentAlgorithm() instanceof AlgoVectorPoint) { AlgoVectorPoint algoVector = (AlgoVectorPoint) in .getParentAlgorithm(); GeoElement p = (GeoElement) algoVector.getP(); if (p.isIndependent()) { movedGeo = p.movePoint(rwTransVec, endPosition); addParentToUpdateList(p, updateGeos, tempMoveObjectList); } } } } else { ArrayList<GeoElement> tempMoveObjectList = kernel.getApplication() .getSelectionManager().getTempMoveGeoList(); movedGeo = moveFromChangeableCoordParentNumbers(rwTransVec, endPosition, viewDirection, updateGeos, tempMoveObjectList, view); } return movedGeo; } /** * try to move the geo with coord parent numbers (e.g. point defined by * sliders) * * @param rwTransVec * translation vector * @param endPosition * end position * @param viewDirection * view direction * @param updateGeos * geos to be updated * @param tempMoveObjectList1 * temporary list * @param view * TODO * @return false if not moveable this way */ public boolean moveFromChangeableCoordParentNumbers(final Coords rwTransVec, final Coords endPosition, final Coords viewDirection, final ArrayList<GeoElement> updateGeos, final ArrayList<GeoElement> tempMoveObjectList1, EuclidianView view) { return false; } /** * * @return true if has changeable coord parent numbers (e.g. point defined * by sliders) */ public boolean hasChangeableCoordParentNumbers() { return false; } /** * record values when mouse pressed * * @param view * TODO */ public void recordChangeableCoordParentNumbers(EuclidianView view) { // do nothing } /** * add changeable coord parent number to update list * * @param number * changeable number * @param updateGeos * set of geos * @param tempMoveObjectList1 * temporary list */ static final protected void addChangeableCoordParentNumberToUpdateList( final GeoElement number, final ArrayList<GeoElement> updateGeos, ArrayList<GeoElement> tempMoveObjectList1) { addParentToUpdateList(number, updateGeos, tempMoveObjectList1); } static final private void addParentToUpdateList(final GeoElement number, final ArrayList<GeoElement> updateGeos, ArrayList<GeoElement> tempMoveObjectList1) { if (updateGeos != null) { // add number to update list updateGeos.add(number); } else { // update number right now ArrayList<GeoElement> tempMoveObjectList2 = tempMoveObjectList1; if (tempMoveObjectList1 == null) { tempMoveObjectList2 = new ArrayList<GeoElement>(); } tempMoveObjectList2.add(number); updateCascade(tempMoveObjectList2, getTempSet(), false); } } // private ArrayList<GeoElement> tempMoveObjectList; /** * Returns the position of this GeoElement in GeoGebra's spreadsheet view. * The x-coordinate of the returned point specifies its column and the * y-coordinate specifies its row location. Note that this method may return * null if no position was specified so far. * * @return position of this GeoElement in GeoGebra's spreadsheet view. */ public GPoint getSpreadsheetCoords() { if (spreadsheetCoords == null) { updateSpreadsheetCoordinates(); } return spreadsheetCoords; } /** * Sets the position of this GeoElement in GeoGebra's spreadsheet. The * x-coordinate specifies its column and the y-coordinate specifies its row * location. * * @param spreadsheetCoords * point (col,row) */ public void setSpreadsheetCoords(final GPoint spreadsheetCoords) { this.spreadsheetCoords = spreadsheetCoords; } /** * @return old spreadsheet coords */ public GPoint getOldSpreadsheetCoords() { return oldSpreadsheetCoords; } /** * @return true for macro outputs */ final public boolean isAlgoMacroOutput() { return isAlgoMacroOutput; } /** * @param isAlgoMacroOutput * mark/unmark this geo as macro output */ public void setAlgoMacroOutput(final boolean isAlgoMacroOutput) { this.isAlgoMacroOutput = isAlgoMacroOutput; } /** * @author Michael Borcherds * @version 2008-04-30 * @param geo * other geo * @return true if these elements are algebraically equal */ @Override public abstract boolean isEqual(GeoElementND geo); /** * Returns whether this - f gives 0 in the CAS. * * @param f * other geo * @return whether this - f gives 0 in the CAS. */ final public boolean isDifferenceZeroInCAS(final GeoElementND f) { // use CAS to check f - g = 0 String myFormula = getFormulaString(StringTemplate.defaultTemplate, true); String otherFormula = f.getFormulaString(StringTemplate.defaultTemplate, true); if (myFormula.equals(otherFormula)) { return true; } try { final StringBuilder diffSb = new StringBuilder(); diffSb.append("Simplify["); diffSb.append(myFormula); diffSb.append("-("); diffSb.append(otherFormula); diffSb.append(")]"); final String diff = kernel.evaluateGeoGebraCAS(diffSb.toString(), null); return (Double.valueOf(diff) == 0d); } catch (final Throwable e) { return false; } } /** * String getFormulaString(int, boolean substituteNumbers) substituteNumbers * determines (for a function) whether you want "2*x^2" or "a*x^2" returns a * string representing the formula of the GeoElement in the following * formats: getFormulaString(StringType.GIAC) eg sqrt(x) * getFormulaString(StringType.LATEX) eg \sqrt(x) * getFormulaString(StringType.LIBRE_OFFICE) eg sqrt {x} * getFormulaString(StringType.GEOGEBRA) eg sqrt(x) * getFormulaString(StringType.GEOGEBRA_XML) * * @param tpl * string template * @param substituteNumbers * true to substitute numbers * @return formula string */ @Override public String getFormulaString(final StringTemplate tpl, final boolean substituteNumbers) { String ret = ""; // GeoFunction & GeoFunctionNVar override this, no need to care about // them // only inequalities call this // matrices if (isGeoList() && tpl.hasType(StringType.LATEX) && ((GeoList) this).isMatrix()) { ret = toLaTeXString(!substituteNumbers, tpl); } // vectors else if (isGeoVector() && tpl.hasType(StringType.LATEX)) { ret = toLaTeXString(!substituteNumbers, tpl); } // curves else if (isGeoCurveCartesian() && tpl.hasType(StringType.LATEX)) { ret = toLaTeXString(!substituteNumbers, tpl); } else if (isGeoSurfaceCartesian() && tpl.hasType(StringType.LATEX)) { ret = toLaTeXString(!substituteNumbers, tpl); } else { ret = substituteNumbers ? toValueString(tpl) : getDefinition(tpl); } // GeoNumeric eg a=1 if ("".equals(ret) && isGeoNumeric() && !substituteNumbers && isLabelSet()) { ret = tpl.printVariableName(label); } if ("".equals(ret) && isGeoCasCell() && ((GeoCasCell) this).getAssignmentVariable() != null) { ret = getLabel(tpl); } if ("".equals(ret) && !isGeoText()) { // eg Text[ (1,2), false] ret = toOutputValueString(tpl); } /* * we don't want to deal with list bracess in here since * GeoList.toOutputValueString() takes care of it */ if (tpl.hasType(StringType.LATEX)) { if ("?".equals(ret)) { ret = "?"; } else if ((Unicode.INFINITY + "").equals(ret)) { ret = "\\infty"; } else if ((Unicode.MINUS_INFINITY + "").equals(ret)) { ret = "-\\infty"; } } return ret; } // =================================================== // G.Sturr 2010-5-14 // New code for spreadsheet tracing with trace manager // =================================================== /** Spreadsheet tracing on/off flag */ private boolean spreadsheetTrace; /** @return true if this geo is tracing to the spreadsheet */ @Override public boolean getSpreadsheetTrace() { return spreadsheetTrace; } /** * Set tracing flag for this geo * * @param traceFlag * true to trace to spreadsheet */ public void setSpreadsheetTrace(final boolean traceFlag) { if (!traceFlag) { traceSettings = null; } spreadsheetTrace = traceFlag; // #2153 if (spreadsheetTrace) { cons.addTracingGeo(); } } /** * Request spreadsheet trace manager to auto-reset the tracing columns. * Called after mouse_release. */ public void resetTraceColumns() { if (kernel.getApplication().isUsingFullGui()) { kernel.getApplication().resetTraceColumn(this); } } /** @return if geos of this type can be traced to the spreadsheet */ public boolean isSpreadsheetTraceable() { return this instanceof SpreadsheetTraceable; } /** * Used by list to check if geos are compatible. * * @return has spreadsheet mode that is a traceable mode */ public boolean hasSpreadsheetTraceModeTraceable() { return isSpreadsheetTraceable(); } private SpreadsheetTraceSettings traceSettings; /** * @return spreadsheet trace settings */ public SpreadsheetTraceSettings getTraceSettings() { if (traceSettings == null) { traceSettings = new SpreadsheetTraceSettings(); // if only copy is possible, set it immediately if (getTraceModes() == TraceModesEnum.ONLY_COPY) { traceSettings.doTraceGeoCopy = true; } } return traceSettings; } /** * @param t * spreadsheet trace settings */ public void setTraceSettings(final SpreadsheetTraceSettings t) { traceSettings = t; } /** * over-ridden in GeoList * * @return element for properties dialog */ public GeoElement getGeoElementForPropertiesDialog() { return this; } /** * over-ridden in GeoText * * @return true if this was created by Text command */ public boolean isTextCommand() { return false; } private boolean inTree = false; @Override final public boolean isInTree() { return inTree; } @Override final public void setInTree(final boolean flag) { inTree = flag; } /* * Scripting */ private Script[] scripts = null; /** * @param script * script */ public void setClickScript(Script script) { setScript(script, EventType.CLICK); } /** * Sets update script * * @param script * script */ public void setUpdateScript(Script script) { setScript(script, EventType.UPDATE); } /** * Set a script for this geo * * @param script * source code for the new script * @param evt * the event type that will trigger the script */ public void setScript(Script script, EventType evt) { if (evt == EventType.UPDATE && !canHaveUpdateScript() || evt == EventType.CLICK && !canHaveClickScript()) { return; } if (this.scripts == null) { this.scripts = new Script[EventType.values().length]; } // Make sure we're listening to events for this script kernel.getApplication().startGeoScriptRunner(); Script oldScript = scripts[evt.ordinal()]; if (oldScript != null) { oldScript.unbind(this, evt); } scripts[evt.ordinal()] = script; script.bind(this, evt); } /** * @return true if this can have update script */ public boolean canHaveUpdateScript() { return true; } /** * Return script for event type (localized if ggbscript) * * @param type * event type * @return script */ @Override public Script getScript(EventType type) { if (scripts == null) { return null; } return scripts[type.ordinal()]; } /** * Runs the click script of this object * * @param arg * argument that replaces all %0 in the script */ public void runClickScripts(final String arg) { // "%0" is replaced in the script by "arg" kernel.getApplication().dispatchEvent( new Event(EventType.CLICK, this, arg == null ? label : arg)); } private boolean showTrimmedIntersectionLines = false; /** * @param show * true to show trimmed lines */ public void setShowTrimmedIntersectionLines(final boolean show) { showTrimmedIntersectionLines = show; } /** * @return true if showing trimmed lines */ @Override public boolean getShowTrimmedIntersectionLines() { return showTrimmedIntersectionLines; } /** * @return true for points in region */ @Override public boolean isPointInRegion() { return false; } /** * @param flag * mark/unmark this geo as random */ public void setRandomGeo(final boolean flag) { isRandomGeo = flag; } private boolean isRandomGeo = false; /** * @return true for random geos (numbers, lists) */ @Override public boolean isRandomGeo() { return isRandomGeo; } /** * Randomize this geo */ @Override public void updateRandomGeo() { // update parent algorithm, like AlgoRandom final AlgoElement algo = getParentAlgorithm(); if (algo != null) { algo.compute(); // eg AlgoRandom etc } } /** * @return true if this implemnts MatrixTransformable */ public boolean isMatrixTransformable() { return false; } // ============================================= // Control which views are allowed to add a geo. // G.Sturr, 2010-6-30 // ============================================= /** * @param viewId * view id * @param setVisible * true make this geo visible in given view */ public void setVisibility(final int viewId, final boolean setVisible) { if (this.viewFlags == null) { this.viewFlags = new ArrayList<Integer>(); } if (setVisible) { if (!viewFlags.contains(viewId)) { viewFlags.add(viewId); } } else { viewFlags.remove(Integer.valueOf(viewId)); } } /** * @param viewId * view id * @return whether this geo is visible in given view */ public boolean isVisibleInView(final int viewId) { if (viewFlags == null) { return viewId == App.VIEW_EUCLIDIAN; } return viewFlags.contains(viewId); } // private Set<Integer> viewSet = new HashSet<Integer>(); /** * @param viewId * view id */ @Override final public void addView(final int viewId) { if (App.isView3D(viewId)) { addViews3D(); } else { setVisibility(viewId, true); } } /** * set visible in 3D views */ final public void addViews3D() { visibleInView3D = VisibleInView.TRUE; } /** * Make this invisible in given view * * @param viewId * view id */ @Override public void removeView(final int viewId) { if (App.isView3D(viewId)) { removeViews3D(); } else { setVisibility(viewId, false); } } /** * set not visible in 3D views */ final public void removeViews3D() { visibleInView3D = VisibleInView.FALSE; } /** * Make this visible in given views * * @param flags * list of view ids */ @Override public void setViewFlags(List<Integer> flags) { if (flags == null) { viewFlags = null; return; } if (this.viewFlags == null) { this.viewFlags = new ArrayList<Integer>(); } else { viewFlags.clear(); } viewFlags.addAll(flags); // Collections.copy(list, viewFlags); } /** * @return set of views in which this is visible */ @Override public List<Integer> getViewSet() { if (viewFlags == null) { return null; } final List<Integer> list = new ArrayList<Integer>(); list.addAll(viewFlags); // Collections.copy(list, viewFlags); return list; } /** * * @return true if visible in 3D view */ @Override public boolean isVisibleInView3D() { switch (visibleInView3D) { case NOT_SET: default: return isVisibleInView3DNotSet(); case TRUE: return hasDrawable3D(); case FALSE: return false; } } /** * decide if visible in 3D view when flag is not already set * * @return true if should be visible in 3D view */ protected boolean isVisibleInView3DNotSet() { if (hasDrawable3D()) { if (isGeoElement3D() || isVisibleInView(App.VIEW_EUCLIDIAN)) { // visible: we set it visibleInView3D = VisibleInView.TRUE; return true; } // not visible: we set it visibleInView3D = VisibleInView.FALSE; return false; } return false; } /** * * @return true if visible in view for plane */ public boolean isVisibleInViewForPlane() { switch (visibleInViewForPlane) { case NOT_SET: default: if (isVisibleInView3D()) { visibleInViewForPlane = VisibleInView.TRUE; return true; } visibleInViewForPlane = VisibleInView.FALSE; return false; case TRUE: return true; case FALSE: return false; } } /** * For 3D / plane view visiblity where we need a default value * */ protected enum VisibleInView { /** default */ NOT_SET, /** force visible */ TRUE, /** force hidden */ FALSE } /** Flag for visibility in 3D view(s) */ protected VisibleInView visibleInView3D = VisibleInView.NOT_SET; /** Flag for visibility in plane view(s) */ private VisibleInView visibleInViewForPlane = VisibleInView.NOT_SET; /** * set if this is visible in 3D view or not * * @param flag * flag */ public void setVisibleInView3D(boolean flag) { if (flag) { visibleInView3D = VisibleInView.TRUE; } else { visibleInView3D = VisibleInView.FALSE; } } /** * set if this is visible in view for plane or not * * @param flag * flag */ public void setVisibleInViewForPlane(boolean flag) { if (flag) { visibleInViewForPlane = VisibleInView.TRUE; } else { visibleInViewForPlane = VisibleInView.FALSE; } } /** * set visibility in 3D view equal to geo * * @param geo * geo */ @Override public void setVisibleInView3D(GeoElement geo) { visibleInView3D = geo.visibleInView3D; } /** * set visibility in view for plane equal to geo * * @param geo * geo */ @Override public void setVisibleInViewForPlane(GeoElement geo) { visibleInViewForPlane = geo.visibleInViewForPlane; } /** * @param selected2 * true to allow selection */ @Override public void setSelectionAllowed(final boolean selected2) { selectionAllowed = selected2; } /** * @param ev * view * @return true if selection is allowed */ public boolean isSelectionAllowed(EuclidianViewInterfaceSlim ev) { return selectionAllowed; } /** * In case this geo is part of macro construction, it keeps its own label. * To get correct output of Name[geo] we need to keep the label of the * real-world geo represented by this formal geo. * * @param realLabel * Label of the real geo represented by this one */ public void setRealLabel(final String realLabel) { this.realLabel = realLabel; } /** * @return true if current fill style is hatch */ @Override public boolean isHatchingEnabled() { return fillType.isHatch(); } /** * @param angle * hatching angle in degrees */ @Override public void setHatchingAngle(final int angle) { hatchingAngle = angle; } /** * @return hatching angle in degrees */ @Override public double getHatchingAngle() { return hatchingAngle; } /** * @param distance * hatching distance */ @Override public void setHatchingDistance(final int distance) { hatchingDistance = distance; } /** * @return hatching distance */ @Override public int getHatchingDistance() { return hatchingDistance; } /** * @return fill image */ @Override public MyImage getFillImage() { return graphicsadapter.getFillImage(); } // public void setFillImage(BufferedImage image){ // this.fillImage = image; // } /** * @param filename * filename of fill image */ public void setFillImage(final String filename) { graphicsadapter.setFillImage(filename); } /** * @return fill type (standard/hatch/image) */ @Override public FillType getFillType() { return fillType; } /** * @param fillType * new fill type */ @Override public void setFillType(final FillType fillType) { this.fillType = fillType; } /** * Tries to load the image using the given fileName. * * @param fileName * filename */ @Override public void setImageFileName(final String fileName) { graphicsadapter.setImageFileName(fileName); } /** * @return filename of fill image */ @Override public String getImageFileName() { return graphicsadapter.getImageFileName(); } /** * @param inverseFill * the inverseFill to set */ public void setInverseFill(final boolean inverseFill) { this.inverseFill = inverseFill; } /** * @return the inverseFill */ @Override public boolean isInverseFill() { return inverseFill; } // private Coords mainDirection = Coords.VZ; /** * * @return "main" direction of the element, e.g. for seeing it in a * "standard" view (for 3D). E.g. orthogonal to a plane, along a * line, ... */ public Coords getMainDirection() { return Coords.VZ; } /** * gets shortest distance to point p overridden in eg GeoPoint, GeoLine for * compound paths * * @param p * other point * @return distance */ public double distance(final GeoPoint p) { return Double.POSITIVE_INFINITY; } /** * @param p * point * @return distance from point */ @Override public double distance(final GeoPointND p) { if ((p instanceof GeoElement) && (p instanceof GeoPoint)) { return distance((GeoPoint) p); } Log.debug("TODO : distance from " + getGeoClassType() + " to ND point"); return Double.POSITIVE_INFINITY; } /** * @return true if this can have click script */ public boolean canHaveClickScript() { return true; } // ///////////////////////////// // 3D // ///////////////////////////// /** says if it's a pickable object */ private boolean isPickable = true; private boolean needsReplacingInExpressionNode = false; /** * sets the pickability of the object * * @param v * pickability */ public void setIsPickable(final boolean v) { isPickable = v; } /** * says if the object is pickable * * @return true if the object is pickable */ public boolean isPickable() { return isPickable && isSelectionAllowed(null); } /** * @return true if needs replacing in expression nodes */ public boolean needsReplacingInExpressionNode() { return needsReplacingInExpressionNode; } /** * Call this to make sure that this is replaced in expression nodes */ public void setNeedsReplacingInExpressionNode() { needsReplacingInExpressionNode = true; } /** * @return true for intervals */ public boolean isGeoInterval() { return false; } /** * @return length or area, overriden in subclasses */ public double getMeasure() { return 0; } /** * Removes dependencies (conditional visibility, min, max, corner, EV * bounds) from oldgeo and moves them to this * * @param oldGeo * geo whose dependencies should be moved */ public void moveDependencies(final GeoElement oldGeo) { // in general case do nothing; overriden in GeoPoint, GeoNumeric and // GeoBoolean } private Stack<GeoElement> tempClone; private boolean cloneInUse = false; /** * Srore copy of this geo in stack */ public void storeClone() { if (tempClone == null) { tempClone = new Stack<GeoElement>(); } tempClone.push(copy()); cloneInUse = true; } /** * */ public void recoverFromClone() { if (tempClone != null) { set(tempClone.pop()); } cloneInUse = false; } /** * Randomize for probability chacking overriden in subclasses that allow * randomization */ public void randomizeForProbabilisticChecking() { // overode by subclasses } /** * @return true if this allows randomization */ public boolean isRandomizable() { return false; } /** * Returns corresponding GeoCasCell. See GeoCasCell.setTwinGeo(). * * @return twin GeoElement */ final public GeoCasCell getCorrespondingCasCell() { return correspondingCasCell; } /** * Sets corresponding GeoCasCell for this GeoElement. See * GeoCasCell.getTwinGeo(). * * @param correspondingCasCell * corresponding CAS cell */ final public void setCorrespondingCasCell( final GeoCasCell correspondingCasCell) { this.correspondingCasCell = correspondingCasCell; } /** * @return true if the given GeoElement geo is to be drawn with LaTeX in * AV/Spreadsheet */ @Override public boolean isLaTeXDrawableGeo() { return false; } /** * @return true if this has a background color */ public boolean hasBackgroundColor() { return false; } /** * @param i * algo * @return true if algo is in update set */ public boolean algoUpdateSetContains(final AlgoElement i) { return getAlgoUpdateSet().contains(i); } /** * Makes sure that column headings are empty list of GeoTexts */ protected void resetSpreadsheetColumnHeadings() { if (spreadsheetColumnHeadings == null) { spreadsheetColumnHeadings = new ArrayList<GeoText>(); } else { spreadsheetColumnHeadings.clear(); } } /** * for the SpreadsheetTraceable interface. Default: just return the label * * @return list of column headings */ final public ArrayList<GeoText> getColumnHeadings() { // if no values / only copy if (getTraceSettings().doTraceGeoCopy) { // trace copy updateColumnHeadingsForTraceGeoCopy(); } else { // update column headings for trace values updateColumnHeadingsForTraceValues(); } return spreadsheetColumnHeadings; } /** update column headings for trace values */ public void updateColumnHeadingsForTraceValues() { // for NumberValue updateColumnHeadingsForTraceGeoCopy(); } /** * * @return string description of values traced */ public String getTraceDialogAsValues() { return getLabelTextOrHTML(false);// columnHeadingsForTraceDialog.toString(); } /** Used by TraceDialog for "Trace as... value of/copy of */ static public enum TraceModesEnum { /** no value for this geo, only copy */ ONLY_COPY, /** one value / copy (e.g. text) */ ONE_VALUE_OR_COPY, /** one value / no copy (e.g. segment) */ ONE_VALUE_ONLY, /** at least two values (e.g. point) */ SEVERAL_VALUES_OR_COPY, /** at least two values (e.g. point) */ SEVERAL_VALUES_ONLY, /** not traceable */ NOT_TRACEABLE } /** * * @return possible modes for trace to spreadsheet */ public TraceModesEnum getTraceModes() { return TraceModesEnum.ONE_VALUE_ONLY;// default for NumberValue } /** * * update column headings when "trace geo copy" */ protected void updateColumnHeadingsForTraceGeoCopy() { resetSpreadsheetColumnHeadings(); spreadsheetColumnHeadings.add(getNameGeo()); } /** * * @return geo text = Name[this] */ protected GeoText getNameGeo() { AlgoName algo = new AlgoName(cons, this); GeoText ret = algo.getGeoText(); ret.setEuclidianVisible(false); return ret; } /** * * @param node * expression describing the text * @return GeoText linked to expression */ protected GeoText getColumnHeadingText(ExpressionNode node) { GeoText ret; if (node.getGeoElementVariables() == null) { // no variables in expression node : compute only once ret = new GeoText(cons); AlgoDependentText.nodeToGeoText(node, ret, ret.getStringTemplate()); } else { AlgoDependentText algo = new AlgoDependentText(cons, node, false); algo.setProtectedInput(true); ret = algo.getGeoText(); } ret.setEuclidianVisible(false); return ret; } /** * default for elements implementing NumberValue interface eg GeoSegment, * GeoPolygon * * @param spreadsheetTraceList * list of numbers for spreadsheet */ public void addToSpreadsheetTraceList( ArrayList<GeoNumeric> spreadsheetTraceList) { if (this instanceof NumberValue) { final GeoNumeric xx = new GeoNumeric(cons, ((NumberValue) this).getDouble()); spreadsheetTraceList.add(xx); } else { Log.debug("error in getSpreadsheetTraceList(), not a NumberValue"); } } @Override public String toString(StringTemplate tpl) { return label; } @Override final public ExpressionValue traverse(Traversing t) { return t.process(this); } @Override final public boolean inspect(Inspecting t) { return t.check(this); } /** * Says if this geo has a "meta geo", e.g. a segment coming from a polygon * * @return length of metas */ public int getMetasLength() { return 0; } @Override final public GeoElement unwrap() { return this; } @Override final public ExpressionNode wrap() { return new ExpressionNode(getKernel(), this); } /** * @return whether to show pin in stylebar/geo context menu */ public boolean isPinnable() { return false; } /** * @return whether this element has fixed screen location in some view */ final public boolean isPinned() { if (this instanceof AbsoluteScreenLocateable) { return ((AbsoluteScreenLocateable) this) .isAbsoluteScreenLocActive(); } if (!isPinnable()) { return false; } return getParentAlgorithm() instanceof AlgoAttachCopyToView; } @Override public boolean hasCoords() { return false; } /** * copies the scripts from another geo. Used when redefining (so that the * scripts aren't "deleted") * * @param oldGeo * old GeoElement */ @Override public void setScripting(GeoElement oldGeo) { if (oldGeo.scripts == null) { this.scripts = null; return; } if (this.scripts == null) { this.scripts = new Script[EventType.values().length]; } for (int i = 0; i < oldGeo.scripts.length; i++) { if (oldGeo.scripts[i] != null) { scripts[i] = oldGeo.scripts[i].copy(); } else { scripts[i] = null; } } } /** * Implementation for numbers, segments etc. */ @Override public ExpressionValue derivative(FunctionVariable fv, Kernel kernel0) { return new MyDouble(kernel, 0); } @Override public ExpressionValue integral(FunctionVariable fv, Kernel kernel0) { return null; } /** * @return whether it's a matrix. Overridden in GeoList */ public boolean isMatrix() { return false; } /** * @return Unicode symbol used for fill */ @Override public String getFillSymbol() { return fillSymbol; } /** * Just aets the fill symbol, fill type must be changed to SYMBOL separately * * @param symbol * Unicode symbol used for fill */ @Override public void setFillSymbol(String symbol) { fillSymbol = symbol; } /** * * @return decoration type, eg 3 lines */ public final int getDecorationType() { return decorationType; } /** * Sets the flag wheter this objects value or label should be sent to CAS * * @param var * true if the value should be sent to cas false otherwise */ public void setSendValueToCas(boolean var) { sendValueToCas = var; } /** * @return flag wheter this objects value or label should be sent to CAS */ public boolean getSendValueToCas() { return sendValueToCas; } /** * Adds or modifies the caption to contain the label in P(v1,v2) form * (LaTeX) * * @param vars * in LaTeX format */ public void setCaptionBotanaVars(String vars) { setLabelMode(LABEL_CAPTION); labelVisible = true; String labelWithVars = "{\\bf\\it " + label + vars + "}\\\\"; if (caption == null) { caption = "$" + labelWithVars + "$"; return; } if (caption.startsWith(labelWithVars)) { return; } caption = "$" + labelWithVars + "\\\\" + caption.substring(1, caption.length()); } /** * Adds a new poly to the caption (LaTeX) * * @param poly * in LaTeX format */ public void addCaptionBotanaPolynomial(String poly) { setLabelMode(LABEL_CAPTION); labelVisible = true; if (caption != null) { caption = caption.substring(0, caption.length() - 1) + poly + "\\\\$"; } else { caption = "$" + poly + "\\\\$"; } } /** * @return whether line properties of this object should be editable by * Prop. View */ public boolean showLineProperties() { return isPath(); } @Override public boolean evaluatesTo3DVector() { return false; } ////////////////////////////// // specific input protection ///////////////////////////// private boolean canBeRemovedAsInput = true; private ExpressionNode definition; /** * set this can (not) be removed when input of algo * * @param flag * flag */ public void setCanBeRemovedAsInput(boolean flag) { canBeRemovedAsInput = flag; } /** * * @return true if can be removed as input of algo -- only if just one algo * left */ @Override public boolean canBeRemovedAsInput() { return canBeRemovedAsInput && (algorithmList == null || algorithmList.size() <= 1); } @Override public boolean hasLineOpacity() { return false; } @Override public int getLineOpacity() { return lineOpacity; } @Override public void setLineOpacity(int lineOpacity) { this.lineOpacity = lineOpacity; } @Override public boolean evaluatesToNumber(boolean def) { return this.isNumberValue(); } /** * @return whether it's tracing or not */ public boolean getTrace() { return false; } /** hit type (no/boundary/inside) */ public enum HitType { /** not hit */ NONE, /** boundary hit */ ON_BOUNDARY, /** fill hit */ ON_FILLING } /** * @return last hit type */ abstract public HitType getLastHitType(); /** * @return whether this evaluates to angle */ public final boolean isAngle() { return getAngleDim() == 1; } @Override public String getAssignmentOperator() { return ":="; } /** * @return whether this geo can be parametrized */ @Override public boolean isParametric() { return false; } @Override public int getListDepth() { return 0; } /** * @param val * number * @return val as geoelement or null for MyDouble / MyBoolean */ public static GeoElement as(NumberValue val) { return val instanceof GeoElement ? (GeoElement) val : null; } @Override public String toString(GTemplate tpl) { return toString(tpl.getTemplate()); } @Override public void setLabelWanted(boolean b) { this.labelWanted = b; } /** * * @param geo * other geo * @return whether this and geo are congruent */ public ExtendedBoolean isCongruent(GeoElement geo) { return isEqual(geo) ? ExtendedBoolean.TRUE : ExtendedBoolean.UNKNOWN; } @Override public final void setDefinition(ExpressionNode root) { if (definition != null && root == null && algoParent != null) { return; } this.definition = root; } /** * @param geo * template element */ protected void reuseDefinition(GeoElementND geo) { this.definition = !geo.isIndependent() ? null : geo.getDefinition(); } @Override public ExpressionNode getDefinition() { return definition; } /** * @return whether value == definition */ public final boolean isSimple() { if (!isIndependent()) { return false; } if (definition == null) { return true; } if(definition.getOperation() == Operation.MULTIPLY){ if(definition.getLeft().unwrap() instanceof NumberValue && definition.getRight().unwrap() instanceof MyDouble && MyDouble.exactEqual(definition.getRight() .evaluateDouble(), MyMath.DEG)) { return true; } } if (definition.unwrap() instanceof ExpressionNode) { return false; } if (definition.unwrap() instanceof NumberValue) { double val = evaluateDouble(); return MyDouble.isFinite(val) && !Kernel.isEqual(val, Math.PI) && !Kernel.isEqual(val, Math.E); } return true; } @Override public ExpressionValue getUndefinedCopy(Kernel kernel1) { GeoElement ret = copy(); ret.setUndefined(); return ret; } @Override public ExpressionValue toValidExpression() { return this; } @Override public boolean evaluatesToNDVector() { ValueType vt = getValueType(); return vt == ValueType.NONCOMPLEX2D || vt == ValueType.VECTOR3D; } /** * Updates visual properties and repaints this object */ @Override public final void updateVisualStyleRepaint(GProperty prop) { updateVisualStyle(prop); kernel.notifyRepaint(); } /** * Decides if definition differs from value as String. If so, AV should * display both rows. * * @return true, only if AV should display 2 rows in 'Definition And Value' * style. */ @Override public boolean needToShowBothRowsInAV() { String def0 = getDefinition(StringTemplate.defaultTemplate); if ("".equals(def0)) { return false; } IndexHTMLBuilder sbDef = new IndexHTMLBuilder(false); IndexHTMLBuilder sbVal = new IndexHTMLBuilder(false); addLabelTextOrHTML(def0, sbDef); String def = sbDef.toString(); String val = getAlgebraDescriptionTextOrHTMLDefault(sbVal); return !def.equals(val); } /** * * @return true if this can be listed as input for a macro */ @Override public boolean isVisibleInputForMacro() { return isLabelSet(); } /** * @param cons1 * construction * @return reference to this */ public GeoElement toGeoElement(Construction cons1) { return this; } /** * @return this wrapped in array */ public GeoElement[] asArray() { return new GeoElement[] { this }; } /** * Set definition to null, no checks */ @Override public void resetDefinition() { definition = null; } /** * @return whether the to-be-drawn geoElement is filled, meaning the * alpha-value is greater zero, or hatching is enabled. */ public boolean isFilled() { return getAlphaValue() > 0 || isHatchingEnabled(); } /** * @param labelSet * the labelSet flag */ public void setLabelSet(boolean labelSet) { this.labelSet = labelSet; } /** * @param var * variable name * @return default label */ protected String getPointVectorDefault(String var) { return getDefaultLabel(!Character.isLowerCase(var.charAt(0)) ? GeoElement.pointLabels : null, false); } // @Override // public NumberDerivativePair getDualNumber(Kernel kernel) { // return new NumberDerivativePair(kernel, evaluateDouble(), 0); // } /** * @return whether this is locusable (locus or function) */ public boolean isGeoLocusable() { return false; } /** * * @return the original screen location */ public ScreenLocation getScreenLocation() { return screenLocation; } /** * * @param screenLocation * to set */ public void setScreenLocation(ScreenLocation screenLocation) { this.screenLocation = screenLocation; } /** * Sets the original (x, y) location of the geo. * * @param x * to set * @param y * to set */ public void setScreenLocation(int x, int y) { this.screenLocation = new ScreenLocation(x, y); } /** * * @return if this has original screen location comes from file. */ public boolean hasScreenLocation() { return screenLocation != null; } /** * resets original location to null */ public void resetScreenLocation() { screenLocation = null; } /** * * @return if geo can be duplicated from Algebra View. */ public boolean isAlgebraDuplicateable() { return true; } }