/* GeoGebra - Dynamic Mathematics for Everyone http://www.geogebra.org This file is part of GeoGebra. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. */ /* * GeoNumeric.java * * Created on 18. September 2001, 12:04 */ package org.geogebra.common.kernel.geos; import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.TreeSet; import org.geogebra.common.euclidian.EuclidianConstants; import org.geogebra.common.euclidian.EuclidianViewInterfaceCommon; import org.geogebra.common.euclidian.EuclidianViewInterfaceSlim; import org.geogebra.common.kernel.AnimationManager; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.ConstructionDefaults; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.algos.AlgoDependentFunction; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.algos.SymbolicParametersBotanaAlgo; import org.geogebra.common.kernel.arithmetic.Evaluate2Var; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.ExpressionValue; import org.geogebra.common.kernel.arithmetic.Function; import org.geogebra.common.kernel.arithmetic.FunctionNVar; import org.geogebra.common.kernel.arithmetic.FunctionVariable; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.kernel.arithmetic.NumberValue; import org.geogebra.common.kernel.arithmetic.ValidExpression; import org.geogebra.common.kernel.arithmetic.ValueType; import org.geogebra.common.kernel.cas.AlgoIntegralDefiniteInterface; import org.geogebra.common.kernel.kernelND.GeoElementND; import org.geogebra.common.kernel.prover.NoSymbolicParametersException; import org.geogebra.common.kernel.prover.polynomial.PPolynomial; import org.geogebra.common.kernel.prover.polynomial.PVariable; import org.geogebra.common.main.App; import org.geogebra.common.main.Feature; import org.geogebra.common.plugin.GeoClass; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.MyMath; import org.geogebra.common.util.StringUtil; import org.geogebra.common.util.debug.Log; /** * * @author Markus */ public class GeoNumeric extends GeoElement implements GeoNumberValue, AbsoluteScreenLocateable, GeoFunctionable, Animatable, HasExtendedAV, SymbolicParametersBotanaAlgo, HasSymbolicMode, AnimationExportSlider, Evaluate2Var { private PVariable[] botanaVars; /** eg boxplot */ public static final int DEFAULT_THICKNESS = 2; /** sliders */ public static final int DEFAULT_SLIDER_THICKNESS = 10; private static final double AUTO_STEP_MUL = 0.0025; private static final double AUTO_STEP_MUL_ANGLE = 0.0025; /** placeholder for autostep */ public static final double AUTO_STEP = Double.NaN; private static int DEFAULT_SLIDER_WIDTH_RW = 4; /** default slider width in pixels */ public final static int DEFAULT_SLIDER_WIDTH_PIXEL = 200; /** * Default width of angle slider in pixels * * Should be a factor of 360 to work well 72 gives increment of 5 degrees * 144 gives increment of 2.5 degrees (doesn't look good) 180 gives * increment of 2 degrees */ public final static int DEFAULT_SLIDER_WIDTH_PIXEL_ANGLE = 180; /** Default maximum value when displayed as slider */ public final static double DEFAULT_SLIDER_MIN = -5; /** Default minimum value when displayed as slider */ public final static double DEFAULT_SLIDER_MAX = 5; /** Default increment when displayed as slider */ public final static double DEFAULT_SLIDER_INCREMENT = 0.1; /** Default increment when displayed as slider */ public final static double DEFAULT_SLIDER_SPEED = 1; /** value of the number or angle */ public double value; /** true if drawable */ public boolean isDrawable = false; // private boolean isRandomNumber = false; private int slopeTriangleSize = 1; // for slider private boolean intervalMinActive = false; private boolean intervalMaxActive = false; private NumberValue intervalMin; private NumberValue intervalMax; private double sliderWidth = this instanceof GeoAngle ? DEFAULT_SLIDER_WIDTH_PIXEL_ANGLE : DEFAULT_SLIDER_WIDTH_PIXEL; private SliderPosition sliderPos; private boolean sliderFixed = false; private boolean sliderHorizontal = true; private double animationValue = Double.NaN; /** absolute screen location, true by default */ boolean hasAbsoluteScreenLocation = true; private boolean autoStep = false; private boolean symbolicMode = false; // is a constant depending on a function private boolean isDependentConst = false; /** * Creates new GeoNumeric * * @param c * Construction */ public GeoNumeric(Construction c) { this(c, true); } /** * Creates new numeric * * @param c * construction * @param setDefaults * true to set from defaults */ public GeoNumeric(Construction c, boolean setDefaults) { super(c); // moved from GeoElement's constructor // must be called from the subclass, see // http://benpryor.com/blog/2008/01/02/dont-call-subclass-methods-from-a-superclass-constructor/ if (setDefaults) { setConstructionDefaults(); // init visual settings } setEuclidianVisible(false); // setAlphaValue(ConstructionDefaults.DEFAULT_POLYGON_ALPHA); // setAnimationStep(DEFAULT_SLIDER_INCREMENT); } @Override public int getRelatedModeID() { return EuclidianConstants.MODE_SLIDER; } @Override public GeoClass getGeoClassType() { return GeoClass.NUMERIC; } /** * Creates new number * * @param c * Cons * @param x * Number value */ public GeoNumeric(Construction c, double x) { this(c); value = x; } @Override public GeoNumeric copy() { return new GeoNumeric(cons, value); } @Override public void setZero() { setValue(0); } @Override public boolean isDrawable() { return isDrawable || (getDrawAlgorithm() != getParentAlgorithm()) || (isIndependent() && isLabelSet() && isSimple()); } @Override public boolean isFillable() { return isDrawable; } /** * Sets whether the number should be drawable (as slider or angle in case of * GeoAngle) If possible, makes the number also visible. * * @param flag * true iff this number should be drawable */ public final void setDrawable(boolean flag) { setDrawable(flag, true); } /** * Sets whether the number should be drawable (as slider or angle in case of * GeoAngle) and visible. * * @param flag * true iff this number should be drawable * @param visible * true iff this number should be visible */ public final void setDrawable(boolean flag, boolean visible) { isDrawable = flag; if (visible && isDrawable && kernel.isNotifyViewsActive() && kernel.isAllowVisibilitySideEffects()) { setEuclidianVisible(true); } } @Override public void setEuclidianVisible(boolean visible) { if (visible == isSetEuclidianVisible() || kernel.isMacroKernel()) { return; } // slider is only possible for independent // number with given min and max if (isIndependent()) { if (visible) { // TODO: Remove cast from GeoNumeric GeoNumeric num = kernel.getAlgoDispatcher() .getDefaultNumber(isAngle()); // make sure the slider value is not fixed setFixed(false); if (!isIntervalMinActive() && !(intervalMin instanceof GeoNumeric)) { if (!isIntervalMaxActive() && !(intervalMax instanceof GeoNumeric)) { // set both to default setMinFrom(num); setMaxFrom(num); } else { // max is available but no min double min = Math.min(num.getIntervalMin(), Math.floor(value)); setIntervalMin(new MyDouble(kernel, min)); } } else { // min exists if (!isIntervalMaxActive() && !(intervalMax instanceof GeoNumeric)) { // min is available but no max double max = Math.max(num.getIntervalMax(), Math.ceil(value)); setIntervalMax(new MyDouble(kernel, max)); } } // init screen location if (sliderPos == null) { initScreenLocation(); } // make sure } /* * we don't want to remove min, max values when slider is hidden * else { // !visible intervalMinActive = false; intervalMaxActive = * false; } */ } super.setEuclidianVisible(visible); } private void setMaxFrom(GeoNumeric num) { double max = num.getIntervalMax(); if (value > max) { if (Math.ceil(value) < 0) { max = 0; } else { max = MyMath.nextPrettyNumber(value, 0); } } setIntervalMax(new MyDouble(kernel, max)); } private void setMinFrom(GeoNumeric num) { double min = num.getIntervalMin(); if (value < min) { if (Math.floor(value) > 0) { min = 0; } else { min = -MyMath.nextPrettyNumber(Math.abs(value), 0); } } setIntervalMin(new MyDouble(kernel, min)); } private void initScreenLocation() { int count = countSliders(); sliderPos = new SliderPosition(); if (isAbsoluteScreenLocActive()) { sliderPos.x = 30; EuclidianViewInterfaceSlim ev = kernel.getApplication() .getActiveEuclidianView(); if (ev != null) { sliderPos.y = ev.getSliderOffsetY() + 40 * count; } else { sliderPos.y = 50 + 40 * count; } // make sure slider is visible on screen sliderPos.y = (int) sliderPos.y / 400 * 10 + sliderPos.y % 400; } else { sliderPos.x = -5; sliderPos.y = 10 - count; } } private int countSliders() { int count = 0; // get all number and angle sliders TreeSet<GeoElement> numbers = cons .getGeoSetLabelOrder(GeoClass.NUMERIC); TreeSet<GeoElement> angles = cons.getGeoSetLabelOrder(GeoClass.ANGLE); numbers.addAll(angles); Iterator<GeoElement> it = numbers.iterator(); while (it.hasNext()) { GeoNumeric num = (GeoNumeric) it.next(); if (num.isSlider()) { count++; } } return count; } /** * @return true if displayed as slider */ public boolean isSlider() { return isIndependent() && isEuclidianVisible(); } @Override public boolean showInEuclidianView() { if (!isDrawable()) { return false; } if (!isDefined()) { return false; } // Double.isNaN(value) is tested in isDefined() if (Double.isInfinite(value)) { return false; } if (intervalMin == null) { return true; } if (intervalMax == null) { return true; } if (!isIntervalMinActive()) { return false; } if (!isIntervalMaxActive()) { return false; } return (getIntervalMin() < getIntervalMax()); } @Override public final boolean showInAlgebraView() { return true; } @Override public void set(GeoElementND geo) { NumberValue num = (NumberValue) geo; setValue(num.getDouble()); reuseDefinition(geo); } @Override final public void setUndefined() { value = Double.NaN; } @Override final public boolean isDefined() { AlgoElement algo; // make sure shaded-only integrals are drawn if ((algo = getParentAlgorithm()) instanceof AlgoIntegralDefiniteInterface) { AlgoIntegralDefiniteInterface aid = (AlgoIntegralDefiniteInterface) algo; if (aid.evaluateOnly()) { return true; } } return !Double.isNaN(value); } /** * Returns true iff defined and infinite * * @return true iff defined and infinite */ final public boolean isFinite() { return isDefined() && !isInfinite(); } @Override final public boolean isInfinite() { return Double.isInfinite(value); } @Override public String getLaTeXdescription() { if (strLaTeXneedsUpdate) { if (!isDefined()) { strLaTeX = "?"; } else if (isInfinite()) { if (value >= 0) { strLaTeX = "\\infty"; } else { strLaTeX = "-\\infty"; } } else { strLaTeX = toLaTeXString(false, StringTemplate.latexTemplate); } } return strLaTeX; } // Michael Borcherds 2008-04-30 @Override final public boolean isEqual(GeoElementND geo) { // return false if it's a different type, otherwise use equals() method if (geo.isGeoNumeric()) { return Kernel.isEqual(value, ((GeoNumeric) geo).value); } return false; } @Override public double getAnimationStep() { if (getAnimationStepObject() == null) { GeoNumeric num = kernel.getAlgoDispatcher() .getDefaultNumber(isGeoAngle()); setAnimationStep(num.getAnimationStep()); } if (isAutoStep()) { return getAutoStepValue(); } return super.getAnimationStep(); } private double getAutoStepValue() { if (intervalMin == null || intervalMax == null) { return isAngle() ? Math.PI / 180 : 0.05; } if (isAngle()) { // default 360 *10/200 -> 2deg return MyMath.nextPrettyNumber( (intervalMax.getDouble() - intervalMin.getDouble()) * getAnimationSpeed() * (180 / Math.PI) * AUTO_STEP_MUL_ANGLE, 0) * (Math.PI / 180); } return MyMath.nextPrettyNumber( (intervalMax.getDouble() - intervalMin.getDouble()) * getAnimationSpeed() * AUTO_STEP_MUL, 0); } /** * indicates that animation step is computed automatically or not. * * @return true is automatic animation step is set */ public boolean isAutoStep() { return autoStep; } /** * Sets automatic animation step on or off. * * @param autoStep * true if step should be computed automatically. */ public void setAutoStep(boolean autoStep) { this.autoStep = autoStep; } @Override public double getAnimationSpeed() { if (getAnimationSpeedObject() == null) { GeoNumeric num = kernel.getAlgoDispatcher() .getDefaultNumber(isGeoAngle()); setAnimationSpeed(num.getAnimationSpeed()); } return super.getAnimationSpeed(); } /** * Sets value of the number * * @param x * number value */ @Override final public void setValue(double x) { setValue(x, true); } /** * @param val0 * preferred value * @return value that respects min, max, step */ final public double restrictToSliderValues(double val0) { double min = getIntervalMin(); double max = getIntervalMax(); double val = val0; if (val > max) { val = max; } else { if (val < min) { val = min; } } // round to animation step scale val = Kernel.roundToScale(val - min, getAnimationStep()) + min; if (getAnimationStep() > Kernel.MIN_PRECISION) { // round to decimal fraction, e.g. 2.800000000001 to 2.8 val = Kernel.checkDecimalFraction(val); } return val; } /** * Sets value of the number * * @param x * number value * @param changeAnimationValue * if true, value is changed also for animation TODO reduce * visibility again */ public synchronized void setValue(double x, boolean changeAnimationValue) { setDefinition(null); if (Double.isNaN(x)) { value = Double.NaN; } else if (isIntervalMinActive() && x < getIntervalMin()) { value = getIntervalMin(); if (getCorrespondingCasCell() != null) { getCorrespondingCasCell().setInputFromTwinGeo(true, false); } } else if (isIntervalMaxActive() && x > getIntervalMax()) { value = getIntervalMax(); if (getCorrespondingCasCell() != null) { getCorrespondingCasCell().setInputFromTwinGeo(true, false); } } else { value = x; } // remember value for animation also if (changeAnimationValue) { animationValue = value; } } /** * Returns value of the number * * @return number value */ final public synchronized double getValue() { return value; } @Override public String toString(StringTemplate tpl) { if (sbToString == null) { sbToString = new StringBuilder(50); } // #4186 if (tpl.hasCASType()) { return toValueString(tpl); } sbToString.setLength(0); sbToString.append(label); sbToString.append(" = "); sbToString.append(toValueString(tpl)); return sbToString.toString(); } /** * @return string representation for regression output */ final public String toStringMinimal() { if (sbToString == null) { sbToString = new StringBuilder(50); } sbToString.setLength(0); sbToString.append(toValueStringMinimal()); return sbToString.toString(); } /** * @return string representation of value for regression output */ public String toValueStringMinimal() { return regrFormat(value); } private StringBuilder sbToString; private ArrayList<GeoNumeric> minMaxListeners; private boolean randomSlider = false; private Double origSliderWidth = null; private Double origSliderX = null; private Double origSliderY = null; @Override public String toValueString(StringTemplate tpl) { // see MyDouble.toString() if (tpl.hasCASType()) { if (this.label != null && (this.label.startsWith("c_") || this.label.startsWith("k_"))) { // needed for GGB-903 // if label starts with c_ // look up if it is stored as constant GeoNumeric geo = this.cons.lookupConstantLabel(label); if (geo != null) { this.setSendValueToCas(false); } else { this.setSendValueToCas(true); } } if (!sendValueToCas) { return "(ggbtmpvar" + label + ")"; } // make sure random() works inside Sequence, see #3558 if (this.isRandomGeo() && !this.isLabelSet()) { return "exact(rand(0,1))"; } if (Double.isNaN(value)) { return "undef"; } if (Double.isInfinite(value)) { if (value > 0) { return "inf"; } return "-inf"; } if (getDefinition() != null) { return getDefinition().toValueString(tpl); } return StringUtil.wrapInExact(kernel.format(value, tpl), tpl); } if (symbolicMode && getDefinition() != null && tpl.supportsFractions()) { return getDefinition().toFractionString(tpl); } return kernel.format(value, tpl); } /** * interface NumberValue */ @Override public MyDouble getNumber() { return new MyDouble(kernel, value); } @Override final public double getDouble() { return value; } @Override public void setAllVisualPropertiesExceptEuclidianVisible(GeoElement geo, boolean keepAdvanced) { super.setAllVisualPropertiesExceptEuclidianVisible(geo, keepAdvanced); if (geo.isGeoNumeric()) { isDrawable = ((GeoNumeric) geo).isDrawable; } } @Override public void setVisualStyle(GeoElement geo) { super.setVisualStyle(geo); if (geo.isGeoNumeric()) { slopeTriangleSize = ((GeoNumeric) geo).slopeTriangleSize; setAutoStep(((GeoNumeric) geo).autoStep); symbolicMode = ((GeoNumeric) geo).symbolicMode; sliderFixed = ((GeoNumeric) geo).sliderFixed; } } /** * returns all class-specific xml tags for saveXML */ @Override protected void getXMLtags(StringBuilder sb) { sb.append("\t<value val=\""); sb.append(value); sb.append("\""); if (isRandom()) { sb.append(" random=\"true\""); } sb.append("/>\n"); if (symbolicMode) { sb.append("\t<symbolic val=\"true\" />\n"); } // colors getXMLvisualTags(sb); // if number is drawable then we need to save visual options too if (isDrawable || isSliderable()) { // save slider info before show to have min and max set // before setEuclidianVisible(true) is called getXMLsliderTag(sb); // line thickness and type getLineStyleXML(sb); // for slope triangle if (slopeTriangleSize > 1) { sb.append("\t<slopeTriangleSize val=\""); sb.append(slopeTriangleSize); sb.append("\"/>\n"); } } getXMLanimationTags(sb); getXMLfixedTag(sb); getAuxiliaryXML(sb); getBreakpointXML(sb); getScriptTags(sb); } /** * Returns true iff slider is possible * * @return true iff slider is possible */ protected boolean isSliderable() { return isIndependent() && (isIntervalMinActive() || isIntervalMaxActive()); } @Override public boolean isFixable() { // visible slider should not be fixable if whiteboard doesn't active return (!isSetEuclidianVisible() || (kernel.getApplication().isWhiteboardActive() && kernel.getApplication().has(Feature.IMPROVE_CONTEXT_MENU))) && !isDefaultGeo(); } /** * @param b * - true, if is constant depending on function */ public void setIsDependentConst(boolean b) { this.isDependentConst = b; } /** * @return true - if is constant depending on function */ public boolean isDependentConst() { return this.isDependentConst; } /** * Adds the slider tag to the string builder * * @param sb * String builder to be written to */ protected void getXMLsliderTag(StringBuilder sb) { if (!isSliderable()) { return; } StringTemplate tpl = StringTemplate.xmlTemplate; sb.append("\t<slider"); if (isIntervalMinActive() || intervalMin instanceof GeoNumeric) { sb.append(" min=\""); StringUtil.encodeXML(sb, getIntervalMinObject().getLabel(tpl)); sb.append("\""); } if (isIntervalMaxActive() || intervalMax instanceof GeoNumeric) { sb.append(" max=\""); StringUtil.encodeXML(sb, getIntervalMaxObject().getLabel(tpl)); sb.append("\""); } if (hasAbsoluteScreenLocation) { sb.append(" absoluteScreenLocation=\"true\""); } sb.append(" width=\""); sb.append(sliderWidth); if (sliderPos != null) { sb.append("\" x=\""); sb.append(sliderPos.x); sb.append("\" y=\""); sb.append(sliderPos.y); } sb.append("\" fixed=\""); sb.append(sliderFixed); sb.append("\" horizontal=\""); sb.append(sliderHorizontal); sb.append("\" showAlgebra=\""); sb.append(isShowingExtendedAV()); sb.append("\"/>\n"); } @Override public boolean isNumberValue() { return true; } @Override public boolean isGeoNumeric() { return true; } /** * Returns size of the triangle when used for slop * * @return size of the triangle when used for slope */ final public int getSlopeTriangleSize() { return slopeTriangleSize; } /** * Set size of the triangle when used for slop * * @param i * Size of the slope triangle */ public void setSlopeTriangleSize(int i) { slopeTriangleSize = i; } /** * Changes maximal value for slider * * @param max * New maximum for slider */ public void setIntervalMax(NumberValue max) { if (intervalMax instanceof GeoNumeric) { ((GeoNumeric) intervalMax).unregisterMinMaxListener(this); } intervalMax = max; setIntervalMaxActive(!Double.isNaN(max.getDouble())); if (max instanceof GeoNumeric) { ((GeoNumeric) max).registerMinMaxListener(this); } resolveMinMax(); } /** * Changes minimal value for slider * * @param min * New minimum for slider */ public void setIntervalMin(NumberValue min) { if (intervalMin instanceof GeoNumeric) { ((GeoNumeric) intervalMin).unregisterMinMaxListener(this); } intervalMin = min; setIntervalMinActive(!Double.isNaN(min.getDouble())); if (min instanceof GeoNumeric) { ((GeoNumeric) min).registerMinMaxListener(this); } resolveMinMax(); } /** * Changes slider width in pixels * * @param width * slider width in pixels */ public final void setSliderWidth(double width) { if (width > 0 && !Double.isInfinite(width)) { if (getOrigSliderWidth() == null) { setOrigSliderWidth(width); } } sliderWidth = width; } /** * Sets the location of the slider for this number. * * @param x * x-coord of the slider * @param y * y-coord of the slider * @param force * when false, this method ignores fixed sliders */ public final void setSliderLocation(double x, double y, boolean force) { if (!force && sliderFixed) { return; } if (sliderPos == null) { sliderPos = new SliderPosition(); } sliderPos.x = x; sliderPos.y = y; if (origSliderX == null) { origSliderX = x; origSliderY = y; } } /** * Returns maximal value for slider * * @return maximal value for slider */ @Override public final double getIntervalMax() { return intervalMax.getDouble(); } /** * Returns minimal value for slider * * @return minimal value for slider */ @Override public final double getIntervalMin() { return intervalMin.getDouble(); } /** * Returns slider width in pixels * * @return slider width in pixels */ public final double getSliderWidth() { return sliderWidth; } /** * Returns x-coord of the slider * * @return x-coord of the slider */ public final double getSliderX() { return sliderPos == null ? 0 : sliderPos.x; } /** * Returns y-coord of the slider * * @return y-coord of the slider */ public final double getSliderY() { return sliderPos == null ? 0 : sliderPos.y; } /** * Returns true if slider max value wasn't disabled in Properties * * @return true if slider max value wasn't disabled */ public final boolean isIntervalMaxActive() { return intervalMaxActive; } /** * Returns true if slider min value wasn't disabled in Properties * * @return true if slider min value wasn't disabled */ public final boolean isIntervalMinActive() { return intervalMinActive; } /** * Returns true iff slider is fixed in graphics view * * @return true iff slider is fixed in graphics view */ public final boolean isSliderFixed() { return sliderFixed; } /** * Sets whether slider is fixed in graphics view * * @param isSliderFixed * true iff slider is fixed in graphics view */ public final void setSliderFixed(boolean isSliderFixed) { sliderFixed = isSliderFixed; } /** * Returns whether slider shoud be horizontal or vertical * * @return true iff should be horizontal */ public final boolean isSliderHorizontal() { return sliderHorizontal; } /** * Sets whether slider should be horizontal or vertical * * @param sliderHorizontal * true iff should be horizontal */ public void setSliderHorizontal(boolean sliderHorizontal) { this.sliderHorizontal = sliderHorizontal; } /** * Sets the location of the slider for this number. * * @param x * x-coord of the slider * @param y * y-coord of the slider * @param force * when false, this method ignores fixed sliders */ public void setAbsoluteScreenLoc(int x, int y, boolean force) { setSliderLocation(x, y, force); } @Override public void setAbsoluteScreenLoc(int x, int y) { setSliderLocation(x, y, true); } @Override public int getAbsoluteScreenLocX() { return sliderPos == null ? 0 : (int) sliderPos.x; } @Override public int getAbsoluteScreenLocY() { return sliderPos == null ? 0 : (int) sliderPos.y; } @Override public void setRealWorldLoc(double x, double y) { if (sliderPos == null) { sliderPos = new SliderPosition(); } sliderPos.x = x; sliderPos.y = y; } @Override public double getRealWorldLocX() { return sliderPos == null ? 0 : sliderPos.x; } @Override public double getRealWorldLocY() { return sliderPos == null ? 0 : sliderPos.y; } @Override public void setAbsoluteScreenLocActive(boolean flag) { hasAbsoluteScreenLocation = flag; if (flag) { sliderWidth = this instanceof GeoAngle ? DEFAULT_SLIDER_WIDTH_PIXEL_ANGLE : DEFAULT_SLIDER_WIDTH_PIXEL; } else { sliderWidth = DEFAULT_SLIDER_WIDTH_RW; } } @Override public boolean isAbsoluteScreenLocActive() { return hasAbsoluteScreenLocation; } @Override public boolean isAbsoluteScreenLocateable() { return isSliderable(); } /** * Creates a GeoFunction of the form f(x) = thisNumber * * @return constant function */ @Override public GeoFunction getGeoFunction() { ExpressionNode en = new ExpressionNode(kernel, this); Function fun = new Function(en, new FunctionVariable(kernel)); GeoFunction ret; // we get a dependent function if this number has a label or is // dependent if (isLabelSet() || !isIndependent()) { ret = new AlgoDependentFunction(cons, fun, false).getFunction(); } else { ret = new GeoFunction(cons); ret.setFunction(fun); } return ret; } @Override public boolean isGeoFunctionable() { return true; } @Override public void doRemove() { super.doRemove(); // if this was a random number, make sure it's removed cons.removeRandomGeo(this); if (intervalMin instanceof GeoNumeric) { ((GeoNumeric) intervalMin).unregisterMinMaxListener(this); } if (intervalMax instanceof GeoNumeric) { ((GeoNumeric) intervalMax).unregisterMinMaxListener(this); } } /** * Given geo depends on this one (via min or max value for slider) and * should be updated * * @param geo * geo to be updated */ public void registerMinMaxListener(GeoNumeric geo) { if (minMaxListeners == null) { minMaxListeners = new ArrayList<GeoNumeric>(); } minMaxListeners.add(geo); } /** * Given geo no longer depends on this one (via min or max value for slider) * and should not be updated any more * * @param geo * slider whose min/max is this numeric */ public void unregisterMinMaxListener(GeoNumeric geo) { if (minMaxListeners == null) { minMaxListeners = new ArrayList<GeoNumeric>(); } minMaxListeners.remove(geo); } /** * @return list of min/max listeners */ public List<GeoNumeric> getMinMaxListeners() { return minMaxListeners; } /** * @param random * true for random slider */ public void setRandom(boolean random) { randomSlider = random; if (random) { cons.addRandomGeo(this); } else { cons.removeRandomGeo(this); } } /** * returns true for random sliders (can be hidden to make random numbers * which still use intervalMin, Max, interval) * * @return true for random sliders */ public boolean isRandom() { return randomSlider; } /** * Updates random slider */ public void updateRandom() { if (randomSlider && isIntervalMaxActive() && isIntervalMinActive()) { // update all algorithms in the algorithm set of this GeoElement value = getRandom(); updateCascade(); } } /** * Updates random slider - no updateCascade() */ public void updateRandomNoCascade() { if (randomSlider && isIntervalMaxActive() && isIntervalMinActive()) { // update all algorithms in the algorithm set of this GeoElement value = getRandom(); } } /* * returns a random number in the slider's range (and using step-size) */ private double getRandom() { double min = getIntervalMin(); double max = getIntervalMax(); double increment = getAnimationStep(); int n = 1 + (int) Math.round((max - min) / increment); return Kernel.checkDecimalFraction( Math.floor(kernel.getApplication().getRandomNumber() * n) * increment + min); } @Override public void update(boolean drag) { super.update(drag); if (minMaxListeners != null) { for (int i = 0; i < minMaxListeners.size(); i++) { GeoNumeric geo = minMaxListeners.get(i); geo.resolveMinMax(); } } if (evListeners != null) { for (EuclidianViewInterfaceSlim ev : evListeners) { ev.updateBounds(true, true); } } } private void resolveMinMax() { double oldValue = value; if (intervalMin == null || intervalMax == null) { return; } boolean okMin = !Double.isNaN(getIntervalMin()) && !Double.isInfinite(getIntervalMin()); boolean okMax = !Double.isNaN(getIntervalMax()) && !Double.isInfinite(getIntervalMax()); boolean ok = (getIntervalMin() <= getIntervalMax()); setIntervalMinActive(ok && okMin); setIntervalMaxActive((ok && okMin && okMax) || (getIntervalMin() == getIntervalMax() && okMin && okMax)); if (ok && okMin && okMax) { setValue(isDefined() ? value : 1.0); } else if (okMin && okMax) { setUndefined(); } if (oldValue != value) { updateCascade(); } else { // we want to make the slider visible again if it was not // do what GeoElement.update does (no need to call listeners) // also don't update the CAS updateGeo(false); kernel.notifyUpdate(this); } } /** * Returns whether this number can be animated. Only free numbers with min * and max interval values can be animated (i.e. shown or hidden sliders). */ @Override public boolean isAnimatable() { return isIndependent() && isIntervalMinActive() && isIntervalMaxActive(); } /** * Sets the state of this object to animating on or off. */ @Override public synchronized void setAnimating(boolean flag) { animationValue = Double.NaN; super.setAnimating(flag); } /** * Performs the next automatic animation step for this numbers. This changes * the value but will NOT call update() or updateCascade(). * * @return this or null, depending on whether the value of this number was * changed */ @Override final public synchronized GeoNumeric doAnimationStep(double frameRate, GeoList parent) { // check that we have valid min and max values if (!isIntervalMinActive() || !isIntervalMaxActive()) { return null; } // special case for random slider // animationValue goes from 0 to animationStep if (isRandom()) { double animationStep = getAnimationStep(); // check not silly value if (animationValue < -2 * animationStep) { animationValue = 0; } double intervalWidth = getIntervalMax() - getIntervalMin(); double step = intervalWidth * getAnimationSpeed() / (AnimationManager.STANDARD_ANIMATION_TIME * frameRate); // update animation value if (Double.isNaN(animationValue) || animationValue < 0) { animationValue = 0; } animationValue = animationValue + Math.abs(step); if (animationValue > animationStep) { animationValue -= animationStep; setValue(getRandom(), false); return this; } // no update needed return null; } // remember old value of number to decide whether update is necessary double oldValue = getValue(); // compute animation step based on speed and frame rates double intervalWidth = getIntervalMax() - getIntervalMin(); double step = intervalWidth * getAnimationSpeed() * getAnimationDirection() / (AnimationManager.STANDARD_ANIMATION_TIME * frameRate); // update animation value if (Double.isNaN(animationValue)) { animationValue = oldValue; } animationValue = animationValue + step; // make sure we don't get outside our interval switch (getAnimationType()) { case GeoElement.ANIMATION_DECREASING: case GeoElement.ANIMATION_INCREASING: // jump to other end of slider if (animationValue > getIntervalMax()) { animationValue = animationValue - intervalWidth; } else if (animationValue < getIntervalMin()) { animationValue = animationValue + intervalWidth; } break; case GeoElement.ANIMATION_INCREASING_ONCE: // stop if outside range if (animationValue > getIntervalMax()) { setAnimating(false); boolean changed = getIntervalMax() != value; setValue(getIntervalMax(), false); return changed ? this : null; } else if (animationValue < getIntervalMin()) { setAnimating(false); setValue(getIntervalMin(), false); return this; } break; case GeoElement.ANIMATION_OSCILLATING: default: boolean parentStep = false; if (animationValue >= getIntervalMax()) { animationValue = getIntervalMax(); changeAnimationDirection(); parentStep = true; } else if (animationValue <= getIntervalMin()) { animationValue = getIntervalMin(); changeAnimationDirection(); parentStep = true; } if (parentStep && parent != null) { parent.selectNext(); return null; } break; } double newValue; // take current slider increment size into account: // round animationValue to newValue using slider's increment setting double param = animationValue - getIntervalMin(); param = Kernel.roundToScale(param, getAnimationStep()); newValue = getIntervalMin() + param; if (getAnimationStep() > Kernel.MIN_PRECISION) { // round to decimal fraction, e.g. 2.800000000001 to 2.8 newValue = Kernel.checkDecimalFraction(newValue); } // change slider's value without changing animationValue setValue(newValue, false); // return whether value of slider has changed return getValue() != oldValue ? this : null; } /** * Returns a comparator for NumberValue objects. If equal, doesn't return * zero (otherwise TreeSet deletes duplicates) * * @return 1 if first is greater (or same but sooner in construction), -1 * otherwise */ public static Comparator<GeoNumberValue> getComparator() { if (comparator == null) { comparator = new Comparator<GeoNumberValue>() { @Override public int compare(GeoNumberValue itemA, GeoNumberValue itemB) { double comp = itemA.getDouble() - itemB.getDouble(); if (Kernel.isZero(comp)) { // don't return 0 for equal objects, otherwise the // TreeSet deletes duplicates return itemA.getConstructionIndex() > itemB .getConstructionIndex() ? -1 : 1; } return comp < 0 ? -1 : +1; } }; } return comparator; } private static volatile Comparator<GeoNumberValue> comparator; // protected void setRandomNumber(boolean flag) { // isRandomNumber = flag; // } // public boolean isRandomNumber() { // return isRandomNumber; // } @Override final public void updateRandomGeo() { // set random value (for numbers used in trees using random()) setValue(kernel.getApplication().getRandomNumber()); final AlgoElement algo = getParentAlgorithm(); if (algo != null) { algo.compute(); // eg AlgoRandom etc } else { updateRandom(); } } /** * @return minimum line thickness (normally 1, but 0 for polygons, integrals * etc) */ @Override public int getMinimumLineThickness() { return (isSlider() ? 1 : 0); } /** * Set interval min * * @param value * new min for this slider */ public void setIntervalMin(double value) { setIntervalMin(new MyDouble(kernel, value)); } /** * Set interval max * * @param value * new max for this slider */ public void setIntervalMax(double value) { setIntervalMax(new MyDouble(kernel, value)); } /** * Get interval min as geo * * @return interval min */ public NumberValue getIntervalMinObject() { if (intervalMin == null) { return null; } return intervalMin; } /** * Get interval max as geo * * @return interval max */ public NumberValue getIntervalMaxObject() { if (intervalMax == null) { return null; } return intervalMax; } @Override public boolean canHaveClickScript() { return false; } @Override final public boolean isCasEvaluableObject() { return true; } private ArrayList<EuclidianViewInterfaceSlim> evListeners = null; /** * @param ev * euclidian view which listens to this numeric */ public void addEVSizeListener(EuclidianViewInterfaceSlim ev) { if (evListeners == null) { evListeners = new ArrayList<EuclidianViewInterfaceSlim>(); } if (!evListeners.contains(ev)) { evListeners.add(ev); } } /** * @param ev * euclidian view which listens to this numeric */ public void removeEVSizeListener(EuclidianViewInterfaceSlim ev) { if (evListeners != null) { evListeners.remove(ev); } } @Override public void moveDependencies(GeoElement oldGeo) { if (!oldGeo.isGeoNumeric()) { return; } GeoNumeric num = (GeoNumeric) oldGeo; if (num.evListeners != null) { evListeners = num.evListeners; for (EuclidianViewInterfaceSlim ev : num.evListeners) { ev.replaceBoundObject(num, this); } num.evListeners = null; } if (num.minMaxListeners != null) { minMaxListeners = num.minMaxListeners; for (GeoNumeric slider : minMaxListeners) { if (slider.getIntervalMaxObject() == num) { slider.setIntervalMax(this); } if (slider.getIntervalMinObject() == num) { slider.setIntervalMin(this); } } } } @Override public boolean isLaTeXDrawableGeo() { return false; } @Override public boolean hasLineOpacity() { return true; } @Override public void addToSpreadsheetTraceList( ArrayList<GeoNumeric> spreadsheetTraceList) { GeoNumeric xx = this.copy(); // should handle GeoAngle // too spreadsheetTraceList.add(xx); } private void setIntervalMinActive(boolean intervalMinActive) { this.intervalMinActive = intervalMinActive; } private void setIntervalMaxActive(boolean intervalMaxActive) { this.intervalMaxActive = intervalMaxActive; } @Override public boolean isPinnable() { return isSlider(); } @Override public ExpressionValue integral(FunctionVariable fv, Kernel kernel0) { return new ExpressionNode(kernel0, this, Operation.MULTIPLY, fv); } /** * @param num * number to update * @param isAngle * whether it's angle * @return num */ public static GeoNumeric setSliderFromDefault(GeoNumeric num, boolean isAngle) { return setSliderFromDefault(num, isAngle, true); } /** * @param num * number to update * @param isAngle * whether it's angle * @param visible * visible in EV * @return num */ public static GeoNumeric setSliderFromDefault(GeoNumeric num, boolean isAngle, boolean visible) { GeoNumeric defaultNum = num.getKernel().getAlgoDispatcher() .getDefaultNumber(false); GeoNumeric defaultAngleOrNum = num.getKernel().getAlgoDispatcher() .getDefaultNumber(isAngle); num.setSliderFixed(defaultNum.isSliderFixed()); num.setEuclidianVisible(visible); num.setIntervalMin(defaultAngleOrNum.getIntervalMinObject()); num.setIntervalMax(defaultAngleOrNum.getIntervalMaxObject()); num.setAnimationStep(defaultAngleOrNum.getAnimationStep()); num.setAutoStep(defaultAngleOrNum.isAutoStep()); num.setAbsoluteScreenLocActive(true); num.setAnimationType(defaultNum.getAnimationType()); num.setSliderWidth(defaultAngleOrNum.getSliderWidth()); num.setRandom(defaultNum.isRandom()); num.setLineThickness(DEFAULT_SLIDER_THICKNESS); num.setDrawable(false, false); num.update(); return num; } @Override final public HitType getLastHitType() { return HitType.ON_BOUNDARY; } private boolean showExtendedAV = true; @Override public boolean isShowingExtendedAV() { return showExtendedAV; } @Override public void setShowExtendedAV(boolean showExtendedAV) { this.showExtendedAV = showExtendedAV; } @Override protected void setLabelModeDefault() { // label visibility App app = getKernel().getApplication(); int labelingStyle = app == null ? ConstructionDefaults.LABEL_VISIBLE_USE_DEFAULTS : app.getCurrentLabelingStyle(); // automatic labelling: // if algebra window open -> all labels // else -> no labels boolean visible = false; switch (labelingStyle) { case ConstructionDefaults.LABEL_VISIBLE_ALWAYS_ON: visible = true; break; case ConstructionDefaults.LABEL_VISIBLE_ALWAYS_OFF: visible = false; break; case ConstructionDefaults.LABEL_VISIBLE_POINTS_ONLY: // we want sliders and angles to be labeled always visible = true; break; default: case ConstructionDefaults.LABEL_VISIBLE_USE_DEFAULTS: // don't change anything visible = true; break; } if (visible) { labelMode = LABEL_NAME_VALUE; } else { labelMode = LABEL_VALUE; } } @Override public PVariable[] getBotanaVars(GeoElementND geo) throws NoSymbolicParametersException { if (algoParent instanceof SymbolicParametersBotanaAlgo) { return ((SymbolicParametersBotanaAlgo) algoParent) .getBotanaVars(this); } if (algoParent == null) { if (botanaVars == null) { botanaVars = new PVariable[1]; botanaVars[0] = new PVariable(kernel, true); Log.debug("Variable " + geo.getLabelSimple() + "(" + botanaVars[0] + ")"); } } return botanaVars; } @Override public PPolynomial[] getBotanaPolynomials(GeoElementND geo) throws NoSymbolicParametersException { if (algoParent instanceof SymbolicParametersBotanaAlgo) { return ((SymbolicParametersBotanaAlgo) algoParent) .getBotanaPolynomials(this); } return null; // Here maybe an exception should be thrown...? } /** * @param geoElement * make sure min/max interval is big enough to contain the value */ public void extendMinMax(GeoElement geoElement) { if (geoElement instanceof GeoNumeric) { value = geoElement.evaluateDouble(); if (getIntervalMaxObject() != null && isChangeable(getIntervalMaxObject())) { setMaxFrom(this); } value = geoElement.evaluateDouble(); if (getIntervalMinObject() != null && isChangeable(getIntervalMinObject())) { setMinFrom(this); } } } /** * @param val * value * @return whether value is either not numeric or it's unlabeled independent * numeric */ public static boolean isChangeable(NumberValue val) { if (!(val instanceof GeoElement)) { return true; } return ((GeoElement) val).isIndependent() && !((GeoElement) val).isLabelSet(); } @Override public ValueType getValueType() { return ValueType.NUMBER; } /** * Update min and max for slider in Algebra */ public void initAlgebraSlider() { if (!showExtendedAV) { return; } SliderPosition old = sliderPos; setEuclidianVisible(true); setEuclidianVisible(false); sliderPos = old; } @Override public ExpressionValue getUndefinedCopy(Kernel kernel1) { return new MyDouble(kernel1, Double.NaN); } @Override public ValidExpression toValidExpression() { return getNumber(); } @Override public void setSymbolicMode(boolean mode, boolean update) { this.symbolicMode = mode; } @Override public boolean isSymbolicMode() { return symbolicMode; } @Override public boolean needToShowBothRowsInAV() { return super.needToShowBothRowsInAV() || (getDefinition() != null && getDefinition().isFraction()); } /** * @return original slider width from XML */ public Double getOrigSliderWidth() { return origSliderWidth; } /** * @param origSliderWidth * original slider width from XML */ public void setOrigSliderWidth(Double origSliderWidth) { this.origSliderWidth = origSliderWidth; } /** * @return original slider x-coord from XML */ public Double getOrigSliderX() { return origSliderX; } /** * @param origSliderX * original slider x-coord from XML */ public void setOrigSliderX(Double origSliderX) { this.origSliderX = origSliderX; } /** * @return original slider y-coord from XML */ public Double getOrigSliderY() { return origSliderY; } /** * @param origSliderY * original slider y-coord from XML */ public void setOrigSliderY(Double origSliderY) { this.origSliderY = origSliderY; } @Override public double evaluate(double x, double y) { return value; } @Override public ExpressionNode getFunctionExpression() { return getGeoFunction().getFunctionExpression(); } @Override public FunctionNVar getFunction() { return getGeoFunction().getFunction(); } @Override public String getVarString(StringTemplate defaulttemplate) { return "x"; } public int getTotalWidth(EuclidianViewInterfaceCommon ev) { return 0; } public int getTotalHeight(EuclidianViewInterfaceCommon ev) { return 0; } @Override public double evaluateDouble() { return getDouble(); } }