/*
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.
*/
/*
* GeoAngle.java
*
* The toString() depends on the kernels angle unit state (DEGREE or RADIANT)
*
* Created on 18. September 2001, 12:04
*/
package org.geogebra.common.kernel.geos;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.algos.AlgoAngle;
import org.geogebra.common.kernel.arithmetic.MyDouble;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.plugin.GeoClass;
import org.geogebra.common.util.lang.Unicode;
/**
*
* @author Markus
*/
public class GeoAngle extends GeoNumeric implements AngleProperties {
// public int arcSize = EuclidianStyleConstants.DEFAULT_ANGLE_SIZE;
private int arcSize;
// allow angle > pi
// private boolean allowReflexAngle = true;
// shows whether the current value was changed to (2pi - value)
// private boolean changedReflexAngle;
// states whether a special right angle appearance should be used to draw
// this angle
private boolean emphasizeRightAngle = true;
// Michael Borcherds 2007-10-20
private double rawValue;
/** Default minimum value when displayed as slider */
final public static double DEFAULT_SLIDER_MIN_ANGLE = 0;
/** Default maximum value when displayed as slider */
final public static double DEFAULT_SLIDER_MAX_ANGLE = Kernel.PI_2;
/**
* default increment when displayed as slider
*
* 5 degrees allows 45 degrees to be selected when the slider width is 180
*
*/
final public static double DEFAULT_SLIDER_INCREMENT_ANGLE = 5 * Math.PI
/ 180.0;
/**
* different angle styles
*
*/
public enum AngleStyle {
/**
* (old allowReflexAngle=true)
*/
ANTICLOCKWISE(0),
/**
* Force angle not to be reflex ie [0,180] (old allowReflexAngle=true)
*/
NOTREFLEX(1),
/**
* Force angle to be reflex ie [180,360]
*/
ISREFLEX(2),
/**
* allow angles to be in the range (-infinity, infinity)
*
* only for Angles which aren't drawable
*/
UNBOUNDED(3);
private int xmlVal;
/**
* @return number for this style in XML
*/
public int getXmlVal() {
return xmlVal;
}
AngleStyle(int xmlVal) {
this.xmlVal = xmlVal;
}
/**
* @param style
* integer from XML
* @return Enum
*/
public static AngleStyle getStyle(int style) {
for (AngleStyle l : AngleStyle.values()) {
if (l.xmlVal == style) {
return l;
}
}
return AngleStyle.ANTICLOCKWISE;
}
}
private AngleStyle angleStyle = AngleStyle.ANTICLOCKWISE;
/**
* @author Loic
* @return List of decoration types.
*/
public static final Integer[] getDecoTypes() {
Integer[] ret = { Integer.valueOf(GeoElement.DECORATION_NONE),
Integer.valueOf(GeoElement.DECORATION_ANGLE_TWO_ARCS),
Integer.valueOf(GeoElement.DECORATION_ANGLE_THREE_ARCS),
Integer.valueOf(GeoElement.DECORATION_ANGLE_ONE_TICK),
Integer.valueOf(GeoElement.DECORATION_ANGLE_TWO_TICKS),
Integer.valueOf(GeoElement.DECORATION_ANGLE_THREE_TICKS),
Integer.valueOf(
GeoElement.DECORATION_ANGLE_ARROW_ANTICLOCKWISE),
Integer.valueOf(GeoElement.DECORATION_ANGLE_ARROW_CLOCKWISE) };
return ret;
}
//////////////////////////////////////////
// INTERVAL
//////////////////////////////////////////
/** interval minima for different angle styles */
private static final String[] INTERVAL_MIN = { "0" + Unicode.DEGREE_CHAR,
"0" + Unicode.DEGREE_CHAR, "180" + Unicode.DEGREE_CHAR,
"-" + Unicode.INFINITY };
/**
* @param i
* index
* @return i-th interval maximum
*/
public static String getIntervalMinList(int i) {
return INTERVAL_MIN[i];
}
/**
* @return number of min/max intervals
*/
public static int getIntervalMinListLength() {
return INTERVAL_MIN.length;
}
/**
* @param i
* index
* @return i-th interval minimum
*/
public static String getIntervalMaxList(int i) {
return INTERVAL_MAX[i];
}
/** interval maxima for different angle styles */
private static final String[] INTERVAL_MAX = { "360" + Unicode.DEGREE_CHAR,
"180" + Unicode.DEGREE_CHAR, "360" + Unicode.DEGREE_CHAR,
"" + Unicode.INFINITY };
/**
* Creates new GeoAngle
*
* @param c
* Construction
*/
public GeoAngle(Construction c) {
super(c);
// setAlphaValue(ConstructionDefaults.DEFAULT_ANGLE_ALPHA);
// setLabelMode(GeoElement.LABEL_NAME);
// moved from GeoElement's constructor
// must be called from the subclass, see
// http://benpryor.com/blog/2008/01/02/dont-call-subclass-methods-from-a-superclass-constructor/
setConstructionDefaults(); // init visual settings
// setEuclidianVisible(false);
}
@Override
public void setAllVisualPropertiesExceptEuclidianVisible(GeoElement geo,
boolean keepAdvanced) {
super.setAllVisualPropertiesExceptEuclidianVisible(geo, keepAdvanced);
if (geo.isGeoAngle()) {
setAngleStyle(((GeoAngle) geo).getAngleStyle());
}
}
/**
* Creates labeled angle of given size
*
* @param c
* Construction
* @param label
* Name for angle
* @param x
* Size of the angle
*/
public GeoAngle(Construction c, String label, double x) {
this(c, x);
setLabel(label);
}
/**
* Creates labeled angle of given size
*
* @param c
* Construction
* @param x
* Size of the angle
* @param style
* eg UNBOUNDED
*/
public GeoAngle(Construction c, double x, AngleStyle style) {
this(c);
// must set style before value
setAngleStyle(style);
setValue(x);
}
@Override
public GeoClass getGeoClassType() {
return GeoClass.ANGLE;
}
/**
* Creates new angle of given size
*
* @param c
* Construction
* @param x
* Size of the angle
*/
public GeoAngle(Construction c, double x) {
this(c);
setValue(x);
}
@Override
final public boolean isGeoAngle() {
return true;
}
@Override
final public int getAngleDim() {
return 1;
}
@Override
public void set(GeoElementND geo) {
GeoNumeric num = (GeoNumeric) geo;
setValue(num.getValue());
}
@Override
public void setVisualStyle(GeoElement geo) {
super.setVisualStyle(geo);
if (geo.isGeoAngle()) {
GeoAngle ang = (GeoAngle) geo;
arcSize = ang.arcSize;
if (!ang.isIndependent() || this.isDefaultGeo()) { // avoids also
// default angle
// to apply its
// style (angle
// interval)
// to all new angles (e.g. independent angles)
setAngleStyle(ang.angleStyle); // to update the value
}
emphasizeRightAngle = ang.emphasizeRightAngle;
}
}
// Michael Borcherds 2007-10-21 BEGIN
/**
* Sets the value of this angle. Every value is limited between 0 and 2pi.
* Under some conditions a value > pi will be changed to (2pi - value).
*
* @see #setAngleStyle(int)
*/
@Override
public synchronized void setValue(double val,
boolean changeAnimationValue) {
double angVal = calcAngleValue(val);
super.setValue(angVal, changeAnimationValue);
}
/**
* Converts the val to a value between 0 and 2pi.
*/
private double calcAngleValue(double val) {
// limit to [0, 2pi]
double angVal;
if (angleStyle != AngleStyle.UNBOUNDED) {
angVal = Kernel.convertToAngleValue(val);
} else {
angVal = val;
}
rawValue = angVal;
// if needed: change angle
switch (angleStyle) {
// case ANGLE_ISCLOCKWISE:
// angVal = 2.0 * Math.PI - angVal;
// break;
case NOTREFLEX:
if (angVal > Math.PI) {
angVal = 2.0 * Math.PI - angVal;
}
break;
case ISREFLEX:
if (angVal < Math.PI) {
angVal = 2.0 * Math.PI - angVal;
}
break;
case ANTICLOCKWISE:
break;
case UNBOUNDED:
break;
default:
break;
}
return angVal;
}
/*
* not needed now that angles can be unbounded (AngleStyle.UNBOUNDED)
*
* @Override public void setIntervalMax(double max) { if (max > Kernel.PI_2)
* return; super.setIntervalMax(max); }
*
* @Override public void setIntervalMin(double min) { if (min < 0) return;
* super.setIntervalMin(min); }
*/
/*
* removed - overrides setting in XML this is set from
* SliderDialog.actionPerformed for new sliders
*
* @Override public void setEuclidianVisible(boolean flag) { if (flag &&
* isIndependent()) { setLabelMode(GeoElement.LABEL_NAME_VALUE); }
* super.setEuclidianVisible(flag); }
*/
@Override
public GeoNumeric copy() {
GeoAngle angle = new GeoAngle(cons);
angle.setValue(rawValue);
angle.setAngleStyle(angleStyle);
return angle;
}
/**
* Depending upon angleStyle, some values > pi will be changed to (2pi -
* value). raw_value contains the original value.
*
* @param allowReflexAngle
* If true, angle is allowed to be> 180 degrees
*
* @see #setValue(double)
*/
@Override
final public void setAllowReflexAngle(boolean allowReflexAngle) {
switch (angleStyle) {
case NOTREFLEX:
if (allowReflexAngle) {
setAngleStyle(AngleStyle.ANTICLOCKWISE);
}
break;
case ISREFLEX:
// do nothing
break;
default: // ANGLE_ISANTICLOCKWISE
if (!allowReflexAngle) {
setAngleStyle(AngleStyle.NOTREFLEX);
}
break;
}
if (allowReflexAngle) {
setAngleStyle(AngleStyle.ANTICLOCKWISE);
} else {
setAngleStyle(AngleStyle.NOTREFLEX);
}
}
/**
* Forces angle to be reflex or switches it to anticlockwise
*
* @param forceReflexAngle
* switch to reflex for true
*/
@Override
final public void setForceReflexAngle(boolean forceReflexAngle) {
if (forceReflexAngle) {
setAngleStyle(AngleStyle.ISREFLEX);
} else if (angleStyle == AngleStyle.ISREFLEX) {
setAngleStyle(AngleStyle.ANTICLOCKWISE);
}
}
@Override
public void setAngleStyle(int style) {
setAngleStyle(AngleStyle.getStyle(style));
}
/**
* Changes angle style and recomputes the value from raw. See
* GeoAngle.ANGLE_*
*
* @param angleStyle
* clockwise, anticlockwise, (force) reflex or (force) not reflex
*/
@Override
public void setAngleStyle(AngleStyle angleStyle) {
if (angleStyle == this.angleStyle) {
return;
}
this.angleStyle = angleStyle;
// we have to reset the value of this angle
if (algoParent == null) {
// setValue(value);
setValue(rawValue);
} else {
algoParent.update();
}
}
/**
* Returns angle style. See GeoAngle.ANGLE_*
*
* @return Clockwise, counterclockwise reflex or not reflex
*/
@Override
public AngleStyle getAngleStyle() {
return angleStyle;
}
/**
*
* @return true if has a "super" orientation (e.g. in 3D, from a specific
* oriented plane)
*/
@Override
public boolean hasOrientation() {
return true; // orientation of xOyPlane
}
/**
* Returns the raw value of angle
*
* @return raw value of angle (irrespective of angle style)
*/
final public double getRawAngle() {
return rawValue;
}
@Override
final public String toValueString(StringTemplate tpl) {
if (isEuclidianVisible()) {
return kernel.formatAngle(value, 1 / getAnimationStep(), tpl,
angleStyle == AngleStyle.UNBOUNDED).toString();
}
return kernel
.formatAngle(value, tpl, angleStyle == AngleStyle.UNBOUNDED)
.toString();
}
// overwrite
@Override
final public MyDouble getNumber() {
MyDouble ret = new MyDouble(kernel, value);
ret.setAngle();
return ret;
}
/**
* returns size of the arc in pixels
*
* @return arc size in pixels
*/
@Override
public int getArcSize() {
return arcSize;
}
/**
* Change the size of the arc in pixels,
*
* @param i
* arc size, should be in <10,100>
*/
@Override
public void setArcSize(int i) {
arcSize = i;
}
/**
* returns all class-specific xml tags for saveXML
*/
@Override
protected void getXMLtags(StringBuilder sb) {
// from ggb44 need to save before value in case it's unbounded
getXMLAngleStyleTag(sb);
sb.append("\t<value val=\"");
sb.append(rawValue);
sb.append("\"");
if (isRandom()) {
sb.append(" random=\"true\"");
}
sb.append("/>\n");
// if angle 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);
getXMLvisualTags(sb);
getLineStyleXML(sb);
// arc size
sb.append("\t<arcSize val=\"");
sb.append(arcSize);
sb.append("\"/>\n");
} else if (GeoElementSpreadsheet.isSpreadsheetLabel(label)) {
// make sure colors saved for spreadsheet objects
appendObjectColorXML(sb);
}
getXMLEmphasizeRightAngleTag(sb);
getXMLanimationTags(sb);
getXMLfixedTag(sb);
getAuxiliaryXML(sb);
getBreakpointXML(sb);
getScriptTags(sb);
}
/**
* returns some class-specific xml tags for getConstructionRegressionOut
*/
@Override
public void getXMLtagsMinimal(StringBuilder sb, StringTemplate tpl) {
sb.append(regrFormat(rawValue));
if (isDrawable() || isSliderable()) {
sb.append(" ");
sb.append(regrFormat(arcSize));
}
}
private void getXMLAngleStyleTag(StringBuilder sb) {
/*
* old ggb42 code if (angleStyle == ANGLE_ISANTICLOCKWISE) return;
*
* sb.append("\t<allowReflexAngle val=\""); sb.append(angleStyle !=
* ANGLE_ISNOTREFLEX); sb.append("\"/>\n"); if (angleStyle ==
* ANGLE_ISREFLEX) { sb.append("\t<forceReflexAngle val=\"");
* sb.append(true); sb.append("\"/>\n"); }
*
*/
sb.append("\t<angleStyle val=\"");
sb.append(angleStyle.getXmlVal());
sb.append("\"/>\n");
}
private void getXMLEmphasizeRightAngleTag(StringBuilder sb) {
if (emphasizeRightAngle) {
return;
}
// only store emphasizeRightAngle if "false"
sb.append("\t<emphasizeRightAngle val=\"");
sb.append(emphasizeRightAngle);
sb.append("\"/>\n");
}
@Override
public void setDecorationType(int type) {
setDecorationType(type, getDecoTypes().length);
}
/**
* Returns true if this angle shuld be drawn differently when right
*
* @return true iff this angle shuld be drawn differently when right
*/
@Override
public boolean isEmphasizeRightAngle() {
return emphasizeRightAngle;
}
/**
* Sets this angle shuld be drawn differently when right
*
* @param emphasizeRightAngle
* true iff this angle shuld be drawn differently when right
*/
@Override
public void setEmphasizeRightAngle(boolean emphasizeRightAngle) {
this.emphasizeRightAngle = emphasizeRightAngle;
}
@Override
public void setZero() {
rawValue = 0;
}
@Override
public boolean isDrawable() {
return isDrawable || (getDrawAlgorithm() != getParentAlgorithm())
|| (isIndependent() && isLabelSet()
|| getParentAlgorithm() instanceof AlgoAngle);
}
@Override
public boolean hasDrawable3D() {
return true;
}
@Override
public boolean canHaveClickScript() {
return isDrawable();
}
}