package org.geogebra.common.kernel; import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.TreeSet; import org.geogebra.common.GeoGebraConstants; import org.geogebra.common.cas.GeoGebraCAS; import org.geogebra.common.euclidian.EuclidianView; import org.geogebra.common.euclidian.EuclidianViewInterfaceCommon; import org.geogebra.common.euclidian.EuclidianViewInterfaceSlim; import org.geogebra.common.factories.FormatFactory; import org.geogebra.common.gui.SetLabels; import org.geogebra.common.gui.SetOrientation; import org.geogebra.common.gui.dialog.options.OptionsCAS; import org.geogebra.common.io.MyXMLHandler; import org.geogebra.common.kernel.algos.AlgoCasBase; import org.geogebra.common.kernel.algos.AlgoDispatcher; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.algos.AlgoIf; import org.geogebra.common.kernel.algos.AlgoMacro; import org.geogebra.common.kernel.algos.AlgoPointVector; import org.geogebra.common.kernel.algos.AlgoPolygon; import org.geogebra.common.kernel.algos.AlgoVectorPoint; import org.geogebra.common.kernel.algos.ConstructionElement; import org.geogebra.common.kernel.algos.DependentAlgo; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.ExpressionNodeConstants.StringType; import org.geogebra.common.kernel.arithmetic.ExpressionNodeEvaluator; import org.geogebra.common.kernel.arithmetic.ExpressionValue; import org.geogebra.common.kernel.arithmetic.FunctionNVar; import org.geogebra.common.kernel.arithmetic.FunctionVariable; import org.geogebra.common.kernel.arithmetic.FunctionalNVar; import org.geogebra.common.kernel.arithmetic.MyArbitraryConstant; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.kernel.arithmetic.MySpecialDouble; import org.geogebra.common.kernel.arithmetic.Traversing; import org.geogebra.common.kernel.arithmetic.ValidExpression; import org.geogebra.common.kernel.arithmetic.Variable; import org.geogebra.common.kernel.cas.AlgoUsingTempCASalgo; import org.geogebra.common.kernel.cas.UsesCAS; import org.geogebra.common.kernel.commands.AlgebraProcessor; import org.geogebra.common.kernel.geos.GProperty; import org.geogebra.common.kernel.geos.GeoAxis; import org.geogebra.common.kernel.geos.GeoCasCell; import org.geogebra.common.kernel.geos.GeoCurveCartesian; import org.geogebra.common.kernel.geos.GeoDummyVariable; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoElementSpreadsheet; import org.geogebra.common.kernel.geos.GeoLine; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.geos.GeoPolygon; import org.geogebra.common.kernel.geos.GeoSegment; import org.geogebra.common.kernel.geos.GeoVec2D; import org.geogebra.common.kernel.geos.GeoVec3D; import org.geogebra.common.kernel.implicit.GeoImplicit; import org.geogebra.common.kernel.kernelND.GeoAxisND; import org.geogebra.common.kernel.kernelND.GeoConicND; import org.geogebra.common.kernel.kernelND.GeoCoordSys2D; import org.geogebra.common.kernel.kernelND.GeoCurveCartesianND; import org.geogebra.common.kernel.kernelND.GeoDirectionND; import org.geogebra.common.kernel.kernelND.GeoElementND; import org.geogebra.common.kernel.kernelND.GeoPlaneND; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.kernel.kernelND.GeoRayND; import org.geogebra.common.kernel.kernelND.GeoSegmentND; import org.geogebra.common.kernel.kernelND.GeoVectorND; import org.geogebra.common.kernel.optimization.ExtremumFinder; import org.geogebra.common.kernel.optimization.ExtremumFinderI; import org.geogebra.common.kernel.parser.GParser; import org.geogebra.common.kernel.parser.Parser; import org.geogebra.common.main.App; import org.geogebra.common.main.Feature; import org.geogebra.common.main.Localization; import org.geogebra.common.main.MyError; import org.geogebra.common.main.SelectionManager; import org.geogebra.common.main.error.ErrorHelper; 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.script.GgbScript; import org.geogebra.common.plugin.script.Script; import org.geogebra.common.util.Exercise; import org.geogebra.common.util.MaxSizeHashMap; import org.geogebra.common.util.MyMath; import org.geogebra.common.util.NumberFormatAdapter; import org.geogebra.common.util.ScientificFormatAdapter; import org.geogebra.common.util.StringUtil; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.lang.Unicode; /** * Provides methods for computation * * @author Markus */ public class Kernel { /** * Maximal number of spreadsheet columns if these are increased above 32000, * you need to change traceRow to an int[] */ final public static int MAX_SPREADSHEET_COLUMNS_DESKTOP = 9999; /** Maximal number of spreadsheet rows */ final public static int MAX_SPREADSHEET_ROWS_DESKTOP = 9999; /** * Maximal number of spreadsheet columns if these are increased above 32000, * you need to change traceRow to an int[] */ final public static int MAX_SPREADSHEET_COLUMNS_WEB = 200; /** Maximal number of spreadsheet rows */ final public static int MAX_SPREADSHEET_ROWS_WEB = 350; /** string for +- */ final public static String STRING_PLUS_MINUS = "\u00B1 "; /** string for -+ */ final public static String STRING_MINUS_PLUS = "\u2213 "; // G.Sturr 2009-10-18 // algebra style /** Algebra view style: value */ final public static int ALGEBRA_STYLE_VALUE = 0; /** Algebra view style: description */ final public static int ALGEBRA_STYLE_DESCRIPTION = 1; /** Algebra view style: definition */ final public static int ALGEBRA_STYLE_DEFINITION = 2; /** Algebra view style: definition and value */ final public static int ALGEBRA_STYLE_DEFINITION_AND_VALUE = 3; // critical for exam mode // must use getter private int algebraStyle = Kernel.ALGEBRA_STYLE_VALUE; private int algebraStyleSpreadsheet = Kernel.ALGEBRA_STYLE_VALUE; // end G.Sturr private MacroManager macroManager; /** * Specifies whether possible line breaks are to be marked in the String * representation of {@link ExpressionNode ExpressionNodes}. */ protected boolean insertLineBreaks = false; // angle unit: degree, radians // although this is initialized from the default preferences XML, // we need to initialize this here too for GeoGebraWeb private int angleUnit = Kernel.ANGLE_DEGREE; private boolean viewReiniting = false; private boolean undoActive = false; // Views may register to be informed about // changes to the Kernel // (add, remove, update) /** List of attached views */ protected ArrayList<View> views = new ArrayList<View>(); private boolean addingPolygon = false; private GeoElement newPolygon; private ArrayList<GeoElement> deleteList; /** Construction */ protected Construction cons; /** Algebra processor */ protected AlgebraProcessor algProcessor; /** Evaluator for ExpressionNode */ protected ExpressionNodeEvaluator expressionNodeEvaluator; /** * CAS variable handling * * so ggb variable "a" is sent to Giac as "ggbtmpvara" which is then * converted back to "a" when the result comes back from Giac * * must start with a letter before 'x' so that variable ordering works in * Giac * */ public static final String TMP_VARIABLE_PREFIX = "ggbtmpvar"; /** * used in the Prover */ public static final String TMP_VARIABLE_PREFIX2 = TMP_VARIABLE_PREFIX + "2"; // Continuity on or off, default: false since V3.0 private boolean continuous = false; public PathRegionHandling usePathAndRegionParameters = PathRegionHandling.ON; private GeoGebraCasInterface ggbCAS; /** Angle type: radians */ final public static int ANGLE_RADIANT = 1; /** Angle type: degrees */ final public static int ANGLE_DEGREE = 2; /** Coord system: cartesian */ final public static int COORD_CARTESIAN = 3; /** Coord system: polar */ final public static int COORD_POLAR = 4; /** Coord system: complex numbers */ final public static int COORD_COMPLEX = 5; /** Coord system: 3D cartesian */ final public static int COORD_CARTESIAN_3D = 6; /** Coord system: 3D spherical polar */ final public static int COORD_SPHERICAL = 7; /** 2*Pi */ final public static double PI_2 = 2.0 * Math.PI; /** Pi/2 */ final public static double PI_HALF = Math.PI / 2.0; /** sqrt(1/2) */ final public static double SQRT_2_HALF = Math.sqrt(2.0) / 2.0; /** One degree (Pi/180) */ final public static double PI_180 = Math.PI / 180; /** Radian to degree ratio (180/Pi) */ final public static double CONST_180_PI = 180 / Math.PI; /** maximum precision of double numbers */ public final static double MAX_DOUBLE_PRECISION = 1E-15; /** reciprocal of maximum precision of double numbers */ public final static double INV_MAX_DOUBLE_PRECISION = 1E15; /** maximum CAS results cached */ final public static int GEOGEBRA_CAS_CACHE_SIZE = 500; private MySpecialDouble eulerConstant; // print precision public static final int STANDARD_PRINT_DECIMALS = 2; // private double PRINT_PRECISION = 1E-2; private NumberFormatAdapter nf; private final ScientificFormatAdapter sf; public boolean useSignificantFigures = false; // style of point/vector coordinates /** A = (3, 2) and B = (3; 90^o) */ public static final int COORD_STYLE_DEFAULT = 0; /** A(3|2) and B(3; 90^o) */ public static final int COORD_STYLE_AUSTRIAN = 1; /** A: (3, 2) and B: (3; 90^o) */ public static final int COORD_STYLE_FRENCH = 2; private int coordStyle = 0; /** standard precision */ public final static double STANDARD_PRECISION = 1E-8; /** square root of standard precision */ public final static double STANDARD_PRECISION_SQRT = 1E-4; /** square of standard precision */ public final static double STANDARD_PRECISION_SQUARE = 1E-16; /** cube of standard precision */ public final static double STANDARD_PRECISION_CUBE = 1E-24; /** minimum precision */ public final static double MIN_PRECISION = 1E-5; private final static double INV_MIN_PRECISION = 1E5; /** maximum reasonable precision */ public final static double MAX_PRECISION = 1E-12; /** maximum axes can zoom to */ private final static double AXES_PRECISION = 1E-14; // private String stringTemplate.getPi(); // for pi // before May 23, 2005 the function acos(), asin() and atan() // had an angle as result. Now the result is a number. // this flag is used to distinguish the different behaviour // depending on the the age of saved construction files /** * if true, cyclometric functions return GeoAngle, if false, they return * GeoNumeric **/ private boolean useInternalCommandNames = false; private boolean notifyConstructionProtocolViewAboutAddRemoveActive = true; private boolean allowVisibilitySideEffects = true; // this flag was introduced for Copy & Paste private boolean saveScriptsToXML = true; private boolean elementDefaultAllowed = false; // silentMode is used to create helper objects without any side effects // i.e. in silentMode no labels are created and no objects are added to // views private boolean silentMode = false; private boolean wantAnimationStarted = false; // setResolveUnkownVarsAsDummyGeos private boolean resolveUnkownVarsAsDummyGeos = false; private boolean updateEVAgain = false; // used for DrawEquationWeb and // DrawText in GGW private boolean forceUpdatingBoundingBox = false; // used for // DrawEquationWeb and // DrawText in GGW private final StringBuilder sbBuildExplicitLineEquation = new StringBuilder( 50); /** Application */ protected App app; private EquationSolver eqnSolver; private SystemOfEquationsSolver sysEqSolv; private ExtremumFinderI extrFinder; /** Parser */ protected Parser parser; /** 3D manager */ private Manager3DInterface manager3D; private AlgoDispatcher algoDispatcher; private GeoFactory geoFactory; private GeoVec2D imaginaryUnit; private Exercise exercise; private Object concurrentModificationLock = new Object(); /** * @param app * Application */ public Kernel(App app, GeoFactory factory) { this(factory); this.app = app; newConstruction(); getExpressionNodeEvaluator(); setManager3D(newManager3D(this)); } /** * Creates kernel and initializes number formats and CAS prefix * * @param factory * factory for new elements */ protected Kernel(GeoFactory factory) { nf = FormatFactory.getPrototype().getNumberFormat(2); sf = FormatFactory.getPrototype().getScientificFormat(5, 16, false); this.deleteList = new ArrayList<GeoElement>(); this.geoFactory = factory; } /** * Returns this kernel's algebra processor that handles all input and * commands. * * @return Algebra processor */ public AlgebraProcessor getAlgebraProcessor() { if (algProcessor == null) { algProcessor = newAlgebraProcessor(this); } return algProcessor; } /** * @param kernel * kernel * @return a new algebra processor (used for 3D) */ public AlgebraProcessor newAlgebraProcessor(Kernel kernel) { return new AlgebraProcessor(kernel, app.getCommandDispatcher(kernel)); } /** * @param kernel * kernel * @return a new 3D manager */ protected Manager3DInterface newManager3D(Kernel kernel) { return null; } /** * Synchronized means that no two Threads can simultaneously enter any * blocks locked by the same lock object, but they can only wait for the * active Thread to exit from these blocks... as there is only one lock * object and these methods probably do not call other synchronized code * blocks, it probably does not cause any problem * * @return Object unique to the Application instance */ public Object getConcurrentModificationLock() { return concurrentModificationLock; } /** * sets the 3D manager * * @param manager */ public void setManager3D(Manager3DInterface manager) { this.manager3D = manager; } /** * * @return default plane (null for 2D implementation, xOy plane for 3D) */ public GeoPlaneND getDefaultPlane() { return null; } /** * @return the 3D manager of this */ public Manager3DInterface getManager3D() { return manager3D; } /** * creates the construction cons */ protected void newConstruction() { cons = new Construction(this); } /** * creates a new MyXMLHandler (used for 3D) * * @param cons1 * construction used in MyXMLHandler constructor * @return a new MyXMLHandler */ public MyXMLHandler newMyXMLHandler(Construction cons1) { return newMyXMLHandler(this, cons1); } /** * creates a new MyXMLHandler (used for 3D) * * @param kernel * kernel * @param cons1 * construction * @return a new MyXMLHandler */ public MyXMLHandler newMyXMLHandler(Kernel kernel, Construction cons1) { return new MyXMLHandler(kernel, cons1); } /** * @return app */ final public App getApplication() { return app; } /** * @return (polynomial) equation solver */ final public EquationSolver getEquationSolver() { if (eqnSolver == null) { eqnSolver = new EquationSolver(); } return eqnSolver; } /** * @param eSolver * single equation solver * @return system solver */ final public SystemOfEquationsSolver getSystemOfEquationsSolver( EquationSolverInterface eSolver) { if (sysEqSolv == null) { sysEqSolv = new SystemOfEquationsSolver(eSolver); } return sysEqSolv; } /** * @return extremum finding utility */ final public ExtremumFinderI getExtremumFinder() { if (extrFinder == null) { extrFinder = new ExtremumFinder(); } return extrFinder; } /** * @return parser for GGB and CAS expressions */ final public Parser getParser() { if (parser == null) { parser = new GParser(this, cons); } return parser; } /** * creates the Evaluator for ExpressionNode * * @param kernel * kernel to be used for new expression * * @return the Evaluator for ExpressionNode */ public ExpressionNodeEvaluator newExpressionNodeEvaluator(Kernel kernel) { return new ExpressionNodeEvaluator(app.getLocalization(), kernel); } /** * return the Evaluator for ExpressionNode * * @return the Evaluator for ExpressionNode */ public ExpressionNodeEvaluator getExpressionNodeEvaluator() { if (expressionNodeEvaluator == null) { expressionNodeEvaluator = newExpressionNodeEvaluator(this); } return expressionNodeEvaluator; } /** * * @param precision * max absolute value of difference * @return a double comparator which says doubles are equal if their diff is * less than precision */ final static public Comparator<Double> doubleComparator(double precision) { final double eps = precision; Comparator<Double> ret = new Comparator<Double>() { @Override public int compare(Double d1, Double d2) { if (Math.abs(d1 - d2) < eps) { return 0; } else if (d1 < d2) { return -1; } else { return 1; } } }; return ret; } // This is a temporary place for abstract adapter methods which will go into // factories later // Arpad Fekete, 2011-12-01 // public abstract ColorAdapter getColorAdapter(int red, int green, int // blue); protected AnimationManager animationManager; /* * If the data-param-showAnimationButton parameter for applet is false, be * sure not to show the animation button. In this case the value of * showAnimationButton is false, otherwise true. */ private boolean showAnimationButton = true; public void setShowAnimationButton(boolean showAB) { showAnimationButton = showAB; } final public boolean isAnimationRunning() { return animationManager != null && animationManager.isRunning(); } final public boolean isAnimationPaused() { return animationManager != null && animationManager.isPaused(); } final public double getFrameRate() { return animationManager.getFrameRate(); } final public boolean needToShowAnimationButton() { if (!showAnimationButton) { return false; } return animationManager != null && animationManager.needToShowAnimationButton(); } final public void udpateNeedToShowAnimationButton() { if (animationManager != null) { animationManager.updateNeedToShowAnimationButton(); } } /* * ******************************************* Methods for MyXMLHandler * ******************************************* */ public boolean handleCoords(GeoElement geo, LinkedHashMap<String, String> attrs) { if (!(geo instanceof GeoVec3D)) { Log.debug("wrong element type for <coords>: " + geo.getClass()); return false; } GeoVec3D v = (GeoVec3D) geo; try { double x = StringUtil.parseDouble(attrs.get("x")); double y = StringUtil.parseDouble(attrs.get("y")); double z = StringUtil.parseDouble(attrs.get("z")); v.hasUpdatePrevilege = true; v.setCoords(x, y, z); return true; } catch (Exception e) { return false; } } /* * ******************************************* Construction specific methods * ******************************************* */ /** * Returns the ConstructionElement for the given GeoElement. If geo is * independent geo itself is returned. If geo is dependent it's parent * algorithm is returned. * * @param geo * geo * @return geo or parent algo */ public static ConstructionElement getConstructionElement(GeoElement geo) { AlgoElement algo = geo.getParentAlgorithm(); if (algo == null) { return geo; } return algo; } /** * Returns the Construction object of this kernel. * * @return construction */ public Construction getConstruction() { return cons; } /** * Returns the ConstructionElement for the given construction index. * * @param index * construction index * @return corresponding element */ public ConstructionElement getConstructionElement(int index) { return cons.getConstructionElement(index); } /** * * @return first geo if exists */ public GeoElement getFirstGeo() { return cons.getFirstGeo(); } /** * @param step * new construction step */ public void setConstructionStep(int step) { if (cons.getStep() != step) { cons.setStep(step); getApplication().setUnsaved(); } } /** * @return construction step being done now */ public int getConstructionStep() { return cons.getStep(); } /** * @return */ public int getLastConstructionStep() { return cons.steps() - 1; } /** * Sets construction step to first step of construction protocol. Note: * showOnlyBreakpoints() is important here */ public void firstStep() { int step = 0; if (cons.showOnlyBreakpoints()) { setConstructionStep(getNextBreakpoint(step)); } else { setConstructionStep(step); } } /** * Sets construction step to last step of construction protocol. Note: * showOnlyBreakpoints() is important here */ public void lastStep() { int step = getLastConstructionStep(); if (cons.showOnlyBreakpoints()) { setConstructionStep(getPreviousBreakpoint(step)); } else { setConstructionStep(step); } } /** * Sets construction step to next step of construction protocol. Note: * showOnlyBreakpoints() is important here */ public void nextStep() { int step = cons.getStep() + 1; if (cons.showOnlyBreakpoints()) { setConstructionStep(getNextBreakpoint(step)); } else { ConstructionElement next = cons.getConstructionElement(step); if (next instanceof GeoElement && ((GeoElement) next).getCorrespondingCasCell() != null) { step++; } setConstructionStep(step); } } private int getNextBreakpoint(int initStep) { int step = initStep; int lastStep = getLastConstructionStep(); // go to next breakpoint while (step <= lastStep) { if (cons.getConstructionElement(step).isConsProtocolBreakpoint()) { return step; } step++; } return lastStep; } /** * Sets construction step to previous step of construction protocol Note: * showOnlyBreakpoints() is important here */ public void previousStep() { int step = cons.getStep() - 1; cons.setStep(getClosestStep(step)); } /** * @param step * raw step number * @return closest step number showable in construction protocol */ public int getClosestStep(int step) { if (cons.showOnlyBreakpoints()) { return getPreviousBreakpoint(step); } ConstructionElement prev = cons.getConstructionElement(step); /* * if (prev instanceof GeoElement && ((GeoElement) * prev).getCorrespondingCasCell() != null) { step--; } */ if (prev instanceof GeoCasCell && ((GeoCasCell) prev).getTwinGeo() != null && ((GeoCasCell) prev).getTwinGeo().isAlgebraVisible()) { return step - 1; } return step; } private int getPreviousBreakpoint(int initStep) { int step = initStep; // go to previous breakpoint while (step >= 0) { if (cons.getConstructionElement(step).isConsProtocolBreakpoint()) { return step; } step--; } return -1; } /** * Move object at position from to position to in current construction. * * @param from * original position * @param to * target position * @return true if succesful */ public boolean moveInConstructionList(int from, int to) { return cons.moveInConstructionList(from, to); } /** * Find geos with caption ending %style=... in this kernel, and set their * visual styles to those geos in the otherKernel which have the same * %style=... caption ending; as well as set %style=defaultStyle for all * geos * * @param otherKernel */ public void setVisualStyles(Kernel otherKernel) { TreeSet<GeoElement> okts = otherKernel.getConstruction() .getGeoSetWithCasCellsConstructionOrder(); ArrayList<GeoElement> selected = getApplication().getSelectionManager() .getSelectedGeos(); // maybe it's efficient to pre-filter this set to only contain // elements that have "%style=" styling Iterator<GeoElement> okit = okts.iterator(); GeoElement okactual; String okcapt; int okpos; while (okit.hasNext()) { okactual = okit.next(); okcapt = okactual.getCaptionSimple(); if (okcapt == null) { okpos = -1; } else { okpos = okcapt.indexOf("%style="); } if (okpos < 0) { // not having "%style=" setting, can be removed // lucky that iterator has this method okit.remove(); } } // okts is ready, now to the main loop Iterator<GeoElement> it; if (selected.isEmpty()) { it = cons.getGeoSetWithCasCellsConstructionOrder().iterator(); } else { it = selected.iterator(); } GeoElement actual; String capt; int pos; while (it.hasNext()) { actual = it.next(); // at first, apply default styles! // these are applied anyway, caption is not needed; // however, Geo type is needed! GeoClass gc = actual.getGeoClassType(); okit = okts.iterator(); while (okit.hasNext()) { okactual = okit.next(); okcapt = okactual.getCaptionSimple(); // as okts is pre-filtered, okcapt is not null okpos = okcapt.indexOf("%style=defaultStyle"); if (okpos > -1 && okactual.getGeoClassType() == gc) { // match! actual.setVisualStyle(okactual); } } // now, okit, okactual, okcapt, okpos can be redefined... capt = actual.getCaptionSimple(); if (capt == null) { pos = -1; } else { pos = capt.indexOf("%style="); } if (pos > -1) { // capt will not be needed until the next iteration capt = capt.substring(pos); // now, it's time to search for geos in otherKernel, // whether any of them has the same style ending okit = okts.iterator(); while (okit.hasNext()) { okactual = okit.next(); okcapt = okactual.getCaptionSimple(); // as okts is pre-filtered, okcapt is not null okpos = okcapt.indexOf("%style="); // as okts is pre-filtered, okpos is not -1 // although we could double-check, it's not important // okcapt will not be needed until the next iteration okcapt = okcapt.substring(okpos); if (capt.equals(okcapt)) { // match! actual.setVisualStyle(okactual); } } } } } public void setConstructionDefaults(Kernel otherKernel) { getConstruction().getConstructionDefaults().setConstructionDefaults( otherKernel.getConstruction().getConstructionDefaults()); } /** * @param flag * switches on or off putting scripts into XML */ public void setSaveScriptsToXML(boolean flag) { saveScriptsToXML = flag; } /** * * @return whether scripts should be put into XML or not */ public boolean getSaveScriptsToXML() { return saveScriptsToXML; } public void setElementDefaultAllowed(boolean flag) { elementDefaultAllowed = flag; } public boolean getElementDefaultAllowed() { return elementDefaultAllowed; } /** * States whether the continuity heuristic is active. * * @returns whether continuous mode is on */ final public boolean isContinuous() { return continuous; } /** * Turns the continuity heuristic on or off. Note: the macro kernel always * turns continuity off. */ public void setContinuous(boolean continuous) { this.continuous = continuous; } /** * States whether path/region parameters are used. Also test if point is * defined (if not, use parameters). * * @param point * point * @return true if given point should use path/region parameter */ final public boolean usePathAndRegionParameters(GeoPointND point) { return usePathAndRegionParameters == PathRegionHandling.ON || (!point.isDefined()); } /** * Turns the using of path/region parameters on or off. * * @param flag * new flag for using path/region parameters */ public void setUsePathAndRegionParameters(PathRegionHandling flag) { this.usePathAndRegionParameters = flag; } // loading mode: true when a ggb file is being loaded. Devised for backward // compatibility. private boolean loadingMode; /** * * @param b * true to indicate that file is being loaded */ public void setLoadingMode(boolean b) { loadingMode = b; } /** * @return whether file is being loaded */ public boolean getLoadingMode() { return loadingMode; } final private static char sign(double x) { if (x > 0) { return '+'; } return '-'; } public void setNotifyConstructionProtocolViewAboutAddRemoveActive( boolean flag) { notifyConstructionProtocolViewAboutAddRemoveActive = flag; } public boolean isNotifyConstructionProtocolViewAboutAddRemoveActive() { return notifyConstructionProtocolViewAboutAddRemoveActive; } // see http://code.google.com/p/google-web-toolkit/issues/detail?id=4097 public final StringBuilder buildImplicitEquation(double[] numbers, String[] vars, boolean KEEP_LEADING_SIGN, boolean CANCEL_DOWN, boolean needsZ, char op, StringTemplate tpl, boolean implicit) { sbBuildImplicitEquation.setLength(0); double[] temp = buildImplicitVarPart(sbBuildImplicitEquation, numbers, vars, KEEP_LEADING_SIGN || (op == '='), CANCEL_DOWN, needsZ, tpl); if (!implicit && !isZeroFigure(temp[vars.length], tpl)) { String sign; double abs; if (temp[vars.length] < 0.0) { sign = " - "; abs = -temp[vars.length]; } else { sign = " + "; abs = temp[vars.length]; } sbBuildImplicitEquation.append(sign); sbBuildImplicitEquation.append(format(abs, tpl)); } sbBuildImplicitEquation.append(' '); sbBuildImplicitEquation.append(op); sbBuildImplicitEquation.append(' '); if (implicit) { // temp is set by buildImplicitVarPart sbBuildImplicitEquation.append(format(-temp[vars.length], tpl)); } else { sbBuildImplicitEquation.append(format(0.0, tpl)); } return sbBuildImplicitEquation; } private StringBuilder sbFormat; final public void formatSignedCoefficient(double x, StringBuilder sb, StringTemplate tpl) { if (x == -1.0) { sb.append("- "); return; } if (x == 1.0) { sb.append("+ "); return; } formatSigned(x, sb, tpl); } final public void formatSigned(double x, StringBuilder sb, StringTemplate tpl) { if (x >= 0.0d) { sb.append("+ "); sb.append(format(x, tpl)); return; } sb.append("- "); sb.append(format(-x, tpl)); } final public void formatSignedCoefficientPlusMinus(double x, StringBuilder sb, StringTemplate tpl) { if (x == -1.0) { sb.append(STRING_MINUS_PLUS); return; } if (x == 1.0) { sb.append(STRING_PLUS_MINUS); return; } formatSignedPlusMinus(x, sb, tpl); } final public void formatSignedPlusMinus(double x, StringBuilder sb, StringTemplate tpl) { if (x >= 0.0d) { sb.append(STRING_PLUS_MINUS); sb.append(format(x, tpl)); return; } sb.append(STRING_MINUS_PLUS); sb.append(format(-x, tpl)); } final private String formatPiERaw(double x, NumberFormatAdapter numF, StringTemplate tpl) { // PI if (x == Math.PI && tpl.allowPiHack()) { return tpl.getPi(); } // MULTIPLES OF PI/2 // i.e. x = a * pi/2 double a = (2 * x) / Math.PI; int aint = (int) Math.round(a); if (sbFormat == null) { sbFormat = new StringBuilder(); } sbFormat.setLength(0); if (isEqual(a, aint, AXES_PRECISION)) { switch (aint) { case 0: return "0"; case 1: // pi/2 sbFormat.append(tpl.getPi()); sbFormat.append("/2"); return sbFormat.toString(); case -1: // -pi/2 sbFormat.append('-'); sbFormat.append(tpl.getPi()); sbFormat.append("/2"); return sbFormat.toString(); case 2: // 2pi/2 = pi return tpl.getPi(); case -2: // -2pi/2 = -pi sbFormat.append('-'); sbFormat.append(tpl.getPi()); return sbFormat.toString(); default: // even long half = aint / 2; if (aint == (2 * half)) { // half * pi sbFormat.append(half); if (!tpl.hasType(StringType.GEOGEBRA)) { sbFormat.append("*"); } sbFormat.append(tpl.getPi()); return sbFormat.toString(); } // odd // aint * pi/2 sbFormat.append(aint); if (!tpl.hasType(StringType.GEOGEBRA)) { sbFormat.append("*"); } sbFormat.append(tpl.getPi()); sbFormat.append("/2"); return sbFormat.toString(); } } // STANDARD CASE // use numberformat to get number string // checkDecimalFraction() added to avoid 2.19999999999999 when set to // 15dp String str = numF.format(checkDecimalFraction(x)); sbFormat.append(str); // if number is in scientific notation and ends with "E0", remove this if (str.endsWith("E0")) { sbFormat.setLength(sbFormat.length() - 2); } return sbFormat.toString(); } /** * Converts the double into a fraction based on the current kernel rounding * precision. * * @param x * input number to be rationalized * @return numerator and denominator */ public long[] doubleToRational(double x) { long[] ret = new long[2]; ret[1] = precision(); ret[0] = Math.round(x * precision()); long gcd = gcd(ret[0], ret[1]); ret[0] /= gcd; ret[1] /= gcd; return ret; } /** * Formats the value of x using the currently set NumberFormat or * ScientificFormat. * * @param number * number * @param tpl * string template * @return formated number as string */ final public String formatRaw(double number, StringTemplate tpl) { double x = number; // format integers without significant figures boolean isLongInteger = false; long rounded = Math.round(x); if ((x == rounded) && (x >= Long.MIN_VALUE) && (x < Long.MAX_VALUE)) { isLongInteger = true; } StringType casPrintForm = tpl.getStringType(); switch (casPrintForm) { // to avoid 1/3 = 0 case PSTRICKS: case PGF: return MyDouble.toString(x); // number formatting for XML string output case GEOGEBRA_XML: if (isLongInteger) { return Long.toString(rounded); } else if (Double.isNaN(number) && tpl.hasQuestionMarkForNaN()) { return "?"; } // #5149 return MyDouble.toString(x); // number formatting for CAS case GIAC: if (Double.isNaN(x)) { return "?"; } else if (Double.isInfinite(x)) { return (x < 0) ? "-inf" : "inf"; } else if (isLongInteger) { return Long.toString(rounded); } else if (Kernel.isZero(x, Kernel.MAX_PRECISION)) { // #4802 return "0"; } else { double abs = Math.abs(x); // number small enough that Double.toString() won't create E // notation if ((abs >= 10E-3) && (abs < 10E7)) { String ret = MyDouble.toString(x); // convert 0.125 to 1/8 so Giac treats it as an exact number // Note: exact(0.3333333333333) gives 1/3 if (ret.indexOf('.') > -1) { return StringUtil.wrapInExact(x, ret, tpl, this); } return ret; } // convert scientific notation 1.0E-20 to 1*10^(-20) String scientificStr = MyDouble.toString(x); return tpl.convertScientificNotation(scientificStr); } // number formatting for screen output default: if (Double.isNaN(x)) { return "?"; } else if (Double.isInfinite(x)) { return (x > 0) ? "\u221e" : "-\u221e"; // infinity } else if (x == Math.PI && tpl.allowPiHack()) { return tpl.getPi(); } boolean useSF = tpl.useScientific(useSignificantFigures); // ROUNDING hack // NumberFormat and SignificantFigures use ROUND_HALF_EVEN as // default which is not changeable, so we need to hack this // to get ROUND_HALF_UP like in schools: increase abs(x) slightly // x = x * ROUND_HALF_UP_FACTOR; // We don't do this for large numbers as if (!isLongInteger) { double abs = Math.abs(x); // increase abs(x) slightly to round up x = x * tpl.getRoundHalfUpFactor(abs, nf, sf, useSF); } if (useSF) { return formatSF(x, tpl); } return formatNF(x, tpl); } } /** * Uses current NumberFormat nf to format a number. */ final private String formatNF(double x, StringTemplate tpl) { // "<=" catches -0.0000000000000005 // should be rounded to -0.000000000000001 (15 d.p.) // but nf.format(x) returns "-0" double printPrecision = tpl.getPrecision(nf); if (((-printPrecision / 2) <= x) && (x < (printPrecision / 2))) { // avoid output of "-0" for eg -0.0004 return "0"; } // standard case // nf = FormatFactory.prototype.getNumberFormat(2); NumberFormatAdapter nfa = tpl.getNF(nf); return nfa.format(x); } private StringBuilder formatSB; /** * Formats the value of x using the currently set NumberFormat or * ScientificFormat. * * converts to localised digits if appropriate * * @param x * number * @param tpl * string template * @return formated string */ final public String format(double x, StringTemplate tpl) { // App.printStacktrace(x+""); String ret = formatRaw(x, tpl); if (app.getLocalization().unicodeZero != '0') { ret = internationalizeDigits(ret, tpl); } return ret; } /** * swaps the digits in num to the current locale's * * @param num * english number * @param tpl * template * @return localized number */ public String internationalizeDigits(String num, StringTemplate tpl) { if (!tpl.internationalizeDigits() || !getLocalization().isUsingLocalizedDigits()) { return num; } if (formatSB == null) { formatSB = new StringBuilder(17); } else { formatSB.setLength(0); } boolean negative = num.charAt(0) == '-'; int start = 0; // make sure minus sign works in Arabic boolean RTL = getLocalization().isRightToLeftDigits(tpl); if (RTL) { formatSB.append(Unicode.RightToLeftMark); if (negative) { formatSB.append(Unicode.RightToLeftUnaryMinusSign); start = 1; } } for (int i = start; i < num.length(); i++) { char c = RTL ? num.charAt(num.length() - (negative ? 0 : 1) - i) : num.charAt(i); if (c == '.') { c = getLocalization().unicodeDecimalPoint; } else if ((c >= '0') && (c <= '9')) { // convert to eg Arabic Numeral c += app.getLocalization().unicodeZero - '0'; } formatSB.append(c); } if (RTL) { formatSB.append(Unicode.RightToLeftMark); } return formatSB.toString(); } /** * calls formatPiERaw() and converts to localised digits if appropriate * * @param x * number * @param numF * number format * @param tpl * string template * @return formated number with e's and pi's replaced by suitable symbols */ final public String formatPiE(double x, NumberFormatAdapter numF, StringTemplate tpl) { if (app.getLocalization().unicodeZero != '0') { String num = formatPiERaw(x, numF, tpl); return internationalizeDigits(num, tpl); } return formatPiERaw(x, numF, tpl); } private final StringBuilder sbBuildImplicitEquation = new StringBuilder(80); /** * copy array a to array b * * @param a * input array * @param b * output array */ final static void copy(double[] a, double[] b) { for (int i = 0; i < a.length; i++) { b[i] = a[i]; } } /** * change signs of double array values, write result to array b * * @param a * input array * @param b * output array */ final static void negative(double[] a, double[] b) { for (int i = 0; i < a.length; i++) { b[i] = -a[i]; } } /** * Computes c[] = a[] / b * * @param a * array of dividends * @param b * divisor * @param c * array for results */ final static void divide(double[] a, double b, double[] c) { for (int i = 0; i < a.length; i++) { c[i] = a[i] / b; } } /** * greatest common divisor * * @param m * firs number * @param n * second number * @return GCD of given numbers */ final public static long gcd(long m, long n) { // Return the GCD of positive integers m and n. if ((m == 0) || (n == 0)) { return Math.max(Math.abs(m), Math.abs(n)); } long p = m, q = n; while ((p % q) != 0) { long r = p % q; p = q; q = r; } return q; } /** * Compute greatest common divisor of given doubles. Note: all double values * are cast to long. * * @param numbers * array of numbers * @return GCD of given numbers */ final public static double gcd(double[] numbers) { long gcd = (long) numbers[0]; for (int i = 0; i < numbers.length; i++) { gcd = gcd((long) numbers[i], gcd); } return Math.abs(gcd); } /** * Round a double to the given scale e.g. roundToScale(5.32, 1) = 5.0, * roundToScale(5.32, 0.5) = 5.5, roundToScale(5.32, 0.25) = 5.25, * roundToScale(5.32, 0.1) = 5.3 * * @param x * number * @param scale * rounding step * @return rounded number */ final public static double roundToScale(double x, double scale) { if (scale == 1.0) { return Math.round(x); } return Math.round(x / scale) * scale; } /** * compares double arrays: * * @param a * first array * @param b * second array * @return true if (isEqual(a[i], b[i]) == true) for all i */ final static boolean isEqual(double[] a, double[] b) { for (int i = 0; i < a.length; ++i) { if (!isEqual(a[i], b[i])) { return false; } } return true; } /* * // calc acos(x). returns 0 for x > 1 and pi for x < -1 final static * double trimmedAcos(double x) { if (Math.abs(x) <= 1.0d) return * Math.acos(x); else if (x > 1.0d) return 0.0d; else if (x < -1.0d) return * Math.PI; else return Double.NaN; } */ /** * Computes max of abs(a[i]) * * @param a * array of numbers * @return max of abs(a[i]) */ final static double maxAbs(double[] a) { double temp, max = Math.abs(a[0]); for (int i = 1; i < a.length; i++) { temp = Math.abs(a[i]); if (temp > max) { max = temp; } } return max; } /** * @param numbers * coefficients * @param vars * variables * @param KEEP_LEADING_SIGN * whether output may start with - * @param CANCEL_DOWN * whether we want to cancel GCD * @param needsZ * whether explicit Z is necessary * @param tpl * template * @return LHS string */ final public StringBuilder buildLHS(double[] numbers, String[] vars, boolean KEEP_LEADING_SIGN, boolean CANCEL_DOWN, boolean needsZ, StringTemplate tpl) { return buildLHS(numbers, vars, KEEP_LEADING_SIGN, CANCEL_DOWN, needsZ, false, tpl); } /** * Builds lhs of lhs = 0 * * @param numbers * coefficients * @param vars * variable names * @param KEEP_LEADING_SIGN * true to keep leading sign * @param CANCEL_DOWN * true to allow canceling 2x+4y -> x+2y * @param needsZ * whether "+0z" is needed when z is not present * @param setConstantIfNoLeading * whether constant or 0 should be added if all var coeff are 0 * @param tpl * string template * @return string representing LHS */ final public StringBuilder buildLHS(double[] numbers, String[] vars, boolean KEEP_LEADING_SIGN, boolean CANCEL_DOWN, boolean needsZ, boolean setConstantIfNoLeading, StringTemplate tpl) { sbBuildLHS.setLength(0); double[] temp = buildImplicitVarPart(sbBuildLHS, numbers, vars, KEEP_LEADING_SIGN, CANCEL_DOWN, needsZ, setConstantIfNoLeading, tpl); // add constant coeff double coeff = temp[vars.length]; appendConstant(sbBuildLHS, coeff, tpl); return sbBuildLHS; } /** * append +/- constant * * @param sb * string builder to append to * @param coeff * constant * @param tpl * string template */ public final void appendConstant(StringBuilder sb, double coeff, StringTemplate tpl) { if ((Math.abs(coeff) >= tpl.getPrecision(nf)) || useSignificantFigures) { sb.append(' '); sb.append(sign(coeff)); sb.append(' '); sb.append(format(Math.abs(coeff), tpl)); } } private final StringBuilder sbBuildLHS = new StringBuilder(80); private final StringBuilder sbBuildExplicitConicEquation = new StringBuilder( 80); // y = k x + d /** * Inverts the > or < sign * * @param op * =,<,>,\u2264 or \u2265 * @return opposite sign */ public static char oppositeSign(char op) { switch (op) { case '=': return '='; case '<': return '>'; case '>': return '<'; case '\u2264': return '\u2265'; case '\u2265': return '\u2264'; default: return '?'; // should never happen } } // lhs of implicit equation without constant coeff final private double[] buildImplicitVarPart( StringBuilder sbBuildImplicitVarPart, double[] numbers, String[] vars, boolean KEEP_LEADING_SIGN, boolean CANCEL_DOWN, boolean needsZ, StringTemplate tpl) { return buildImplicitVarPart(sbBuildImplicitVarPart, numbers, vars, KEEP_LEADING_SIGN, CANCEL_DOWN, needsZ, false, tpl); } // lhs of implicit equation without constant coeff final private double[] buildImplicitVarPart( StringBuilder sbBuildImplicitVarPart, double[] numbers, String[] vars, boolean KEEP_LEADING_SIGN, boolean CANCEL_DOWN, boolean needsZ, boolean setConstantIfNoLeading, StringTemplate tpl) { double[] temp = new double[numbers.length]; int leadingNonZero = -1; sbBuildImplicitVarPart.setLength(0); for (int i = 0; i < vars.length; i++) { if (!isZero(numbers[i])) { leadingNonZero = i; break; } } if (CANCEL_DOWN) { // check if integers and divide through gcd boolean allIntegers = true; for (int i = 0; i < numbers.length; i++) { allIntegers = allIntegers && isInteger(numbers[i]); } if (allIntegers) { // divide by greates common divisor divide(numbers, gcd(numbers), numbers); } } // no left hand side if (leadingNonZero == -1) { if (setConstantIfNoLeading) { double coeff = numbers[vars.length]; if ((Math.abs(coeff) >= tpl.getPrecision(nf)) || useSignificantFigures) { sbBuildImplicitVarPart.append(format(coeff, tpl)); } else { sbBuildImplicitVarPart.append("0"); } return temp; } sbBuildImplicitVarPart.append("0"); return temp; } // don't change leading coefficient if (KEEP_LEADING_SIGN) { copy(numbers, temp); } else { if (numbers[leadingNonZero] < 0) { negative(numbers, temp); } else { copy(numbers, temp); } } // BUILD EQUATION STRING // valid left hand side // leading coefficient String strCoeff = formatCoeff(temp[leadingNonZero], tpl); sbBuildImplicitVarPart.append(strCoeff); sbBuildImplicitVarPart.append(vars[leadingNonZero]); // other coefficients on lhs String sign; double abs; for (int i = leadingNonZero + 1; i < vars.length; i++) { if (temp[i] < 0.0) { sign = " - "; abs = -temp[i]; } else { sign = " + "; abs = temp[i]; } if ((abs >= tpl.getPrecision(nf)) || useSignificantFigures || (needsZ && i == 2)) { sbBuildImplicitVarPart.append(sign); sbBuildImplicitVarPart.append(formatCoeff(abs, tpl)); sbBuildImplicitVarPart.append(vars[i]); } } return temp; } // private final StringBuilder sbBuildImplicitVarPart = new // StringBuilder(80); /** * * @param x * value * @param tpl * @return true if x is built as "0" */ private boolean isZeroFigure(double x, StringTemplate tpl) { return !useSignificantFigures && (Math.abs(x) <= tpl.getPrecision(nf)); } /** * form: y^2 = f(x) (coeff of y = 0) * * @param numbers * coefficients * @param vars * variables * @param pos * position of y^2 coefficient * @param KEEP_LEADING_SIGN * whether leading sign should be kept * @param tpl * string template * @return explicit equation of conic */ public final StringBuilder buildExplicitConicEquation(double[] numbers, String[] vars, int pos, boolean KEEP_LEADING_SIGN, StringTemplate tpl) { // y^2-coeff is 0 double d, dabs, q = numbers[pos]; // coeff of y^2 is 0 or coeff of y is not 0 if (isZero(q)) { return buildImplicitEquation(numbers, vars, KEEP_LEADING_SIGN, true, false, '=', tpl, true); } int i, leadingNonZero = numbers.length; for (i = 0; i < numbers.length; i++) { if ((i != pos) && // except y^2 coefficient ((Math.abs(numbers[i]) >= tpl.getPrecision(nf)) || useSignificantFigures)) { leadingNonZero = i; break; } } // BUILD EQUATION STRING sbBuildExplicitConicEquation.setLength(0); sbBuildExplicitConicEquation.append(vars[pos]); sbBuildExplicitConicEquation.append(" = "); if (leadingNonZero == numbers.length) { sbBuildExplicitConicEquation.append("0"); return sbBuildExplicitConicEquation; } else if (leadingNonZero == (numbers.length - 1)) { // only constant coeff d = -numbers[leadingNonZero] / q; sbBuildExplicitConicEquation.append(format(d, tpl)); return sbBuildExplicitConicEquation; } else { // leading coeff d = -numbers[leadingNonZero] / q; sbBuildExplicitConicEquation.append(formatCoeff(d, tpl)); sbBuildExplicitConicEquation.append(vars[leadingNonZero]); // other coeffs for (i = leadingNonZero + 1; i < vars.length; i++) { if (i != pos) { d = -numbers[i] / q; dabs = Math.abs(d); if ((dabs >= tpl.getPrecision(nf)) || useSignificantFigures) { sbBuildExplicitConicEquation.append(' '); sbBuildExplicitConicEquation.append(sign(d)); sbBuildExplicitConicEquation.append(' '); sbBuildExplicitConicEquation .append(formatCoeff(dabs, tpl)); sbBuildExplicitConicEquation.append(vars[i]); } } } // constant coeff d = -numbers[i] / q; dabs = Math.abs(d); if ((dabs >= tpl.getPrecision(nf)) || useSignificantFigures) { sbBuildExplicitConicEquation.append(' '); sbBuildExplicitConicEquation.append(sign(d)); sbBuildExplicitConicEquation.append(' '); sbBuildExplicitConicEquation.append(format(dabs, tpl)); } // Application.debug(sbBuildExplicitConicEquation.toString()); return sbBuildExplicitConicEquation; } } public static String squared(StringTemplate tpl) { String squared; switch (tpl.getStringType()) { case LATEX: squared = "^{2}"; break; case GIAC: squared = "^2"; break; default: squared = "\u00b2"; } return squared; } /** * y = a (x + h)^2 + k * * @param numbers * coefficients * @param vars * strings to substitute for xx,yy,xy,x,y (only indices 3 and 4 * are used) * @param tpl * template * @return vertex equation of the parabola */ public final StringBuilder buildVertexformEquation(double[] numbers, String[] vars, StringTemplate tpl) { double a = -1 * numbers[0] / numbers[4]; double h = numbers[3] / numbers[0] / 2; double k = (numbers[3] * numbers[3]) / (4 * numbers[4] * numbers[0]) - (numbers[5] / numbers[4]); StringBuilder sbBuildVertexformEquation = new StringBuilder(80); sbBuildVertexformEquation.append(vars[4]); sbBuildVertexformEquation.append(" = "); sbBuildVertexformEquation.append(formatCoeff(a, tpl)); if (h == 0) { sbBuildVertexformEquation.append(vars[3]); sbBuildVertexformEquation.append(squared(tpl)); } else { sbBuildVertexformEquation.append("("); sbBuildVertexformEquation.append(vars[3]); sbBuildVertexformEquation.append(" "); sbBuildVertexformEquation.append(sign(h)); sbBuildVertexformEquation.append(' '); sbBuildVertexformEquation.append(format(Math.abs(h), tpl)); sbBuildVertexformEquation.append(")"); sbBuildVertexformEquation.append(squared(tpl)); } if (k != 0) { sbBuildVertexformEquation.append(" "); sbBuildVertexformEquation.append(sign(k)); sbBuildVertexformEquation.append(format(Math.abs(k), tpl)); } return sbBuildVertexformEquation; } /* * 4p(y-k) = (x-h)^2 */ public final StringBuilder buildConicformEquation(double[] numbers, String[] vars, StringTemplate tpl) { StringBuilder sbBuildConicformEquation = new StringBuilder(80); double h, p4, k, a, b, c, d; String var1, var2; if (numbers[0] == 0) { // directrix parallel with y axis a = numbers[2]; b = numbers[4]; c = numbers[3]; d = numbers[5]; var1 = vars[4]; var2 = vars[3]; } else { // directrix parallel with x axis a = numbers[0]; b = numbers[3]; c = numbers[4]; d = numbers[5]; var1 = vars[3]; var2 = vars[4]; } h = -b / a / 2; p4 = -c / a; k = b * b / (4 * a * c) - d / c; sbBuildConicformEquation.append(formatCoeff(p4, tpl) + "(" + var2 + " " + sign(-k) + " " + format(Math.abs(k), tpl) + ") = " + "(" + var1 + " " + sign(-h) + " " + format(Math.abs(h), tpl) + ")" + squared(tpl)); return sbBuildConicformEquation; } /** * Uses current ScientificFormat sf to format a number. Makes sure ".123" is * returned as "0.123". */ final private String formatSF(double x, StringTemplate tpl) { if (sbFormatSF == null) { sbFormatSF = new StringBuilder(); } else { sbFormatSF.setLength(0); } ScientificFormatAdapter sfa = tpl.getSF(sf); // get scientific format String absStr; if (x == 0) { // avoid output of "-0.00" absStr = sfa.format(0); } else if (x > 0) { absStr = sfa.format(x); } else { sbFormatSF.append('-'); absStr = sfa.format(-x); } // make sure ".123" is returned as "0.123". if (absStr.charAt(0) == '.') { sbFormatSF.append('0'); } sbFormatSF.append(absStr); return sbFormatSF.toString(); } private StringBuilder sbFormatSF; /** * append "two coeffs" expression * * @param plusMinusX * says if we want "+-" before x coeffs * @param x * first coeff * @param y * second coeff * @param s1 * first string * @param s2 * second string * @param tpl * template * @param sbBuildValueString * string builder */ public final void appendTwoCoeffs(boolean plusMinusX, double x, double y, String s1, String s2, StringTemplate tpl, StringBuilder sbBuildValueString) { if (isZeroFigure(x, tpl)) { if (isZeroFigure(y, tpl)) { sbBuildValueString.append("0"); } else { String coeff = formatCoeff(y, tpl); sbBuildValueString.append(coeff); if (coeff.length() > 0) { sbBuildValueString.append(" "); // no need if no coeff } sbBuildValueString.append(s2); } } else { if (plusMinusX) { formatSignedCoefficientPlusMinus(x, sbBuildValueString, tpl); sbBuildValueString.append(" "); } else { String coeff = formatCoeff(x, tpl); sbBuildValueString.append(coeff); if (coeff.length() > 0) { sbBuildValueString.append(" "); // no need if no coeff } } sbBuildValueString.append(s1); if (!isZeroFigure(y, tpl)) { sbBuildValueString.append(" "); formatSignedCoefficient(y, sbBuildValueString, tpl); sbBuildValueString.append(" "); sbBuildValueString.append(s2); } } } /** * Appends one of "0", "x", "y", "x + y" * * @param x * first coefficient * @param y * second coefficient * @param tpl * template * @param sbBuildValueString * string builder */ public final void appendTwoCoeffs(double x, double y, StringTemplate tpl, StringBuilder sbBuildValueString) { if (isZeroFigure(x, tpl)) { if (isZeroFigure(y, tpl)) { sbBuildValueString.append("0"); } else { formatSignedPlusMinus(y, sbBuildValueString, tpl); } } else { sbBuildValueString.append(format(x, tpl)); if (!isZeroFigure(y, tpl)) { sbBuildValueString.append(" "); formatSignedPlusMinus(y, sbBuildValueString, tpl); } } } /** doesn't show 1 or -1 */ private final String formatCoeff(double x, StringTemplate tpl) { // TODO // make // private if (Math.abs(x) == 1.0) { if (x > 0.0) { return ""; } return "-"; } String numberStr = format(x, tpl); switch (tpl.getStringType()) { case GIAC: return numberStr + "*"; default: // standard case return numberStr; } } public final StringBuilder buildExplicitEquation(double[] numbers, String[] vars, char opDefault, StringTemplate tpl, boolean explicit) { char op = opDefault; double d, dabs, q = numbers[1]; sbBuildExplicitLineEquation.setLength(0); // BUILD EQUATION STRING // special case // y-coeff is 0: if explicit equation: form x = constant // if general eq: form x + constant = 0 if (isZero(q)) { sbBuildExplicitLineEquation.append(vars[0]); sbBuildExplicitLineEquation.append(' '); if (numbers[0] < MIN_PRECISION) { op = oppositeSign(op); } if (!explicit) { double constant = numbers[2] / numbers[0]; String sign; double abs; if (constant < 0.0) { sign = " - "; abs = -constant; } else { sign = " + "; abs = constant; } sbBuildExplicitLineEquation .append(sign + " " + format(abs, tpl) + " "); } sbBuildExplicitLineEquation.append(op); sbBuildExplicitLineEquation.append(' '); if (explicit) { sbBuildExplicitLineEquation .append(format(-numbers[2] / numbers[0], tpl)); } else { sbBuildExplicitLineEquation.append(format(0.0, tpl)); } return sbBuildExplicitLineEquation; } // standard case: y-coeff not 0 sbBuildExplicitLineEquation.append(vars[1]); sbBuildExplicitLineEquation.append(' '); if (numbers[1] < MIN_PRECISION) { op = oppositeSign(op); } // general line equation, coeff of x is null if (!explicit) { if (useSignificantFigures) { sbBuildExplicitLineEquation .append("+ " + format(0.0, tpl) + vars[0]); } d = numbers[2] / q; dabs = Math.abs(d); sbBuildExplicitLineEquation.append(sign(d)); sbBuildExplicitLineEquation.append(' '); sbBuildExplicitLineEquation.append(format(dabs, tpl)); sbBuildExplicitLineEquation.append(' '); sbBuildExplicitLineEquation.append(op); sbBuildExplicitLineEquation.append(formatCoeff(0.0, tpl)); return sbBuildExplicitLineEquation; } sbBuildExplicitLineEquation.append(op); sbBuildExplicitLineEquation.append(' '); // x coeff d = -numbers[0] / q; dabs = Math.abs(d); if ((dabs >= tpl.getPrecision(nf)) || useSignificantFigures) { sbBuildExplicitLineEquation.append(formatCoeff(d, tpl)); if (tpl.hasType(StringType.LATEX)) { sbBuildExplicitLineEquation.append(' '); } sbBuildExplicitLineEquation.append(vars[0]); // constant d = -numbers[2] / q; dabs = Math.abs(d); if ((dabs >= tpl.getPrecision(nf)) || useSignificantFigures) { sbBuildExplicitLineEquation.append(' '); sbBuildExplicitLineEquation.append(sign(d)); sbBuildExplicitLineEquation.append(' '); sbBuildExplicitLineEquation.append(format(dabs, tpl)); } } else { // only constant sbBuildExplicitLineEquation.append(format(-numbers[2] / q, tpl)); } return sbBuildExplicitLineEquation; } /** * if x is nearly zero, 0.0 is returned, else x is returned */ final public static double chop(double x) { if (isZero(x)) { return 0.0d; } return x; } /** is abs(x) < epsilon ? */ final public static boolean isZero(double x) { return (-STANDARD_PRECISION < x) && (x < STANDARD_PRECISION); } /** is abs(x) < epsilon ? */ final public static boolean isZero(double x, double eps) { return (-eps < x) && (x < eps); } /** * * check if e is zero in comparison to STANDARD_PRECISION and x * * @param e * @param x * @return */ final public static boolean isEpsilon(double e, double x) { return isEpsilonWithPrecision(e, x, STANDARD_PRECISION); } /** * * check if e is zero in comparison to eps and x * * @param e * @param x * @return */ final public static boolean isEpsilonWithPrecision(double e, double x, double eps) { double eAbs = Math.abs(e); if (eAbs > eps) { return false; } if (eAbs > Math.abs(x) * eps) { return false; } return true; } /** * * check if e is zero in comparison to x * * @param e * @param x * @return */ final public static boolean isEpsilonToX(double e, double x) { return Math.abs(e) < Math.abs(x) * STANDARD_PRECISION; } /** * * check if a point is zero, see #5202 * * @param e * @param x * @param y * @return */ final public static boolean isEpsilon(double e, double x, double y) { double eAbs = Math.abs(e); if (eAbs > STANDARD_PRECISION) { return false; } if (eAbs > Math.abs(x) * STANDARD_PRECISION) { return false; } if (eAbs > Math.abs(y) * STANDARD_PRECISION) { return false; } return true; } /** * * check if a point is zero, see #5202 * * @param e * @param x * @param y * @param z * * @return */ final public static boolean isEpsilon(double e, double x, double y, double z) { double eAbs = Math.abs(e); if (eAbs > STANDARD_PRECISION) { return false; } if (eAbs > Math.abs(x) * STANDARD_PRECISION) { return false; } if (eAbs > Math.abs(y) * STANDARD_PRECISION) { return false; } if (eAbs > Math.abs(z) * STANDARD_PRECISION) { return false; } return true; } /** * @param a * array of numbers * @return whether all given numbers are zero within current precision */ final static boolean isZero(double[] a) { for (int i = 0; i < a.length; i++) { if (!isZero(a[i])) { return false; } } return true; } /** * @param x * number * @return whether fractional part of the number is zero within current * precision (false for +/-Infinity, NaN */ final public static boolean isInteger(double x) { if (Double.isInfinite(x) || Double.isNaN(x)) { return false; } if (x > 1E17 || x < -1E17) { return true; } return isEqual(x, Math.round(x)); } /** * Check difference is less than a constant * * infinity == infinity returns true eg 1/0 * * -infinity == infinity returns false eg -1/0 * * -infinity == -infinity returns true * * undefined == undefined returns false eg 0/0 * * @return whether x is equal to y * * */ final public static boolean isEqual(double x, double y) { if (x == y) { return true; } return ((x - STANDARD_PRECISION) <= y) && (y <= (x + STANDARD_PRECISION)); } /** * Check difference is small, proportional to numbers * * @param x * first number * @param y * second number * @return x==y */ final public static boolean isRatioEqualTo1(double x, double y) { if (x == y) { return true; } double eps = STANDARD_PRECISION * Math.min(Math.abs(x), Math.abs(y)); return ((x - eps) <= y) && (y <= (x + eps)); } /** * @param x * first compared number * @param y * second compared number * @param eps * maximum difference * @return whether the x-eps < y < x+eps */ final public static boolean isEqual(double x, double y, double eps) { if (x == y) { return true; } return ((x - eps) < y) && (y < (x + eps)); } /** * Returns whether x is greater than y * * @param x * first compared number * @param y * second compared number * @return x > y + STANDARD_PRECISION */ final public static boolean isGreater(double x, double y) { return x > (y + STANDARD_PRECISION); } /** * * @param x * first value * @param y * second value * @return 0 if x ~ y ; -1 if x < y ; 1 if x > y */ final public static int compare(double x, double y) { if (isGreater(x, y)) { return 1; } if (isGreater(y, x)) { return -1; } return 0; } /** * Returns whether x is greater than y * * @param x * x * @param y * y * @param eps * tolerance * @return true if x > y + eps */ final public static boolean isGreater(double x, double y, double eps) { return x > (y + eps); } /** * Returns whether x is greater than or equal to y */ final public static boolean isGreaterEqual(double x, double y) { return (x + STANDARD_PRECISION) > y; } final public static double convertToAngleValue(double val) { if ((val > STANDARD_PRECISION) && (val < PI_2)) { return val; } double value = val % PI_2; if (isZero(value)) { if (val < 1.0) { value = 0.0; } else { value = PI_2; } } else if (value < 0.0) { value += PI_2; } return value; } // ////////////////////////////////////////////// // FORMAT FOR NUMBERS // ////////////////////////////////////////////// /** * Checks if x is close (Kernel.MIN_PRECISION) to a decimal fraction, eg * 2.800000000000001. If it is, the decimal fraction eg 2.8 is returned, * otherwise x is returned. * * @param x * input number * @param precision * specifies how many decimals digits are accepted in results -- * e.g. 0.001 to allow three digits * @return input number; rounded with given precision if the rounding error * is less than this kernel's minimal precision */ final public static double checkDecimalFraction(double x, double precision) { double prec = precision; // Application.debug(precision+" "); prec = Math.pow(10, Math.floor(Math.log(Math.abs(prec)) / Math.log(10))); double fracVal = x * INV_MIN_PRECISION; double roundVal = Math.round(fracVal); // Application.debug(precision+" "+x+" "+fracVal+" "+roundVal+" // "+isEqual(fracVal, // roundVal, precision)+" "+roundVal / INV_MIN_PRECISION); if (isEqual(fracVal, roundVal, STANDARD_PRECISION * prec)) { return roundVal / INV_MIN_PRECISION; } return x; } final public static double checkDecimalFraction(double x) { return checkDecimalFraction(x, 1); } /** * Checks if x is very close (1E-8) to an integer. If it is, the integer * value is returned, otherwise x is returnd. */ final public static double checkInteger(double x) { double roundVal = Math.round(x); if (Math.abs(x - roundVal) < STANDARD_PRECISION) { return roundVal; } return x; } /** * Returns formated angle (in degrees if necessary) * * @param phi * angle in radians * @param tpl * string template * @return formated angle */ final public StringBuilder formatAngle(double phi, StringTemplate tpl, boolean unbounded) { // STANDARD_PRECISION * 10 as we need a little leeway as we've converted // from radians StringBuilder ret = formatAngle(phi, 10, tpl, unbounded); return ret; } final public StringBuilder formatAngle(double alpha, double precision, StringTemplate tpl, boolean unbounded) { double phi = alpha; sbFormatAngle.setLength(0); switch (tpl.getStringType()) { default: // STRING_TYPE_GEOGEBRA_XML // STRING_TYPE_GEOGEBRA if (Double.isNaN(phi)) { sbFormatAngle.append("?"); return sbFormatAngle; } if (getAngleUnit() == ANGLE_DEGREE) { boolean rtl = getLocalization().isRightToLeftDigits(tpl); if (rtl) { if (tpl.hasCASType()) { sbFormatAngle.append("pi/180*"); } else { sbFormatAngle.append(Unicode.DEGREE_CHAR); } } phi = Math.toDegrees(phi); // make sure 360.0000000002 -> 360 phi = checkInteger(phi); if (!unbounded) { if (phi < 0) { phi += 360; } else if (phi > 360) { phi = phi % 360; } } // STANDARD_PRECISION * 10 as we need a little leeway as we've // converted from radians sbFormatAngle.append( format(checkDecimalFraction(phi, precision), tpl)); if (tpl.hasType(StringType.GEOGEBRA_XML)) { sbFormatAngle.append("*"); } if (!rtl) { if (tpl.hasCASType()) { sbFormatAngle.append("*pi/180"); } else { sbFormatAngle.append(Unicode.DEGREE_CHAR); } } return sbFormatAngle; } // RADIANS sbFormatAngle.append(format(phi, tpl)); if (!tpl.hasType(StringType.GEOGEBRA_XML)) { sbFormatAngle.append(" rad"); } return sbFormatAngle; } } /** default global JavaScript */ final public static String defaultLibraryJavaScript = "function ggbOnInit() {}"; private String libraryJavaScript = defaultLibraryJavaScript; /** Resets global JavaSrcript to default value */ public void resetLibraryJavaScript() { setLibraryJavaScript(defaultLibraryJavaScript); } /** * @param str * global javascript */ public void setLibraryJavaScript(String str) { Log.debug(str); libraryJavaScript = str; // libraryJavaScript = // "function ggbOnInit() // {ggbApplet.evalCommand('A=(1,2)');ggbApplet.registerObjectUpdateListener('A','listener');}function // listener() {//java.lang.System.out.println('add listener called'); // var x = ggbApplet.getXcoord('A');var y = ggbApplet.getYcoord('A');var // len = Math.sqrt(x*x + y*y);if (len > 5) { x=x*5/len; y=y*5/len; // }ggbApplet.unregisterObjectUpdateListener('A');ggbApplet.setCoords('A',x,y);ggbApplet.registerObjectUpdateListener('A','listener');}"; // libraryJavaScript = // "function ggbOnInit() {ggbApplet.evalCommand('A=(1,2)');}"; if (app.getScriptManager() != null) { app.getScriptManager().setGlobalScript(); } } // public String getLibraryJavaScriptXML() { // return Util.encodeXML(libraryJavaScript); // } /** * @return global JavaScript */ public String getLibraryJavaScript() { return libraryJavaScript; } /** return all points of the current construction */ public TreeSet<GeoElement> getPointSet() { return getConstruction().getGeoSetLabelOrder(GeoClass.POINT); } /******************************************************* * SAVING *******************************************************/ private boolean isSaving; public synchronized boolean isSaving() { return isSaving; } public synchronized void setSaving(boolean saving) { isSaving = saving; } private final StringBuilder sbFormatAngle = new StringBuilder(40); private boolean arcusFunctionCreatesAngle; /** * @param returnAngle * whether angle should be returned from asin /acos/.. * (compatibility setting for v < 5.0.290) */ public void setInverseTrigReturnsAngle(boolean returnAngle) { arcusFunctionCreatesAngle = returnAngle; } /** * @return whether angle should be returned from asin / acos /... */ public boolean getInverseTrigReturnsAngle() { return arcusFunctionCreatesAngle && loadingMode; } /** * * @param unit * Kernel.ANGLE_DEGREE or Kernel.ANGLE_RADIANT */ final public void setAngleUnit(int unit) { angleUnit = unit; } final public int getAngleUnit() { return angleUnit; } /** * Returns whether the variable name "z" may be used. Note that the 3D * kernel does not allow this as it uses "z" in plane equations like 3x + 2y * + z = 5. * * @return whether z may be used as a variable name */ public boolean isZvarAllowed() { return true; } /** * @param str * string, possibly containing CAS prefix several times * @return string without CAS prefixes */ final public static String removeCASVariablePrefix(final String str) { return removeCASVariablePrefix(str, ""); } /** * @return String where CAS variable prefixes are removed again, e.g. * "ggbcasvar1a" is turned into "a" and */ final public static String removeCASVariablePrefix(final String str, final String replace) { // need a space when called from GeoGebraCAS.evaluateGeoGebraCAS() // so that eg Derivative[1/(-x+E2)] works (want 2 E2 not 2E2) #1595, // #1616 // e.g. "ggbtmpvara" needs to be changed to "a" return str.replace(TMP_VARIABLE_PREFIX, replace); } final public void setPrintFigures(int figures) { if (figures >= 0) { useSignificantFigures = true; sf.setSigDigits(figures); sf.setMaxWidth(16); // for scientific notation } } final public void setPrintDecimals(int decimals) { if (decimals >= 0) { useSignificantFigures = false; nf = FormatFactory.getPrototype().getNumberFormat(decimals); } } final public int getPrintDecimals() { if (nf == null) { return 5; } return nf.getMaximumFractionDigits(); } /* * returns number of significant digits, or -1 if using decimal places */ final public int getPrintFigures() { if (!useSignificantFigures) { return -1; } return sf.getSigDigits(); } /** * Returns whether the parser should read internal command names and not * translate them. * * @return true if internal command names should be read */ public boolean isUsingInternalCommandNames() { return useInternalCommandNames; } /** * Sets whether the parser should read internal command names and not * translate them. * * @param b * true if internal command names should be read */ public void setUseInternalCommandNames(boolean b) { useInternalCommandNames = b; } /** * @return whether setDrawable() on numeric should make them visible as well */ public final boolean isAllowVisibilitySideEffects() { return allowVisibilitySideEffects; } /** * @param allowVisibilitySideEffects * whether setDrawable() on numeric should make them visible as * well */ public final void setAllowVisibilitySideEffects( boolean allowVisibilitySideEffects) { this.allowVisibilitySideEffects = allowVisibilitySideEffects; } public boolean isMacroKernel() { return false; } /** * Returns whether silent mode is turned on. * * @see #setSilentMode(boolean) */ public final boolean isSilentMode() { return silentMode; } /** * @return Whether the GeoGebraCAS has been initialized before */ public synchronized boolean isGeoGebraCASready() { return ggbCAS != null; } /** * Turns silent mode on (true) or off (false). In silent mode, commands can * be used to create objects without any side effects, i.e. no labels are * created, algorithms are not added to the construction list and the views * are not notified about new objects. */ public final void setSilentMode(boolean silentMode) { this.silentMode = silentMode; // no new labels, no adding to construction list getConstruction().setSuppressLabelCreation(silentMode); // no notifying of views // ggb3D - 2009-07-17 // removing : // notifyViewsActive = !silentMode; // (seems not to work with loading files) // Application.printStacktrace(""+silentMode); } /** * Sets whether unknown variables should be resolved as GeoDummyVariable * objects. */ public final void setResolveUnkownVarsAsDummyGeos( boolean resolveUnkownVarsAsDummyGeos) { this.resolveUnkownVarsAsDummyGeos = resolveUnkownVarsAsDummyGeos; } /** * Returns whether unkown variables are resolved as GeoDummyVariable * objects. * * @see #setSilentMode(boolean) */ public final boolean isResolveUnkownVarsAsDummyGeos() { return resolveUnkownVarsAsDummyGeos; } /** * @param casString * String to evaluate * @param arbconst * arbitrary constant * @return result string (null possible) * @throws Throwable */ public String evaluateGeoGebraCAS(String casString, MyArbitraryConstant arbconst) throws Throwable { return evaluateGeoGebraCAS(casString, arbconst, StringTemplate.numericNoLocal); } /** * Evaluates an expression in GeoGebraCAS syntax. * * @return result string (null possible) * @throws Throwable */ final public String evaluateGeoGebraCAS(String exp, MyArbitraryConstant arbconst, StringTemplate tpl) throws Throwable { return evaluateGeoGebraCAS(exp, false, arbconst, tpl); } /** * Evaluates an expression in GeoGebraCAS syntax where the cache or previous * evaluations is used. Make sure to only use this method when exp only * includes values and no (used) variable names. * * @return result string (null possible) * @throws Throwable */ final public String evaluateCachedGeoGebraCAS(String exp, MyArbitraryConstant arbconst) throws Throwable { return evaluateGeoGebraCAS(exp, true, arbconst, StringTemplate.numericNoLocal); } /** * Evaluates an expression in GeoGebraCAS syntax with. * * @param useCaching * only set to true when exp only includes values and no (used) * variable names * @return result string (null possible) * @throws Throwable */ private String evaluateGeoGebraCAS(String exp, boolean useCaching, MyArbitraryConstant arbconst, StringTemplate tpl) throws Throwable { String result = null; if (useCaching && hasCasCache()) { result = getCasCache().get(exp); if (result != null) { // caching worked return result; } } // evaluate in GeoGebraCAS result = getGeoGebraCAS().evaluateGeoGebraCAS(exp, arbconst, tpl, this); if (useCaching) { getCasCache().put(exp, result); } return result; } /** * @param exp * RAW Giac expression to evaluate * @return result from Giac * @throws Throwable * error */ public String evaluateRawGeoGebraCAS(String exp) throws Throwable { String result = null; if (hasCasCache()) { result = getCasCache().get(exp); if (result != null) { // Log.debug("result from cache " + result); // caching worked return result; } } // evaluate in GeoGebraCAS result = getGeoGebraCAS().evaluateRaw(exp); getCasCache().put(exp, result); return result; } public void putToCasCache(String exp, String result) { getCasCache().put(exp, result); } /** * G.Sturr 2009-10-18 * * @param style * Algebra style, see ALGEBRA_STYLE_* */ final public void setAlgebraStyle(int style) { algebraStyle = style; } final public void setAlgebraStyleSpreadsheet(int style) { if (style == Kernel.ALGEBRA_STYLE_DEFINITION_AND_VALUE) { algebraStyleSpreadsheet = Kernel.ALGEBRA_STYLE_VALUE; } else { algebraStyleSpreadsheet = style; } } /** * @return algebra style, one of ALGEBRA_STYLE_* */ final public int getAlgebraStyle() { if (app != null && algebraStyle == Kernel.ALGEBRA_STYLE_DEFINITION_AND_VALUE && !app.has(Feature.AV_DEFINITION_AND_VALUE)) { return Kernel.ALGEBRA_STYLE_VALUE; } return algebraStyle; } final public int getAlgebraStyleSpreadsheet() { return algebraStyleSpreadsheet; } private MaxSizeHashMap<String, String> ggbCasCache; /** * @return Hash map for caching CAS results. */ public MaxSizeHashMap<String, String> getCasCache() { if (ggbCasCache == null) { ggbCasCache = new MaxSizeHashMap<String, String>( GEOGEBRA_CAS_CACHE_SIZE); } return ggbCasCache; } /** * @return Whether kernel is already using CAS caching. */ public boolean hasCasCache() { return ggbCasCache != null; } protected double[] xmin = new double[1], xmax = new double[1], ymin = new double[1], ymax = new double[1], xscale = new double[1], yscale = new double[1]; // for 2nd Graphics View private boolean graphicsView2showing = false; /** * Tells this kernel about the bounds and the scales for x-Axis and y-Axis * used in EudlidianView. The scale is the number of pixels per unit. * (useful for some algorithms like findminimum). All * * @param view * view * @param xmin * left x-coord * @param xmax * right x-coord * @param ymin * bottom y-coord * @param ymax * top y-coord * @param xscale * x scale (pixels per unit) * @param yscale * y scale (pixels per unit) */ final public void setEuclidianViewBounds(int viewNo, double xmin, double xmax, double ymin, double ymax, double xscale, double yscale) { int view = viewNo - 1; if (view < 0 || viewNo < 0) { return; } if (view >= this.xmin.length) { this.xmin = prolong(this.xmin, viewNo); this.xmax = prolong(this.xmin, viewNo); this.ymin = prolong(this.ymin, viewNo); this.ymax = prolong(this.ymax, viewNo); this.xscale = prolong(this.xscale, viewNo); this.yscale = prolong(this.yscale, viewNo); } this.xmin[view] = xmin; this.xmax[view] = xmax; this.ymin[view] = ymin; this.ymax[view] = ymax; this.xscale[view] = xscale; this.yscale[view] = yscale; graphicsView2showing = getApplication().isShowingMultipleEVs(); notifyEuclidianViewCE(EVProperty.ZOOM); } protected double[] prolong(double[] xmin2, int viewNo) { double[] ret = new double[viewNo]; System.arraycopy(xmin2, 0, ret, 0, xmin2.length); return ret; } /** * * {@linkplain #getViewBoundsForGeo} * * @see #getViewBoundsForGeo * @param geo * geo * @return minimal x-bound of all views displaying geo */ public double getViewsXMin(GeoElementND geo) { return getViewBoundsForGeo(geo)[0]; } /** * @see #getViewBoundsForGeo * @param geo * geo * @return maximal x-bound of all views displaying geo */ public double getViewsXMax(GeoElementND geo) { return getViewBoundsForGeo(geo)[1]; } /** * @see #getViewBoundsForGeo * @param geo * geo * @return minimal y-bound of all views displaying geo */ public double getViewsYMin(GeoElementND geo) { return getViewBoundsForGeo(geo)[2]; } /** * @see #getViewBoundsForGeo * @param geo * geo * @return maximal y-bound of all views displaying geo */ public double getViewsYMax(GeoElementND geo) { return getViewBoundsForGeo(geo)[3]; } public double getViewsXScale(GeoElementND geo) { return getViewBoundsForGeo(geo)[4]; } public double getViewsYScale(GeoElementND geo) { return getViewBoundsForGeo(geo)[5]; } public void notifyEuclidianViewCE(EVProperty prop) { if (macroManager != null) { macroManager.notifyEuclidianViewCE(prop); } cons.notifyEuclidianViewCE(prop); } /** * @param clearScripts * (true when called from File -> New, false after loading a file * otherwise the GlobalJavascript is wrongly deleted) */ public synchronized void clearConstruction(boolean clearScripts) { if (clearScripts) { resetLibraryJavaScript(); // This needs to happen *before* cons.clearConstruction() is called // as clearConstruction calls notifyClearView which triggers the // updating of the Python Script // resetLibraryPythonScript(); } if (this.ggbCAS != null) { this.ggbCAS.getCurrentCAS().clearResult(); } if (macroManager != null) { macroManager.setAllMacrosUnused(); } // clear animations if (animationManager != null) { animationManager.stopAnimation(); animationManager.clearAnimatedGeos(); } if (clearScripts) { cons.getArbitraryConsTable().clear(); } cons.clearConstruction(); notifyClearView(); notifyRepaint(); } public double getXmax() { if (graphicsView2showing) { return MyMath.max(xmax); } return xmax[0]; } public double getXmin() { if (graphicsView2showing) { return MyMath.min(xmin); } return xmin[0]; } public double getXscale() { if (graphicsView2showing) { // xscale = pixel per unit // higher xscale means more pixels per unit, i.e. higher precision return MyMath.max(xscale); } return xscale[0]; } public double getYmax() { if (graphicsView2showing) { return MyMath.max(ymax); } return ymax[0]; } public double getYmin() { if (graphicsView2showing) { return MyMath.min(ymin); } return ymin[0]; } public double getYscale() { if (graphicsView2showing) { // yscale = pixel per unit // higher xscale means more pixels per unit, i.e. higher precision return MyMath.max(yscale); } return yscale[0]; } public double getXmax(boolean ev1, boolean ev2) { if (ev2 && !ev1) { return xmax[1]; } else if (ev1 && !ev2) { return xmax[0]; } return getXmax(); } final public double getXmax(int i) { return xmax[i]; } final public double getXmin(int i) { return xmin[i]; } final public double getYmax(int i) { return ymax[i]; } final public double getYmin(int i) { return ymin[i]; } final public double getYscale(int i) { return yscale[i]; } final public double getXscale(int i) { return xscale[i]; } /** * @param i * used in 3D */ public double getZmax(int i) { return 0; } /** * @param i * used in 3D */ public double getZmin(int i) { return 0; } /** * * @param i * used in 3D only * @return 3D view z scale */ public double getZscale(int i) { // use xscale since there is no z return getXscale(); } public double getXmin(boolean ev1, boolean ev2) { if (ev2 && !ev1) { return xmin[1]; } else if (ev1 && !ev2) { return xmin[0]; } return getXmin(); } public double getXscale(boolean ev1, boolean ev2) { if (ev2 && !ev1) { // xscale = pixel per unit // higher xscale means more pixels per unit, i.e. higher precision return xscale[1]; } else if (ev1 && !ev2) { return xscale[0]; } return getXscale(); } public double getYmax(boolean ev1, boolean ev2) { if (ev2 && !ev1) { return ymax[1]; } else if (ev1 && !ev2) { return ymax[0]; } return getYmax(); } public double getYmin(boolean ev1, boolean ev2) { if (ev2 && !ev1) { return ymin[1]; } else if (ev1 && !ev2) { return ymin[0]; } return getYmin(); } public double getYscale(boolean ev1, boolean ev2) { if (ev2 && !ev1) { return yscale[1]; } else if (ev1 && !ev2) { return yscale[0]; } return getYscale(); } public synchronized GeoGebraCasInterface getGeoGebraCAS() { if (ggbCAS == null) { ggbCAS = new GeoGebraCAS(this); } return ggbCAS; } final public int getCoordStyle() { return coordStyle; } public void setCoordStyle(int coordStlye) { coordStyle = coordStlye; } /** * Returns a GeoElement for the given label. * * @return may return null */ final public GeoElement lookupLabel(String label) { return lookupLabel(label, false, isResolveUnkownVarsAsDummyGeos()); } /** * Returns a GeoCasCell for the given label. * * @return may return null */ final public GeoCasCell lookupCasCellLabel(String label) { return cons.lookupCasCellLabel(label); } /** * Returns a GeoCasCell for the given cas row. * * @return may return null * @throws CASException * thrown if one or more row references are invalid (like $x or * if the number is higher than the number of rows) */ final public GeoCasCell lookupCasRowReference(String label) throws CASException { return cons.lookupCasRowReference(label); } /** * Finds element with given the label and possibly creates it * * @param label * Label of element we are looking for * @param autoCreate * true iff new geo should be created if missing * @return GeoElement with given label */ final public GeoElement lookupLabel(String label, boolean autoCreate, boolean useDummies) { GeoElement geo = cons.lookupLabel(label, autoCreate); if ((geo == null) && useDummies) { // lookup CAS variables too geo = lookupCasCellLabel(label); // resolve unknown variable as dummy geo to keep its name and // avoid an "unknown variable" error message if (geo == null) { geo = new GeoDummyVariable(cons, label); } } return geo; } public GeoClass getClassType(String type) throws MyError { switch (type.charAt(0)) { case 'a': // angle return GeoClass.ANGLE; case 'b': // angle if ("boolean".equals(type)) { return GeoClass.BOOLEAN; } return GeoClass.BUTTON; // "button" case 'c': // conic if ("conic".equals(type)) { return GeoClass.CONIC; } else if ("conicpart".equals(type)) { return GeoClass.CONICPART; } else if ("circle".equals(type)) { // bug in GeoGebra 2.6c return GeoClass.CONIC; } case 'd': // doubleLine // bug in GeoGebra 2.6c return GeoClass.CONIC; case 'e': // ellipse, emptyset // bug in GeoGebra 2.6c return GeoClass.CONIC; case 'f': // function return GeoClass.FUNCTION; case 'h': // hyperbola // bug in GeoGebra 2.6c return GeoClass.CONIC; case 'i': // image,implicitpoly if ("image".equals(type)) { return GeoClass.IMAGE; } else if ("intersectinglines".equals(type)) { return GeoClass.CONIC; } else if ("implicitpoly".equals(type)) { return GeoClass.IMPLICIT_POLY; } case 'l': // line, list, locus if ("line".equals(type)) { return GeoClass.LINE; } else if ("list".equals(type)) { return GeoClass.LIST; } else { return GeoClass.LOCUS; } case 'n': // numeric return GeoClass.NUMERIC; case 'p': // point, polygon if ("point".equals(type)) { return GeoClass.POINT; } else if ("polygon".equals(type)) { return GeoClass.POLYGON; } else if ("polyline".equals(type)) { return GeoClass.POLYLINE; } else if ("penstroke".equals(type)) { return GeoClass.PENSTROKE; } else { return GeoClass.CONIC; } case 'r': // ray return GeoClass.RAY; case 's': // segment return GeoClass.SEGMENT; case 't': if ("text".equals(type)) { return GeoClass.TEXT; // text } return GeoClass.TEXTFIELD; // textfield case 'v': // vector return GeoClass.VECTOR; default: throw new MyError(cons.getApplication().getLocalization(), "Kernel: GeoElement of type " + type + " could not be created."); } } /** * Finds the polynomial coefficients of the given expression and returns it * in ascending order. If exp is not a polynomial null is returned. * * @param exp * expression in Giac syntax, e.g. "3*a*x^2 + b*x" * @param variable * e.g "x" * @return array of coefficients, e.g. ["0", "b", "3*a"] */ final public String[] getPolynomialCoeffs(String exp, String variable) { return getGeoGebraCAS().getPolynomialCoeffs(exp, variable); } /** * returns GeoElement at (row,col) in spreadsheet may return nully * * @param col * Spreadsheet column * @param row * Spreadsheet row * @return Spreadsheet cell content (may be null) */ public GeoElement getGeoAt(int col, int row) { return lookupLabel( GeoElementSpreadsheet.getSpreadsheetCellName(col, row)); } final public AnimationManager getAnimatonManager() { if (animationManager == null) { animationManager = getApplication().newAnimationManager(this); } return animationManager; } /** * @param geo * @return RealWorld Coordinates of the rectangle covering all euclidian * views in which <b>geo</b> is shown.<br /> * Format: {xMin,xMax,yMin,yMax,xScale,yScale} */ public double[] getViewBoundsForGeo(GeoElementND geo) { List<Integer> viewSet = geo.getViewSet(); double[] viewBounds = new double[6]; for (int i = 0; i < 6; i++) { viewBounds[i] = Double.NEGATIVE_INFINITY; } viewBounds[0] = viewBounds[2] = Double.POSITIVE_INFINITY; if (geo.isVisibleInView3D() && app.isEuclidianView3Dinited()) { addViews(App.VIEW_EUCLIDIAN3D, viewBounds); } if (viewSet == null) { addViews(App.VIEW_EUCLIDIAN, viewBounds); return viewBounds; } // we can't use foreach here because of GWT for (int i = 0; i < viewSet.size(); i++) { addViews(viewSet.get(i), viewBounds); } // if (viewBounds[0]==Double.POSITIVE_INFINITY){ // //standard values if no view // viewBounds[0]=viewBounds[2]=-10; // viewBounds[1]=viewBounds[3]=10; // viewBounds[5]=viewBounds[6]=1; // } return viewBounds; } private void addViews(Integer id, double[] viewBounds) { View view = getApplication().getView(id); if ((view != null) && (view instanceof EuclidianViewInterfaceSlim)) { EuclidianViewInterfaceSlim ev = (EuclidianViewInterfaceSlim) view; viewBounds[0] = Math.min(viewBounds[0], ev.getXmin()); viewBounds[1] = Math.max(viewBounds[1], ev.getXmax()); viewBounds[2] = Math.min(viewBounds[2], ev.getYmin()); viewBounds[3] = Math.max(viewBounds[3], ev.getYmax()); viewBounds[4] = Math.max(viewBounds[4], ev.getXscale()); viewBounds[5] = Math.max(viewBounds[5], ev.getYscale()); } } final public GeoAxis getXAxis() { return cons.getXAxis(); } final public GeoAxis getYAxis() { return cons.getYAxis(); } final public boolean isAxis(GeoElement geo) { return ((geo == cons.getXAxis()) || (geo == cons.getYAxis())); } public void updateLocalAxesNames() { if (cons != null) { cons.updateLocalAxesNames(); } } private boolean notifyRepaint = true; public void setNotifyRepaintActive(boolean flag) { if (flag != notifyRepaint) { notifyRepaint = flag; if (notifyRepaint) { notifyRepaint(); } } } final public boolean isNotifyRepaintActive() { return notifyRepaint; } public final void notifyRepaint() { if (notifyRepaint && notifyViewsActive) { for (View view : views) { view.repaintView(); } } } public final void notifyScreenChanged() { for (View view : views) { if (view instanceof EuclidianViewInterfaceCommon) { ((EuclidianViewInterfaceCommon) view).screenChanged(); } } } public final void notifyControllersMoveIfWaiting() { if (notifyRepaint && notifyViewsActive) { for (View view : views) { if (view instanceof EuclidianView) { ((EuclidianView) view).getEuclidianController() .moveIfWaiting(); } } } } public final boolean notifySuggestRepaint() { boolean needed = false; if (notifyViewsActive) { for (View view : views) { needed = view.suggestRepaint() || needed; } } return needed; } final public void notifyReset() { if (notifyViewsActive) { for (View view : views) { view.reset(); } } } /** * Clears all views, even if notifyViewsActive is false */ protected final void notifyClearView() { for (View view : views) { view.clearView(); } } public void clearJustCreatedGeosInViews() { if (notifyViewsActive) { for (View view : views) { if (view instanceof EuclidianViewInterfaceSlim) { ((EuclidianViewInterfaceSlim) view).getEuclidianController() .clearJustCreatedGeos(); } } } } // notify only construction protocol public void notifyConstructionProtocol(GeoElement geo) { for (View view : views) { if (view.getViewID() == App.VIEW_CONSTRUCTION_PROTOCOL) { view.add(geo); } } } public void setNotifyViewsActive(boolean flag) { // Application.debug("setNotifyViews: " + flag); if (flag != notifyViewsActive) { notifyViewsActive = flag; if (flag) { // Application.debug("Activate VIEWS"); viewReiniting = true; // "attach" views again // add all geos to all views for (View view : views) { notifyAddAll(view); } notifyEuclidianViewCE(EVProperty.ZOOM); notifyReset(); // algebra settings need to be applied after remaking tree if (app.getGuiManager() != null) { app.getGuiManager().applyAlgebraViewSettings(); } viewReiniting = false; } else { // Application.debug("Deactivate VIEWS"); // "detach" views notifyClearView(); } } } /* * ******************************************************* methods for * view-Pattern (Model-View-Controller) * ****************************************************** */ private EuclidianView lastAttachedEV = null; final public EuclidianView getLastAttachedEV() { return lastAttachedEV; } public void attach(View view) { if (!views.contains(view)) { views.add(view); } if (view instanceof EuclidianView) { lastAttachedEV = (EuclidianView) view; } printAttachedViews(); } private void printAttachedViews() { // can give java.util.ConcurrentModificationException try { if (!notifyViewsActive) { Log.debug("Number of registered views = 0"); } else { StringBuilder sb = new StringBuilder(); sb.append("Number of registered views = "); sb.append(views.size()); for (View view : views) { sb.append("\n * "); sb.append(view.getClass()); } Log.debug(sb.toString()); } } catch (Exception e) { Log.debug(e.getMessage()); } } private boolean notifyViewsActive = true; public void detach(View view) { views.remove(view); printAttachedViews(); } /** * Notify the views that the mode changed. * * @param mode */ final public void notifyModeChanged(int mode, ModeSetter m) { if (notifyViewsActive) { for (View view : views) { view.setMode(mode, m); } } } final public void notifyAddAll(View view) { if (cons == null) { return; } int consStep = cons.getStep(); notifyAddAll(view, consStep); } /** * Registers an algorithm that needs to be updated when notifyRename(), * notifyAdd(), or notifyRemove() is called. */ public void registerRenameListenerAlgo(AlgoElement algo) { if (renameListenerAlgos == null) { renameListenerAlgos = new ArrayList<AlgoElement>(); } if (!renameListenerAlgos.contains(algo)) { renameListenerAlgos.add(algo); } } void unregisterRenameListenerAlgo(AlgoElement algo) { if (renameListenerAlgos != null) { renameListenerAlgos.remove(algo); } } private ArrayList<AlgoElement> renameListenerAlgos; private boolean spreadsheetBatchRunning; private void notifyRenameListenerAlgos() { // #4073 command Object[] registers rename listeners if (cons != null && !cons.isFileLoading() && !this.isSpreadsheetBatchRunning()) { AlgoElement.updateCascadeAlgos(renameListenerAlgos); } } public boolean isSpreadsheetBatchRunning() { return this.spreadsheetBatchRunning; } public void setSpreadsheetBatchRunning(boolean b) { this.spreadsheetBatchRunning = b; if (!b) { notifyRenameListenerAlgos(); } } /** * Currently, this method should rename every oldLabel to newLabel in * GgbScript-type objects, for use of CopyPaste and InsertFile * * @param oldLabel * the label to be renamed from * @param newLabel * the label to be renamed to * @return whether any renaming happened */ final public boolean renameLabelInScripts(String oldLabel, String newLabel) { Script work; boolean somethingHappened = false; for (GeoElement geo : cons.getGeoSetWithCasCellsConstructionOrder()) { work = geo.getScript(EventType.UPDATE); if (work instanceof GgbScript) { somethingHappened |= work.renameGeo(oldLabel, newLabel); } work = geo.getScript(EventType.CLICK); if (work instanceof GgbScript) { somethingHappened |= work.renameGeo(oldLabel, newLabel); } } return somethingHappened; } final public void notifyAddAll(View view, int consStep) { if (!notifyViewsActive) { return; } for (GeoElement geo : cons.getGeoSetWithCasCellsConstructionOrder()) { // stop when not visible for current construction step if (!geo.isAvailableAtConstructionStep(consStep)) { break; } view.add(geo); } if (getUpdateAgain()) { setUpdateAgain(false, null); app.scheduleUpdateConstruction(); } } public final void notifyAdd(GeoElement geo) { if (notifyViewsActive) { if (addingPolygon && geo.isLabelSet()) { if (geo.getXMLtypeString().equalsIgnoreCase("Polygon")) { this.newPolygon = geo; } } for (View view : views) { if ((view.getViewID() != App.VIEW_CONSTRUCTION_PROTOCOL) || isNotifyConstructionProtocolViewAboutAddRemoveActive()) { view.add(geo); } } } notifyRenameListenerAlgos(); } public final void addingPolygon() { if (notifyViewsActive) { this.addingPolygon = true; for (View view : views) { if (view instanceof ClientView) { ((ClientView) view).addingPolygon(); } } } } public final void notifyPolygonAdded() { if (notifyViewsActive) { for (View view : views) { if (view instanceof ClientView) { ((ClientView) view).addPolygonComplete(this.newPolygon); } } } } public final void notifyRemoveGroup() { if (notifyViewsActive) { for (View view : views) { if (view instanceof ClientView) { ((ClientView) view).deleteGeos(deleteList); } } } this.deleteList.clear(); } public final void notifyRemove(GeoElement geo) { if (notifyViewsActive) { if (geo.isLabelSet()) { this.deleteList.add(geo); } for (View view : views) { if ((view.getViewID() != App.VIEW_CONSTRUCTION_PROTOCOL) || isNotifyConstructionProtocolViewAboutAddRemoveActive()) { // needed for GGB-808 // geoCasCell is already removed from cas view if (view.getViewID() == App.VIEW_CAS) { removeFromCAS(view, geo); } else { view.remove(geo); } } } } notifyRenameListenerAlgos(); } /** * Remove object from CAS, ignore CAS cells with index -1 as they were * removed in Construction.removeFromConstructionList * * NB we can't ignore all cells so that construction protocol navigation * works */ private static void removeFromCAS(View view, GeoElement geo) { if (geo instanceof GeoCasCell && geo.getConstructionIndex() < 0) { return; } view.remove(geo); } public final void movingGeoSet() { if (notifyViewsActive) { for (View view : views) { if (view instanceof ClientView) { ((ClientView) view).movingGeos(); } } } } public final void movedGeoSet(ArrayList<GeoElement> elmSet) { if (notifyViewsActive) { for (View view : views) { if (view instanceof ClientView) { ((ClientView) view).movedGeos(elmSet); } } } } public final void notifyUpdate(GeoElement geo) { // event dispatcher should not collect calls to stay compatible with 4.0 if (notifyViewsActive) { for (View view : views) { view.update(geo); } } } public final void notifyUpdateLocation(GeoElement geo) { // event dispatcher should not collect calls to stay compatible with 4.0 if (notifyViewsActive) { for (View view : views) { // we already told event dispatcher if (view instanceof UpdateLocationView) { ((UpdateLocationView) view).updateLocation(geo); } else { view.update(geo); } } } // App.printStacktrace("notifyUpdate " + geo); } public final void notifyUpdateVisualStyle(GeoElement geo, GProperty prop) { if (notifyViewsActive) { for (View view : views) { view.updateVisualStyle(geo, prop); } } } public final void notifyUpdateAuxiliaryObject(GeoElement geo) { if (notifyViewsActive) { for (View view : views) { view.updateAuxiliaryObject(geo); } } } public final void notifyRename(GeoElement geo) { if (notifyViewsActive) { for (View view : views) { view.rename(geo); } } notifyRenameListenerAlgos(); } public final void notifyTypeChanged(GeoElement geo) { if (notifyViewsActive) { for (View view : views) { if (view.getViewID() == App.VIEW_ALGEBRA) { view.rename(geo); } } } } public final void notifyRenameUpdatesComplete() { if (notifyViewsActive) { for (View view : views) { if (view instanceof ClientView) { ((ClientView) view).renameUpdatesComplete(); } } } } public void notifyPaste(String pasteXml) { if (notifyViewsActive) { for (View view : views) { if (view instanceof ClientView) { ((ClientView) view).pasteElms(pasteXml); } } } } public void notifyPasteComplete() { if (notifyViewsActive) { for (View view : views) { if (view instanceof ClientView) { ((ClientView) view).pasteElmsComplete( app.getSelectionManager().getSelectedGeos()); } } } } public boolean isNotifyViewsActive() { return notifyViewsActive && !viewReiniting; } public boolean isViewReiniting() { return viewReiniting; } /* * /************************** Undo /Redo */ public void updateConstruction() { // views are notified about update at the end of this method cons.updateConstruction(); // latexes in GeoGebraWeb are rendered afterwards and set updateEVAgain if (getUpdateAgain()) { setUpdateAgain(false, null); app.scheduleUpdateConstruction(); } else { notifyRepaint(); } } public void updateConstructionLanguage() { // views are notified about update at the end of this method cons.updateConstructionLanguage(); if (getUpdateAgain()) { setUpdateAgain(false, null); app.scheduleUpdateConstruction(); } else { notifyRepaint(); } } /** * update construction n times * * @param n */ public void updateConstruction(boolean randomize, int n) { // views are notified about update at the end of this method for (int i = 0; i < n; i++) { cons.updateConstruction(randomize); } // latexes in GeoGebraWeb are rendered afterwards and set updateEVAgain if (getUpdateAgain()) { setUpdateAgain(false, null); app.scheduleUpdateConstruction(); } else { notifyRepaint(); } } /** * Tests if the current construction has no elements. * * @return true if the current construction has no GeoElements; false * otherwise. */ public boolean isEmpty() { return cons.isEmpty(); } /* * ****************************** redo / undo for current construction * ***************************** */ private boolean isGettingUndo; public synchronized boolean isGettingUndo() { return isGettingUndo; } public synchronized void setIsGettingUndo(boolean flag) { isGettingUndo = flag; } public void setUndoActive(boolean flag) { undoActive = flag; } public boolean isUndoActive() { return undoActive; } public void storeUndoInfo() { if (undoActive && cons != null) { cons.storeUndoInfo(); } } public void restoreCurrentUndoInfo() { if (undoActive) { cons.restoreCurrentUndoInfo(); } } public void initUndoInfo() { if (undoActive && cons != null) { cons.initUndoInfo(); } } public void redo() { if (undoActive && cons.getUndoManager().redoPossible()) { app.batchUpdateStart(); app.startCollectingRepaints(); app.getSelectionManager().storeSelectedGeosNames(); app.getCompanion().storeViewCreators(); notifyReset(); clearJustCreatedGeosInViews(); cons.redo(); notifyReset(); app.getCompanion().recallViewCreators(); app.getSelectionManager().recallSelectedGeosNames(this); app.stopCollectingRepaints(); app.batchUpdateEnd(); storeStateForModeStarting(); app.getEventDispatcher() .dispatchEvent(new Event(EventType.REDO, null)); app.setUnAutoSaved(); } } private StringBuilder stateForModeStarting; public void restoreStateForInitNewMode() { if (undoActive && getSelectionManager().isGeoToggled()) { restoreStateForModeStarting(); } } public void storeStateForModeStarting() { stateForModeStarting = cons.getCurrentUndoXML(true); getSelectionManager().resetGeoToggled(); } public void storeUndoInfoAndStateForModeStarting() { if (cons != null) { storeStateForModeStarting(); if (cons.isUndoEnabled()) { // reuse cons.getCurrentUndoXML(true) cons.getUndoManager().storeUndoInfo(stateForModeStarting, false); } } } private SelectionManager getSelectionManager() { return app.getSelectionManager(); } private void restoreStateForModeStarting() { app.batchUpdateStart(); app.getCompanion().storeViewCreators(); app.getScriptManager().disableListeners(); notifyReset(); getApplication().getActiveEuclidianView().getEuclidianController() .clearSelections(); cons.processXML(stateForModeStarting); notifyReset(); app.getScriptManager().enableListeners(); app.getCompanion().recallViewCreators(); app.batchUpdateEnd(); app.setUnAutoSaved(); } public void undo() { if (undoActive) { if (getApplication().getActiveEuclidianView() .getEuclidianController().isUndoableMode()) { if (getSelectionManager().isGeoToggled() && !getSelectionManager().getSelectedGeos().isEmpty()) { restoreStateForModeStarting(); getSelectionManager().resetGeoToggled(); return; } } if (cons.getUndoManager().undoPossible()) { app.batchUpdateStart(); app.startCollectingRepaints(); app.getSelectionManager().storeSelectedGeosNames(); app.getCompanion().storeViewCreators(); notifyReset(); clearJustCreatedGeosInViews(); getApplication().getActiveEuclidianView() .getEuclidianController().clearSelections(); cons.undo(); notifyReset(); app.getCompanion().recallViewCreators(); app.getSelectionManager().recallSelectedGeosNames(this); // repaint needed for last undo in second EuclidianView (bugfix) if (!undoPossible()) { notifyRepaint(); } app.stopCollectingRepaints(); app.batchUpdateEnd(); storeStateForModeStarting(); app.getEventDispatcher() .dispatchEvent(new Event(EventType.UNDO, null)); app.setUnAutoSaved(); } } } public boolean undoPossible() { return undoActive && cons != null && cons.undoPossible(); } public boolean redoPossible() { return undoActive && cons != null && cons.redoPossible(); } /** * Get {@link Kernel#insertLineBreaks insertLineBreaks}. * * @return {@link Kernel#insertLineBreaks insertLineBreaks}. */ public boolean isInsertLineBreaks() { return insertLineBreaks; } static public String getXMLFileFormat() { return GeoGebraConstants.XML_FILE_FORMAT; } /** * Set {@link Kernel#insertLineBreaks insertLineBreaks}. * * @param insertLineBreaks * The value to set {@link Kernel#insertLineBreaks * insertLineBreaks} to. */ public void setInsertLineBreaks(boolean insertLineBreaks) { this.insertLineBreaks = insertLineBreaks; } final public ExpressionNode handleTrigPower(String image, ValidExpression en, String operation) { if ("x".equals(operation) || "y".equals(operation) || "z".equals(operation)) { return new ExpressionNode(this, new ExpressionNode(this, new FunctionVariable(this, operation), Operation.POWER, convertIndexToNumber(image)), Operation.MULTIPLY_OR_FUNCTION, en); } GeoElement ge = lookupLabel(operation); Operation type = app.getParserFunctions().get(operation, 1); if (ge != null || type == null) { return new ExpressionNode(this, new ExpressionNode(this, new Variable(this, operation), Operation.POWER, convertIndexToNumber(image)), Operation.MULTIPLY_OR_FUNCTION, en); } // sin^(-1)(x) -> ArcSin(x) // sin^(-1)(x) -> ArcSin(x) if (image.indexOf(Unicode.Superscript_Minus) > -1) { // String check = ""+Unicode.Superscript_Minus + // Unicode.Superscript_1 + '('; int index = image.indexOf(Unicode.superscriptMinusOneBracket); // sin^-1 -> index = 3 // sinh^-1 -> index =4 if (index == 3 || index == 4) { return inverseTrig(type, en); } // eg sin^-2(x) return new MyDouble(this, Double.NaN).wrap(); } return new ExpressionNode(this, new ExpressionNode(this, en, type, null), Operation.POWER, convertIndexToNumber(image)); } public ExpressionNode inverseTrig(Operation type, ExpressionValue en) { switch (type) { case SIN: case COS: case TAN: case SINH: case COSH: case TANH: return new ExpressionNode(this, en, Operation.inverse(type), null); // asec(x) = acos(1/x) case SEC: return new ExpressionNode(this, new ExpressionNode(this, (new MyDouble(this, 1)).wrap(), Operation.DIVIDE, en), Operation.ARCCOS, null); case CSC: return new ExpressionNode(this, new ExpressionNode(this, (new MyDouble(this, 1)).wrap(), Operation.DIVIDE, en), Operation.ARCSIN, null); case SECH: return new ExpressionNode(this, new ExpressionNode(this, (new MyDouble(this, 1)).wrap(), Operation.DIVIDE, en), Operation.ACOSH, null); case CSCH: return new ExpressionNode(this, new ExpressionNode(this, (new MyDouble(this, 1)).wrap(), Operation.DIVIDE, en), Operation.ASINH, null); case COTH: return new ExpressionNode(this, new ExpressionNode(this, (new MyDouble(this, 1)).wrap(), Operation.DIVIDE, en), Operation.ATANH, null); // acot(x) = pi/2 - atan(x) case COT: ExpressionNode halfPi = new ExpressionNode(this, (new MyDouble(this, Math.PI)).wrap(), Operation.DIVIDE, (new MyDouble(this, 2)).wrap()); return new ExpressionNode(this, halfPi, Operation.MINUS, new ExpressionNode(this, en, Operation.ARCTAN, null)); default: return new MyDouble(this, Double.NaN).wrap(); } } final public MyDouble convertIndexToNumber(String str) { int i = 0; while ((i < str.length()) && !Unicode.isSuperscriptDigit(str.charAt(i))) { i++; } // Application.debug(str.substring(i, str.length() - 1)); MyDouble md = new MyDouble(this, str.substring(i, str.length() - 1)); // strip // off // eg // "sin" // at // start, // "(" // at // end return md; } /*********************************** * FACTORY METHODS FOR GeoElements ***********************************/ public GeoVec2D getImaginaryUnit() { if (imaginaryUnit == null) { imaginaryUnit = new GeoVec2D(this, 0, 1); imaginaryUnit.setMode(COORD_COMPLEX); } return imaginaryUnit; } /** * @return E as MySpecialDouble */ public MySpecialDouble getEulerNumber() { if (eulerConstant == null) { eulerConstant = new MySpecialDouble(this, Math.E, Unicode.EULER_STRING); } return eulerConstant; } /** * Creates a new algorithm that uses the given macro. * * @return output of macro algorithm */ final public GeoElement[] useMacro(String[] labels, Macro macro, GeoElement[] input) { try { AlgoMacro algo = new AlgoMacro(cons, labels, macro, input, true); return algo.getOutput(); } catch (Exception e) { e.printStackTrace(); return null; } } public Localization getLocalization() { return getApplication().getLocalization(); } /** * Returns the kernel settings in XML format. */ public void getKernelXML(StringBuilder sb, boolean asPreference) { // kernel settings sb.append("<kernel>\n"); // is 3D? if (cons.has3DObjects()) { // DO NOT REMOVE // it's important we pick up errors involving this quickly Log.error("************************************"); Log.error("****** file has 3D objects *********"); Log.error("************************************"); sb.append("\t<uses3D val=\"true\"/>\n"); } // continuity: true or false, since V3.0 sb.append("\t<continuous val=\""); sb.append(isContinuous()); sb.append("\"/>\n"); sb.append("\t<usePathAndRegionParameters val=\""); sb.append(usePathAndRegionParameters.getXML()); sb.append("\"/>\n"); if (useSignificantFigures) { // significant figures sb.append("\t<significantfigures val=\""); sb.append(getPrintFigures()); sb.append("\"/>\n"); } else { // decimal places sb.append("\t<decimals val=\""); sb.append(getPrintDecimals()); sb.append("\"/>\n"); } // angle unit sb.append("\t<angleUnit val=\""); sb.append( getAngleUnit() == Kernel.ANGLE_RADIANT ? "radiant" : "degree"); sb.append("\"/>\n"); // algebra style sb.append("\t<algebraStyle val=\""); sb.append(getAlgebraStyle()); sb.append("\" spreadsheet=\""); sb.append(getAlgebraStyleSpreadsheet()); sb.append("\"/>\n"); // coord style sb.append("\t<coordStyle val=\""); sb.append(getCoordStyle()); sb.append("\"/>\n"); // animation if (isAnimationRunning()) { sb.append("\t<startAnimation val=\""); sb.append(isAnimationRunning()); sb.append("\"/>\n"); } if (asPreference) { sb.append("\t<localization digits=\""); sb.append(getLocalization().isUsingLocalizedDigits()); sb.append("\" labels=\""); sb.append(getLocalization().isUsingLocalizedLabels()); sb.append("\"/>\n"); sb.append("\t<casSettings timeout=\""); sb.append(OptionsCAS.getTimeoutOption( app.getSettings().getCasSettings().getTimeoutMilliseconds() / 1000)); sb.append("\" expRoots=\""); sb.append(app.getSettings().getCasSettings().getShowExpAsRoots()); sb.append("\"/>\n"); } sb.append("</kernel>\n"); } /* * ********************************** MACRO handling * ********************************* */ /** * Creates a new macro within the kernel. A macro is a user defined command * in GeoGebra. */ public void addMacro(Macro macro) { if (macroManager == null) { macroManager = new MacroManager(); } macroManager.addMacro(macro); app.dispatchEvent( new Event(EventType.ADD_MACRO, null, macro.getCommandName())); } /** * Removes a macro from the kernel. */ public void removeMacro(Macro macro) { if (macroManager != null) { macroManager.removeMacro(macro); } // Also remove Assignments using this macro from Exercise Exercise ex = getExercise(); if (!ex.isEmpty() && ex.usesMacro(macro)) { ex.removeAssignment(macro); } app.dispatchEvent(new Event(EventType.REMOVE_MACRO, null, macro.getCommandName())); } /** * Removes all macros from the kernel. */ public void removeAllMacros() { if (macroManager != null) { getApplication().removeMacroCommands(); macroManager.removeAllMacros(); } // Also remove all Assignments getExercise().removeAllAssignments(); app.dispatchEvent(new Event(EventType.REMOVE_MACRO, null, null)); } /** * Sets the command name of a macro. Note: if the given name is already used * nothing is done. * * @return if the command name was really set */ public boolean setMacroCommandName(Macro macro, String cmdName) { boolean nameUsed = macroManager.getMacro(cmdName) != null; if (nameUsed || cmdName == null || cmdName.length() == 0) { return false; } app.dispatchEvent(new Event(EventType.RENAME_MACRO, null, "[\"" + macro.getCommandName() + "\",\"" + cmdName + "\"]")); macroManager.setMacroCommandName(macro, cmdName); return true; } /** * Returns the macro object for a given macro command name. Note: null may * be returned. */ public Macro getMacro(String commandName) { return (macroManager == null) ? null : macroManager.getMacro(commandName); } /** * Returns an XML represenation of the given macros in this kernel. * * @return */ public String getMacroXML(ArrayList<Macro> macros) { if (hasMacros()) { return MacroManager.getMacroXML(macros); } return ""; } /** * Returns whether any macros have been added to this kernel. * * @return whether any macros have been added to this kernel. */ public boolean hasMacros() { return (macroManager != null && macroManager.getMacroNumber() > 0); } /** * Returns the number of currently registered macros */ public int getMacroNumber() { if (macroManager == null) { return 0; } return macroManager.getMacroNumber(); } /** * Returns a list with all currently registered macros. */ public ArrayList<Macro> getAllMacros() { if (macroManager == null) { return null; } return macroManager.getAllMacros(); } /** * Returns i-th registered macro */ public Macro getMacro(int i) { try { return macroManager.getMacro(i); } catch (Exception e) { return null; } } /** * Returns the ID of the given macro. */ public int getMacroID(Macro macro) { return (macroManager == null) ? -1 : macroManager.getMacroID(macro); } public Exercise getExercise() { if (exercise == null) { exercise = new Exercise(getApplication()); } return exercise; } public boolean hasExercise() { return exercise != null; } /* * used to delay animation start until everything loaded */ public void setWantAnimationStarted(boolean want) { wantAnimationStarted = want; } public boolean wantAnimationStarted() { return wantAnimationStarted; } /** * Converts a NumberValue object to an ExpressionNode object. */ public ExpressionNode convertNumberValueToExpressionNode(GeoElement geo) { AlgoElement algo = geo.getParentAlgorithm(); Traversing ifReplacer = new Traversing() { @Override public ExpressionValue process(ExpressionValue ev) { if (ev instanceof GeoElement) { return Kernel.this .convertNumberValueToExpressionNode((GeoElement) ev) .unwrap(); } return ev; } }; if (!geo.isLabelSet() && algo != null && algo instanceof DependentAlgo) { DependentAlgo algoDep = (DependentAlgo) algo; if (algoDep.getExpression() != null) { return algoDep.getExpression().getCopy(this) .traverse(ifReplacer).wrap(); } } if (!geo.isLabelSet() && algo != null && algo instanceof AlgoIf) { return ((AlgoIf) algo).toExpression(); } return new ExpressionNode(this, geo); } final public GeoElement[] vectorPolygon(String[] labels, GeoPointND[] points) { /* * cons.setSuppressLabelCreation(true); getAlgoDispatcher().Circle(null, * (GeoPoint) points[0], new MyDouble(cons.getKernel(), * points[0].distance(points[1]))); * cons.setSuppressLabelCreation(oldMacroMode); */ StringBuilder sb = new StringBuilder(); double xA = points[0].getInhomX(); double yA = points[0].getInhomY(); for (int i = 1; i < points.length; i++) { double xC = points[i].getInhomX(); double yC = points[i].getInhomY(); GeoNumeric nx = new GeoNumeric(cons, xC - xA); GeoNumeric ny = new GeoNumeric(cons, yC - yA); nx.setLabel(null); ny.setLabel(null); StringTemplate tpl = StringTemplate.maxPrecision; // make string like this // (a+x(A),b+y(A)) sb.setLength(0); sb.append('('); sb.append(nx.getLabel(tpl)); sb.append("+x("); sb.append(points[0].getLabel(tpl)); sb.append("),"); sb.append(ny.getLabel(tpl)); sb.append("+y("); sb.append(points[0].getLabel(tpl)); sb.append("))"); // Application.debug(sb.toString()); GeoPoint pp = (GeoPoint) getAlgebraProcessor() .evaluateToPoint(sb.toString(), ErrorHelper.silent(), true); try { cons.replace((GeoElement) points[i], pp); points[i] = pp; // points[i].setEuclidianVisible(false); points[i].update(); } catch (Exception e) { e.printStackTrace(); return null; } } points[0].update(); return getAlgoDispatcher().Polygon(labels, points); } /** * makes a copy of a polygon that can be dragged and rotated but stays * congruent to original * * @param poly * @param offset2 * @param offset * @return */ final public GeoElement[] rigidPolygon(GeoPolygon poly, double offsetX, double offsetY) { GeoPointND[] p = new GeoPointND[poly.getPointsLength()]; // create free point p0 p[0] = poly.getPoint(0).copy(); p[0].setLabel(null); GeoPointND[] pts = poly.getPoints(); boolean oldMacroMode = cons.isSuppressLabelsActive(); cons.setSuppressLabelCreation(true); // create p1 = point on circle (so it can be dragged to rotate the whole // shape) GeoSegment radius = getAlgoDispatcher().Segment((String) null, (GeoPoint) pts[0], (GeoPoint) pts[1]); GeoConicND circle = getAlgoDispatcher().Circle(null, p[0], radius); cons.setSuppressLabelCreation(oldMacroMode); p[1] = getAlgoDispatcher().Point(null, circle, poly.getPoint(1).inhomX, poly.getPoint(1).inhomY, true, false, true); p[1].setLabel(null); boolean oldVal = isUsingInternalCommandNames(); setUseInternalCommandNames(true); StringBuilder sb = new StringBuilder(); int n = poly.getPointsLength(); for (int i = 2; i < n; i++) { // build string like // Rotate[E+ (Segment[B,C], 0), Angle[C-B] + Angle[E-D] - // Angle[B-A],E] sb.setLength(0); sb.append("Rotate["); sb.append(p[i - 1].getLabel(StringTemplate.noLocalDefault)); // #5445 sb.append("+ (Segment["); sb.append(pts[i - 1].getLabel(StringTemplate.noLocalDefault)); sb.append(","); sb.append(pts[i % n].getLabel(StringTemplate.noLocalDefault)); sb.append("]"); sb.append(", 0), Angle["); sb.append(pts[i].getLabel(StringTemplate.noLocalDefault)); // C sb.append("-"); sb.append(pts[i - 1].getLabel(StringTemplate.noLocalDefault)); // B sb.append("] + Angle["); sb.append(p[i - 1].getLabel(StringTemplate.noLocalDefault)); sb.append("-"); sb.append(p[i - 2].getLabel(StringTemplate.noLocalDefault)); sb.append("] - Angle["); sb.append(pts[i - 1].getLabel(StringTemplate.noLocalDefault)); // B sb.append("-"); sb.append(pts[i - 2].getLabel(StringTemplate.noLocalDefault)); // A sb.append("],"); sb.append(p[i - 1].getLabel(StringTemplate.noLocalDefault)); sb.append("]"); // Log.error(sb.toString()); p[i] = getAlgebraProcessor().evaluateToPoint(sb.toString(), ErrorHelper.silent(), false); p[i].setLabel(null); p[i].setEuclidianVisible(false); p[i].update(); } setUseInternalCommandNames(oldVal); AlgoPolygon algo = new AlgoPolygon(cons, null, p); GeoElement[] ret = { algo.getOutput(0) }; GeoPointND firstPoint = ((GeoPolygon) ret[0]).getPoints()[0]; firstPoint.updateCoords2D(); firstPoint.setCoords(firstPoint.getX2D() + offsetX, firstPoint.getY2D() + offsetY, 1.0); firstPoint.updateRepaint(); return ret; } protected GeoPointND rigidPolygonPointOnCircle(GeoConicND circle, GeoPointND point1) { return getAlgoDispatcher().Point(null, circle, point1.getInhomX(), point1.getInhomY(), true, false, true); } final public GeoElement[] rigidPolygon(String[] labels, GeoPointND[] points) { boolean oldMacroMode = cons.isSuppressLabelsActive(); cons.setSuppressLabelCreation(true); GeoConicND circle = getAlgoDispatcher().Circle(null, points[0], new GeoNumeric(cons, points[0].distance(points[1]))); cons.setSuppressLabelCreation(oldMacroMode); GeoPointND p = rigidPolygonPointOnCircle(circle, points[1]); try { (cons).replace((GeoElement) points[1], (GeoElement) p); points[1] = p; } catch (Exception e) { e.printStackTrace(); return null; } StringBuilder sb = new StringBuilder(); double xA = points[0].getInhomX(); double yA = points[0].getInhomY(); double xB = points[1].getInhomX(); double yB = points[1].getInhomY(); GeoVec2D a = new GeoVec2D(cons.getKernel(), xB - xA, yB - yA); // vector // AB GeoVec2D b = new GeoVec2D(cons.getKernel(), yA - yB, xB - xA); // perpendicular // to // AB // changed to use this instead of Unit(Orthoganal)Vector // https://www.geogebra.org/forum/viewtopic.php?f=13&p=82764#p82764 double aLength = Math.sqrt(a.inner(a)); boolean oldVal = isUsingInternalCommandNames(); setUseInternalCommandNames(true); a.makeUnitVector(); b.makeUnitVector(); StringTemplate tpl = StringTemplate.maxPrecision; boolean is3D = points[0].isGeoElement3D() || points[1].isGeoElement3D(); for (int i = 2; i < points.length; i++) { double xC = points[i].getInhomX(); double yC = points[i].getInhomY(); GeoVec2D d = new GeoVec2D(cons.getKernel(), xC - xA, yC - yA); // vector // AC // make string like this // A+3.76UnitVector[Segment[A,B]]+-1.74UnitPerpendicularVector[Segment[A,B]] sb.setLength(0); sb.append(points[0].getLabel(tpl)); sb.append('+'); sb.append(format(a.inner(d) / aLength, tpl)); // use internal command name sb.append("Vector["); sb.append(points[0].getLabel(tpl)); sb.append(','); sb.append(points[1].getLabel(tpl)); sb.append("]+"); sb.append(format(b.inner(d) / aLength, tpl)); // use internal command name sb.append("OrthogonalVector[Segment["); sb.append(points[0].getLabel(tpl)); sb.append(','); sb.append(points[1].getLabel(tpl)); rigidPolygonAddEndOfCommand(sb, is3D); // Application.debug(sb.toString()); GeoPointND pp = getAlgebraProcessor().evaluateToPoint(sb.toString(), ErrorHelper.silent(), true); try { (cons).replace((GeoElement) points[i], (GeoElement) pp); points[i] = pp; points[i].setEuclidianVisible(false); points[i].update(); } catch (Exception e) { e.printStackTrace(); return null; } } setUseInternalCommandNames(oldVal); points[0].update(); return getAlgoDispatcher().Polygon(labels, points); } /** * @param is3D * used in 3D */ protected void rigidPolygonAddEndOfCommand(StringBuilder sb, boolean is3D) { sb.append("]]"); } /** * tangent to Curve f in point P: (b'(t), -a'(t), a'(t)*b(t)-a(t)*b'(t)) */ final public GeoLine tangent(String label, GeoPointND P, GeoCurveCartesian f) { return KernelCAS.tangent(cons, label, P, f); } /** * Numeric search for extremum of function f in interval [left,right] Ulven * 2011-2-5 * * final public GeoPoint[] Extremum(String label,GeoFunction f,NumberValue * left,NumberValue right) { AlgoExtremumNumerical algo=new * AlgoExtremumNumerical(cons,label,f,left,right); GeoPoint * g=algo.getNumericalExtremum(); //All variants return array... GeoPoint[] * result=new GeoPoint[1]; result[0]=g; return result; * }//Extremum(label,geofunction,numbervalue,numbervalue) */ /*********************************** * PACKAGE STUFF ***********************************/ // temp for buildEquation /* * final private String formatAbs(double x) { if (isZero(x)) return "0"; * else return formatNF(Math.abs(x)); } */ private GeoElementSpreadsheet ges = new GeoElementSpreadsheet(); public GeoElementSpreadsheet getGeoElementSpreadsheet() { return ges; } public MacroKernel newMacroKernel() { return new MacroKernel(this); } public void notifyChangeLayer(GeoElement ge, int layer, int layer2) { app.updateMaxLayerUsed(layer2); if (notifyViewsActive) { for (View view : views) { if (view instanceof LayerView) { ((LayerView) view).changeLayer(ge, layer, layer2); } } } } /** * When function (or parabola) is transformed to curve, we need some good * estimate for which part of curve should be ploted * * @return lower bound for function -> curve transform */ public double getXmaxForFunctions() { if (getXmin() == getXmax()) { return 10; } return (((2 * getXmax()) - getXmin()) + getYmax()) - getYmin(); } /** * @see #getXmaxForFunctions() * @return upper bound for function -> curve transform */ public double getXminForFunctions() { if (getXmin() == getXmax()) { return -10; } return (((2 * getXmin()) - getXmax()) + getYmin()) - getYmax(); } /** * clear cache (needed in web when CAS loaded) */ public synchronized void clearCasCache() { if (ggbCasCache != null) { ggbCasCache.clear(); } if (ggbCAS != null) { ggbCAS.clearCache(); } } /* * used by web once CAS is loaded */ public void refreshCASCommands() { clearCasCache(); cons.recomputeCASalgos(); TreeSet<GeoElement> treeset = new TreeSet<GeoElement>( getConstruction().getGeoSetWithCasCellsConstructionOrder()); ArrayList<GeoElement> al = new ArrayList<GeoElement>(); Iterator<GeoElement> it = treeset.iterator(); while (it.hasNext()) { GeoElement geo = it.next(); if (geo instanceof FunctionalNVar) { FunctionNVar fun = ((FunctionalNVar) geo).getFunction(); if (fun != null) { fun.clearCasEvalMap(""); } } else if (geo instanceof GeoCurveCartesianND) { GeoCurveCartesianND curve = (GeoCurveCartesian) geo; curve.clearCasEvalMap(""); } AlgoElement algo = geo.getParentAlgorithm(); if (algo instanceof AlgoCasBase) { ((AlgoCasBase) algo).clearCasEvalMap(""); algo.compute(); } else if (algo instanceof AlgoUsingTempCASalgo) { ((AlgoUsingTempCASalgo) algo).refreshCASResults(); algo.compute(); } else if (algo instanceof UsesCAS || algo instanceof AlgoCasCellInterface) { // eg Limit, LimitAbove, LimitBelow, SolveODE // AlgoCasCellInterface: eg Solve[x^2] algo.compute(); } if (geo.isGeoCasCell() && algo == null) { ((GeoCasCell) geo).computeOutput(); } al.add(geo); } cons.setUpdateConstructionRunning(true); GeoElement.updateCascade(al, new TreeSet<AlgoElement>(), true); cons.setUpdateConstructionRunning(false); } public GeoElement[] polygonND(String[] labels, GeoPointND[] P) { return getAlgoDispatcher().Polygon(labels, P); } public GeoElement[] polyLineND(String label, GeoPointND[] P) { return getAlgoDispatcher().PolyLine(label, P, false); } /** * over-ridden in Kernel3D * * @param transformedLabel * @param geoPointND * @param geoPointND2 * @return */ public GeoRayND rayND(String transformedLabel, GeoPointND geoPointND, GeoPointND geoPointND2) { return getAlgoDispatcher().ray(transformedLabel, (GeoPoint) geoPointND, (GeoPoint) geoPointND2); } /** * over-ridden in Kernel3D * * @param label * @param P * @param Q * @return */ public GeoSegmentND segmentND(String label, GeoPointND P, GeoPointND Q) { return getAlgoDispatcher().Segment(label, (GeoPoint) P, (GeoPoint) Q); } public AlgoDispatcher getAlgoDispatcher() { if (algoDispatcher == null) { algoDispatcher = newAlgoDispatcher(cons); } return algoDispatcher; } /** * * @return new instance of AlgoDispatcher */ protected AlgoDispatcher newAlgoDispatcher(Construction cons1) { return new AlgoDispatcher(cons1); } public GeoRayND ray(String label, GeoPoint p, GeoPoint q) { return getAlgoDispatcher().ray(label, p, q); } public GeoSegmentND segment(String label, GeoPoint p, GeoPoint q) { return getAlgoDispatcher().Segment(label, p, q); } public GeoElement[] polygon(String[] labels, GeoPointND[] p) { return getAlgoDispatcher().Polygon(labels, p); } public GeoElement[] polyLine(String label, GeoPointND[] p, boolean b) { return getAlgoDispatcher().PolyLine(label, p, b); } public boolean hasAlgebraProcessor() { return algProcessor != null; } public void setAlgebraProcessor(AlgebraProcessor algebraProcessor) { algProcessor = algebraProcessor; } public AlgebraProcessor getAlgPForAsync() { return algProcessor; } /** * used in 3D * * @return xOy plane */ public GeoCoordSys2D getXOYPlane() { return null; } /** * used in 3D * * @return global space */ public GeoDirectionND getSpace() { return null; } /** * used for DrawEquationWeb and DrawText in GeoGebraWeb */ public void setUpdateAgain(boolean value, GeoElement geo) { updateEVAgain = value; if (value) { this.cons.addLaTeXGeo(geo); } } /** * used for DrawEquationWeb and DrawText in GeoGebraWeb */ public boolean getUpdateAgain() { return updateEVAgain && app.isHTML5Applet(); } /** * used for DrawEquationWeb and DrawText in GeoGebraWeb */ public void setForceUpdatingBoundingBox(boolean value) { forceUpdatingBoundingBox = value; } /** * used for DrawEquationWeb and DrawText in GeoGebraWeb */ public boolean getForceUpdatingBoundingBox() { return forceUpdatingBoundingBox && app.isHTML5Applet(); } public boolean useCASforDerivatives() { return !app.isScreenshotGenerator(); } public boolean useCASforIntegrals() { return !app.isScreenshotGenerator(); } public void notifyBatchUpdate() { if (notifyViewsActive) { for (View view : views) { view.startBatchUpdate(); } } } public void notifyEndBatchUpdate() { if (notifyViewsActive) { for (View view : views) { view.endBatchUpdate(); } } } public void setViewsLabels() { if (notifyViewsActive) { for (View view : views) { if (view instanceof SetLabels) { ((SetLabels) view).setLabels(); } } } } public void setViewsOrientation() { if (notifyViewsActive) { for (View view : views) { if (view instanceof SetOrientation) { ((SetOrientation) view).setOrientation(); } } } } /** * * @return true if algo (e.g. AlgoOrthoLinePointLine) doens't need to say * that we work in (or parallel to) xOy plane */ final public boolean noNeedToSpecifyXOYPlane() { return getXOYPlane() == null || getApplication().getActiveEuclidianView().isDefault2D(); } /** * * @param geo * @return 3D copy of the geo (if exists) */ public final GeoElement copy3D(GeoElement geo) { return geoFactory.copy3D(geo); } /** * * @param cons1 * target cons * @param geo * @return 3D copy internal of the geo (if exists) */ public final GeoElement copyInternal3D(Construction cons1, GeoElement geo) { return geoFactory.copyInternal3D(cons1, geo); } /** * set correct string mode regarding active euclidian view * * @param point * point */ public void setStringMode(GeoPointND point) { if (cons.isFileLoading()) { // nothing to do : string mode will be set from the XML return; } if (app.getActiveEuclidianView().isEuclidianView3D()) { point.setCartesian3D(); } else { point.setCartesian(); } point.update(); } public boolean isParsingFor3D() { if (getLoadingMode()) { return false; } EuclidianViewInterfaceCommon ev = getApplication() .getActiveEuclidianView(); if (ev.isEuclidianView3D() || ev.isShowing()) { return ev.isEuclidianView3D(); } return getApplication().showView(App.VIEW_EUCLIDIAN3D); } public GeoElement wrapInVector(GeoPointND pt) { AlgoVectorPoint algo = new AlgoVectorPoint(cons, pt); cons.removeFromConstructionList(algo); return (GeoElement) algo.getVector(); } public GeoPointND wrapInPoint(GeoVectorND pt) { AlgoPointVector algo = new AlgoPointVector(cons, cons.getOrigin(), pt); cons.removeFromConstructionList(algo); return algo.getQ(); } // for compatibility/interfacing with 3D public GeoAxisND getXAxis3D() { return null; } // for compatibility/interfacing with 3D public GeoAxisND getYAxis3D() { return null; } // for compatibility/interfacing with 3D public GeoAxisND getZAxis3D() { return null; } public int getXmaxLength() { return xmax.length; } /** * Creates a new GeoElement object for the given type string. * * @param type * String as produced by GeoElement.getXMLtypeString() */ public GeoElement createGeoElement(Construction cons1, String type) { return geoFactory.createGeoElement(cons1, type); } public GeoImplicit newImplicitPoly(Construction cons2) { return geoFactory.newImplicitPoly(cons2); } public GeoFactory getGeoFactory() { return geoFactory; } private ScheduledPreviewFromInputBar scheduledPreviewFromInputBar = new ScheduledPreviewFromInputBar( this); /** * try to create/update preview for input typed * * @param input * current algebra input */ public ScheduledPreviewFromInputBar getInputPreviewHelper() { return scheduledPreviewFromInputBar; } public final void notifyUpdatePreviewFromInputBar(GeoElement[] geos) { // event dispatcher should not collect calls to stay compatible with 4.0 if (notifyViewsActive) { for (View view : views) { view.updatePreviewFromInputBar(geos); } } } public ConstructionCompanion createConstructionCompanion( Construction cons) { return new ConstructionCompanion(cons); } private boolean userStopsLoading = false; public boolean userStopsLoading() { return userStopsLoading; } public void setUserStopsLoading(boolean flag) { userStopsLoading = flag; } /** * Computes precision. * * @return the size of a unit on the screen in pixels */ public long precision() { EuclidianView ev = this.getLastAttachedEV(); double xscale = ev == null ? EuclidianView.SCALE_STANDARD : ev.getXscale(); double yscale = ev == null ? EuclidianView.SCALE_STANDARD : ev.getYscale(); double scale = xscale < yscale ? xscale : yscale; long p = (long) scale; if (p < GeoGebraConstants.PROVER_MIN_PRECISION) { p = GeoGebraConstants.PROVER_MIN_PRECISION; } return p; } }