/*
GeoGebra - Dynamic Mathematics for Everyone
http://www.geogebra.org
This file is part of GeoGebra.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation.
*/
/*
* DrawPoint.java
*
* Created on 11. Oktober 2001, 23:59
*/
package org.geogebra.common.euclidian.draw;
import org.geogebra.common.awt.GArea;
import org.geogebra.common.awt.GBasicStroke;
import org.geogebra.common.awt.GColor;
import org.geogebra.common.awt.GEllipse2DDouble;
import org.geogebra.common.awt.GGeneralPath;
import org.geogebra.common.awt.GGraphics2D;
import org.geogebra.common.awt.GLine2D;
import org.geogebra.common.awt.GRectangle;
import org.geogebra.common.euclidian.BoundingBox;
import org.geogebra.common.euclidian.Drawable;
import org.geogebra.common.euclidian.EuclidianStatic;
import org.geogebra.common.euclidian.EuclidianView;
import org.geogebra.common.euclidian.event.PointerEventType;
import org.geogebra.common.factories.AwtFactory;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.algos.AlgoElement;
import org.geogebra.common.kernel.algos.AlgoIntersectAbstract;
import org.geogebra.common.kernel.geos.GeoConic;
import org.geogebra.common.kernel.geos.GeoConicPart;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoFunction;
import org.geogebra.common.kernel.geos.GeoLine;
import org.geogebra.common.kernel.geos.GeoSegment;
import org.geogebra.common.kernel.kernelND.GeoLineND;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.plugin.EuclidianStyleConstants;
import org.geogebra.common.util.debug.Log;
/**
*
* @author Markus
* @version 2011-01-10
*/
public final class DrawPoint extends Drawable {
private int HIGHLIGHT_OFFSET;
// used by getSelectionDiamaterMin()
private static final int SELECTION_RADIUS_MIN = 12;
/**
*
* @param threshold
* controller threshold
* @return distance threshold to select a point
*/
public static final int getSelectionThreshold(int threshold) {
return threshold + SELECTION_RADIUS_MIN;
}
private GeoPointND P;
private int diameter, hightlightDiameter, pointSize;
private boolean isVisible, labelVisible;
// for dot and selection
private GEllipse2DDouble circle = AwtFactory.getPrototype()
.newEllipse2DDouble();
private GEllipse2DDouble circleHighlight = AwtFactory.getPrototype()
.newEllipse2DDouble();
private GLine2D line1, line2, line3, line4;// for cross
private GGeneralPath gp = null;
private static GBasicStroke borderStroke = EuclidianStatic
.getDefaultStroke();
private static GBasicStroke[] fillStrokes = new GBasicStroke[10];
private static GBasicStroke[] emptyStrokes = new GBasicStroke[10];
private boolean isPreview;
private double[] coords;
/**
* Creates new DrawPoint
*
* @param view
* view
* @param P
* point to be drawn
*/
public DrawPoint(EuclidianView view, GeoPointND P) {
this(view, P, false);
}
/**
* Creates new DrawPoint
*
* @param view
* View
* @param P
* point to be drawn
* @param isPreview
* true iff preview
*/
public DrawPoint(EuclidianView view, GeoPointND P, boolean isPreview) {
this.view = view;
this.P = P;
geo = (GeoElement) P;
this.isPreview = isPreview;
this.coords = new double[2];
// crossStrokes[1] = new BasicStroke(1f);
update();
}
private double[] coords1 = new double[2];
@Override
final public void update() {
if (gp != null)
{
gp.reset(); // stop trace being left when (filled diamond) point
// moved
}
isVisible = geo.isEuclidianVisible();
if (isPreview) {
Coords p = P.getInhomCoordsInD2();
coords1[0] = p.getX();
coords1[1] = p.getY();
} else {
// looks if it's on view
Coords p = view.getCoordsForView(P.getInhomCoordsInD3());
if (!Kernel.isZero(p.getZ())) {
isVisible = false;
} else {
coords1[0] = p.getX();
coords1[1] = p.getY();
}
}
// trace to spreadsheet is no longer bound to EV
if (!isVisible) {
return;
}
update(coords1);
}
/**
* update regarding coords values
*
* @param coords2
* (x,y) real world coords
*/
final public void update(double[] coords2) {
update(coords2, true);
}
private void update(double[] coords2, boolean rwCoords) {
isVisible = true;
labelVisible = geo.isLabelVisible();
this.coords = coords2;
if (rwCoords) {
// convert to screen
view.toScreenCoords(coords);
// point outside screen?
if (Double.isNaN(coords[0]) || Double.isNaN(coords[1])) { // fix for
// #63
isVisible = false;
} else if (coords[0] > view.getWidth() + P.getPointSize()
|| coords[0] < -P.getPointSize()
|| coords[1] > view.getHeight() + P.getPointSize()
|| coords[1] < -P.getPointSize()) {
isVisible = false;
// don't return here to make sure that getBounds() works for
// offscreen points too
}
}
if (pointSize != P.getPointSize()) {
updateDiameter();
}
double xUL = (coords[0] - pointSize);
double yUL = (coords[1] - pointSize);
int pointStyle = P.getPointStyle();
if (pointStyle == -1) {
pointStyle = EuclidianStyleConstants.POINT_STYLE_DOT;
}
double root3over2;
switch (pointStyle) {
case EuclidianStyleConstants.POINT_STYLE_FILLED_DIAMOND:
double xR = coords[0] + pointSize;
double yB = coords[1] + pointSize;
if (gp == null) {
gp = AwtFactory.getPrototype().newGeneralPath();
}
gp.moveTo((xUL + xR) / 2, yUL);
gp.lineTo(xUL, (yB + yUL) / 2);
gp.lineTo((xUL + xR) / 2, yB);
gp.lineTo(xR, (yB + yUL) / 2);
gp.closePath();
break;
case EuclidianStyleConstants.POINT_STYLE_TRIANGLE_SOUTH:
case EuclidianStyleConstants.POINT_STYLE_TRIANGLE_NORTH:
double direction = 1.0;
if (pointStyle == EuclidianStyleConstants.POINT_STYLE_TRIANGLE_NORTH) {
direction = -1.0;
}
if (gp == null) {
gp = AwtFactory.getPrototype().newGeneralPath();
}
root3over2 = Math.sqrt(3.0) / 2.0;
gp.moveTo(coords[0], (coords[1] + direction * pointSize));
gp.lineTo((coords[0] + pointSize * root3over2),
(coords[1] - direction * pointSize / 2));
gp.lineTo((coords[0] - pointSize * root3over2),
(coords[1] - direction * pointSize / 2));
gp.lineTo(coords[0], (coords[1] + direction * pointSize));
gp.closePath();
break;
case EuclidianStyleConstants.POINT_STYLE_TRIANGLE_EAST:
case EuclidianStyleConstants.POINT_STYLE_TRIANGLE_WEST:
direction = 1.0;
if (pointStyle == EuclidianStyleConstants.POINT_STYLE_TRIANGLE_WEST) {
direction = -1.0;
}
if (gp == null) {
gp = AwtFactory.getPrototype().newGeneralPath();
}
root3over2 = Math.sqrt(3.0) / 2.0;
gp.moveTo((coords[0] + direction * pointSize), coords[1]);
gp.lineTo((coords[0] - direction * pointSize / 2),
(coords[1] + pointSize * root3over2));
gp.lineTo((coords[0] - direction * pointSize / 2),
(coords[1] - pointSize * root3over2));
gp.lineTo((coords[0] + direction * pointSize), coords[1]);
gp.closePath();
break;
case EuclidianStyleConstants.POINT_STYLE_EMPTY_DIAMOND:
xR = coords[0] + pointSize;
yB = coords[1] + pointSize;
if (line1 == null) {
line1 = AwtFactory.getPrototype().newLine2D();
line2 = AwtFactory.getPrototype().newLine2D();
}
if (line3 == null) {
line3 = AwtFactory.getPrototype().newLine2D();
line4 = AwtFactory.getPrototype().newLine2D();
}
line1.setLine((xUL + xR) / 2, yUL, xUL, (yB + yUL) / 2);
line2.setLine(xUL, (yB + yUL) / 2, (xUL + xR) / 2, yB);
line3.setLine((xUL + xR) / 2, yB, xR, (yB + yUL) / 2);
line4.setLine(xR, (yB + yUL) / 2, (xUL + xR) / 2, yUL);
break;
case EuclidianStyleConstants.POINT_STYLE_PLUS:
xR = coords[0] + pointSize;
yB = coords[1] + pointSize;
if (line1 == null) {
line1 = AwtFactory.getPrototype().newLine2D();
line2 = AwtFactory.getPrototype().newLine2D();
}
line1.setLine((xUL + xR) / 2, yUL, (xUL + xR) / 2, yB);
line2.setLine(xUL, (yB + yUL) / 2, xR, (yB + yUL) / 2);
break;
case EuclidianStyleConstants.POINT_STYLE_CROSS:
xR = coords[0] + pointSize;
yB = coords[1] + pointSize;
if (line1 == null) {
line1 = AwtFactory.getPrototype().newLine2D();
line2 = AwtFactory.getPrototype().newLine2D();
}
line1.setLine(xUL, yUL, xR, yB);
line2.setLine(xUL, yB, xR, yUL);
break;
case EuclidianStyleConstants.POINT_STYLE_CIRCLE:
break;
// case EuclidianStyleConstants.POINT_STYLE_DOT:
// default:
}
// circle might be needed at least for tracing
circle.setFrame(xUL, yUL, diameter, diameter);
// selection area
circleHighlight.setFrame(xUL - HIGHLIGHT_OFFSET, yUL - HIGHLIGHT_OFFSET,
hightlightDiameter, hightlightDiameter);
// draw trace
if (P.getTrace()) {
isTracing = true;
GGraphics2D g2 = view.getBackgroundGraphics();
if (g2 != null) {
drawTrace(g2);
}
} else {
if (isTracing) {
isTracing = false;
// view.updateBackground();
}
}
if (isVisible && labelVisible) {
labelDesc = geo.getLabelDescription();
xLabel = (int) Math.round(coords[0] + 4);
yLabel = (int) Math.round(yUL - pointSize);
addLabelOffsetEnsureOnScreen(view.getFontPoint());
}
}
private void updateDiameter() {
pointSize = P.getPointSize();
diameter = 2 * pointSize;
HIGHLIGHT_OFFSET = pointSize / 2 + 1;
// HIGHLIGHT_OFFSET = pointSize / 2 + 1;
hightlightDiameter = diameter + 2 * HIGHLIGHT_OFFSET;
}
private Drawable drawable;
private void drawClippedSection(GeoElement geo2, GGraphics2D g2) {
switch (geo2.getGeoClassType()) {
case LINE:
drawable = new DrawLine(view, (GeoLine) geo2);
break;
case SEGMENT:
drawable = new DrawSegment(view, (GeoSegment) geo2);
break;
case RAY:
drawable = new DrawRay(view, (GeoLineND) geo2);
break;
case CONIC:
drawable = new DrawConic(view, (GeoConic) geo2, false);
break;
case FUNCTION:
drawable = new DrawParametricCurve(view, (GeoFunction) geo2);
break;
case AXIS:
drawable = null;
break;
case CONICPART:
drawable = new DrawConicPart(view, (GeoConicPart) geo2);
break;
default:
drawable = null;
Log.debug("Unsupported type for restricted drawing "
+ geo2.getGeoClassType());
}
if (drawable != null) {
P.getInhomCoords(coords1);
view.toScreenCoords(coords1);
GEllipse2DDouble circleClip = AwtFactory.getPrototype()
.newEllipse2DDouble(coords1[0] - 30, coords1[1] - 30, 60,
60);
g2.setClip(circleClip);
geo2.forceEuclidianVisible(true);
drawable.update();
drawable.draw(g2);
geo2.forceEuclidianVisible(false);
g2.resetClip();
}
}
@Override
final public void draw(GGraphics2D g2) {
if (isVisible) {
if (geo.doHighlighting()) {
g2.setPaint(geo.getSelColor());
g2.fill(circleHighlight);
g2.setStroke(borderStroke);
g2.draw(circleHighlight);
}
// option "show trimmed intersecting lines"
if (geo.getShowTrimmedIntersectionLines()) {
AlgoElement algo = geo.getParentAlgorithm();
if (algo instanceof AlgoIntersectAbstract) {
GeoElement[] geos = algo.getInput();
drawClippedSection(geos[0], g2);
if (geos.length > 1) {
drawClippedSection(geos[1], g2);
}
}
}
int pointStyle = P.getPointStyle();
if (pointStyle == -1) {
pointStyle = EuclidianStyleConstants.POINT_STYLE_DOT;
}
switch (pointStyle) {
case EuclidianStyleConstants.POINT_STYLE_PLUS:
case EuclidianStyleConstants.POINT_STYLE_CROSS:
// draw cross like: X or +
g2.setPaint(geo.getObjectColor());
g2.setStroke(getEmptyStroke(pointSize));
g2.draw(line1);
g2.draw(line2);
break;
case EuclidianStyleConstants.POINT_STYLE_EMPTY_DIAMOND:
// draw diamond
g2.setPaint(geo.getObjectColor());
g2.setStroke(getEmptyStroke(pointSize));
g2.draw(line1);
g2.draw(line2);
g2.draw(line3);
g2.draw(line4);
break;
case EuclidianStyleConstants.POINT_STYLE_FILLED_DIAMOND:
case EuclidianStyleConstants.POINT_STYLE_TRIANGLE_NORTH:
case EuclidianStyleConstants.POINT_STYLE_TRIANGLE_SOUTH:
case EuclidianStyleConstants.POINT_STYLE_TRIANGLE_EAST:
case EuclidianStyleConstants.POINT_STYLE_TRIANGLE_WEST:
// draw diamond
g2.setPaint(geo.getObjectColor());
g2.setStroke(getFillStroke(pointSize));
g2.draw(gp);
g2.fill(gp);
break;
case EuclidianStyleConstants.POINT_STYLE_CIRCLE:
// draw a circle
g2.setPaint(geo.getObjectColor());
g2.setStroke(getEmptyStroke(pointSize));
g2.draw(circle);
break;
// case EuclidianStyleConstants.POINT_STYLE_CIRCLE:
default:
// draw a dot
g2.setPaint(geo.getObjectColor());
g2.fill(circle);
// black stroke
g2.setPaint(GColor.BLACK);
g2.setStroke(borderStroke);
g2.draw(circle);
}
// label
if (labelVisible) {
g2.setFont(view.getFontPoint());
g2.setPaint(geo.getLabelColor());
drawLabel(g2);
}
}
}
@Override
protected final void drawTrace(GGraphics2D g2) {
g2.setPaint(geo.getObjectColor());
int pointStyle = P.getPointStyle();
switch (pointStyle) {
case EuclidianStyleConstants.POINT_STYLE_CIRCLE:
g2.setStroke(getEmptyStroke(pointSize));
g2.draw(circle);
break;
case EuclidianStyleConstants.POINT_STYLE_CROSS:
default: // case EuclidianStyleConstants.POINT_STYLE_CIRCLE:
g2.fill(circle);
}
}
/**
* was this object clicked at? (mouse pointer location (x,y) in screen
* coords)
*/
@Override
final public boolean hit(int x, int y, int hitThreshold) {
int r = getSelectionThreshold(hitThreshold);
double dx = coords[0] - x;
double dy = coords[1] - y;
return dx < r && dx > -r && dx * dx + dy * dy <= r * r;
}
@Override
final public boolean isInside(GRectangle rect) {
return rect.contains(circle.getBounds());
}
@Override
public boolean intersectsRectangle(GRectangle rect) {
return circle.intersects(rect);
}
/**
* Returns the bounding box of this DrawPoint in screen coordinates.
*/
@Override
final public GRectangle getBounds() {
// return selection circle's bounding box
if (!geo.isEuclidianVisible()) {
return null;
}
int selRadius = pointSize + HIGHLIGHT_OFFSET;
int minRadius = view.getApplication().getCapturingThreshold(
PointerEventType.MOUSE) + SELECTION_RADIUS_MIN;
if (selRadius < minRadius) {
selRadius = minRadius;
}
return AwtFactory.getPrototype().newRectangle(
(int) coords[0] - selRadius, (int) coords[1] - selRadius,
2 * selRadius, 2 * selRadius);
}
@Override
final public GeoElement getGeoElement() {
return geo;
}
@Override
final public void setGeoElement(GeoElement geo) {
this.geo = geo;
}
/*
* pointSize can be more than 9 (set from JavaScript, SetPointSize[])
* CAP_BUTT, JOIN_MITER behaves differently on JRE & GWT see #1699
*/
final private static GBasicStroke getEmptyStroke(int pointSize) {
if (pointSize > 9) {
return AwtFactory.getPrototype()
.newBasicStrokeJoinMitre(pointSize / 2f);
}
if (emptyStrokes[pointSize] == null) {
emptyStrokes[pointSize] = AwtFactory.getPrototype()
.newBasicStrokeJoinMitre(pointSize / 2f);
}
return emptyStrokes[pointSize];
}
/*
* pointSize can be more than 9 (set from JavaScript, SetPointSize[])
* CAP_BUTT, JOIN_MITER behaves differently on JRE & GWT see #1699
*/
final private static GBasicStroke getFillStroke(int pointSize) {
if (pointSize > 9) {
return AwtFactory.getPrototype().newBasicStroke(pointSize / 2f);
}
if (fillStrokes[pointSize] == null) {
fillStrokes[pointSize] = AwtFactory.getPrototype()
.newBasicStroke(pointSize / 2f);
}
return fillStrokes[pointSize];
}
/**
* @param pointType
* point style
*/
public void setPointStyle(int pointType) {
if (pointType == this.P.getPointStyle()) {
return;
}
P.setPointStyle(pointType);
update();
}
/**
* @return the circle as area
*/
public GArea getDot() {
return AwtFactory.getPrototype().newArea(this.circle);
}
@Override
public BoundingBox getBoundingBox() {
// TODO Auto-generated method stub
return null;
}
@Override
public void updateBoundingBox() {
// TODO Auto-generated method stub
}
/**
* Draw a point with given size and style for preview.
*
* @param x
* x-coord.
* @param y
* y-coord.
*/
final public void updateStylePreview(double x, double y) {
if (gp != null) {
gp.reset();
}
double coords2[] = { x, y };
update(coords2, false);
}
}