package org.geogebra.common.main.settings;
import org.geogebra.common.awt.GColor;
import org.geogebra.common.awt.GDimension;
import org.geogebra.common.awt.GFont;
import org.geogebra.common.euclidian.EuclidianConstants;
import org.geogebra.common.euclidian.EuclidianView;
import org.geogebra.common.factories.AwtFactory;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.arithmetic.NumberValue;
import org.geogebra.common.kernel.geos.GeoNumberValue;
import org.geogebra.common.kernel.geos.GeoNumeric;
import org.geogebra.common.main.App;
import org.geogebra.common.plugin.EuclidianStyleConstants;
import org.geogebra.common.util.StringUtil;
import org.geogebra.common.util.lang.Unicode;
/**
* Settings for an euclidian view. To which view these settings are associated
* is determined in {@link Settings}.
*/
public class EuclidianSettings extends AbstractSettings {
public static final int[] DELETE_SIZES = { 20, 40, 80 };
/**
* Color of the euclidian view's background.
*/
protected GColor backgroundColor;
/**
* Color of the axes.
*/
private GColor axesColor;
/**
* Color of the grid lines.
*/
private GColor gridColor;
/**
* Line style of axes.
*/
private int axesLineStyle = EuclidianStyleConstants.AXES_LINE_TYPE_ARROW;
/**
* Line style of grid.
*/
private int gridLineStyle = EuclidianStyleConstants.LINE_TYPE_FULL;
/**
* Various distances between lines of the grid.
*/
double[] gridDistances = null;// { 2, 2, Math.PI/6 };
// we need 3 values for 3D view, as it may copy values from ev1
private final double[] axisCross = { 0, 0, 0 };
private final boolean[] positiveAxes = { false, false, false };
private final boolean[] drawBorderAxes = { false, false, false };
private NumberValue xminObject, xmaxObject, yminObject, ymaxObject;
private int tooltipsInThisView = EuclidianStyleConstants.TOOLTIPS_AUTOMATIC;
private GDimension sizeFromFile, size;
protected App app;
// settings for the base EuclidianView (or null if this is the base)
// private final EuclidianSettings euclidianSettings1;
public EuclidianSettings(App app) {
// this.euclidianSettings1 = euclidianSettings1;
xZero = EuclidianView.XZERO_STANDARD; // needs to be positive
yZero = EuclidianView.YZERO_STANDARD; // needs to be positive
preferredSize = AwtFactory.getPrototype().newDimension(0, 0);
this.app = app;
resetNoFire();
}
/*
* some settings are not stored in XML, eg eg automaticGridDistance so we
* need to clear these parameters to make sure the others are set OK see
* EuclidianView.settingsChanged()
*/
public void reset() {
resetNoFire();
settingChanged();
}
protected void resetNoFire() {
sizeFromFile = AwtFactory.getPrototype().newDimension(0, 0);
size = null;
gridDistances = null;
// length might be 2 or 3
for (int i = 0; i < axisNumberingDistances.length; i++) {
axisNumberingDistances[i] = null;
}
xminObject = null;
xmaxObject = null;
yminObject = null;
ymaxObject = null;
setGridLineStyle(EuclidianStyleConstants.LINE_TYPE_FULL);
setAxesLineStyle(EuclidianStyleConstants.AXES_LINE_TYPE_ARROW);
setAxesColor(GColor.BLACK);
setGridColor(GColor.LIGHT_GRAY);
setBackground(GColor.WHITE);
setGridType(EuclidianView.GRID_CARTESIAN);
pointCapturingMode = EuclidianStyleConstants.POINT_CAPTURING_AUTOMATIC;
// length might be 2 or 3
for (int i = 0; i < showAxesNumbers.length; i++) {
showAxesNumbers[i] = true;
}
// length might be 2 or 3
for (int i = 0; i < axesLabels.length; i++) {
axesLabels[i] = null;
}
// length might be 2 or 3
for (int i = 0; i < axesUnitLabels.length; i++) {
axesUnitLabels[i] = null;
}
// length might be 2 or 3
for (int i = 0; i < piAxisUnit.length; i++) {
piAxisUnit[i] = false;
}
// length might be 2 or 3
for (int i = 0; i < axesTickStyles.length; i++) {
axesTickStyles[i] = EuclidianStyleConstants.AXES_TICK_STYLE_MAJOR;
}
// for axes labeling with numbers
// length might be 2 or 3
for (int i = 0; i < automaticAxesNumberingDistances.length; i++) {
automaticAxesNumberingDistances[i] = true;
}
// length might be 2 or 3
for (int i = 0; i < automaticAxesNumberingDistances.length; i++) {
automaticAxesNumberingDistances[i] = true;
}
// distances between grid lines
automaticGridDistance = true;
// length might be 2 or 3
for (int i = 0; i < axisCross.length; i++) {
axisCross[i] = 0;
}
// length might be 2 or 3
for (int i = 0; i < positiveAxes.length; i++) {
positiveAxes[i] = false;
}
// length might be 2 or 3
for (int i = 0; i < selectionAllowed.length; i++) {
selectionAllowed[i] = true;
}
// length might be 2 or 3
for (int i = 0; i < showAxes.length; i++) {
showAxes[i] = true;
}
showGrid = false;
axisFontStyle = GFont.PLAIN;
axesLabelsSerif = false;
}
/**
* Change background color.
*
* @param col
*/
public void setBackground(GColor col) {
if (!col.equals(backgroundColor)) {
backgroundColor = col;
settingChanged();
}
}
/**
* @return background color
*/
public GColor getBackground() {
return backgroundColor;
}
/**
* Change axes color.
*
* @param col
*/
public void setAxesColor(GColor col) {
if (!col.equals(axesColor)) {
axesColor = col;
settingChanged();
}
}
/**
* @return axes color
*/
public GColor getAxesColor() {
return axesColor;
}
/**
* Change grid color.
*
* @param col
*/
public void setGridColor(GColor col) {
if (!col.equals(gridColor)) {
gridColor = col;
settingChanged();
}
}
/**
* @return color of the grid
*/
public GColor getGridColor() {
return gridColor;
}
/**
* Change line style of axes.
*
* @param style
*/
public void setAxesLineStyle(int style) {
if (axesLineStyle != style) {
axesLineStyle = style;
settingChanged();
}
}
/**
* @return line style of axes
*/
public int getAxesLineStyle() {
return axesLineStyle;
}
/**
* Change line style of grid.
*
* @param style
*/
public void setGridLineStyle(int style) {
if (gridLineStyle != style) {
gridLineStyle = style;
settingChanged();
}
}
/**
* @return line style of grid
*/
public int getGridLineStyle() {
return gridLineStyle;
}
/**
* Change grid distances.
*
* @param dists
*/
public void setGridDistances(double[] dists) {
boolean changed = false;
if (gridDistances == null) {
changed = true;
} else if (gridDistances.length != dists.length) {
changed = true;
} else {
for (int i = 0; i < dists.length; ++i) {
if (dists[i] != gridDistances[i]) {
changed = true;
break;
}
}
}
if (changed) {
gridDistances = dists;
if (dists == null) {
setAutomaticGridDistance(true, false);
} else {
setAutomaticGridDistance(false, false);
}
settingChanged();
}
}
/**
* @return grid distances
*/
public double[] getGridDistances() {
return gridDistances;
}
public void setAutomaticGridDistance(boolean agd, boolean callsc) {
if (automaticGridDistance != agd) {
automaticGridDistance = agd;
if (agd) {
gridDistances = null;
if (callsc) {
settingChanged();
}
} else if (callsc) {
settingChanged();
}
}
}
public boolean getAutomaticGridDistance() {
return automaticGridDistance;
}
protected boolean[] showAxes = { true, true, true };
protected boolean[] selectionAllowed = { true, true, true };
protected boolean[] showAxesNumbers = { true, true, true };
protected boolean[] logAxes = { false, false, false };
protected String[] axesLabels = { null, null, null };
protected String[] axesUnitLabels = { null, null, null };
protected boolean[] piAxisUnit = { false, false, false };
protected int[] axesTickStyles = {
EuclidianStyleConstants.AXES_TICK_STYLE_MAJOR,
EuclidianStyleConstants.AXES_TICK_STYLE_MAJOR,
EuclidianStyleConstants.AXES_TICK_STYLE_MAJOR };
// for axes labeling with numbers
protected boolean[] automaticAxesNumberingDistances = { true, true, true };
protected GeoNumberValue axisNumberingDistances[] = new GeoNumeric[] { null,
null, null };
// distances between grid lines
protected boolean automaticGridDistance = true;
protected double xZero;
protected double yZero;
protected double xscale = EuclidianView.SCALE_STANDARD;
protected double yscale = EuclidianView.SCALE_STANDARD;
private GDimension preferredSize;
private boolean showGrid;
protected boolean gridIsBold;
private int gridType = EuclidianView.GRID_CARTESIAN;
private int pointCapturingMode = EuclidianStyleConstants.POINT_CAPTURING_AUTOMATIC;
// set to false because it was set to false in Desktop anyway
// (due to a bug in MyXMLHandler?), and it does some speedup in Web
private boolean allowShowMouseCoords = false;
private double lockedAxesRatio = -1;
private int deleteToolSize = EuclidianConstants.DEFAULT_ERASER_SIZE;
private int axisFontStyle = GFont.PLAIN;
private boolean axesLabelsSerif = false;
private Boolean enabled = null;
public boolean getAllowShowMouseCoords() {
return allowShowMouseCoords;
}
public void setAllowShowMouseCoords(boolean neverShowMouseCoords) {
if (neverShowMouseCoords == this.allowShowMouseCoords) {
return;
}
this.allowShowMouseCoords = neverShowMouseCoords;
settingChanged();
}
/*
* change visibility of axes
*/
public boolean setShowAxis(int axis, boolean flag) {
boolean changed = flag != showAxes[axis];
if (changed) {
showAxes[axis] = flag;
settingChanged();
}
return changed;
}
/*
* change logarithmic of axes
*/
public boolean setLogAxis(int axis, boolean flag) {
boolean changed = flag != logAxes[axis];
if (changed) {
logAxes[axis] = flag;
settingChanged();
}
return changed;
}
/**
* says if the axis is shown or not
*
* @param axis
* id of the axis
* @return if the axis is shown
*/
public boolean getShowAxis(int axis) {
return showAxes[axis];
}
public boolean getLogAxis(int axis) {
return logAxes[axis];
}
/**
* sets the axis label to axisLabel
*
* @param axis
* @param axisLabel
*/
public void setAxisLabel(int axis, String axisLabel) {
boolean changed = false;
if (StringUtil.empty(axisLabel)) {
changed = axesLabels[axis] != null;
axesLabels[axis] = null;
} else {
changed = !axisLabel.equals(axesLabels[axis]);
axesLabels[axis] = axisLabel;
}
if (changed) {
settingChanged();
}
}
public String[] getAxesLabels() {
return axesLabels;
}
public String[] getAxesUnitLabels() {
return axesUnitLabels;
}
public void setAxesUnitLabels(String[] axesUnitLabels) {
this.axesUnitLabels = axesUnitLabels;
// check if pi is an axis unit
for (int i = 0; i < 2; i++) {
piAxisUnit[i] = (axesUnitLabels[i] != null)
&& axesUnitLabels[i].equals(Unicode.PI_STRING);
}
// setAxesIntervals(xscale, 0);
// setAxesIntervals(yscale, 1);
settingChanged();
}
public void setShowAxisNumbers(int axis, boolean showAxisNumbers) {
showAxesNumbers[axis] = showAxisNumbers;
settingChanged();
}
public boolean[] getShowAxisNumbers() {
return showAxesNumbers;
}
public GeoNumberValue getAxisNumberingDistance(int i) {
return axisNumberingDistances[i];
}
public void setAxisNumberingDistance(int i, GeoNumberValue dist) {
axisNumberingDistances[i] = dist;
setAutomaticAxesNumberingDistance(false, i, false);
settingChanged();
}
public void setAutomaticAxesNumberingDistance(boolean flag, int axis,
boolean callsc) {
if (automaticAxesNumberingDistances[axis] != flag) {
automaticAxesNumberingDistances[axis] = flag;
if (flag) {
axisNumberingDistances[axis] = null;
if (callsc) {
settingChanged();
}
} else if (callsc) {
settingChanged();
}
}
}
public boolean getAutomaticAxesNumberingDistance(int axis) {
return automaticAxesNumberingDistances[axis];
}
public int[] getAxesTickStyles() {
return axesTickStyles;
}
public void setAxisTickStyle(int axis, int tickStyle) {
if (axesTickStyles[axis] != tickStyle) {
axesTickStyles[axis] = tickStyle;
settingChanged();
}
}
public double[] getAxesCross() {
return axisCross;
}
public void setAxisCross(int axis, double cross) {
if (axisCross[axis] != cross) {
axisCross[axis] = cross;
settingChanged();
}
}
public boolean[] getPositiveAxes() {
return positiveAxes;
}
// for xml handler
public void setPositiveAxis(int axis, boolean isPositiveAxis) {
if (positiveAxes[axis] == isPositiveAxis) {
return;
}
positiveAxes[axis] = isPositiveAxis;
settingChanged();
}
/**
* @return the xminObject
*/
public GeoNumeric getXminObject() {
return (GeoNumeric) xminObject;
}
/**
* @param xminObjectNew
* the xminObject to set
* @param callsc
* whether settingChanged should be called
*/
public void setXminObject(NumberValue xminObjectNew, boolean callsc) {
this.xminObject = xminObjectNew;
if (callsc) {
settingChanged();
}
}
/**
* @return the xmaxObject
*/
public GeoNumeric getXmaxObject() {
return (GeoNumeric) xmaxObject;
}
/**
* @param xmaxObjectNew
* the xmaxObject to set
* @param callsc
* whether settingChanged should be called
*/
public void setXmaxObject(NumberValue xmaxObjectNew, boolean callsc) {
this.xmaxObject = xmaxObjectNew;
if (callsc) {
settingChanged();
}
}
/**
* @return the yminObject
*/
public GeoNumeric getYminObject() {
return (GeoNumeric) yminObject;
}
/**
* @param yminObjectNew
* the yminObject to set
* @param callsc
* whether settingChanged should be called
*/
public void setYminObject(NumberValue yminObjectNew, boolean callsc) {
this.yminObject = yminObjectNew;
if (callsc) {
settingChanged();
}
}
/**
* @return the ymaxObject
*/
public GeoNumeric getYmaxObject() {
return (GeoNumeric) ymaxObject;
}
/**
* @param ymaxObjectNew
* the ymaxObject to set
* @param callsc
* whether settingChanged should be called
*/
public void setYmaxObject(NumberValue ymaxObjectNew, boolean callsc) {
this.ymaxObject = ymaxObjectNew;
if (callsc) {
settingChanged();
}
}
/**
* Returns x coordinate of axes origin.
*/
public double getXZero() {
return xZero;
}
/**
* Returns y coordinate of axes origin.
*/
public double getYZero() {
return yZero;
}
/**
* Returns xscale of this view. The scale is the number of pixels in screen
* space that represent one unit in user space.
*/
public double getXscale() {
return xscale;
}
/**
* Returns the yscale of this view. The scale is the number of pixels in
* screen space that represent one unit in user space.
*/
public double getYscale() {
return yscale;
}
public boolean hasDynamicBounds() {
return xminObject != null && yminObject != null && xmaxObject != null
&& ymaxObject != null;
}
public void setCoordSystem(double xZero, double yZero, double xscale,
double yscale, boolean fire) {
if (Double.isNaN(xscale) || (xscale < Kernel.MAX_DOUBLE_PRECISION)
|| (xscale > Kernel.INV_MAX_DOUBLE_PRECISION)) {
return;
}
if (Double.isNaN(yscale) || (yscale < Kernel.MAX_DOUBLE_PRECISION)
|| (yscale > Kernel.INV_MAX_DOUBLE_PRECISION)) {
return;
}
this.xZero = xZero;
this.yZero = yZero;
this.xscale = xscale;
this.yscale = yscale;
if (fire) {
settingChanged();
}
}
public void setAxesNumberingDistance(GeoNumberValue tickDist, int axis) {
setAxisNumberingDistance(axis, tickDist);
settingChanged();
}
public void setPreferredSize(GDimension dimension) {
preferredSize = dimension;
settingChanged();
}
public GDimension getPreferredSize() {
return preferredSize;
}
public boolean setShowAxes(boolean x, boolean y) {
boolean changedX = this.setShowAxis(0, x);
return this.setShowAxis(1, y) || changedX;
// settingChanged() is called from those above
}
public boolean setShowAxes(boolean flag) {
boolean changed = this.setShowAxis(0, flag);
changed = this.setShowAxis(1, flag) || changed;
return this.setShowAxis(2, flag) || changed;
// settingChanged() is called from those above
}
public boolean setLogAxes(boolean x, boolean y) {
boolean changedX = this.setLogAxis(0, x);
return this.setLogAxis(1, y) || changedX;
// settingChanged() is called from those above
}
public boolean setLogAxes(boolean flag) {
boolean changed = this.setLogAxis(0, flag);
changed = this.setLogAxis(1, flag) || changed;
return this.setLogAxis(2, flag) || changed;
// settingChanged() is called from those above
}
public boolean showGrid(boolean show) {
if (show == showGrid) {
return false;
}
setShowGridSetting(show);
settingChanged();
return true;
}
public void setShowGridSetting(boolean show) {
showGrid = show;
}
public boolean getShowGrid() {
return showGrid;
}
public boolean getGridIsBold() {
return gridIsBold;
}
public void setGridIsBold(boolean gridIsBold) {
if (this.gridIsBold == gridIsBold) {
return;
}
this.gridIsBold = gridIsBold;
settingChanged();
}
final public int getGridType() {
return gridType;
}
/**
* Set grid type.
*/
public void setGridType(int type) {
if (gridType == type) {
return;
}
gridType = type;
settingChanged();
}
/**
* Returns point capturing mode.
*/
final public int getPointCapturingMode() {
return pointCapturingMode;
}
/**
* Set capturing of points to the grid.
*
* @return true if setting changed
*/
public boolean setPointCapturing(int mode) {
if (pointCapturingMode == mode) {
return false;
}
pointCapturingMode = mode;
settingChanged();
return false;
}
public void setAllowToolTips(int setto) {
if (setto == tooltipsInThisView) {
return;
}
tooltipsInThisView = setto;
settingChanged();
}
final public int getAllowToolTips() {
return tooltipsInThisView;
}
public void setDrawBorderAxes(int axis, boolean value) {
if ((axis == 0) || (axis == 1)) {
if (drawBorderAxes[axis] == value) {
return;
}
drawBorderAxes[axis] = value;
settingChanged();
}
}
final public boolean[] getDrawBorderAxes() {
return drawBorderAxes;
}
public void setLockedAxesRatio(double ratio) {
if (Kernel.isEqual(lockedAxesRatio, ratio)) {
return;
}
lockedAxesRatio = ratio;
settingChanged();
}
public double getLockedAxesRatio() {
return lockedAxesRatio;
}
public void setBoldAxes(boolean bold) {
int oldAxesLineStyle = axesLineStyle;
axesLineStyle = EuclidianView.getBoldAxes(bold, axesLineStyle);
if (oldAxesLineStyle != axesLineStyle) {
settingChanged();
}
}
public int getDeleteToolSize() {
return this.deleteToolSize;
}
public void setDeleteToolSize(int size) {
this.deleteToolSize = size;
}
public void addAxisXML(int i, StringBuilder sbxml) {
sbxml.append("\t<axis id=\"");
sbxml.append(i);
sbxml.append("\" show=\"");
sbxml.append(getShowAxis(i));
sbxml.append("\" label=\"");
if (axesLabels[i] != null) {
StringUtil.encodeXML(sbxml, axisLabelForXML(i));
}
sbxml.append("\" unitLabel=\"");
if (axesUnitLabels[i] != null) {
StringUtil.encodeXML(sbxml, axesUnitLabels[i]);
}
sbxml.append("\" tickStyle=\"");
sbxml.append(axesTickStyles[i]);
sbxml.append("\" showNumbers=\"");
sbxml.append(showAxesNumbers[i]);
// the tick distance should only be saved if
// it isn't calculated automatically
if (!automaticAxesNumberingDistances[i]
&& axisNumberingDistances[i] != null) {
sbxml.append("\" tickDistance=\"");
sbxml.append(axisNumberingDistances[i].getDouble());
sbxml.append("\" tickExpression=\"");
sbxml.append(axisNumberingDistances[i]
.getDefinition(StringTemplate.xmlTemplate));
}
// axis crossing values
if (drawBorderAxes[i]) {
sbxml.append("\" axisCrossEdge=\"");
sbxml.append(true);
} else if (!Kernel.isZero(axisCross[i]) && !drawBorderAxes[i]) {
sbxml.append("\" axisCross=\"");
sbxml.append(axisCross[i]);
}
// positive direction only flags
if (positiveAxes[i]) {
sbxml.append("\" positiveAxis=\"");
sbxml.append(positiveAxes[i]);
}
// selection allowed flags
if (!selectionAllowed[i]) {
sbxml.append("\" selectionAllowed=\"");
sbxml.append(selectionAllowed[i]);
}
sbxml.append("\"/>\n");
}
/**
* Returns axis label including <b> and <i>
*
* @param i
* index of axis (0 for x, 1 for y)
* @return axis label including formating tags
*/
public String axisLabelForXML(int i) {
return axesLabels[i];
}
final public void setXscale(double scale) {
if (this.xscale != scale) {
setXscaleValue(scale);
settingChanged();
}
}
protected void setXscaleValue(double scale) {
this.xscale = scale;
}
final public void setYscale(double scale) {
if (this.yscale != scale) {
setYscaleValue(scale);
settingChanged();
}
}
protected void setYscaleValue(double scale) {
this.yscale = scale;
}
/**
* @return if it's about 3D
*/
public boolean is3D() {
return false;
}
/**
*
* @return if it's a view for plane
*/
public boolean isViewForPlane() {
return false;
}
/**
* convert screen coordinate x to real world coordinate x
*
* @param x
* screen coord
* @return real world equivalent of screen x-coord
*/
final public double toRealWorldCoordX(double x) {
return (x - xZero) / xscale;
}
/**
* convert screen coordinate y to real world coordinate y
*
* @param y
* screen coord
* @return real world equivalent of screen y-coord
*/
final public double toRealWorldCoordY(double y) {
return (yZero - y) / yscale;
}
/**
* Axis font style
*
* @return eg GFont.PLAIN + GFont.BOLD
*/
public int getAxisFontStyle() {
// #5320
return axisFontStyle;
}
/**
* @return whether axes labels are in serif font
*/
public boolean getAxesLabelsSerif() {
return axesLabelsSerif;
}
public void setAxesLabelsSerif(boolean b) {
if (axesLabelsSerif != b) {
axesLabelsSerif = b;
settingChanged();
}
}
public void setAxisFontStyle(int style) {
if (axisFontStyle != style) {
axisFontStyle = style;
settingChanged();
}
}
/**
* set size from XML file. Reset size value
*
* @param newDimension
*/
public void setSizeFromFile(GDimension newDimension) {
this.sizeFromFile = newDimension;
this.size = null;
}
public void setSize(int w, int h) {
size = AwtFactory.getPrototype().newDimension(w, h);
}
public void setOriginNoUpdate(double xZero, double yZero) {
this.xZero = xZero;
this.yZero = yZero;
}
/**
* @return width from XML
*/
public int getFileWidth() {
return sizeFromFile.getWidth();
}
/**
* @return height from XML
*/
public int getFileHeight() {
return sizeFromFile.getHeight();
}
/**
* @return width
*/
public int getWidth() {
if (size == null) {
return getFileWidth();
}
return size.getWidth();
}
/**
* @return height
*/
public int getHeight() {
if (size == null) {
return getFileHeight();
}
return size.getHeight();
}
/**
* @param axis
* axis index
* @param flag
* whether to allow selection
* @return whether setting was changed
*/
public boolean setSelectionAllowed(int axis, boolean flag) {
boolean changed = flag != selectionAllowed[axis];
if (changed) {
selectionAllowed[axis] = flag;
settingChanged();
}
return changed;
}
/**
* @param axisNo
* axis index
* @return whether axis selection is allowed
*/
public boolean isSelectionAllowed(int axisNo) {
return selectionAllowed[axisNo];
}
/**
* @param enable
* whether this view is enabled (for 3D only)
*/
public void setEnabled(boolean enable) {
if (enabled == null || enabled != enable) {
this.enabled = enable;
settingChanged();
}
}
/**
* reset 3d enable (needed for exam mode)
*/
public void resetEnabled() {
enabled = null;
}
/**
* @return whether this view is enabled
*/
public boolean isEnabled() {
return enabled == null || enabled;
}
/**
* @return whether this view was explicitly disabled
*/
public boolean isEnabledSet() {
return enabled != null;
}
}