package org.geogebra.common.euclidian; import java.util.ArrayList; import java.util.HashMap; import java.util.TreeSet; import org.geogebra.common.awt.GAffineTransform; import org.geogebra.common.awt.GBasicStroke; import org.geogebra.common.awt.GBufferedImage; import org.geogebra.common.awt.GColor; import org.geogebra.common.awt.GDimension; import org.geogebra.common.awt.GEllipse2DDouble; import org.geogebra.common.awt.GFont; import org.geogebra.common.awt.GGeneralPath; import org.geogebra.common.awt.GGraphics2D; import org.geogebra.common.awt.GLine2D; import org.geogebra.common.awt.GPoint; import org.geogebra.common.awt.GRectangle; import org.geogebra.common.awt.GShape; import org.geogebra.common.awt.font.GTextLayout; import org.geogebra.common.euclidian.DrawableList.DrawableIterator; import org.geogebra.common.euclidian.draw.CanvasDrawable; import org.geogebra.common.euclidian.draw.DrawAngle; import org.geogebra.common.euclidian.draw.DrawButton; import org.geogebra.common.euclidian.draw.DrawConic; import org.geogebra.common.euclidian.draw.DrawDropDownList; import org.geogebra.common.euclidian.draw.DrawImage; import org.geogebra.common.euclidian.draw.DrawInputBox; import org.geogebra.common.euclidian.draw.DrawLine; import org.geogebra.common.euclidian.draw.DrawLine.PreviewType; import org.geogebra.common.euclidian.draw.DrawPolyLine; import org.geogebra.common.euclidian.draw.DrawPolygon; import org.geogebra.common.euclidian.draw.DrawRay; import org.geogebra.common.euclidian.draw.DrawSegment; import org.geogebra.common.euclidian.draw.DrawVector; import org.geogebra.common.euclidian.event.PointerEventType; import org.geogebra.common.factories.AwtFactory; import org.geogebra.common.factories.FormatFactory; import org.geogebra.common.gui.SetLabels; import org.geogebra.common.gui.dialog.options.OptionsEuclidian; import org.geogebra.common.gui.inputfield.AutoCompleteTextField; import org.geogebra.common.javax.swing.GBox; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.ModeSetter; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.Matrix.CoordMatrix; import org.geogebra.common.kernel.Matrix.CoordSys; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.algos.AlgoAngle; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.arithmetic.Function; import org.geogebra.common.kernel.arithmetic.NumberValue; import org.geogebra.common.kernel.geos.GProperty; import org.geogebra.common.kernel.geos.GeoCurveCartesian; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoElement.HitType; import org.geogebra.common.kernel.geos.GeoFunction; import org.geogebra.common.kernel.geos.GeoInputBox; import org.geogebra.common.kernel.geos.GeoList; import org.geogebra.common.kernel.geos.GeoNumberValue; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.geos.GeoSegment; import org.geogebra.common.kernel.geos.GeoText; import org.geogebra.common.kernel.kernelND.GeoDirectionND; import org.geogebra.common.kernel.kernelND.GeoElementND; import org.geogebra.common.kernel.kernelND.GeoLineND; import org.geogebra.common.kernel.kernelND.GeoPlaneND; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.main.App; import org.geogebra.common.main.App.ExportType; import org.geogebra.common.main.Feature; import org.geogebra.common.main.GuiManagerInterface; import org.geogebra.common.main.SelectionManager; import org.geogebra.common.main.settings.AbstractSettings; import org.geogebra.common.main.settings.EuclidianSettings; import org.geogebra.common.plugin.EuclidianStyleConstants; import org.geogebra.common.plugin.GeoClass; import org.geogebra.common.util.AsyncOperation; import org.geogebra.common.util.MyMath; import org.geogebra.common.util.NumberFormatAdapter; import org.geogebra.common.util.StringUtil; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.lang.Unicode; /** * View containing graphic representation of construction elements */ public abstract class EuclidianView implements EuclidianViewInterfaceCommon, SetLabels { /** says if the view has the mouse */ protected boolean hasMouse; /** View other than EV1 and EV2 **/ public static final int EVNO_GENERAL = 1001; /** * 3D View TODO: probably needs changing when we support more than 2 views **/ public static final int EVNO_3D = -1; /** euclidian view number */ protected int evNo = 1; /** old onscreen x-coord of origin for animate zoom */ protected double xZeroOld; /** old onscreen y-coord of origin for animate zoom */ protected double yZeroOld; /** old onscreen x-scale for animate zoom */ protected double xScaleStart; /** old onscreen y-scale for animate zoom */ protected double yScaleStart; private int mode = EuclidianConstants.MODE_MOVE; /** minimal width */ public static final int MIN_WIDTH = 50; /** minimal height */ protected static final int MIN_HEIGHT = 50; /** corner of export area */ public static final String EXPORT1 = "Export_1"; // Points used to define // corners for export // (if they exist) /** corner of export area */ public static final String EXPORT2 = "Export_2"; /** pixel per centimeter (at 72dpi) */ protected static final double PRINTER_PIXEL_PER_CM = 72.0 / 2.54; /** zoom factor of zoom mode */ public static final double MODE_ZOOM_FACTOR = 1.5; /** zoom factor of mouse wheel */ public static final double MOUSE_WHEEL_ZOOM_FACTOR = 1.1; /** standard pixels per unit */ public static final double SCALE_STANDARD = 50; /** border in which axis numbers are not drawn */ protected static final int SCREEN_BORDER = 10; // public static final double SCALE_MAX = 10000; // public static final double SCALE_MIN = 0.1; /** default screen x-coord of origin */ public static final double XZERO_STANDARD = 215; // needs to be positive /** default screen y-coord of origin */ public static final double YZERO_STANDARD = 315; // needs to be positive // or use volatile image // protected int drawMode = DRAW_MODE_BACKGROUND_IMAGE; /** background image */ protected GBufferedImage bgImage; /** * g2d of bgImage: used for axis, grid, background images and object traces */ protected GGraphics2D bgGraphics; // zoom rectangle colors private static final GColor colZoomRectangle = GColor.newColor(200, 200, 230); private static final GColor colZoomRectangleFill = GColor.newColor(200, 200, 230, 50); // deletion square design protected static final GColor colDeletionSquare = GColor .newColor(128, 0, 0); protected static final GBasicStroke strokeDeletionSquare = AwtFactory .getPrototype().newBasicStroke(1.0f); protected GRectangle deletionRectangle; // bounding box protected BoundingBox boundingBox; private EuclidianBoundingBoxHandler hitHandler = EuclidianBoundingBoxHandler.UNDEFINED; // shape tools /** * preview shape for rectangle */ protected GRectangle shapeRectangle; /** * preview shape for ellipse */ protected GEllipse2DDouble shapeEllipse; /** * preview shape for line */ protected GLine2D shapeLine; /** * preview shape for triangle/regular polygon/polygon */ protected GGeneralPath shapePolygon; // design for shapes /** * fill color of shape (transparent) */ private GColor shapeFillCol = GColor.newColor(192, 192, 192, 0.0); /** * object color of shape (black by default) */ private GColor shapeObjCol = GColor.BLACK; /** * stroke of shape */ private GBasicStroke shapeStroke = AwtFactory .getPrototype().newBasicStroke(2.0f, GBasicStroke.CAP_BUTT, GBasicStroke.JOIN_MITER); private boolean isRounded = false; // colors: axes, grid, background GColor axesColor; private GColor gridColor; protected GRectangle selectionRectangle; /** * default axes stroke */ static GBasicStroke defAxesStroke = AwtFactory.getPrototype() .newBasicStroke(1.0f, GBasicStroke.CAP_BUTT, GBasicStroke.JOIN_MITER); // changed from 1.8f (same as bold grid) Michael Borcherds 2008-04-12 static GBasicStroke boldAxesStroke = AwtFactory.getPrototype() .newBasicStroke(2.0f, GBasicStroke.CAP_BUTT, GBasicStroke.JOIN_MITER); // axes and grid stroke GBasicStroke axesStroke; GBasicStroke tickStroke; private GBasicStroke gridStroke; /** kernel */ protected Kernel kernel; private final static int[] lineTypes = { EuclidianStyleConstants.LINE_TYPE_FULL, EuclidianStyleConstants.LINE_TYPE_DASHED_LONG, EuclidianStyleConstants.LINE_TYPE_DASHED_SHORT, EuclidianStyleConstants.LINE_TYPE_DOTTED, EuclidianStyleConstants.LINE_TYPE_DASHED_DOTTED, EuclidianStyleConstants.LINE_TYPE_POINTWISE }; public static int getLineType(int i) { return lineTypes[i]; } public static int getLineTypeLength() { return lineTypes.length; } /** @return line types */ public static final Integer[] getLineTypes() { Integer[] ret = new Integer[lineTypes.length]; for (int i = 0; i < lineTypes.length; i++) { ret[i] = lineTypes[i]; } return ret; } private final static int[] pointStyles = { EuclidianStyleConstants.POINT_STYLE_DOT, EuclidianStyleConstants.POINT_STYLE_CROSS, EuclidianStyleConstants.POINT_STYLE_CIRCLE, EuclidianStyleConstants.POINT_STYLE_PLUS, EuclidianStyleConstants.POINT_STYLE_FILLED_DIAMOND, EuclidianStyleConstants.POINT_STYLE_EMPTY_DIAMOND, EuclidianStyleConstants.POINT_STYLE_TRIANGLE_NORTH, EuclidianStyleConstants.POINT_STYLE_TRIANGLE_SOUTH, EuclidianStyleConstants.POINT_STYLE_TRIANGLE_EAST, EuclidianStyleConstants.POINT_STYLE_TRIANGLE_WEST }; public static int getPointStyle(int i) { return pointStyles[i]; } private final static int[] axesStyles = { EuclidianStyleConstants.NO_AXES, EuclidianStyleConstants.AXES_LINE_TYPE_ARROW, EuclidianStyleConstants.AXES_LINE_TYPE_TWO_ARROWS, EuclidianStyleConstants.AXES_LINE_TYPE_FULL }; public static int getAxesStyle(int i) { return axesStyles[i]; } public static int getPointStyleLength() { return pointStyles.length; } /** @return point styles */ public static final Integer[] getPointStyles() { Integer[] ret = new Integer[pointStyles.length]; for (int i = 0; i < pointStyles.length; i++) { ret[i] = pointStyles[i]; } return ret; } // end private int fontSize; private GAffineTransform coordTransform = AwtFactory.getPrototype() .newAffineTransform(); /** tick interval for axes */ protected double[] axesTickInterval; /** number formats for axes */ protected NumberFormatAdapter[] axesNumberFormat; /** Flags for axis visibility */ protected boolean[] showAxes = { true, true }; /** Flags for logarithmic axes */ protected boolean[] logAxes = { false, false }; // distances between grid lines private boolean automaticGridDistance = true; /** distance between gridlines */ protected double[] gridDistances; private int gridLineStyle; /** bit mask for axis style, see EuclidianStyleConstants.AXES_BOLD */ int axesLineType; /** whether grid is bold */ protected boolean gridIsBold = false; /** tooltip mode in this view */ protected int tooltipsInThisView = EuclidianStyleConstants.TOOLTIPS_AUTOMATIC; // Michael Borcherds 2008-04-28 /** for toggle buttons */ public static final int GRID_NOT_SHOWN = -1; /** cartesian grid */ public static final int GRID_CARTESIAN = 0; /** isometric grid */ public static final int GRID_ISOMETRIC = 1; /** polar grid */ public static final int GRID_POLAR = 2; private int gridType = GRID_CARTESIAN; // FONTS private GFont fontPoint; private GFont fontCoords; /** number format for print scale */ protected NumberFormatAdapter printScaleNF; /** true if grid is displayed */ protected boolean showGrid = false; /** whether mouse coords are shown see also allowShowMouseCoords */ protected boolean showMouseCoords = false; /** * whether to allow onscreen mouse coords. * * set to false because it was set to false in Desktop anyway */ protected boolean allowShowMouseCoords = false; /** whether axes ratio should be visible (true while axis zooming) */ protected boolean showAxesRatio = false; /** true if animation button need highlighting */ protected boolean highlightAnimationButtons = false; /** point capturing mode */ protected int pointCapturingMode; /** show coords of view corners */ boolean showAxesCornerCoords = true;// private /** * Whether axes numbers should be shown */ protected boolean[] showAxesNumbers; /** * Labels fo xAxis and yAxis */ protected String[] axesLabels; /** * Styles (GFont.ITALIC, GFont.BOLD) */ protected int[] axesLabelsStyle; /** * Units for axes */ protected String[] axesUnitLabels; private Previewable previewDrawable; /** true if painting this for the first time */ protected boolean firstPaint = true; /** application */ protected App app; private final EuclidianSettings settings; // member variables /** controller */ protected EuclidianController euclidianController; // ggb3D 2009-02-05 private final Hits hits; private static final int MAX_PIXEL_DISTANCE = 10; // pixels private static final double MIN_PIXEL_DISTANCE = 0.5; // pixels // maximum angle between two line segments private static final double MAX_ANGLE = 10; // degrees private static final double MAX_ANGLE_OFF_SCREEN = 45; // degrees private static final double MAX_BEND = Math.tan(MAX_ANGLE * Kernel.PI_180); private static final double MAX_BEND_OFF_SCREEN = Math .tan(MAX_ANGLE_OFF_SCREEN * Kernel.PI_180); // maximum number of bisections (max number of plot points = 2^MAX_DEPTH) private static final int MAX_DEFINED_BISECTIONS = 16; private static final int MAX_PROBLEM_BISECTIONS = 8; // maximum number of times to loop when xDiff, yDiff are both zero // eg Curve[0sin(t), 0t, t, 0, 6] private static final int MAX_ZERO_COUNT = 1000; // the curve is sampled at least at this many positions to plot it private static final int MIN_SAMPLE_POINTS = 80; // Counts of sliders that need to be adjusted, to omit overlaps. // See GGB-334, Feature.ADJUST. // private int adjustedHSliderCount = 0; // private int adjustedVSliderCount = 0; // keep same center after layout resize private boolean keepCenter = false; private boolean screenChanged = false; private boolean tracing = false; protected EuclidianViewCompanion companion; /** * @param ec * controller * @param settings * settings */ public EuclidianView(EuclidianController ec, int viewNo, EuclidianSettings settings) { // 1, 2 or EVNO_GENERAL setEuclidianViewNo(viewNo); companion = newEuclidianViewCompanion(); // Michael Borcherds 2008-03-01 drawLayers = new DrawableList[EuclidianStyleConstants.MAX_LAYERS + 1]; for (int k = 0; k <= EuclidianStyleConstants.MAX_LAYERS; k++) { drawLayers[k] = new DrawableList(); } initAxesValues(); this.euclidianController = ec; kernel = ec.getKernel(); app = kernel.getApplication(); this.settings = settings; // no repaint if (kernel.getConstruction() != null) { kernel.getConstruction().setIgnoringNewTypes(true); xminObject = new GeoNumeric(kernel.getConstruction()); xmaxObject = new GeoNumeric(kernel.getConstruction()); yminObject = new GeoNumeric(kernel.getConstruction()); ymaxObject = new GeoNumeric(kernel.getConstruction()); kernel.getConstruction().setIgnoringNewTypes(false); } // ggb3D 2009-02-05 hits = new Hits(); printScaleNF = FormatFactory.getPrototype().getNumberFormat("#.#####", 5); } /** * * @return new view companion attached to this */ protected EuclidianViewCompanion newEuclidianViewCompanion() { return new EuclidianViewCompanion(this); } /** * * @return companion */ public EuclidianViewCompanion getCompanion() { return companion; } /** * init axes values */ protected void initAxesValues() { axesNumberFormat = new NumberFormatAdapter[2]; showAxesNumbers = new boolean[] { true, true }; axesLabels = new String[] { null, null }; axesLabelsStyle = new int[] { GFont.PLAIN, GFont.PLAIN }; axesUnitLabels = new String[] { null, null }; axesTickStyles = new int[] { EuclidianStyleConstants.AXES_TICK_STYLE_MAJOR, EuclidianStyleConstants.AXES_TICK_STYLE_MAJOR }; automaticAxesNumberingDistances = new boolean[] { true, true }; axesNumberingDistances = new double[] { 2, 2 }; axesDistanceObjects = new GeoNumberValue[] { null, null }; drawBorderAxes = new boolean[] { false, false }; axisCross = new double[] { 0, 0 }; positiveAxes = new boolean[] { false, false }; piAxisUnit = new boolean[] { false, false }; gridDistances = new double[] { 2, 2, Math.PI / 6 }; axesTickInterval = new double[] { 1, 1 }; } @Override public void setAxesColor(GColor axesColor) { if (axesColor != null) { this.axesColor = axesColor; } } /** * Sets the coord system to default * * @param repaint * whether to repaint afterwards */ protected void setStandardCoordSystem(boolean repaint) { setCoordSystem(XZERO_STANDARD, YZERO_STANDARD, SCALE_STANDARD, SCALE_STANDARD, repaint); } /** * Attach this view to kernel and add all objects created so far */ public void attachView() { companion.attachView(); } /** * Returns point capturing mode. */ @Override public int getPointCapturingMode() { if (settings != null) { return settings.getPointCapturingMode(); } return pointCapturingMode; } /** * Set capturing of points to the grid. */ @Override public void setPointCapturing(int mode) { if (settings != null) { settings.setPointCapturing(mode); } else { pointCapturingMode = mode; } } @Override public int getMode() { return mode; } @Override public void setMode(int mode, ModeSetter m) { if (mode == this.mode && mode != EuclidianConstants.MODE_IMAGE) { return; } this.mode = mode; initCursor(); getEuclidianController().clearJustCreatedGeos(); getEuclidianController().setMode(mode, m); if (clearRectangle(mode)) { setSelectionRectangle(null); } setStyleBarMode(mode); getCompanion().setMode(mode, m); } /** @return kernel */ public Kernel getKernel() { return kernel; } /** * whether to clear selection rectangle when mode selected */ final private static boolean clearRectangle(int mode) { switch (mode) { // case EuclidianConstants.MODE_PENCIL: case EuclidianConstants.MODE_PEN: return true; // changed case EuclidianConstants.MODE_MIRROR_AT_LINE: return false; case EuclidianConstants.MODE_MIRROR_AT_POINT: return false; case EuclidianConstants.MODE_ROTATE_BY_ANGLE: return false; case EuclidianConstants.MODE_TRANSLATE_BY_VECTOR: return false; case EuclidianConstants.MODE_DILATE_FROM_POINT: return false; default: return true; } } protected NumberValue xminObject, xmaxObject, yminObject, ymaxObject; /** * @return the xminObject */ @Override public GeoNumeric getXminObject() { return (GeoNumeric) xminObject; } @Override public void updateBoundObjects() { if (isZoomable() && xminObject != null) { ((GeoNumeric) xminObject).setValue(getXmin()); ((GeoNumeric) xmaxObject).setValue(getXmax()); ((GeoNumeric) yminObject).setValue(getYmin()); ((GeoNumeric) ymaxObject).setValue(getYmax()); } } /** * axes ratio if locked; -1 otherwise */ protected double lockedAxesRatio = -1; private boolean updateBackgroundOnNextRepaint; /** * returns true if the axes ratio is 1 * * @return true if the axes ratio is 1 */ @Override public boolean isLockedAxesRatio() { return lockedAxesRatio > 0 || (gridType == GRID_POLAR); } /** * Set unit axes ratio to 1 * * @param flag * true to set to 1, false to allow user */ public void setLockedAxesRatio(double flag) { lockedAxesRatio = flag; if (flag > 0) { updateBounds(true, true); } } private boolean updatingBounds = false; @Override public void updateBounds(boolean updateDrawables, boolean updateSettings) { if (updatingBounds) { return; } updatingBounds = true; for (int i = 0; i < axesDistanceObjects.length; i++) { if (axesDistanceObjects[i] != null && axesDistanceObjects[i].getDouble() > 0) { axesNumberingDistances[i] = axesDistanceObjects[i].getDouble(); } } if (xminObject == null) { return; } double xmin2 = xminObject.getDouble(); double xmax2 = xmaxObject.getDouble(); double ymin2 = yminObject.getDouble(); double ymax2 = ymaxObject.getDouble(); if (isLockedAxesRatio() && (getHeight() > 0) && (getWidth() > 0)) { double ratio = gridType == GRID_POLAR ? 1 : lockedAxesRatio; double newWidth = ratio * ((ymax2 - ymin2) * getWidth()) / (getHeight() + 0.0); double newHeight = 1 / ratio * ((xmax2 - xmin2) * getHeight()) / (getWidth() + 0.0); if ((xmax2 - xmin2) < newWidth) { double c = (xmin2 + xmax2) / 2; xmin2 = c - (newWidth / 2); xmax2 = c + (newWidth / 2); } else { double c = (ymin2 + ymax2) / 2; ymin2 = c - (newHeight / 2); ymax2 = c + (newHeight / 2); } } if (((xmax2 - xmin2) > Kernel.MAX_PRECISION) && ((ymax2 - ymin2) > Kernel.MAX_PRECISION)) { xmax = xmax2; xmin = xmin2; ymin = ymin2; ymax = ymax2; setXscale(getWidth() / (xmax2 - xmin2)); setYscale(getHeight() / (ymax2 - ymin2)); xZero = -xscale * xmin2; yZero = yscale * ymax2; if (updateSettings && settings != null) { settings.setCoordSystem(xZero, yZero, xscale, yscale, false); } setAxesIntervals(getXscale(), 0); setAxesIntervals(getYscale(), 1); calcPrintingScale(); if (isLockedAxesRatio()) { this.updateBoundObjects(); } } // tell kernel if (evNo != EVNO_GENERAL) { kernel.setEuclidianViewBounds(evNo, getXmin(), getXmax(), getYmin(), getYmax(), getXscale(), getYscale()); } // needed for images eg in zoomed EV2 setCoordTransformIfNeeded(); // tell option panel if (optionPanel != null) { optionPanel.updateBounds(); } if (updateDrawables) { this.updateAllDrawablesForView(true); this.updateBackgroundOnNextRepaint = true; } updatingBounds = false; } @Override public boolean isZoomable() { if (!GeoNumeric.isChangeable(xminObject)) { return false; } if (!GeoNumeric.isChangeable(xmaxObject)) { return false; } if (!GeoNumeric.isChangeable(yminObject)) { return false; } if (!GeoNumeric.isChangeable(ymaxObject)) { return false; } return true; } /** * @param xminObjectNew * the xminObject to set */ @Override public void setXminObject(NumberValue xminObjectNew) { if (xminObject != null) { ((GeoNumeric) xminObject).removeEVSizeListener(this); } if (xminObjectNew == null && kernel.getConstruction() != null) { this.xminObject = new GeoNumeric(kernel.getConstruction()); updateBoundObjects(); } else { this.xminObject = xminObjectNew; } setSizeListeners(); } /** * @return the xmaxObject */ @Override public GeoNumeric getXmaxObject() { return (GeoNumeric) xmaxObject; } /** * @param xmaxObjectNew * the xmaxObject to set */ @Override public void setXmaxObject(NumberValue xmaxObjectNew) { if (xmaxObject != null) { ((GeoNumeric) xmaxObject).removeEVSizeListener(this); } if (xmaxObjectNew == null && kernel.getConstruction() != null) { this.xmaxObject = new GeoNumeric(kernel.getConstruction()); updateBoundObjects(); } else { this.xmaxObject = xmaxObjectNew; } setSizeListeners(); } /** * @return the yminObject */ @Override public GeoNumeric getYminObject() { return (GeoNumeric) yminObject; } /** * @param yminObjectNew * the yminObject to set */ @Override public void setYminObject(NumberValue yminObjectNew) { if (yminObject != null) { ((GeoNumeric) yminObject).removeEVSizeListener(this); } if (yminObjectNew == null && kernel.getConstruction() != null) { this.yminObject = new GeoNumeric(kernel.getConstruction()); updateBoundObjects(); } else { this.yminObject = yminObjectNew; } setSizeListeners(); } /** * @return handler that was hit */ public EuclidianBoundingBoxHandler getHitHandler() { return hitHandler; } /** * @param hitHandler * - handler that was hit */ public void setHitHandler(EuclidianBoundingBoxHandler hitHandler) { this.hitHandler = hitHandler; } private void setSizeListeners() { if (xminObject != null) { ((GeoNumeric) xminObject).addEVSizeListener(this); ((GeoNumeric) yminObject).addEVSizeListener(this); ((GeoNumeric) xmaxObject).addEVSizeListener(this); ((GeoNumeric) ymaxObject).addEVSizeListener(this); } } /** * convert real world coordinate x to screen coordinate x * * @param xRW * real world x coord * @return screen equivalent of real world x-coord */ @Override final public int toScreenCoordX(double xRW) { return (int) Math.round(getXZero() + xRW * getXscale()); } /** * convert real world coordinate y to screen coordinate y * * @param yRW * real world y coord * @return screen equivalent of real world y-coord */ @Override final public int toScreenCoordY(double yRW) { return (int) Math.round(getYZero() - (yRW * getYscale())); } /** * convert real world coordinate x to screen coordinate x * * @param xRW * real world x-coord * @return screen equivalent of real world x-coord as double */ @Override final public double toScreenCoordXd(double xRW) { if (getXaxisLog()) { return getWidth() * (Math.log10(xRW) - Math.log10(xmin)) / (Math.log10(xmax) - Math.log10(xmin)); } return getXZero() + (xRW * getXscale()); } /** * convert real world coordinate y to screen coordinate y * * @param yRW * real world y-coord * @return screen equivalent of real world y-coord */ @Override final public double toScreenCoordYd(double yRW) { if (getYaxisLog()) { return getHeight() * (1 - (Math.log10(yRW) - Math.log10(ymin)) / (Math.log10(ymax) - Math.log10(ymin))); } return getYZero() - (yRW * getYscale()); } /** * convert real world coordinate x to screen coordinate x. If the value is * outside the screen it is clipped to one pixel outside. * * @param xRW * real world x coordinate * @return real world coordinate x to screen coordinate x clipped to screen */ final public int toClippedScreenCoordX(double xRW) { if (xRW > getXmax()) { return getWidth() + 1; } else if (xRW < getXmin()) { return -1; } else { return toScreenCoordX(xRW); } } /** remembers the origins values (xzero, ...) */ @Override public void rememberOrigins() { xZeroOld = getXZero(); yZeroOld = getYZero(); xScaleStart = getXscale(); yScaleStart = getYscale(); } /** * convert real world coordinate y to screen coordinate y. If the value is * outside the screen it is clipped to one pixel outside. * * @param yRW * real world y coordinate * @return real world coordinate y to screen coordinate x clipped to screen */ final public int toClippedScreenCoordY(double yRW) { if (yRW > getYmax()) { return -1; } else if (yRW < getYmin()) { return getHeight() + 1; } else { return toScreenCoordY(yRW); } } /** * Converts real world coordinates to screen coordinates. Note that * MAX_SCREEN_COORD is used to avoid huge coordinates. * * @param inOut * input and output array with x and y coords * @return if resulting coords are on screen */ final public boolean toScreenCoords(double[] inOut) { // convert to screen coords if (getXaxisLog()) { inOut[0] = getWidth() * (Math.log10(inOut[0]) - Math.log10(xmin)) / (Math.log10(xmax) - Math.log10(xmin)); } else { inOut[0] = getXZero() + (inOut[0] * getXscale()); } if (getYaxisLog()) { inOut[1] = getHeight() * (1 - (Math.log10(inOut[1]) - Math.log10(ymin)) / (Math.log10(ymax) - Math.log10(ymin))); } else { inOut[1] = getYZero() - (inOut[1] * getYscale()); } // check if (x, y) is on screen boolean onScreen = true; // note that java drawing has problems for huge coord values // so we use FAR_OFF_SCREEN for clipping if (Double.isNaN(inOut[0]) || Double.isInfinite(inOut[0])) { inOut[0] = Double.NaN; onScreen = false; } else if (inOut[0] < 0) { // x left of screen // inOut[0] = Math.max(inOut[0], -MAX_SCREEN_COORD); onScreen = false; } else if (inOut[0] > getWidth()) { // x right of screen // inOut[0] = Math.min(inOut[0], width + MAX_SCREEN_COORD); onScreen = false; } // y undefined if (Double.isNaN(inOut[1]) || Double.isInfinite(inOut[1])) { inOut[1] = Double.NaN; onScreen = false; } else if (inOut[1] < 0) { // y above screen // inOut[1] = Math.max(inOut[1], -MAX_SCREEN_COORD); onScreen = false; } else if (inOut[1] > getHeight()) { // y below screen // inOut[1] = Math.min(inOut[1], height + MAX_SCREEN_COORD); onScreen = false; } return onScreen; } /** * Checks if (screen) coords are on screen. * * @param coords * coords * @return true if coords are on screen */ final public boolean isOnScreen(double[] coords) { return (coords[0] >= 0) && (coords[0] <= getWidth()) && (coords[1] >= 0) && (coords[1] <= getHeight()); } /** * Checks if (real world) coords are on view. * * @param coords * coords * @return true if coords are on view */ public boolean isOnView(double[] coords) { return (coords[0] >= getXmin()) && (coords[0] <= getXmax()) && (coords[1] >= getYmin()) && (coords[1] <= getYmax()); } /** * * @param p1 * first point * @param p2 * second point * @return (p2-p1) vector in screen coordinates */ public double[] getOnScreenDiff(double[] p1, double[] p2) { double[] ret = new double[2]; ret[0] = (p2[0] - p1[0]) * getXscale(); ret[1] = (p2[1] - p1[1]) * getYscale(); return ret; } /** * Performs a quick test whether the segment p1 to p2 is off view. * * @param p1 * first point * @param p2 * second point * @return true if segment is on / close to view */ public boolean isSegmentOffView(double[] p1, double[] p2) { double tolerance = EuclidianStatic.CLIP_DISTANCE / getYscale(); // bottom if (Kernel.isGreater(getYmin(), p1[1], tolerance) && Kernel.isGreater(getYmin(), p2[1], tolerance)) { return true; } // top if (Kernel.isGreater(p1[1], getYmax(), tolerance) && Kernel.isGreater(p2[1], getYmax(), tolerance)) { return true; } tolerance = EuclidianStatic.CLIP_DISTANCE / getXscale(); // left if (Kernel.isGreater(getXmin(), p1[0], tolerance) && Kernel.isGreater(getXmin(), p2[0], tolerance)) { return true; } // right if (Kernel.isGreater(p1[0], getXmax(), tolerance) && Kernel.isGreater(p2[0], getXmax(), tolerance)) { return true; } // close to screen return false; } /** * convert screen coordinate x to real world coordinate x * * @param x * screen coord * @return real world equivalent of screen x-coord */ @Override final public double toRealWorldCoordX(double x) { return (x - getXZero()) * getInvXscale(); } /** * convert screen coordinate y to real world coordinate y * * @param y * screen coord * @return real world equivalent of screen y-coord */ @Override final public double toRealWorldCoordY(double y) { return (getYZero() - y) * getInvYscale(); } /** * Sets real world coord system, where zero point has screen coords (xZero, * yZero) and one unit is xscale pixels wide on the x-Axis and yscale pixels * heigh on the y-Axis. * * Also updates settings *before* all the algos that might need them are * updated */ @Override final public void setCoordSystem(double xZero, double yZero, double xscale, double yscale) { if (settings != null) { settings.setCoordSystem(xZero, yZero, xscale, yscale, false); } setCoordSystem(xZero, yZero, xscale, yscale, true); } /** Sets coord system from mouse move */ @Override public void translateCoordSystemInPixels(int dx, int dy, int dz, int modeForTranslate) { setCoordSystem(xZeroOld + dx, yZeroOld + dy, getXscale(), getYscale()); } /** * Sets coord system from mouse move * * @param dx * x-displacement * @param dy * y-displacement * @param mode * app mode */ public void setCoordSystemFromMouseMove(int dx, int dy, int mode) { translateCoordSystemInPixels(dx, dy, 0, mode); } @Override public void pageUpDownTranslateCoordSystem(int height) { translateCoordSystemInPixels(0, height, 0, EuclidianController.MOVE_VIEW); } /** * Sets real world coord system using min and max values for both axes in * real world values. */ @Override final public void setRealWorldCoordSystem(double xmin2, double xmax2, double ymin2, double ymax2) { double calcXscale = getWidth() / (xmax2 - xmin2); if (getXaxisLog()) { calcXscale = getWidth() / (xmax2 - xmin2); } double calcYscale = getHeight() / (ymax2 - ymin2); double calcXzero = -calcXscale * xmin2; double calcYzero = calcYscale * ymax2; setCoordSystem(calcXzero, calcYzero, calcXscale, calcYscale); } /** * @param xZero * new x coord of origin * @param yZero * new y coord of origin * @param xscale * x scale * @param yscale * y scale * @param repaint * true to repaint */ public final void setCoordSystem(double xZero, double yZero, double xscale, double yscale, boolean repaint) { 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.setXscale(xscale); this.setYscale(yscale); // setScaleRatio(yscale / xscale); setCoordTransformIfNeeded(); // real world values setXYMinMaxForSetCoordSystem(); setRealWorldBounds(); // if (drawMode == DRAW_MODE_BACKGROUND_IMAGE) if (repaint) { updateBackgroundOnNextRepaint = true; updateAllDrawablesForView(repaint); // needed so that eg Corner[2,1] updates properly on zoom / pan if (getApplication().hasEuclidianView2(1)) { kernel.notifyRepaint(); // app.updateStatusLabelAxesRatio(); } } // tells app that set coord system occured app.setCoordSystemOccured(); } /** * If the background was marked for update (axes changed), repaint it */ public void updateBackgroundIfNecessary() { if (updateBackgroundOnNextRepaint) { this.updateBackgroundImage(); } updateBackgroundOnNextRepaint = false; } /** * @return the ymaxObject */ @Override public GeoNumeric getYmaxObject() { return (GeoNumeric) ymaxObject; } /** minimal visible real world x */ private double xmin; /** maximal visible real world x */ private double xmax; /** minimal visible real world y */ private double ymin; /** maximal visible real world y */ private double ymax; private double invXscale; private double invYscale; protected double xZero; protected double yZero; private double xscale; private double yscale; // private double scaleRatio = 1.0; /** print scale ratio */ protected double printingScale; // Map (geo, drawable) for GeoElements and Drawables private HashMap<GeoElement, DrawableND> DrawableMap = new HashMap<GeoElement, DrawableND>( 500); private ArrayList<GeoPointND> stickyPointList = new ArrayList<GeoPointND>(); protected DrawableList allDrawableList = new DrawableList(); /** lists of geos on different layers */ public DrawableList drawLayers[]; // on add: change resetLists() /** list of background images */ private DrawableList bgImageList = new DrawableList(); protected boolean[] piAxisUnit; protected int[] axesTickStyles; // for axes labeling with numbers protected boolean[] automaticAxesNumberingDistances = { true, true }; protected double[] axesNumberingDistances; protected GeoNumberValue[] axesDistanceObjects; private boolean needsAllDrawablesUpdate; protected boolean batchUpdate; /** * This is only needed for second or above euclidian views * * @param evNo * euclidian view number 1, 2 or EVNO_GENERAL */ public void setEuclidianViewNo(int evNo) { if (evNo >= 2) { this.evNo = evNo; } } @Override public int getEuclidianViewNo() { return evNo; } /** * @param ymaxObjectNew * the ymaxObject to set */ @Override public void setYmaxObject(NumberValue ymaxObjectNew) { if (ymaxObject != null) { ((GeoNumeric) ymaxObject).removeEVSizeListener(this); } if (ymaxObjectNew == null && kernel.getConstruction() != null) { this.ymaxObject = new GeoNumeric(kernel.getConstruction()); updateBoundObjects(); } else { this.ymaxObject = ymaxObjectNew; } setSizeListeners(); } private void setXscale(double xscale) { this.xscale = xscale; this.invXscale = 1 / xscale; } private void setYscale(double yscale) { this.yscale = yscale; this.invYscale = 1 / yscale; } /** * @param fontSize * default font size */ protected void setFontSize(int fontSize) { this.fontSize = fontSize; } /** * Returns x coordinate of axes origin. */ @Override public double getXZero() { return xZero; } /** * Returns y coordinate of axes origin. */ @Override public double getYZero() { return yZero; } /** * @return string "x : y= _ : 1" or "x : y= 1 : _" */ protected String getXYscaleRatioString() { StringBuilder ratioSb = new StringBuilder(); ratioSb.append("x : y = "); if (getXscale() >= getYscale()) { ratioSb.append("1 : "); ratioSb.append(printScaleNF.format(getXscale() / getYscale())); } else { ratioSb.append(printScaleNF.format(getYscale() / getXscale())); ratioSb.append(" : 1"); } ratioSb.append(' '); return ratioSb.toString(); } /** * Returns xscale of this view. The scale is the number of pixels in screen * space that represent one unit in user space. */ @Override 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. */ @Override public double getYscale() { return yscale; } @Override public double getInvXscale() { return invXscale; } @Override public double getInvYscale() { return invYscale; } @Override public int getViewWidth() { return getWidth(); } @Override public int getViewHeight() { return getHeight(); } /** * Returns the ratio yscale / xscale of this view. The scale is the number * of pixels in screen space that represent one unit in user space. * * @return yscale / xscale ratio */ public double getScaleRatio() { return getYscale() / getXscale(); } /** * @return Returns the xmax. */ @Override public double getXmax() { return xmax; } /** * @return Returns the xmin. */ @Override public double getXmin() { return xmin; } /** * @return Returns the ymax. */ @Override public double getYmax() { return ymax; } /** * @return Returns the ymin. */ @Override public double getYmin() { return ymin; } /** * Returns grid type. */ @Override final public int getGridType() { return gridType; } /** * Set grid type. */ @Override public void setGridType(int type) { gridType = type; } /* * void setScaleRatio(double scaleRatio) { this.scaleRatio = scaleRatio; } */ /** * TODO check whether this is what we want * * @param minMax * minima and maxima */ public void setXYMinMax(double[][] minMax) { xmin = (minMax[0][0]); xmax = (minMax[0][1]); ymin = (minMax[1][0]); ymax = (minMax[1][1]); } protected void setRealWorldBounds() { updateBoundObjects(); updateBounds(false, false); } /** * Updates xmin, xmax, ... for setCoordSystem() */ protected void setXYMinMaxForSetCoordSystem() { xmin = (-getXZero() * getInvXscale()); xmax = ((getWidth() - getXZero()) * getInvXscale()); ymax = (getYZero() * getInvYscale()); ymin = ((getYZero() - getHeight()) * getInvYscale()); } /** * Zooms around fixed point (center of screen) * * @param zoomFactor * zoom factor */ public final void zoomAroundCenter(double zoomFactor) { if (!isZoomable()) { return; // keep xmin, xmax, ymin, ymax constant, adjust everything else } setXscale(getXscale() * zoomFactor); setYscale(getYscale() * zoomFactor); // setScaleRatio(getYscale() / getXscale()); xZero = -getXmin() * getXscale(); setWidth((int) ((getXmax() * getXscale()) + getXZero())); yZero = getYmax() * getYscale(); setHeight((int) (getYZero() - (getYmin() * getYscale()))); setAxesIntervals(getXscale(), 0); setAxesIntervals(getYscale(), 1); calcPrintingScale(); // tell kernel if (evNo != EVNO_GENERAL) { kernel.setEuclidianViewBounds(evNo, getXmin(), getXmax(), getYmin(), getYmax(), getXscale(), getYscale()); } setCoordTransformIfNeeded(); updateBackgroundImage(); updateAllDrawablesForView(true); } private void setCoordTransformIfNeeded() { if (coordTransform != null) { coordTransform.setTransform(xscale, 0.0d, 0.0d, -yscale, xZero, yZero); } } /** * @param repaint * true to repaint */ final public void updateAllDrawables(boolean repaint) { if (repaint && this.batchUpdate) { this.needsAllDrawablesUpdate = true; return; } allDrawableList.updateAll(); if (repaint) { repaint(); } } /** * @param repaint * true to repaint */ final public void updateAllDrawablesForView(boolean repaint) { if (repaint && this.batchUpdate) { this.needsAllDrawablesUpdate = true; return; } allDrawableList.updateAllForView(); if (repaint) { repaint(); } } // may need to be synchronized for some 3D implementations @Override public void startBatchUpdate() { this.batchUpdate = true; } @Override public void endBatchUpdate() { this.batchUpdate = false; if (this.needsAllDrawablesUpdate) { allDrawableList.updateAll(); repaint(); } } /** * * @return true if currently batch update */ public boolean isBatchUpdate() { return this.batchUpdate; } /** * @param list * list * @param b * whether the list should be drawn as combobox */ public void drawListAsComboBox(GeoList list, boolean b) { DrawableND d = getDrawable(list); if (d != null) { remove(list); add(list); } } /** * Recompute printing scale */ public void calcPrintingScale() { double unitPerCM = PRINTER_PIXEL_PER_CM / getXscale(); int exp = (int) Math.round(Math.log(unitPerCM) / Math.log(10)); printingScale = Math.pow(10, -exp); } /** * axis: 0 for x-axis, 1 for y-axis * * @param scale * axis scale * @param axis * axis index */ protected void setAxesIntervals(double scale, int axis) { double maxPix = 100; // only one tick is allowed per maxPix pixels double units = maxPix / scale; int exp = (int) Math.floor(Math.log(units) / Math.log(10)); // if (logAxes[axis]) { // exp = (int) Math.log10(exp); // } int maxFractionDigits = Math.max(-exp, kernel.getPrintDecimals()); if (automaticAxesNumberingDistances[axis]) { // force same unit if scales are same, see #1082 if ((axis == 1) && automaticAxesNumberingDistances[0] && Kernel.isEqual(getXscale(), getYscale())) { if (piAxisUnit[0] == piAxisUnit[1]) { axesNumberingDistances[1] = axesNumberingDistances[0]; } else if (piAxisUnit[0]) { axesNumberingDistances[1] = axesNumberingDistances[0] / Math.PI; } else if (piAxisUnit[1]) { axesNumberingDistances[1] = axesNumberingDistances[0] * Math.PI; } } else if (piAxisUnit[axis]) { axesNumberingDistances[axis] = Math.PI; } else { // see #2682 double pot = Kernel.checkDecimalFraction(Math.pow(10, exp)); double n = Kernel.checkDecimalFraction(units / pot); if (n > 5) { axesNumberingDistances[axis] = 5 * pot; } else if (n > 2) { axesNumberingDistances[axis] = 2 * pot; } else { axesNumberingDistances[axis] = pot; } } } axesTickInterval[axis] = axesNumberingDistances[axis] / 2.0; // set axes number format // NumberFormatAdapter df = axesNumberFormat[axis]; // display large and small numbers in scienctific notation if ((axesNumberingDistances[axis] < 10E-6) || (axesNumberingDistances[axis] > 10E6)) { // df.applyPattern("0.##E0"); maxFractionDigits = Math.min(14, maxFractionDigits); axesNumberFormat[axis] = FormatFactory.getPrototype() .getNumberFormat("0.##E0", maxFractionDigits); // avoid 4.00000000000004E-11 due to rounding error when // computing // tick mark numbers } else { axesNumberFormat[axis] = FormatFactory.getPrototype() .getNumberFormat("###0.##", maxFractionDigits); } if (automaticGridDistance && axis < 2) { gridDistances[axis] = axesNumberingDistances[axis] * EuclidianStyleConstants.DEFAULT_GRID_DIST_FACTOR; } } /** * @return font size */ public int getFontSize() { return fontSize; } /** * renames an element */ @Override public void rename(GeoElement geo) { Object d = DrawableMap.get(geo); if (d != null) { ((Drawable) d).update(); repaint(); } } @Override public void update(GeoElement geo) { DrawableND d = DrawableMap.get(geo); if (d != null) { if (d instanceof DrawImage) { this.updateBackgroundOnNextRepaint = ((DrawImage) d) .checkInBackground() || this.updateBackgroundOnNextRepaint; return; } // Keep update of input boxes synchronous #4416 if ((!geo.isGeoText() || !((GeoText) geo) .isNeedsUpdatedBoundingBox()) && !geo.isGeoInputBox() && (!geo.getTrace() || d.isTracing())) { d.setNeedsUpdate(true); return; } d.update(); } else if (drawableNeeded(geo) && geosWaiting.contains(geo)) { geosWaiting.remove(geo); add(geo); d = DrawableMap.get(geo); if (d != null) { d.setNeedsUpdate(true); repaint(); } } } private ArrayList<GeoElement> geosWaiting = new ArrayList<GeoElement>(); /** * adds a GeoElement to this view */ @Override public void add(GeoElement geo) { // G.Sturr 2010-6-30 // filter out any geo not marked for this view if (!drawableNeeded(geo)) { if (isVisibleInThisView(geo)) { this.geosWaiting.add(geo); } return; // END G.Sturr } // check if there is already a drawable for geo DrawableND d = getDrawable(geo); if (d != null) { return; } if (createAndAddDrawable(geo)) { repaint(); // if (app.has(Feature.DYNAMIC_STYLEBAR)) { // if (!isDrawingPredecessor(geo)) { // // if (geo.hasChildren()) { // d = companion.newDrawable(geo); // // if (d instanceof Drawable){ // if (d instanceof DrawLine){ // ((DrawLine) d).updateDynamicStylebarPosition(); // this.euclidianController // .setDynamicStylebarVisible(true); // } else if (((Drawable) d).getBounds() != null) { // this.euclidianController.setDynamicStyleBarPosition( // ((Drawable) d).getBounds(), true); // this.euclidianController // .setDynamicStylebarVisible(true); // } // // } // } else { // // this.euclidianController.setDynamicStylebarVisible(false); // } // } } } public boolean isDrawingPredecessor(GeoElement geo) { if (geo instanceof GeoPoint && getEuclidianController() .getMode() != EuclidianConstants.MODE_POINT && getEuclidianController() .getMode() != EuclidianConstants.MODE_SHAPE_LINE) { return true; } if (geo instanceof GeoSegment && getEuclidianController() .getMode() != EuclidianConstants.MODE_SEGMENT) { return true; } // Log.debug("has children? - " + geo.getAlgebraDescriptionDefault()); // if (!geo.hasChildren()) { // Log.debug("HAS NO"); // return true; // } // Log.debug("HAS!"); return false; } /** * create and add geo to this view * * @param geo * geo * @return true if drawable created */ protected boolean createAndAddDrawable(GeoElement geo) { DrawableND d = createDrawable(geo); if (d != null) { addToDrawableLists((Drawable) d); return true; } return false; } private GeoElement[] previewFromInputBarGeos; @Override public void updatePreviewFromInputBar(GeoElement[] geos) { if (previewFromInputBarGeos != null) { for (GeoElement geo : previewFromInputBarGeos) { remove(geo); } } previewFromInputBarGeos = geos; boolean needsRepaint = false; if (previewFromInputBarGeos != null) { for (GeoElement geo : previewFromInputBarGeos) { needsRepaint = createAndAddDrawable(geo) || needsRepaint; } } repaintForPreviewFromInputBar(); } /** * Repaint for input preview, overridden in 3D */ protected void repaintForPreviewFromInputBar() { repaint(); } private boolean drawableNeeded(GeoElement geo) { if (geo.isVisibleInView(App.VIEW_FUNCTION_INSPECTOR)) { return true; } if (isVisibleInThisView(geo) && (geo.isLabelSet() || this.isPlotPanel())) { return geo.isEuclidianVisible() || (geo.isGeoText() && ((GeoText) geo) .isNeedsUpdatedBoundingBox()) || (geo.isGeoAngle() && geo.getParentAlgorithm() instanceof AlgoAngle); } return false; } /** * @return whether this view is used as plotPanel in prob calc */ public boolean isPlotPanel() { return false; } /** * removes a GeoElement from this view */ @Override public void remove(GeoElement geo) { this.geosWaiting.remove(geo); Drawable d = (Drawable) DrawableMap.get(geo); int layer = geo.getLayer(); if (d == null) { return; } if (d instanceof RemoveNeeded) { drawLayers[layer].remove(d); ((RemoveNeeded) d).remove(); } else { drawLayers[layer].remove(d); } allDrawableList.remove(d); DrawableMap.remove(geo); if (geo.isGeoPoint()) { stickyPointList.remove(geo); } euclidianController.clear(geo); if (!d.isCreatedByDrawListVisible()) { repaint(); } } /** get the hits recorded */ @Override public Hits getHits() { return hits; } /** * event coords * * @param x * event screen x-coord * @param y * event screen y-coord * @param type * event type * * @return whether textfield was clicked */ public boolean textfieldClicked(int x, int y, PointerEventType type) { DrawableIterator it = allDrawableList.getIterator(); if (getEuclidianController().isDraggingBeyondThreshold()) { return false; } while (it.hasNext()) { Drawable d = it.next(); if ((d.isCanvasDrawable()) && (d.hit(x, y, app.getCapturingThreshold(type)) || d .hitLabel(x, y))) { GeoElement geo = d.getGeoElement(); if (geo.isEuclidianVisible()) { if (getTextField() != null && getTextField().hasFocus()) { Log.debug("TF geo: " + getTextField().getDrawTextField() .getGeoElement() + " tf text " + getTextField().getText()); } focusTextField((GeoInputBox) geo); ((CanvasDrawable) d).setWidgetVisible(true); return true; } ((CanvasDrawable) d).setWidgetVisible(false); } } return false; } /** * set hits for current mouse loc * * @param type * event type */ final public void setHits(PointerEventType type) { getCompanion().setHits(type); } @Override public void setHits(GPoint p, PointerEventType type) { setHits(p, app.getCapturingThreshold(type)); if (type == PointerEventType.TOUCH && this.hits.size() == 0) { setHits(p, app.getCapturingThreshold(type) * 3); } } private ArrayList<GeoElement> hitPointOrBoundary, hitFilling, hitLabel; /** * sets the hits of GeoElements whose visual representation is at screen * coords (x,y). order: points, vectors, lines, conics */ public void setHits(GPoint p, int hitThreshold) { hits.init(); if (hitPointOrBoundary == null) { hitPointOrBoundary = new ArrayList<GeoElement>(); hitFilling = new ArrayList<GeoElement>(); hitLabel = new ArrayList<GeoElement>(); } else { hitPointOrBoundary.clear(); hitFilling.clear(); hitLabel.clear(); } if (p == null) { return; } DrawableIterator it = allDrawableList.getIterator(); while (it.hasNext()) { Drawable d = it.next(); if (d.isEuclidianVisible()) { if (d.hit(p.x, p.y, hitThreshold)) { GeoElement geo = d.getGeoElement(); if (geo.getLastHitType() == HitType.ON_BOUNDARY) { hitPointOrBoundary.add(geo); } else { hitFilling.add(geo); } } else if (d.hitLabel(p.x, p.y)) { GeoElement geo = d.getGeoElement(); hitLabel.add(geo); } } } // labels first for (GeoElement geo : hitLabel) { hits.add(geo); } // then points and paths for (GeoElement geo : hitPointOrBoundary) { hits.add(geo); } // then regions for (GeoElement geo : hitFilling) { hits.add(geo); } // look for axis if (hits.getImageCount() == 0) { // x axis hit if (showAxes[0] && (Math.abs(getYAxisCrossingPixel() - p.y) < hitThreshold)) { // handle positive axis only if (!positiveAxes[0] || (getXAxisCrossingPixel() < p.x - hitThreshold)) { hits.add(kernel.getXAxis()); } } // y axis hit if (showAxes[1] && (Math.abs(getXAxisCrossingPixel() - p.x) < hitThreshold)) { // handle positive axis only if (!positiveAxes[1] || (getYAxisCrossingPixel() > p.y - hitThreshold)) { hits.add(kernel.getYAxis()); } } } // keep geoelements only on the top layer int maxlayer = 0; for (int i = 0; i < hits.size(); ++i) { GeoElement geo = hits.get(i); if (maxlayer < geo.getLayer()) { maxlayer = geo.getLayer(); } } for (int i = hits.size() - 1; i >= 0; i--) { GeoElement geo = hits.get(i); if (geo.getLayer() < maxlayer) { hits.remove(i); } } // remove all lists and images if there are other objects too if ((hits.size() - (hits.getListCount() + hits.getImageCount())) > 0) { for (int i = hits.size() - 1; i >= 0; i--) { GeoElement geo = hits.get(i); if ((geo.isGeoList() && !((GeoList) geo).drawAsComboBox()) || geo.isGeoImage()) { hits.remove(i); } } } } @Override public MyButton getHitButton(GPoint p, PointerEventType type) { DrawableIterator it = allDrawableList.getIterator(); Drawable d = null; while (it.hasNext()) { Drawable d2 = it.next(); if (d2 instanceof DrawButton && d2.hit(p.x, p.y, app.getCapturingThreshold(type))) { if (d == null || d2.getGeoElement().getLayer() >= d.getGeoElement() .getLayer()) { d = d2; } } } if (d != null) { return ((DrawButton) d).myButton; } return null; } /** * returns GeoElement whose label is at screen coords (x,y). */ @Override public GeoElement getLabelHit(GPoint p, PointerEventType type) { if (!getApplication().isLabelDragsEnabled()) { return null; } DrawableIterator it = allDrawableList.getIterator(); while (it.hasNext()) { Drawable d = it.next(); if (d.hitLabel(p.x, p.y)) { GeoElement geo = d.getGeoElement(); if (geo.isEuclidianVisible()) { return geo; } } } return null; } /** * returns GeoElement whose bounding box handler is at screen coords (x,y). * * @param p * @param type * @return */ public Drawable getBoundingBoxHandlerHit(GPoint p, PointerEventType type) { if(p==null){ return null; } DrawableIterator it = allDrawableList.getIterator(); while (it.hasNext()) { Drawable d = it.next(); hitHandler = d.hitBoundingBoxHandler(p.x, p.y, app.getCapturingThreshold(type)); if (hitHandler != EuclidianBoundingBoxHandler.UNDEFINED) { GeoElement geo = d.getGeoElement(); if (geo.isEuclidianVisible()) { return d; } } } return null; } private boolean labelHitNeedsRefresh = true; private GeoElement labelHitLastGeo = null; /** * says that label hit needs to be refreshed */ public void setLabelHitNeedsRefresh() { labelHitNeedsRefresh = true; } /** * WARNING: ensure setLabelHitNeedsRefresh() is call once since last mouse * event * * @param p * mouse position * @param type * event type * @return geo hit on label since last refresh */ public GeoElement getLabelHitCheckRefresh(GPoint p, PointerEventType type) { if (labelHitNeedsRefresh) { labelHitLastGeo = getLabelHit(p, type); labelHitNeedsRefresh = false; } return labelHitLastGeo; } /** * Returns the drawable for the given GeoElement. * * @param geo * geo * @return drawable for the given GeoElement. */ protected final DrawableND getDrawable(GeoElement geo) { return DrawableMap.get(geo); } @Override public DrawableND getDrawableND(GeoElement geo) { return getDrawable(geo); } /** * adds a GeoElement to this view * * @param draw * drawable to be added */ protected void addToDrawableLists(Drawable draw) { if (draw == null) { return; } Drawable d = draw; GeoElement geo = d.getGeoElement(); int layer = geo.getLayer(); switch (geo.getGeoClassType()) { case ANGLE: if (geo.isIndependent()) { drawLayers[layer].add(d); } else { if (geo.isDrawable()) { drawLayers[layer].add(d); } else { d = null; } } break; case IMAGE: if (!bgImageList.contains(d)) { drawLayers[layer].add(d); } break; default: drawLayers[layer].add(d); break; } if (d != null) { allDrawableList.add(d); } } /** * @param geo * geo * @return true if geo is visible in this view */ public boolean isVisibleInThisView(GeoElementND geo) { return companion.isVisibleInThisView(geo); } /** * adds a GeoElement to this view * * @param geo * GeoElement to be added * @return drawable for given GeoElement */ protected DrawableND createDrawable(GeoElement geo) { DrawableND d = newDrawable(geo); if (d != null) { DrawableMap.put(geo, d); if (geo.isGeoPoint()) { stickyPointList.add((GeoPointND) geo); } } return d; } /** * @param geo * geo * @return new drawable for the geo */ @Override public DrawableND newDrawable(GeoElement geo) { return companion.newDrawable(geo); } @Override public void reset() { resetMode(); updateBackgroundImage(); } /** * clears all selections and highlighting */ @Override public void resetMode() { setMode(mode); } /** * @param mode2 * mode number */ public void setMode(int mode2) { setMode(mode2, ModeSetter.TOOLBAR); } @Override public void repaintView() { repaint(); } @Override public void updateVisualStyle(GeoElement geo, GProperty prop) { update(geo); if (styleBar != null) { styleBar.updateVisualStyle(geo); } if (app.has(Feature.DYNAMIC_STYLEBAR)) { if (dynamicStyleBar != null) { dynamicStyleBar.updateVisualStyle(geo); } } } @Override final public DrawableND getDrawableFor(GeoElementND geo) { return DrawableMap.get(geo); } @Override final public void updateAuxiliaryObject(GeoElement geo) { // repaint(); } /** * Updates font size for all drawables */ protected void updateDrawableFontSize() { allDrawableList.updateFontSizeAll(); repaint(); } /** * @return plain font */ public GFont getFontPoint() { if (fontPoint == null) { return app.getPlainFontCommon(); } return fontPoint; } private void setFontPoint(GFont fontPoint) { this.fontPoint = fontPoint; } /** * @return font for lines */ public GFont getFontLine() { return getFontPoint(); } /** * @return font for vectors */ public GFont getFontVector() { return getFontPoint(); } /** * @return font for conics */ public GFont getFontConic() { return getFontPoint(); } /** * @return font for coords */ public GFont getFontCoords() { if (fontCoords == null) { return app.getPlainFontCommon(); } return fontCoords; } private void setFontCoords(GFont fontCoords) { this.fontCoords = fontCoords; } /** * @return font for axes */ public GFont getFontAxes() { return getFontCoords(); } /** * @return font for angles */ public GFont getFontAngle() { return getFontPoint(); } @Override public ArrayList<GeoPointND> getStickyPointList() { return stickyPointList; } /** * Sets the global size for checkboxes. Michael Borcherds 2008-05-12 * * @param size * 13 or 26 */ public void setBooleanSize(int size) { // only 13 and 26 currently allowed app.setCheckboxSize(size); updateAllDrawables(true); } /** * @return size of booleans (13 or 26) */ final public int getBooleanSize() { return app.getCheckboxSize(); } /** * @param setto * tooltip mode */ public void setAllowToolTips(int setto) { tooltipsInThisView = setto; } @Override final public int getAllowToolTips() { return tooltipsInThisView; } // ///////////////////////////////////////// // FOR EUCLIDIANVIEWFORPLANE // ///////////////////////////////////////// /** * return null if classic 2D view * * @return matrix representation of the plane shown by this view */ public CoordMatrix getMatrix() { return companion.getMatrix(); } /** * return null if classic 2D view * * @return matrix inverse representation of the plane shown by this view */ public CoordMatrix getInverseMatrix() { return companion.getMatrix(); } @Override public String getFromPlaneString() { return companion.getFromPlaneString(); } @Override public String getTranslatedFromPlaneString() { return companion.getTranslatedFromPlaneString(); } @Override public boolean isDefault2D() { return companion.isDefault2D(); } @Override public boolean isEuclidianView3D() { return false; } @Override public int getViewID() { switch (evNo) { case 1: return App.VIEW_EUCLIDIAN; case 2: return App.VIEW_EUCLIDIAN2; default: return App.VIEW_NONE; } } @Override public void changeLayer(GeoElement geo, int oldlayer, int newlayer) { drawLayers[oldlayer].remove((Drawable) DrawableMap.get(geo)); drawLayers[newlayer].add((Drawable) DrawableMap.get(geo)); } /** * * @return null (for 2D) and xOyPlane (for 3D) */ public GeoPlaneND getPlaneContaining() { return companion.getPlaneContaining(); } /** * * @return null (for 2D) and xOyPlane (for 3D) */ @Override public GeoDirectionND getDirection() { return companion.getDirection(); } /** * tranform in view coords * * @param coords * point * @return the same coords for classic 2d view */ @Override public Coords getCoordsForView(Coords coords) { return companion.getCoordsForView(coords); } @Override public boolean isMoveable(GeoElement geo) { return companion.isMoveable(geo); } @Override public ArrayList<GeoPointND> getFreeInputPoints(AlgoElement algoParent) { return companion.getFreeInputPoints(algoParent); } /** * Replaces num by num2 in xmin, xmax,ymin,ymax. Does not add / remove EV * listeners from these numerics * * @param num * old numeric * @param num2 * new numeric */ @Override public void replaceBoundObject(GeoNumeric num, GeoNumeric num2) { if (xmaxObject == num) { xmaxObject = num2; } if (xminObject == num) { xminObject = num2; } if (ymaxObject == num) { ymaxObject = num2; } if (yminObject == num) { yminObject = num2; } for (int i = 0; i < axesDistanceObjects.length; i++) { if (axesDistanceObjects[i] == num) { axesDistanceObjects[i] = num2; } } updateBounds(true, true); } /** * @return right angle style */ final public int getRightAngleStyle() { return getApplication().rightAngleStyle; } @Override public boolean isAutomaticGridDistance() { return automaticGridDistance; } @Override public double[] getGridDistances() { return gridDistances; } @Override public void setGridDistances(double[] dist) { if (dist == null) { return; } gridDistances = dist; setAutomaticGridDistance(false); } @Override public int getGridLineStyle() { return gridLineStyle; } @Override public void setAutomaticGridDistance(boolean flag) { automaticGridDistance = flag; setAxesIntervals(getXscale(), 0); setAxesIntervals(getYscale(), 1); if (flag) { gridDistances[2] = Math.PI / 6; } } @Override public int getAxesLineStyle() { return axesLineType; } @Override public void setAxesLineStyle(int axesLineStyle) { this.axesLineType = axesLineStyle; } /** * @return RW => EV transform; created (but not initialized) when null */ public GAffineTransform getCoordTransform() { if (coordTransform == null) { coordTransform = AwtFactory.getPrototype().newAffineTransform(); } return coordTransform; } /** * @param coordTransform * RW => EV transform */ protected void setCoordTransform(GAffineTransform coordTransform) { this.coordTransform = coordTransform; } @Override final public void updateBackground() { // make sure axis number formats are up to date setAxesIntervals(getXscale(), 0); setAxesIntervals(getYscale(), 1); updateBackgroundImage(); updateAllDrawablesForView(true); // repaint(); } /** * @param fontForGraphics * font * @return graphics correnspondin to given font */ public abstract GGraphics2D getTempGraphics2D(GFont fontForGraphics); /** * @return font */ public abstract GFont getFont(); /** * @param h * new height */ protected abstract void setHeight(int h); /** * @param h * new width */ protected abstract void setWidth(int h); /** * Initializes cursor */ protected abstract void initCursor(); /** * @param mode * new mode for sylebar */ protected abstract void setStyleBarMode(int mode); /** * @param mode * mode * @return true if given mode can use selection rectangle as input */ final public static boolean usesSelectionAsInput(int mode) { switch (mode) { case EuclidianConstants.MODE_TRANSLATE_BY_VECTOR: return false; // changed for new "drag" behaviour case EuclidianConstants.MODE_MIRROR_AT_POINT: case EuclidianConstants.MODE_MIRROR_AT_LINE: case EuclidianConstants.MODE_DILATE_FROM_POINT: case EuclidianConstants.MODE_ROTATE_BY_ANGLE: case EuclidianConstants.MODE_PEN: // case EuclidianConstants.MODE_PENCIL: return true; default: return false; } } /** * @param mode * mode * @return true if mode can handle selection rectangle as input */ final public static boolean usesSelectionRectangleAsInput(int mode) { switch (mode) { case EuclidianConstants.MODE_FITLINE: case EuclidianConstants.MODE_CREATE_LIST: // case EuclidianConstants.MODE_PEN: case EuclidianConstants.MODE_MIRROR_AT_LINE: case EuclidianConstants.MODE_MIRROR_AT_POINT: case EuclidianConstants.MODE_ROTATE_BY_ANGLE: case EuclidianConstants.MODE_TRANSLATE_BY_VECTOR: case EuclidianConstants.MODE_DILATE_FROM_POINT: case EuclidianConstants.MODE_COPY_VISUAL_STYLE: return true; default: return false; } } // axis control vars protected double[] axisCross; protected boolean[] positiveAxes; protected boolean[] drawBorderAxes; // getters and Setters for axis control vars @Override public void setSelectionRectangle(GRectangle selectionRectangle) { // Application.printStacktrace(""); this.selectionRectangle = selectionRectangle; } /** * @param boundingBox * - bounding box for select */ public void setBoundingBox(BoundingBox boundingBox) { this.boundingBox = boundingBox; } /** * @param shapeRectangle * - preview of rectangle for ShapeRectangle */ public void setShapeRectangle(GRectangle shapeRectangle) { this.shapeRectangle = shapeRectangle; } /** * @param shapeEllipse * - preview of ellipse for ShapeEllipse */ public void setShapeEllipse(GEllipse2DDouble shapeEllipse) { this.shapeEllipse = shapeEllipse; } /** * @param shapeLine * - preview of line for ShapeLine */ public void setShapeLine(GLine2D shapeLine) { this.shapeLine = shapeLine; } /** * @param shapePolygon * - preview of polygon for ShapeTriangle/RegularPolygon/Polygon */ public void setShapePolygon(GGeneralPath shapePolygon) { this.shapePolygon = shapePolygon; } public GColor getShapeFillCol() { return shapeFillCol; } public void setShapeFillCol(GColor shapeFillCol) { this.shapeFillCol = shapeFillCol; } public GColor getShapeObjCol() { return shapeObjCol; } public void setShapeObjCol(GColor shapeObjCol) { this.shapeObjCol = shapeObjCol; } public GBasicStroke getShapeStroke() { return shapeStroke; } public void setShapeStroke(GBasicStroke shapeStroke) { this.shapeStroke = shapeStroke; } /** * reset style of shape, needed by new shape */ public void setDefaultShapeStyle() { setShapeFillCol(GColor.newColor(192, 192, 192, 0.0)); setShapeObjCol(GColor.BLACK); setShapeStroke(AwtFactory .getPrototype().newBasicStroke(2.0f, GBasicStroke.CAP_BUTT, GBasicStroke.JOIN_MITER)); } @Override public double[] getAxesCross() { return axisCross; } @Override public void setAxesCross(double[] axisCross) { this.axisCross = axisCross; } // for xml handler @Override public void setAxisCross(int axis, double cross) { axisCross[axis] = cross; } @Override public boolean[] getPositiveAxes() { return positiveAxes; } @Override public void setPositiveAxes(boolean[] positiveAxis) { this.positiveAxes = positiveAxis; } // for xml handler @Override public void setPositiveAxis(int axis, boolean isPositiveAxis) { positiveAxes[axis] = isPositiveAxis; } @Override public boolean[] getDrawBorderAxes() { return drawBorderAxes; } @Override public void setDrawBorderAxes(boolean[] drawBorderAxes) { this.drawBorderAxes = drawBorderAxes; // don't show corner coordinates if one of the axes is sticky this.setAxesCornerCoordsVisible(!(drawBorderAxes[0] || drawBorderAxes[1])); } /** * @return true if showing corner coords */ public boolean isAxesCornerCoordsVisible() { return showAxesCornerCoords; } @Override public void setAxesCornerCoordsVisible(boolean showAxesCornerCoords) { this.showAxesCornerCoords = showAxesCornerCoords; } /** * @return scale factor for print */ public final double getPrintingScale() { return printingScale; } /** * @param printingScale * scale factor for print */ public final void setPrintingScale(double printingScale) { this.printingScale = printingScale; } /** * * setters and getters for EuclidianViewInterface * */ @Override public String[] getAxesLabels(boolean addBoldItalicTags) { String[] ret = new String[axesLabels.length]; for (int axis = 0; axis < axesLabels.length; axis++) { ret[axis] = axesLabels[axis]; } if (addBoldItalicTags) { for (int axis = 0; axis < axesLabels.length; axis++) { if (axesLabels[axis] != null && this.settings != null) { ret[axis] = this.settings.axisLabelForXML(axis); } } } return ret; } public String getAxisLabel(int axis, boolean addBoldItalicTags) { if (addBoldItalicTags) { if (axesLabels[axis] != null && this.settings != null) { return this.settings.axisLabelForXML(axis); } } return axesLabels[axis]; } @Override public void setAxesLabels(String[] axesLabels) { setAxisLabel(0, axesLabels[0]); setAxisLabel(1, axesLabels[1]); } /** * sets the axis label to axisLabel * * @param axis * axis id * @param axLabel * axis label */ @Override public void setAxisLabel(int axis, String axLabel) { String axisLabel = axLabel; if ((axisLabel == null) || (axisLabel.length() == 0)) { axesLabels[axis] = null; } else { axesLabelsStyle[axis] = GFont.PLAIN; if (axisLabel.startsWith("<i>") && axisLabel.endsWith("</i>")) { axisLabel = axisLabel.substring(3, axisLabel.length() - 4); axesLabelsStyle[axis] |= GFont.ITALIC; } if (axisLabel.startsWith("<b>") && axisLabel.endsWith("</b>")) { axisLabel = axisLabel.substring(3, axisLabel.length() - 4); axesLabelsStyle[axis] |= GFont.BOLD; } axesLabels[axis] = axisLabel; } } /** * * @param i * axis index * @return axis scale */ public double getScale(int i) { if (i == 0) { return getXscale(); } return getYscale(); } @Override final public void setAutomaticAxesNumberingDistance(boolean flag, int axis) { automaticAxesNumberingDistances[axis] = flag; setAxesIntervals(getScale(axis), axis); } @Override public boolean[] isAutomaticAxesNumberingDistance() { return automaticAxesNumberingDistances; } @Override public double[] getAxesNumberingDistances() { return axesNumberingDistances; } public GeoNumberValue[] getAxesDistanceObjects() { return axesDistanceObjects; } /** * * @param dist * numbering distance * @param axis * 0 for xAxis, 1 for yAxis */ @Override public void setAxesNumberingDistance(GeoNumberValue dist, int axis) { if (axesDistanceObjects[axis] != null) { ((GeoNumeric) axesDistanceObjects[axis]).removeEVSizeListener(this); } if (dist != null && !Double.isNaN(dist.getDouble()) && dist.getDouble() > 0) { axesNumberingDistances[axis] = dist.getDouble(); axesDistanceObjects[axis] = dist; setAutomaticAxesNumberingDistance(false, axis); ((GeoNumeric) dist).addEVSizeListener(this); } else { axesDistanceObjects[axis] = null; setAutomaticAxesNumberingDistance(true, axis); } } // Michael Borcherds 2008-04-11 @Override public boolean getGridIsBold() { return gridIsBold; } @Override public boolean[] getShowAxesNumbers() { return showAxesNumbers; } @Override public void setShowAxesNumbers(boolean[] showAxesNumbers) { this.showAxesNumbers = showAxesNumbers; } @Override public void setShowAxisNumbers(int axis, boolean showAxisNumbers) { showAxesNumbers[axis] = showAxisNumbers; } @Override public String[] getAxesUnitLabels() { return axesUnitLabels; } @Override public void setAxesUnitLabels(String[] axesUnitLabels) { for (int i = 0; i < this.axesUnitLabels.length; i++) { this.axesUnitLabels[i] = axesUnitLabels[i]; } // check if pi is an axis unit for (int i = 0; i < getDimension(); i++) { piAxisUnit[i] = (axesUnitLabels[i] != null) && axesUnitLabels[i].equals(Unicode.PI_STRING); } setAxesIntervals(getXscale(), 0); setAxesIntervals(getYscale(), 1); } @Override public int[] getAxesTickStyles() { return axesTickStyles; } @Override public void setAxisTickStyle(int axis, int tickStyle) { axesTickStyles[axis] = tickStyle; } @Override public void setAxesTickStyles(int[] axesTickStyles) { this.axesTickStyles = axesTickStyles; } @Override public void setShowMouseCoords(boolean b) { showMouseCoords = b; } @Override public boolean getAllowShowMouseCoords() { return allowShowMouseCoords; } @Override public void setAllowShowMouseCoords(boolean neverShowMouseCoords) { this.allowShowMouseCoords = neverShowMouseCoords; } @Override public boolean getShowMouseCoords() { return showMouseCoords; } @Override public void setShowAxesRatio(boolean b) { showAxesRatio = b; } @Override public Previewable getPreviewDrawable() { return previewDrawable; } @Override public double getGridDistances(int i) { return gridDistances[i]; } @Override public boolean getShowGrid() { return showGrid; } @Override public boolean isGridOrAxesShown() { return showAxes[0] || showAxes[1] || showGrid; } /** * says if the axis is shown or not * * @param axis * id of the axis * @return if the axis is shown */ @Override public boolean getShowAxis(int axis) { return showAxes[axis]; } @Override public boolean getShowXaxis() { // return showAxes[0]; return getShowAxis(AXIS_X); } @Override public boolean getShowYaxis() { return getShowAxis(AXIS_Y); } /* * say if the axis is logarithmic */ /** * @param axis * axis index * @return whether to switch axis to log scale */ public boolean getLogAxis(int axis) { return logAxes[axis]; } @Override public boolean getXaxisLog() { return getLogAxis(AXIS_X); } @Override public boolean getYaxisLog() { return getLogAxis(AXIS_Y); } // /////////////////////////////////////// // previewables @Override public Previewable createPreviewLine(ArrayList<GeoPointND> selectedPoints) { return new DrawLine(this, selectedPoints, PreviewType.LINE); } @Override public Previewable createPreviewPerpendicularBisector( ArrayList<GeoPointND> selectedPoints) { return new DrawLine(this, selectedPoints, PreviewType.PERPENDICULAR_BISECTOR); } @Override public Previewable createPreviewAngleBisector( ArrayList<GeoPointND> selectedPoints) { return new DrawLine(this, selectedPoints, PreviewType.ANGLE_BISECTOR); } @Override public Previewable createPreviewSegment(ArrayList<GeoPointND> selectedPoints) { return new DrawSegment(this, selectedPoints); } @Override public Previewable createPreviewRay(ArrayList<GeoPointND> selectedPoints) { return new DrawRay(this, selectedPoints); } @Override public Previewable createPreviewVector(ArrayList<GeoPointND> selectedPoints) { return new DrawVector(this, selectedPoints); } @Override public Previewable createPreviewConic(int mode1, ArrayList<GeoPointND> selectedPoints) { return new DrawConic(this, mode1, selectedPoints); } @Override public Previewable createPreviewParabola( ArrayList<GeoPointND> selectedPoints, ArrayList<GeoLineND> selectedLines) { return new DrawConic(this, selectedPoints, selectedLines); } @Override public Previewable createPreviewPolygon(ArrayList<GeoPointND> selectedPoints) { return new DrawPolygon(this, selectedPoints); } @Override public Previewable createPreviewAngle(ArrayList<GeoPointND> selectedPoints) { return new DrawAngle(this, selectedPoints); } @Override public Previewable createPreviewPolyLine( ArrayList<GeoPointND> selectedPoints) { return new DrawPolyLine(this, selectedPoints); } @Override public void updatePreviewable() { GPoint mouseLoc = getEuclidianController().mouseLoc; getPreviewDrawable().updateMousePos(toRealWorldCoordX(mouseLoc.x), toRealWorldCoordY(mouseLoc.y)); } @Override public void updatePreviewableForProcessMode() { if (getPreviewDrawable() != null) { getPreviewDrawable().updatePreview(); } } @Override public void mouseEntered() { setHasMouse(true); } @Override public void mouseExited() { setHasMouse(false); } public void setHasMouse(boolean flag) { hasMouse = flag; } /** * @return whether mouse is hovering over this view */ final public boolean hasMouse() { return companion.hasMouse(); } /** * @return whether mouse is hovering over this view */ final public boolean hasMouse2D() { return hasMouse; } @Override public Previewable createPreviewParallelLine( ArrayList<GeoPointND> selectedPoints, ArrayList<GeoLineND> selectedLines) { return new DrawLine(this, selectedPoints, selectedLines, true); } @Override public Previewable createPreviewPerpendicularLine( ArrayList<GeoPointND> selectedPoints, ArrayList<GeoLineND> selectedLines) { return new DrawLine(this, selectedPoints, selectedLines, false); } /** * @param application * application */ protected void setApplication(App application) { this.app = application; } @Override public App getApplication() { return this.app; } /** * Update fonts */ public void updateFonts() { setFontSize(getApplication().getFontSize()); setFontPoint(getApplication().getPlainFontCommon().deriveFont( GFont.PLAIN, getFontSize())); if (getSettings() != null) { setFontCoords(getApplication().getFontCommon( getSettings().getAxesLabelsSerif(), getSettings().getAxisFontStyle(), (int) Math.max(Math.round(getFontSize() * 0.75), 10))); } updateDrawableFontSize(); updateBackground(); } public void setKeepCenter(boolean center) { keepCenter = center; } public boolean getKeepCenter() { return keepCenter; } /** * Size changed, make sure our settings reflect that */ public void updateSize() { if (getKeepCenter()) { updateSizeKeepCenter(); } else { updateSizeChange(); } } protected void updateSizeKeepCenter() { int w = getWidth(); int h = getHeight(); Log.debug(w + "x" + h); Log.debug("xZero: " + xZero + ", yZero: " + yZero); if (getSettings() != null) { int sw = getSettings().getWidth(); int sh = getSettings().getHeight(); double x0 = getSettings().getXZero(); double y0 = getSettings().getYZero(); Log.debug("settings: " + sw + "x" + sh + "," + x0 + "," + y0); if (sw == 0) { // no dimension from file: center the view sw = (int) Math.round(x0 * 2); sh = (int) Math.round(y0 * 2); } int dx = (w - sw) / 2; int dy = (h - sh) / 2; xZero = getSettings().getXZero() + dx; yZero = getSettings().getYZero() + dy; Log.debug(">> xZero: " + xZero + ", yZero: " + yZero); getSettings().setSize(w, h); getSettings().setOriginNoUpdate(xZero, yZero); } else { Log.debug("settings: null"); } updateSizeChange(); } private void updateSizeChange() { updateSizeKeepDrawables(); updateAllDrawablesForView(true); if (app.has(Feature.ADJUST_WIDGETS)) { // adjustedHSliderCount = 0; // adjustedVSliderCount = 0; } } /** * Size changed, make sure our settings reflect that bu do not update * drawables */ protected abstract void updateSizeKeepDrawables(); /** * Try to focus this view * * @return true if successful */ @Override public abstract boolean requestFocusInWindow(); // Michael Borcherds 2008-03-01 /** * Draws all geometric objects * * @param g2 * graphics */ protected void drawGeometricObjects(GGraphics2D g2) { // boolean // isSVGExtensions=g2.getClass().getName().endsWith("SVGExtensions"); int layer; for (layer = 0; layer <= getApplication().getMaxLayerUsed(); layer++) // only // draw // layers // we need { // if (isSVGExtensions) // ((geogebra.export.SVGExtensions)g2).startGroup("layer "+layer); drawLayers[layer].drawAll(g2); // if (isSVGExtensions) // ((geogebra.export.SVGExtensions)g2).endGroup("layer "+layer); } } // Michael Borcherds 2008-03-01 /** * Draws all objects * * @param g2 * graphics */ protected void drawObjects(GGraphics2D g2) { drawGeometricObjects(g2); drawActionObjects(g2); if (previewDrawable != null) { previewDrawable.drawPreview(g2); } adjustObjects(); } /** * Fills background with background color * * @param g * graphics */ final protected void clearBackground(GGraphics2D g) { g.setColor(getBackgroundCommon()); g.updateCanvasColor(); g.fillRect(0, 0, getWidth(), getHeight()); } /** * @param g * graphics * @param transparency * alpha value */ protected void drawBackgroundWithImages(GGraphics2D g, boolean transparency) { if (!transparency) { clearBackground(g); } // GGB-977 setBackgroundUpdating(true); bgImageList.drawAll(g); setBackgroundUpdating(false); drawBackground(g, false); } private boolean backgroundIsUpdating = false; private void setBackgroundUpdating(boolean b) { backgroundIsUpdating = b; } public boolean isBackgroundUpdating() { return backgroundIsUpdating; } /** * Draw axes ratio next to the mouse when mouse zooming. * * @param g2 * graphics */ final protected void drawAxesRatio(GGraphics2D g2) { GPoint pos = euclidianController.mouseLoc; if (pos == null) { return; } g2.setColor(GColor.DARK_GRAY); g2.setFont(getFontLine()); g2.drawString(getXYscaleRatioString(), pos.x + 15, pos.y + 30); } /** * @param g2 * background graphics */ public abstract void paintBackground(GGraphics2D g2); /** reIniting is used by GeoGebraWeb */ protected boolean reIniting = false; /** * Switches re-initing flag. If re-initing, also resets background. * * @param reiniting * re-initing flag */ public void setReIniting(boolean reiniting) { reIniting = reiniting; if (reiniting) { firstPaint = true; bgImage = null; bgGraphics = null; openedComboBox = null; } } /** * Paints content of this view. * * @param g2 * graphics */ public void paint(GGraphics2D g2) { synchronized (kernel.getConcurrentModificationLock()) { // 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 companion.paint(g2); if (getEuclidianController().getPen().needsRepaint()) { getEuclidianController().getPen().doRepaintPreviewLine(g2); } } } /** * @param g2 * graphics for background */ public void paintTheBackground(GGraphics2D g2) { // BACKGROUND // draw background image (with axes and/or grid) if (bgImage == null) { if (firstPaint) { if ((getWidth() > 1) && (getHeight() > 1) && (!reIniting)) { // only set firstPaint to false if the bgImage was generated companion.updateSizeKeepDrawables(); paintBackground(g2); // g2.drawImage(bgImage, 0, 0, null); firstPaint = false; } else { drawBackgroundWithImages(g2); } } else { drawBackgroundWithImages(g2); } } else { paintBackground(g2); } } /** * Updates background image */ final public void updateBackgroundImage() { if (bgGraphics != null) { drawBackgroundWithImages(bgGraphics, false); } } /** * Draws zoom rectangle * * @param g2 * graphics */ protected void drawZoomRectangle(GGraphics2D g2) { g2.setColor(colZoomRectangleFill); g2.setStroke(boldAxesStroke); g2.fill(selectionRectangle); g2.setColor(colZoomRectangle); g2.draw(selectionRectangle); } /** * Draws preview of shape for ShapeTools * * @param g2 * - graphics * @param fillCol * - filling color of shape * @param objCol * - color of line of shape * @param stroke * - stroke of shape * @param shape * - shape to draw */ protected void drawShape(GGraphics2D g2, GColor fillCol, GColor objCol, GBasicStroke stroke, GShape shape) { g2.setColor(fillCol); g2.setStroke(stroke); g2.fill(shape); g2.setColor(objCol); if (!isRounded) { g2.draw(shape); } else { // rectangle with rounded edges g2.drawRoundRect((int) Math.round(((GRectangle) shape).getX()), (int) Math.round(((GRectangle) shape).getY()), (int) Math.round(((GRectangle) shape).getWidth()), (int) Math.round(((GRectangle) shape).getHeight()), 20, 20); } } /** * Draws rectangle with given options * * @param g2 * graphics * @param col * color for stroke * @param stroke * stroke to use * @param rect * rectangle to draw */ protected void drawRect(GGraphics2D g2, GColor col, GBasicStroke stroke, GRectangle rect) { g2.setColor(col); g2.setStroke(stroke); g2.draw(rect); } /** * Draws mouse coords next to the mouse * * @param g2 * graphics */ final protected void drawMouseCoords(GGraphics2D g2) { StringTemplate tpl = StringTemplate.defaultTemplate; if (euclidianController.mouseLoc == null) { return; } GPoint pos = euclidianController.mouseLoc; StringBuilder sb = new StringBuilder(); sb.setLength(0); sb.append('('); sb.append(kernel.format( Kernel.checkDecimalFraction(euclidianController.xRW), tpl)); if (kernel.getCoordStyle() == Kernel.COORD_STYLE_AUSTRIAN) { sb.append(" | "); } else { sb.append(", "); } sb.append(kernel.format( Kernel.checkDecimalFraction(euclidianController.yRW), tpl)); sb.append(')'); g2.setColor(GColor.DARK_GRAY); g2.setFont(getFontCoords()); g2.drawString(sb.toString(), pos.x + 15, pos.y + 15); } /** * @param g * background graphics */ protected void drawBackgroundWithImages(GGraphics2D g) { drawBackgroundWithImages(g, false); } /** * Draws axes, grid and background images * * @param g * graphics * @param clear * clear traces before drawing */ final protected void drawBackground(GGraphics2D g, boolean clear) { if (clear) { clearBackground(g); } g.setAntialiasing(); // handle drawing axes near the screen edge if (drawBorderAxes[0] || drawBorderAxes[1]) { // edge axes are not drawn at the exact edge, instead they // are inset enough to draw the labels // labelOffset = amount of space needed to draw labels GPoint labelOffset = getMaximumLabelSize(g); // force the the axisCross position to be near the edge if (drawBorderAxes[0]) { axisCross[0] = getYmin() + ((labelOffset.y + 10) / getYscale()); } if (drawBorderAxes[1]) { axisCross[1] = getXmin() + ((labelOffset.x + 10) / getXscale()); } } // reset axes label positions axesLabelsPositionsY.clear(); axesLabelsPositionsX.clear(); yLabelMaxWidthPos = 0; yLabelMaxWidthNeg = 0; xLabelHeights = estimateNumberHeight(getFontAxes()); // this will fill axesLabelsBounds with the rectangles where the axes // labels are if (showAxes[0] || showAxes[1]) { if (da == null) { da = new DrawAxis(this); } da.drawAxes(g); if (!app.has(Feature.TICK_NUMBERS_AT_EDGE)) { drawCornerCoordsIfNeeded(g); } } if (showGrid) { drawGrid(g); } if (showResetIcon()) { drawResetIcon(g); } } private void drawCornerCoordsIfNeeded(GGraphics2D g2) { // if both axes visible and // one of the axes is off-screen, show upper left and lower right if (showAxesCornerCoords) { if (showAxes[0] && showAxes[1] && (!xAxisOnscreen() || !yAxisOnscreen())) { // upper left corner StringBuilder sb = new StringBuilder(); sb.setLength(0); sb.append('('); sb.append(kernel.formatPiE(getXmin(), axesNumberFormat[0], StringTemplate.defaultTemplate)); sb.append(app.getLocalization().unicodeComma); sb.append(" "); sb.append(kernel.formatPiE(getYmax(), axesNumberFormat[1], StringTemplate.defaultTemplate)); sb.append(')'); int textHeight = 2 + getFontAxes().getSize(); g2.setFont(getFontAxes()); g2.drawString(sb.toString(), 5, textHeight); // lower right corner sb.setLength(0); sb.append('('); sb.append(kernel.formatPiE(getXmax(), axesNumberFormat[0], StringTemplate.defaultTemplate)); sb.append(app.getLocalization().unicodeComma); sb.append(" "); sb.append(kernel.formatPiE(getYmin(), axesNumberFormat[1], StringTemplate.defaultTemplate)); sb.append(')'); GTextLayout layout = AwtFactory.getPrototype() .newTextLayout(sb.toString(), getFontAxes(), g2.getFontRenderContext()); layout.draw(g2, (int) (getWidth() - 5 - EuclidianView .estimateTextWidth(sb.toString(), getFontAxes())), getHeight() - 5); } } } private DrawAxis da; boolean showResetIcon() { if (!getApplication().showResetIcon() || !(getApplication().isApplet() || getApplication() .isHTML5Applet())) { return false; } return isPrimaryEV(); } private GEllipse2DDouble circle = AwtFactory.getPrototype() .newEllipse2DDouble(); // polar // grid // circles private GLine2D tempLine = AwtFactory.getPrototype().newLine2D(); /** * Get styleBar */ protected org.geogebra.common.euclidian.EuclidianStyleBar styleBar; private DrawGrid drawGrid; /** * Draws grid * * @param g2 * graphics */ final protected void drawGrid(GGraphics2D g2) { if (drawGrid == null) { drawGrid = new DrawGrid(this); } // vars for handling positive-only axes double xCrossPix = this.getXZero() + (axisCross[1] * getXscale()); double yCrossPix = this.getYZero() - (axisCross[0] * getYscale()); // this needs to be after setClip() // bug in FreeHEP (PDF export) // #2683 g2.setColor(gridColor); g2.setStroke(gridStroke); switch (gridType) { case GRID_CARTESIAN: drawGrid.drawCartesianGrid(g2, xCrossPix, yCrossPix); break; case GRID_ISOMETRIC: drawIsometricGrid(g2, xCrossPix, yCrossPix); break; case GRID_POLAR: // G.Sturr 2010-8-13 // find minimum grid radius double min; if ((getXZero() > 0) && (getXZero() < getWidth()) && (getYZero() > 0) && (getYZero() < getHeight())) { // origin onscreen: min = 0 min = 0; } else { // origin offscreen: min = distance to closest screen border double minW = Math.min(Math.abs(getXZero()), Math.abs(getXZero() - getWidth())); double minH = Math.min(Math.abs(getYZero()), Math.abs(getYZero() - getHeight())); min = Math.min(minW, minH); } // find maximum grid radius // max = max distance of origin to screen corners double d1 = MyMath.length(getXZero(), getYZero()); // upper left double d2 = MyMath.length(getXZero(), getYZero() - getHeight()); // lower // left double d3 = MyMath.length(getXZero() - getWidth(), getYZero()); // upper // right double d4 = MyMath.length(getXZero() - getWidth(), getYZero() - getHeight()); // lower // right double max = Math.max(Math.max(d1, d2), Math.max(d3, d4)); // draw the grid circles // note: x tick intervals are used for the radius intervals, // it is assumed that the x/y scaling ratio is 1:1 double tickStepR = getXscale() * gridDistances[0]; double r = min - (min % tickStepR); while (r <= max) { circle.setFrame(getXZero() - r, getYZero() - r, 2 * r, 2 * r); g2.draw(circle); r = r + tickStepR; } // draw the radial grid lines double angleStep = gridDistances[2]; double y1, y2, m; // horizontal axis tempLine.setLine(0, getYZero(), getWidth(), getYZero()); g2.draw(tempLine); // radial lines for (double a = angleStep; a < Math.PI; a = a + angleStep) { if (Math.abs(a - (Math.PI / 2)) < 0.0001) { // vertical axis tempLine.setLine(getXZero(), 0, getXZero(), getHeight()); } else { m = Math.tan(a); y1 = (m * (getXZero())) + getYZero(); y2 = (m * (getXZero() - getWidth())) + getYZero(); tempLine.setLine(0, y1, getWidth(), y2); } g2.draw(tempLine); } break; } } // ================================================= // Draw Axes // ================================================= private void drawIsometricGrid(GGraphics2D g2, double xCrossPix, double yCrossPix) { boolean clipX = positiveAxes[1] && yCrossPix < getHeight(); int yAxisEnd = clipX ? (int) yCrossPix : getHeight(); boolean clipY = positiveAxes[0] && xCrossPix > 0; int xAxisStart = clipY ? (int) xCrossPix : 0; // set the clipping region to the region defined by the axes double tickStepX = getXscale() * gridDistances[0] * Math.sqrt(3.0); double startX = getXZero() % (tickStepX); double startX2 = getXZero() % (tickStepX / 2); double tickStepY = getYscale() * gridDistances[0]; double startY = getYZero() % tickStepY; // vertical double pix = startX2; for (int j = 0; pix <= getWidth(); j++) { if ((!Kernel.isEqual(pix, xCrossPix) || !showAxes[0]) && pix > xAxisStart - Kernel.MIN_PRECISION) { tempLine.setLine(pix, 0, pix, yAxisEnd); g2.draw(tempLine); } pix = startX2 + ((j * tickStepX) / 2.0); } // extra lines needed because it's diagonal int extra = (int) ((((getHeight() * getXscale()) / getYscale()) * Math .sqrt(3.0)) / tickStepX) + 3; // negative gradient pix = startX + (-(extra + 1) * tickStepX); for (int j = -extra; pix <= getWidth(); j += 1) { double endx = pix + (((getHeight() + tickStepY) * Math.sqrt(3) * getXscale()) / getYscale()); if (clipX || clipY) { DrawSegment .drawClipped(new double[] { pix, startY - tickStepY }, new double[] { endx, (startY - tickStepY) + getHeight() + tickStepY }, tempLine, xAxisStart, getWidth(), 0, yAxisEnd); } else { tempLine.setLine(pix, startY - tickStepY, endx, startY + getHeight()); } g2.draw(tempLine); pix = startX + (j * tickStepX); } // positive gradient pix = startX; for (int j = 0; pix <= (getWidth() + ((((getHeight() * getXscale()) / getYscale()) + tickStepY) * Math .sqrt(3.0))); j += 1) // for (int j=0; j<=kk; j+=1) { double endx = pix - (((getHeight() + tickStepY) * Math.sqrt(3) * getXscale()) / getYscale()); if (clipX || clipY) { DrawSegment .drawClipped(new double[] { pix, startY - tickStepY }, new double[] { endx, (startY - tickStepY) + getHeight() + tickStepY }, tempLine, xAxisStart, getWidth(), 0, yAxisEnd); } else { tempLine.setLine(pix, startY - tickStepY, endx, startY + getHeight()); } g2.draw(tempLine); pix = startX + (j * tickStepX); } } double getXAxisCrossingPixel() { return getXZero() + (axisCross[1] * getXscale()); } double getYAxisCrossingPixel() { return this.getYZero() - (axisCross[0] * getYscale()); } boolean xAxisOnscreen() { return showAxes[0] && (getYmin() < axisCross[0]) && (getYmax() > axisCross[0]); } boolean yAxisOnscreen() { return showAxes[1] && (getXmin() < axisCross[1]) && (getXmax() > axisCross[1]); } protected int getYOffsetForXAxis(int fontSize1) { return fontSize1 + 4; } ArrayList<Integer> axesLabelsPositionsY = new ArrayList<Integer>(); ArrayList<Integer> axesLabelsPositionsX = new ArrayList<Integer>(); double yLabelMaxWidthPos = 0; double yLabelMaxWidthNeg = 0; double xLabelHeights = 0; /** * @param g * graphics for reset icon */ protected abstract void drawResetIcon(GGraphics2D g); /** * Draw combos * * @param g * graphics */ public void drawActionObjects(GGraphics2D g) { DrawableIterator it = allDrawableList.getIterator(); it.reset(); DrawDropDownList selected = null; DrawDropDownList opened = null; while (it.hasNext()) { Drawable d = it.next(); if (d instanceof DrawDropDownList && ((DrawDropDownList) d).isCanvasDrawable()) { DrawDropDownList dl = (DrawDropDownList) d; if (dl.needsUpdate()) { dl.setNeedsUpdate(false); dl.update(); } if (selected == null && dl.isSelected()) { selected = dl; } if (opened == null && dl.isOptionsVisible()) { opened = dl; } dl.draw(g); } else if (d instanceof DrawInputBox) { if (d.needsUpdate()) { d.setNeedsUpdate(false); d.update(); } d.draw(g); } } DrawDropDownList topDropDown = opened != null ? opened : selected; if (topDropDown != null) { topDropDown.draw(g); } } /** * Switch antialiasing to true for given graphics * * @param g2 * graphics */ final public static void setAntialiasing(GGraphics2D g2) { g2.setAntialiasing(); } /** * @param g2 * graphics for the animation button */ protected void drawAnimationButtons(GGraphics2D g2) { // it could be abstract, but mess with EuclididanView3D } @Override public abstract void setBackground(GColor bgColor); /** * Update stylebar from settings * * @param evs * settings */ protected void synchronizeMenuBarAndEuclidianStyleBar(EuclidianSettings evs) { if (styleBar != null) { getStyleBar().updateButtonPointCapture(evs.getPointCapturingMode()); } // Actually nothing to do in Menubar since we do not have any EV // settings there anymore } /** * @param preferredSize * prefered size */ public abstract void setPreferredSize(GDimension preferredSize); @Override public boolean showGrid(boolean show) { return companion.showGrid(show); } @Override public void setGridIsBold(boolean gridIsBold) { if (this.gridIsBold == gridIsBold) { return; } this.gridIsBold = gridIsBold; setGridLineStyle(gridLineStyle); updateBackgroundImage(); } @Override public void setGridColor(GColor gridColor) { if (gridColor != null) { this.gridColor = gridColor; } } @Override public void setGridLineStyle(int gridLineStyle) { this.gridLineStyle = gridLineStyle; gridStroke = EuclidianStatic.getStroke(gridIsBold ? 2f : 1f, gridLineStyle); } /** * @param settings * settings */ @Override public void settingsChanged(AbstractSettings settings) { companion.settingsChanged(settings); if (styleBar != null) { styleBar.updateGUI(); } } @Override public EuclidianSettings getSettings() { return this.settings; } /** * sets array of GeoElements whose visual representation is inside of the * given screen rectangle */ @Override public final void setHits(GRectangle rect) { hits.init(); if (rect == null) { return; } DrawableIterator it = allDrawableList.getIterator(); while (it.hasNext()) { Drawable d = it.next(); GeoElement geo = d.getGeoElement(); if (geo.isEuclidianVisible() && d.isInside(rect)) { hits.add(geo); } } } @Override public void updateCursor(GeoPointND point) { // used in 3D } /** * sets array of GeoElements whose visual representation is inside of the * given screen rectangle * * @param rect * rectangle */ public final void setIntersectionHits(GRectangle rect) { hits.init(); if (rect == null) { return; } DrawableIterator it = allDrawableList.getIterator(); while (it.hasNext()) { Drawable d = it.next(); GeoElement geo = d.getGeoElement(); if (geo.isEuclidianVisible() && d.intersectsRectangle(rect)) { hits.add(geo); } } } @Override public GRectangle getSelectionRectangle() { return selectionRectangle; } /** * @return boundingBox */ public BoundingBox getBoundingBox() { return boundingBox; } /** * @return shapeRectangle */ public GRectangle getShapeRectangle() { return shapeRectangle; } /** * @return shapeEllipse */ public GEllipse2DDouble getShapeEllipse() { return shapeEllipse; } /** * @return shapeLine */ public GLine2D getShapeLine() { return shapeLine; } /** * @return shapePolygon */ public GGeneralPath getShapePolygon() { return shapePolygon; } /** * @return true if shape is rounded (e.g. for ShapeRectangleRoundEdges) */ public boolean isRounded() { return isRounded; } /** * @param isRounded * - true if shape is rounded (e.g. for ShapeRectangleRoundEdges) */ public void setRounded(boolean isRounded) { this.isRounded = isRounded; } /** * @return path along border of this view */ public GeneralPathClipped getBoundingPath() { GeneralPathClipped gs = new GeneralPathClipped(this);// AwtFactory.getPrototype().newGeneralPath(); gs.moveTo(0, 0); gs.lineTo(getWidth(), 0); gs.lineTo(getWidth(), getHeight()); gs.lineTo(0, getHeight()); gs.lineTo(0, 0); gs.closePath(); return gs; } /** * @param img * new background image */ final public void addBackgroundImage(DrawImage img) { bgImageList.addUnique(img); // drawImageList.remove(img); // Michael Borcherds 2008-02-29 int layer = img.getGeoElement().getLayer(); drawLayers[layer].remove(img); } /** * @param img * background image */ final public void removeBackgroundImage(DrawImage img) { bgImageList.remove(img); // drawImageList.add(img); // Michael Borcherds 2008-02-29 int layer = img.getGeoElement().getLayer(); drawLayers[layer].add(img); } /** * Reset lists of drawables */ protected void resetLists() { DrawableMap.clear(); stickyPointList.clear(); allDrawableList.clear(); bgImageList.clear(); this.geosWaiting.clear(); for (int i = 0; i <= getApplication().getMaxLayerUsed(); i++) { drawLayers[i].clear(); // Michael Borcherds 2008-02-29 } setToolTipText(null); } /** * Returns the bounding box of all Drawable objects in this view in screen * coordinates. * * @return bounds of this view */ public GRectangle getBounds() { GRectangle result = null; DrawableIterator it = allDrawableList.getIterator(); while (it.hasNext()) { Drawable d = it.next(); GRectangle bb = d.getBounds(); if (bb != null) { if (result == null) { // changed () to (bb) bugfix, otherwise top-left of screen // is always included result = AwtFactory.getPrototype().newRectangle(bb); } // add bounding box of list element result.add(bb); } } // Cong Liu if (result == null) { result = AwtFactory.getPrototype().newRectangle(0, 0, 0, 0); } return result; } @Override public void setPreview(Previewable p) { if (previewDrawable != null) { previewDrawable.disposePreview(); } previewDrawable = p; } private int widthTemp, heightTemp; private double xminTemp, xmaxTemp, yminTemp, ymaxTemp; /** * Finds maximum pixel width and height needed to draw current x and y axis * labels. return[0] = max width, return[1] = max height * * @param g2 * graphics * @return point (width,height) */ public GPoint getMaximumLabelSize(GGraphics2D g2) { GPoint max = new GPoint(0, 0); g2.setFont(getFontAxes()); int yAxisHeight = positiveAxes[1] ? (int) getYZero() : getHeight(); int maxY = positiveAxes[1] ? (int) getYZero() : getHeight() - SCREEN_BORDER; double rw = getYmax() - (getYmax() % axesNumberingDistances[1]); double pix = getYZero() - (rw * getYscale()); double axesStep = getYscale() * axesNumberingDistances[1]; // pixelstep axesNumberingDistances[1] = Kernel .checkDecimalFraction(axesNumberingDistances[1]); int count = 0; double rwBase = Kernel.checkDecimalFraction(rw); // for (; pix <= yAxisHeight; rw -= axesNumberingDistances[1], pix += // axesStep) { for (; pix <= yAxisHeight; count++, pix += axesStep) { // 285, 285.1, 285.2 -> rounding problems rw = rwBase - Kernel.checkDecimalFraction(axesNumberingDistances[1] * count); if (pix <= maxY) { if (showAxesNumbers[1]) { String strNum = kernel.formatPiE(rw, axesNumberFormat[1], StringTemplate.defaultTemplate); StringBuilder sb = new StringBuilder(); sb.setLength(0); sb.append(strNum); if ((axesUnitLabels[1] != null) && !piAxisUnit[1]) { sb.append(axesUnitLabels[1]); } double width = estimateTextWidth(sb.toString(), getFontAxes()); if (max.x < width) { max.x = (int) width; } if (max.y == 0) { max.y = (int) estimateNumberHeight(getFontAxes()); } } } } return max; } /** * Restore coord system from temp variables */ final public void restoreOldCoordSystem() { setWidth(widthTemp); setHeight(heightTemp); setRealWorldCoordSystem(xminTemp, xmaxTemp, yminTemp, ymaxTemp); } /** * used for rescaling applets when the reset button is hit use * setTemporarySize(-1, -1) to disable * * @param w * width * @param h * height */ public void setTemporarySize(int w, int h) { setWidth(w); setHeight(h); updateSize(); } /** * change showing flag of the axis * * @param axis * id of the axis * @param flag * show/hide * @param update * update (or not) the background image */ @Override public boolean setShowAxis(int axis, boolean flag, boolean update) { if (flag == showAxes[axis]) { return false; } showAxes[axis] = flag; if (update) { updateBackgroundImage(); } return true; } @Override public boolean setShowAxes(boolean flag, boolean update) { boolean changedX = setShowAxis(AXIS_X, flag, false); return setShowAxis(AXIS_Y, flag, true) || changedX; } /* * change logarithmic flag of the axes */ @Override public boolean setLogAxis(int axis, boolean flag, boolean update) { if (flag == logAxes[axis]) { return false; } logAxes[axis] = flag; if (update) { updateBackgroundImage(); } return true; } /** * @param bold * true for bold axes */ public void setBoldAxes(boolean bold) { axesLineType = getBoldAxes(bold, axesLineType); } /** * Tells if there are any traces in the background image. * * @return true if there are any traces in background */ protected boolean isTracing() { DrawableIterator it = allDrawableList.getIterator(); while (it.hasNext()) { if (it.next().isTracing()) { return true; } } return false; } /** * Tells if there are any images in the background. * * @return whether there are any images in the background. */ protected boolean hasBackgroundImages() { return bgImageList.size() > 0; } /** * @return background graphics */ final public GGraphics2D getBackgroundGraphics() { this.tracing = true; return bgGraphics; } /** * returns settings in XML format * * @param sbxml * string builder * @param asPreference * true for preferences */ @Override public void getXML(StringBuilder sbxml, boolean asPreference) { companion.getXML(sbxml, asPreference); } /** * start settings in XML format * * @param sbxml * string builder * @param asPreference * true for preferences */ public void startXML(StringBuilder sbxml, boolean asPreference) { StringTemplate tpl = StringTemplate.xmlTemplate; sbxml.append("<euclidianView>\n"); companion.getXMLid(sbxml); int width = getWidth(); int height = getHeight(); if ((width <= MIN_WIDTH) && (height <= MIN_HEIGHT)) { EuclidianSettings evSettings = getSettings(); if (evSettings != null) { width = evSettings.getWidth(); height = evSettings.getHeight(); } } if ((width > MIN_WIDTH) && (height > MIN_HEIGHT)) { sbxml.append("\t<size "); sbxml.append(" width=\""); sbxml.append(width); sbxml.append("\""); sbxml.append(" height=\""); sbxml.append(height); sbxml.append("\""); sbxml.append("/>\n"); } if (!isZoomable() && !asPreference) { sbxml.append("\t<coordSystem"); sbxml.append(" xMin=\""); StringUtil .encodeXML(sbxml, ((GeoNumeric) xminObject).getLabel(tpl)); sbxml.append("\""); sbxml.append(" xMax=\""); StringUtil .encodeXML(sbxml, ((GeoNumeric) xmaxObject).getLabel(tpl)); sbxml.append("\""); sbxml.append(" yMin=\""); StringUtil .encodeXML(sbxml, ((GeoNumeric) yminObject).getLabel(tpl)); sbxml.append("\""); sbxml.append(" yMax=\""); StringUtil .encodeXML(sbxml, ((GeoNumeric) ymaxObject).getLabel(tpl)); sbxml.append("\""); sbxml.append("/>\n"); } else { sbxml.append("\t<coordSystem"); sbxml.append(" xZero=\""); sbxml.append(getXZero()); sbxml.append("\""); sbxml.append(" yZero=\""); sbxml.append(getYZero()); sbxml.append("\""); sbxml.append(" scale=\""); sbxml.append(getXscale()); sbxml.append("\""); sbxml.append(" yscale=\""); sbxml.append(getYscale()); sbxml.append("\""); sbxml.append("/>\n"); } // NOTE: the attribute "axes" for the visibility state of // both axes is no longer needed since V3.0. // Now there are special axis tags, see below. sbxml.append("\t<evSettings axes=\""); sbxml.append(showAxes[0] || showAxes[1]); sbxml.append("\" grid=\""); sbxml.append(showGrid); sbxml.append("\" gridIsBold=\""); // sbxml.append(gridIsBold); // Michael Borcherds 2008-04-11 sbxml.append("\" pointCapturing=\""); // make sure POINT_CAPTURING_STICKY_POINTS isn't written to XML sbxml.append(getPointCapturingMode() > EuclidianStyleConstants.POINT_CAPTURING_XML_MAX ? EuclidianStyleConstants.POINT_CAPTURING_DEFAULT : getPointCapturingMode()); sbxml.append("\" rightAngleStyle=\""); sbxml.append(getApplication().rightAngleStyle); if (asPreference) { sbxml.append("\" allowShowMouseCoords=\""); sbxml.append(getAllowShowMouseCoords()); sbxml.append("\" allowToolTips=\""); sbxml.append(getAllowToolTips()); sbxml.append("\" deleteToolSize=\""); sbxml.append(getEuclidianController().getDeleteToolSize()); } sbxml.append("\" checkboxSize=\""); sbxml.append(app.getCheckboxSize()); // Michael Borcherds // 2008-05-12 sbxml.append("\" gridType=\""); sbxml.append(getGridType()); // cartesian/isometric/polar if (lockedAxesRatio > 0) { sbxml.append("\" lockedAxesRatio=\""); sbxml.append(lockedAxesRatio); } sbxml.append("\"/>\n"); // background color sbxml.append("\t<bgColor r=\""); sbxml.append(getBackgroundCommon().getRed()); sbxml.append("\" g=\""); sbxml.append(getBackgroundCommon().getGreen()); sbxml.append("\" b=\""); sbxml.append(getBackgroundCommon().getBlue()); sbxml.append("\"/>\n"); // axes color sbxml.append("\t<axesColor r=\""); sbxml.append(axesColor.getRed()); sbxml.append("\" g=\""); sbxml.append(axesColor.getGreen()); sbxml.append("\" b=\""); sbxml.append(axesColor.getBlue()); sbxml.append("\"/>\n"); // grid color sbxml.append("\t<gridColor r=\""); sbxml.append(gridColor.getRed()); sbxml.append("\" g=\""); sbxml.append(gridColor.getGreen()); sbxml.append("\" b=\""); sbxml.append(gridColor.getBlue()); sbxml.append("\"/>\n"); // axes line style sbxml.append("\t<lineStyle axes=\""); sbxml.append(axesLineType); sbxml.append("\" grid=\""); sbxml.append(gridLineStyle); sbxml.append("\"/>\n"); // axes label style int style = getSettings().getAxisFontStyle(); boolean serif = getSettings().getAxesLabelsSerif(); if (style != GFont.PLAIN || serif) { sbxml.append("\t<labelStyle axes=\""); sbxml.append(style); sbxml.append("\""); sbxml.append(" serif=\""); sbxml.append(serif); sbxml.append("\""); sbxml.append("/>\n"); } // axis settings for (int i = 0; i < 2; i++) { getSettings().addAxisXML(i, sbxml); } // grid distances if (!automaticGridDistance) { sbxml.append("\t<grid distX=\""); sbxml.append(gridDistances[0]); sbxml.append("\" distY=\""); sbxml.append(gridDistances[1]); sbxml.append("\" distTheta=\""); // polar angle step added in v4.0 sbxml.append(gridDistances[2]); sbxml.append("\"/>\n"); } } /** * end settings in XML format * * @param sbxml * string builder */ public void endXML(StringBuilder sbxml) { sbxml.append("</euclidianView>\n"); } /** * Keeps the zoom, but makes sure the bound objects are free. This is * necessary in File->New because there might have been dynamic xmin bounds */ public void resetXYMinMaxObjects() { if ((evNo == 1) || (evNo == 2)) { EuclidianSettings es = getApplication().getSettings().getEuclidian( evNo); GeoNumeric xmao = new GeoNumeric(kernel.getConstruction(), xmaxObject.getNumber().getDouble()); GeoNumeric xmio = new GeoNumeric(kernel.getConstruction(), xminObject.getNumber().getDouble()); GeoNumeric ymao = new GeoNumeric(kernel.getConstruction(), ymaxObject.getNumber().getDouble()); GeoNumeric ymio = new GeoNumeric(kernel.getConstruction(), yminObject.getNumber().getDouble()); es.setXmaxObject(xmao, false); es.setXminObject(xmio, false); es.setYmaxObject(ymao, false); es.setYminObject(ymio, true); } } /** * Change coord system so that all objects are shown * * @param storeUndo * true to store undo after * @param keepRatio * true to keep ratio of x and y axes */ @Override public void setViewShowAllObjects(boolean storeUndo, boolean keepRatio) { setViewShowAllObjects(storeUndo, keepRatio, 10); } public void setViewShowAllObjects(boolean storeUndo, boolean keepRatio, int steps) { // check for functions TreeSet<GeoElement> allFunctions = kernel.getConstruction() .getGeoSetLabelOrder(GeoClass.FUNCTION); boolean hasFunctions = hasVisibleObjects(allFunctions); // check for curves TreeSet<GeoElement> allCurves = kernel.getConstruction() .getGeoSetLabelOrder(GeoClass.CURVE_CARTESIAN); boolean hasCurves = hasVisibleObjects(allCurves); // check for points, circles etc. GRectangle rect = getBounds(); boolean hasObjects = hasVisibleObjects(rect); if (!hasObjects && !hasCurves && !hasFunctions) { return; } /** curves */ double xMinCurve = Double.MAX_VALUE; double xMaxCurve = -Double.MAX_VALUE; double yMinCurve = Double.MAX_VALUE; double yMaxCurve = -Double.MAX_VALUE; if (hasCurves) { for (GeoElement element : allCurves) { GeoCurveCartesian curve = (GeoCurveCartesian) element; Function funX = curve.getFunX(); Function funY = curve.getFunY(); double min = curve.getMinParameter(); double max = curve.getMaxParameter(); double step = curve.getAnimationStep(); xMinCurve = funX.value(min); xMaxCurve = xMinCurve; yMinCurve = funY.value(min); yMaxCurve = yMinCurve; double helper; while (min < max) { min += step; helper = funX.value(min); if (helper < xMinCurve) { xMinCurve = helper; } else if (helper > xMaxCurve) { xMaxCurve = helper; } helper = funY.value(min); if (helper < yMinCurve) { yMinCurve = helper; } else if (helper > yMaxCurve) { yMaxCurve = helper; } } } } /** objects */ double xMinObj = Double.MAX_VALUE; double xMaxObj = -Double.MAX_VALUE; double yMinObj = Double.MAX_VALUE; double yMaxObj = -Double.MAX_VALUE; if (hasObjects) { // get bounds of points, circles etc xMinObj = toRealWorldCoordX(rect.getMinX()); xMaxObj = toRealWorldCoordX(rect.getMaxX()); yMinObj = toRealWorldCoordY(rect.getMaxY()); yMaxObj = toRealWorldCoordY(rect.getMinY()); } /** * initialize the rectangle around all visible objects do this before * handling functions, because functions need the calculated x-values */ // xMin,xMax,yMin,yMax of all objects on graphics view double x0RW = Double.MAX_VALUE; double x1RW = -Double.MAX_VALUE; double y0RW = Double.MAX_VALUE; double y1RW = -Double.MAX_VALUE; if (hasCurves) { x0RW = Math.min(x0RW, xMinCurve); x1RW = Math.max(x1RW, xMaxCurve); y0RW = Math.min(y0RW, yMinCurve); y1RW = Math.max(y1RW, yMaxCurve); } if (hasObjects) { x0RW = Math.min(x0RW, xMinObj); x1RW = Math.max(x1RW, xMaxObj); y0RW = Math.min(y0RW, yMinObj); y1RW = Math.max(y1RW, yMaxObj); } /** functions */ double yMinFunc = Double.MAX_VALUE; double yMaxFunc = -Double.MAX_VALUE; boolean ok = false; if (hasFunctions) { // if there are functions we don't want to zoom in horizontally x0RW = Math.min(getXmin(), x0RW); x1RW = Math.max(getXmax(), x1RW); for (GeoElement elem : allFunctions) { GeoFunction fun = (GeoFunction) elem; if (fun.isEuclidianVisible()) { double abscissa; // check 100 random heights for (int i = 0; i < 200; i++) { if (i == 0) { abscissa = fun.value(x0RW); // check far left } else if (i == 1) { abscissa = fun.value(x1RW); // check far right } else { abscissa = fun.value( x0RW + (Math.random() * (x1RW - x0RW))); } if (!Double.isInfinite(abscissa) && !Double.isNaN(abscissa)) { ok = true; if (abscissa > yMaxFunc) { yMaxFunc = abscissa; } // no else: there **might** be just one value if (abscissa < yMinFunc) { yMinFunc = abscissa; } } } } } } if (hasFunctions && ok) { y0RW = Math.min(y0RW, yMinFunc); y1RW = Math.max(y1RW, yMaxFunc); } // don't want objects at edge double xGap = (x1RW - x0RW) * 0.03; double yGap = (y1RW - y0RW) * 0.03; x0RW -= xGap; x1RW += xGap; y0RW -= yGap; y1RW += yGap; // enlarge x/y if we want to keep ratio if (keepRatio) { double oldRatio = (xmax - xmin) / (ymax - ymin); double newRatio = (x1RW - x0RW) / (y1RW - y0RW); if (newRatio > oldRatio) { // enlarge y double center = (y1RW + y0RW) / 2; double delta = (y1RW - y0RW) / 2; y0RW = center - delta * newRatio / oldRatio; y1RW = center + delta * newRatio / oldRatio; } else { // enlarge x double center = (x1RW + x0RW) / 2; double delta = (x1RW - x0RW) / 2; x0RW = center - delta * oldRatio / newRatio; x1RW = center + delta * oldRatio / newRatio; } } // check if animation is needed if (steps == 0) { setRealWorldCoordSystem(x0RW, x1RW, y0RW, y1RW); if (storeUndo) { getApplication().storeUndoInfo(); } } else { setAnimatedRealWorldCoordSystem(x0RW, x1RW, y0RW, y1RW, steps, storeUndo); } } private static boolean hasVisibleObjects(GRectangle rect) { return !(Kernel.isZero(rect.getHeight()) || Kernel.isZero(rect .getWidth())); } /** * @return {@code true} if an object of the given TreeSet is visible in the * graphicsView */ private static boolean hasVisibleObjects(TreeSet<GeoElement> allFunctions) { for (GeoElement element : allFunctions) { if (element.isEuclidianVisible()) { return true; } } return false; } /** * @return width of selection rectangle */ public int getSelectedWidth() { if (selectionRectangle == null) { return getWidth(); } return (int) selectionRectangle.getWidth(); } /** * @return height of selection rectangle */ public int getSelectedHeight() { if (selectionRectangle == null) { return getHeight(); } return (int) selectionRectangle.getHeight(); } public int getSelectedWidthInPixels() { return getSelectedWidth(); } public int getSelectedHeightInPixels() { return getSelectedHeight(); } /** * @return export width in pixels */ public int getExportWidth() { if (selectionRectangle != null) { return (int) selectionRectangle.getWidth(); } try { GeoPoint export1 = (GeoPoint) kernel.lookupLabel(EXPORT1); GeoPoint export2 = (GeoPoint) kernel.lookupLabel(EXPORT2); double[] xy1 = new double[2]; double[] xy2 = new double[2]; export1.getInhomCoords(xy1); export2.getInhomCoords(xy2); double x1 = xy1[0]; double x2 = xy2[0]; x1 = (x1 / getInvXscale()) + getXZero(); x2 = (x2 / getInvXscale()) + getXZero(); return (int) Math.abs(x1 - x2) + 2; } catch (Exception e) { return getWidth(); } } /** * @return export height in pixels */ public int getExportHeight() { if (selectionRectangle != null) { return (int) selectionRectangle.getHeight(); } try { GeoPoint export1 = (GeoPoint) kernel.lookupLabel(EXPORT1); GeoPoint export2 = (GeoPoint) kernel.lookupLabel(EXPORT2); double[] xy1 = new double[2]; double[] xy2 = new double[2]; export1.getInhomCoords(xy1); export2.getInhomCoords(xy2); double y1 = xy1[1]; double y2 = xy2[1]; y1 = getYZero() - (y1 / getInvYscale()); y2 = getYZero() - (y2 / getInvYscale()); return (int) Math.abs(y1 - y2) + 2; } catch (Exception e) { return getHeight(); } } private Hits tempArrayList = new Hits(); // for use in AlgebraController @Override final public void clickedGeo(GeoElement geo, boolean isControlDown) { if (geo == null) { return; } tempArrayList.clear(); tempArrayList.add(geo); getEuclidianController().startCollectingMinorRepaints(); AsyncOperation<Boolean> callback = new AsyncOperation<Boolean>() { @Override public void callback(Boolean arg) { if (arg.equals(true)) { euclidianController.storeUndoInfo(); } } }; boolean changedKernel = euclidianController.processMode(tempArrayList, isControlDown, callback); if (changedKernel) { getEuclidianController().storeUndoInfo(); } kernel.notifyRepaint(); getEuclidianController().stopCollectingMinorRepaints(); } /** * instantiate new zoomer * * @return zoomer */ protected abstract MyZoomer newZoomer(); /** * Zooms around fixed point (px, py) */ @Override public void zoom(double px, double py, double zoomFactor, int steps, boolean storeUndo) { if (!isZoomable()) { return; } if (zoomer == null) { zoomer = newZoomer(); } zoomer.init(px, py, zoomFactor, steps, storeUndo); zoomer.startAnimation(); } private MyZoomer zoomer; /** * Zooms towards the given axes scale ratio. Note: Only the y-axis is * changed here. ratio = yscale / xscale; * * @param newRatio * new yscale / xscale ratio * @param storeUndo * true to store undo step after */ @Override public final void zoomAxesRatio(double newRatio, boolean storeUndo) { if (!isZoomable()) { return; } if (isLockedAxesRatio()) { return; } if (axesRatioZoomer == null) { axesRatioZoomer = newZoomer(); } axesRatioZoomer.init(newRatio, storeUndo); axesRatioZoomer.startAnimation(); } private MyZoomer axesRatioZoomer; /** * Restores standard zoom + origin position * * @param storeUndo * true to store undo infor */ @Override public void setStandardView(boolean storeUndo) { if (!isZoomable()) { return; } final double xzero, yzero; // check if the window is so small that we need custom // positions. if (getWidth() < (XZERO_STANDARD * 3)) { xzero = getWidth() / 3.0; } else { xzero = XZERO_STANDARD; } if (getHeight() < (YZERO_STANDARD * 1.6)) { yzero = getHeight() / 1.6; } else { yzero = YZERO_STANDARD; } if (getScaleRatio() != 1.0) { // set axes ratio back to 1 if (axesRatioZoomer == null) { axesRatioZoomer = newZoomer(); } axesRatioZoomer.init(1, false); axesRatioZoomer.setStandardViewAfter(xzero, yzero); axesRatioZoomer.startAnimation(); } else { setAnimatedCoordSystem(xzero, yzero, 15, false); } if (storeUndo) { getApplication().storeUndoInfo(); } } /** * Sets coord system of this view to standard. Just like setCoordSystem but * with previous animation. * * */ public void setAnimatedCoordSystem(double originX, double originY, int steps, boolean storeUndo) { setAnimatedCoordSystem(originX, originY, 0, SCALE_STANDARD, steps, storeUndo); } /** * Sets coord system of this view. Just like setCoordSystem but with * previous animation. * * * @param originX * x coord of old origin * @param originY * y coord of old origin * @param newScale * x scale */ @Override public void setAnimatedCoordSystem(double originX, double originY, double f, double newScale, int steps, boolean storeUndo) { double ox = originX + (getXZero() - originX) * f; double oy = originY + (getYZero() - originY) * f; if (!Kernel.isEqual(getXscale(), newScale)) { // different scales: zoom back to standard view double factor = newScale / getXscale(); zoom((ox - (getXZero() * factor)) / (1.0 - factor), (oy - (getYZero() * factor)) / (1.0 - factor), factor, steps, storeUndo); } else { // same scales: translate view to standard origin // do this with the following action listener if (mover == null) { mover = newZoomer(); } mover.init(ox, oy, storeUndo); mover.startAnimation(); } } private MyZoomer mover; /** * Sets real world coord system using min and max values for both axes in * real world values. */ @Override final public void setAnimatedRealWorldCoordSystem(double xmin, double xmax, double ymin, double ymax, int steps, boolean storeUndo) { if (zoomerRW == null) { zoomerRW = newZoomer(); } zoomerRW.initRW(xmin, xmax, ymin, ymax, steps, storeUndo); zoomerRW.startAnimation(); } private MyZoomer zoomerRW; // for use in AlgebraController @Override final public void mouseMovedOver(GeoElement geo) { Hits geos = null; if (geo != null) { tempArrayList.clear(); tempArrayList.add(geo); geos = tempArrayList; } getEuclidianController().startCollectingMinorRepaints(); boolean repaintNeeded = getEuclidianController().refreshHighlighting( geos, false); if (repaintNeeded) { kernel.notifyRepaint(); } getEuclidianController().stopCollectingMinorRepaints(); } @Override public void highlight(GeoElement geo) { if (getEuclidianController().highlight(geo)) { kernel.notifyRepaint(); } } @Override public void highlight(ArrayList<GeoElement> geos) { if (getEuclidianController().highlight(geos)) { kernel.notifyRepaint(); } } @Override final public void mouseMovedOverList(ArrayList<GeoElement> geoList) { Hits geos = null; tempArrayList.clear(); tempArrayList.addAll(geoList); geos = tempArrayList; getEuclidianController().startCollectingMinorRepaints(); boolean repaintNeeded = getEuclidianController().refreshHighlighting( geos, false); if (repaintNeeded) { kernel.notifyRepaint(); } getEuclidianController().stopCollectingMinorRepaints(); } /** * Updates highlighting of animation buttons. * * @return whether status was changed */ @Override public final boolean setAnimationButtonsHighlighted(boolean flag) { // draw button in focused EV only if (!drawPlayButtonInThisView()) { return false; } if (flag == highlightAnimationButtons) { return false; } highlightAnimationButtons = flag; return true; } /** * @return true if play button belongs to this view */ protected boolean drawPlayButtonInThisView() { GuiManagerInterface gui = getApplication().getGuiManager(); // just one view if (gui == null) { return true; } // eg ev1 just closed GetViewId evp = gui.getLayout().getDockManager() .getFocusedEuclidianPanel(); if (evp == null || evp.getViewId() == App.VIEW_EUCLIDIAN3D) { return isPrimaryEV(); } return this.getViewID() == evp.getViewId(); } private boolean isPrimaryEV() { return this.getEuclidianViewNo() == 1 || (!app.showView(App.VIEW_EUCLIDIAN) && this.isDefault2D()); } /** * @return axes color */ public GColor getAxesColor() { return axesColor; } /** * @return grid color */ public GColor getGridColor() { return gridColor; } /** * @param box * box to be added */ public abstract void add(GBox box); /** * @param box * box to be removed */ public abstract void remove(GBox box); /** * Initializes basic properties of this view * * @param repaint * true if should be repainted after */ protected void initView(boolean repaint) { // init grid's line type setGridLineStyle(EuclidianStyleConstants.LINE_TYPE_DASHED_SHORT); setAxesLineStyle(EuclidianStyleConstants.AXES_LINE_TYPE_ARROW); setAxesColor(GColor.BLACK); // Michael Borcherds // 2008-01-26 was // darkgray setGridColor(GColor.LIGHT_GRAY); setBackground(GColor.WHITE); // showAxes = true; // showGrid = false; pointCapturingMode = EuclidianStyleConstants.POINT_CAPTURING_AUTOMATIC; // added by Loic BEGIN // app.rightAngleStyle = EuclidianView.RIGHT_ANGLE_STYLE_SQUARE; // END showAxesNumbers[0] = true; showAxesNumbers[1] = true; axesLabels[0] = null; axesLabels[1] = null; axesUnitLabels[0] = null; axesUnitLabels[1] = null; piAxisUnit[0] = false; piAxisUnit[1] = false; axesTickStyles[0] = EuclidianStyleConstants.AXES_TICK_STYLE_MAJOR; axesTickStyles[1] = EuclidianStyleConstants.AXES_TICK_STYLE_MAJOR; // for axes labeling with numbers automaticAxesNumberingDistances[0] = true; automaticAxesNumberingDistances[1] = true; // distances between grid lines automaticGridDistance = true; setStandardCoordSystem(repaint); } @Override public void setShowAxis(boolean show) { setShowAxis(0, show, false); setShowAxis(1, show, true); } public void setLogAxis(boolean log) { setLogAxis(0, log, false); setLogAxis(0, log, true); } /** * @return graphics object (for pen) */ public abstract GGraphics2D getGraphicsForPen(); /** * @return whether stylebar of this view exists */ public final boolean hasStyleBar() { return styleBar != null; } /** * @param mode * mode number * @return whether the mode is pen, pencil or freehand */ public static boolean isPenMode(int mode) { return mode == EuclidianConstants.MODE_PEN || mode == EuclidianConstants.MODE_FREEHAND_SHAPE; } private OptionsEuclidian optionPanel = null; private DrawDropDownList openedComboBox = null; protected ViewTextField viewTextField; private EuclidianStyleBar dynamicStyleBar; /** * sets the option panel for gui update * * @param optionPanel * option panel */ public void setOptionPanel(OptionsEuclidian optionPanel) { this.optionPanel = optionPanel; } /** * @return delete tool rectangle */ public GRectangle getDeletionRectangle() { return deletionRectangle; } /** * @param deletionRectangle * delete tool rectangle */ public void setDeletionRectangle(GRectangle deletionRectangle) { this.deletionRectangle = deletionRectangle; } /** * changes style bold <> not bold as necessary * * @param bold * true for bold axes * @param axesLineStyle * old style * @return new style */ public static int getBoldAxes(boolean bold, int axesLineStyle) { if (bold) { return axesLineStyle | EuclidianStyleConstants.AXES_BOLD; } return axesLineStyle & (~EuclidianStyleConstants.AXES_BOLD); } /** * @return whether axes are bold */ public boolean areAxesBold() { return (axesLineType & EuclidianStyleConstants.AXES_BOLD) != 0; } static double estimateNumberHeight(GFont fontAxes2) { return StringUtil.getPrototype().estimateHeight("", fontAxes2); } double estimateNumberWidth(double d, GFont fontAxes2) { String s = kernel.formatPiE(d, axesNumberFormat[0], StringTemplate.defaultTemplate); return StringUtil.getPrototype().estimateLength(s, fontAxes2); } static double estimateTextWidth(String s, GFont fontAxes2) { return StringUtil.getPrototype().estimateLength(s, fontAxes2); } @Override public int getSliderOffsetY() { return 50; } @Override public int getComboOffsetY() { return 70; } public double getMinSamplePoints() { return MIN_SAMPLE_POINTS; } public double getMaxBendOfScreen() { return MAX_BEND_OFF_SCREEN; } public double getMaxBend() { return MAX_BEND; } public int getMaxDefinedBisections() { return MAX_DEFINED_BISECTIONS; } @Override public double getMinPixelDistance() { return MIN_PIXEL_DISTANCE; } public int getMaxZeroCount() { return MAX_ZERO_COUNT; } public double getMaxPixelDistance() { return MAX_PIXEL_DISTANCE; } public int getMaxProblemBisections() { return MAX_PROBLEM_BISECTIONS; } public int getAbsoluteTop() { return -1; } public int getAbsoluteLeft() { return -1; } @Override final public EuclidianStyleBar getStyleBar() { if (styleBar == null) { styleBar = newEuclidianStyleBar(); } return styleBar; } /** * * @return new dynamic style bar */ abstract protected EuclidianStyleBar newEuclidianStyleBar(); /** * * Adds dynamic stylebar to DOM if not added yet */ abstract protected void addDynamicStylebarToEV(EuclidianStyleBar dynamicStylebar); @Override final public EuclidianStyleBar getDynamicStyleBar() { if (dynamicStyleBar == null) { dynamicStyleBar = newDynamicStyleBar(); if (dynamicStyleBar != null) { dynamicStyleBar.setVisible(false); } } addDynamicStylebarToEV(dynamicStyleBar); return dynamicStyleBar; } /** * @return whether dynamic stylebar exists */ public final boolean hasDynamicStyleBar() { return dynamicStyleBar != null; } /** * * @return new euclidian style bar */ abstract protected EuclidianStyleBar newDynamicStyleBar(); public long getLastRepaintTime() { return 0; } @Override public final void setLabels() { if (this.styleBar != null) { styleBar.setLabels(); } } /** * * @return 2 for 2D and 3 for 3D */ public int getDimension() { return 2; } /** * draw background image to graphics * * @param g2d * graphics */ protected void drawBackgroundImage(GGraphics2D g2d) { g2d.drawImage(bgImage, 0, 0); } protected void exportPaintPreScale(GGraphics2D g2d, double scale) { g2d.scale(scale, scale); } public void exportPaintPre(GGraphics2D g2d, double scale, boolean transparency) { exportPaintPreScale(g2d, scale); // clipping on selection rectangle if (getSelectionRectangle() != null) { GRectangle rect = getSelectionRectangle(); g2d.setClip(0, 0, (int) rect.getWidth(), (int) rect.getHeight()); g2d.translate(-rect.getX(), -rect.getY()); // Application.debug(rect.x+" "+rect.y+" "+rect.width+" // "+rect.height); } else { // use points Export_1 and Export_2 to define corner try { // Construction cons = kernel.getConstruction(); GeoPoint export1 = (GeoPoint) kernel.lookupLabel(EXPORT1); GeoPoint export2 = (GeoPoint) kernel.lookupLabel(EXPORT2); double[] xy1 = new double[2]; double[] xy2 = new double[2]; export1.getInhomCoords(xy1); export2.getInhomCoords(xy2); double x1 = xy1[0]; double x2 = xy2[0]; double y1 = xy1[1]; double y2 = xy2[1]; x1 = (x1 / getInvXscale()) + getXZero(); y1 = getYZero() - (y1 / getInvYscale()); x2 = (x2 / getInvXscale()) + getXZero(); y2 = getYZero() - (y2 / getInvYscale()); int x = (int) Math.min(x1, x2); int y = (int) Math.min(y1, y2); int exportWidth = (int) Math.abs(x1 - x2) + 2; int exportHeight = (int) Math.abs(y1 - y2) + 2; g2d.setClip(0, 0, exportWidth, exportHeight); g2d.translate(-x, -y); } catch (Exception e) { // or take full euclidian view g2d.setClip(0, 0, getWidth(), getHeight()); } } // DRAWING if (isTracing() || hasBackgroundImages()) { // draw background image to get the traces if (bgImage == null) { drawBackgroundWithImages(g2d, transparency); } else { drawBackgroundImage(g2d); } } else { // just clear the background if transparency is disabled (clear = // draw background color) drawBackground(g2d, !transparency); } g2d.setAntialiasing(); } /** * Scales construction and draws it to g2d. * * @param g2d * export graphics * @param scale * ratio of desired size and current size of the graphics * * @param transparency * states if export should be optimized for eps. Note: if this is * set to false, no traces are drawn. * @param exportType * SVG, PNG etc * */ public void exportPaint(GGraphics2D g2d, double scale, boolean transparency, ExportType exportType) { getApplication().setExporting(exportType, scale); exportPaintPre(g2d, scale, transparency); drawObjects(g2d); g2d.resetClip(); getApplication().setExporting(ExportType.NONE, 1); } /** * Returns image of drawing pad sized according to the given scale factor. * * @param scale * ratio of desired size and current size of the graphics * @return image of drawing pad sized according to the given scale factor. */ public GBufferedImage getExportImage(double scale) { return getExportImage(scale, false, ExportType.PNG); } /** * @param scale * ratio of desired size and current size of the graphics * @param transparency * true for transparent image * @return image */ public GBufferedImage getExportImage(double scale, boolean transparency, ExportType exportType) { int width = (int) Math.floor(getExportWidth() * scale); int height = (int) Math.floor(getExportHeight() * scale); GBufferedImage img = AwtFactory.getPrototype().createBufferedImage( width, height, transparency); exportPaint(img.createGraphics(), scale, transparency, exportType); img.flush(); return img; } @Override public void centerView(GeoPointND point) { Coords p = getCoordsForView(point.getInhomCoordsInD3()); double px = (toRealWorldCoordX(getWidth()) - toRealWorldCoordX(0)) / 2; double py = (-toRealWorldCoordY(getHeight()) + toRealWorldCoordY(0)) / 2; setRealWorldCoordSystem(p.getX() - px, p.getX() + px, p.getY() - py, p.getY() + py); } public static String getDraggedLabels(ArrayList<String> list) { if (list.size() == 0) { return null; } // single geo if (list.size() == 1) { return "FormulaText[" + list.get(0) + ", true, true]"; } StringBuilder text = new StringBuilder("TableText["); for (int i = 0; i < list.size(); i++) { text.append("{FormulaText["); text.append(list.get(i)); text.append(", true, true]}"); if (i < list.size() - 1) { text.append(","); } } text.append("]"); return text.toString(); } @Override public boolean isViewForPlane() { if (settings == null) { return false; } return settings.isViewForPlane(); } /** * @param pixelRatio * physical x logical pixel ratio */ public void setPixelRatio(double pixelRatio) { // TODO Auto-generated method stub } @Override public void closeDropDowns(int x, int y) { DrawableIterator it = allDrawableList.getIterator(); while (it.hasNext()) { Drawable d = it.next(); boolean repaintNeeded = false; if (d instanceof DrawDropDownList && d.isCanvasDrawable()) { DrawDropDownList dl = (DrawDropDownList) d; if (!(dl.isControlHit(x, y) || dl.isOptionsHit(x, y))) { repaintNeeded = repaintNeeded || dl.closeOptions(); } } if (repaintNeeded) { repaintView(); } } } public void closeAllDropDowns() { DrawableIterator it = allDrawableList.getIterator(); while (it.hasNext()) { Drawable d = it.next(); if (d instanceof DrawDropDownList) { DrawDropDownList dl = (DrawDropDownList) d; dl.closeOptions(); } } } public DrawDropDownList getOpenedComboBox() { return openedComboBox; } public void setOpenedComboBox(DrawDropDownList openedComboBox) { this.openedComboBox = openedComboBox; SelectionManager sm = app.getSelectionManager(); if (openedComboBox != null) { GeoElement geo = openedComboBox.getGeoElement(); if (!sm.containsSelectedGeo(geo)) { sm.addSelectedGeo(geo); } } } public void cancelBlur() { // TODO Auto-generated method stub } public boolean shrinkedSinceLoad() { return (getSettings() != null && getWidth() > 2 && (getWidth() < getSettings() .getFileWidth() || getHeight() < getSettings().getFileHeight())); } @Override public boolean isInPlane(CoordSys sys) { return companion.isInPlane(sys); } public double getPixelRatio() { return 1; } public double getXZeroOld() { return xZeroOld; } public double getYZeroOld() { return yZeroOld; } public double getXScaleStart() { return xScaleStart; } public double getYScaleStart() { return yScaleStart; } @Override public void screenChanged() { screenChanged = true; } private void adjustObjects() { if (app.has(Feature.ADJUST_WIDGETS) && screenChanged) { app.adjustScreen(true); screenChanged = false; repaint(); } } public AutoCompleteTextField getTextField() { return viewTextField == null ? null : viewTextField.getTextField(); } public AutoCompleteTextField getTextField(GeoInputBox input, DrawInputBox drawInputBox) { return viewTextField == null ? null : viewTextField.getTextField( input.getLength(), drawInputBox); } public void focusTextField(GeoInputBox inputBox) { if (viewTextField != null) { viewTextField.focusTo(inputBox); } } public void focusAndShowTextField(GeoInputBox inputBox) { requestFocus(); focusTextField(inputBox); viewTextField.getTextField().getDrawTextField().setWidgetVisible(true); } public GBox getBoxForTextField() { return viewTextField == null ? null : viewTextField.getBox(); } public void removeTextField() { if (viewTextField != null) { viewTextField.remove(); } } protected boolean isTraceDrawn() { return tracing; } public NumberFormatAdapter getAxisNumberFormat(int i) { return axesNumberFormat[i]; } }