/* 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. */ package org.geogebra.common.euclidian.draw; import java.util.ArrayList; import org.geogebra.common.awt.GColor; import org.geogebra.common.awt.GEllipse2DDouble; import org.geogebra.common.awt.GGraphics2D; import org.geogebra.common.awt.GPathIterator; import org.geogebra.common.awt.GPoint2D; import org.geogebra.common.awt.GRectangle; import org.geogebra.common.euclidian.BoundingBox; import org.geogebra.common.euclidian.Drawable; import org.geogebra.common.euclidian.EuclidianConstants; import org.geogebra.common.euclidian.EuclidianStatic; import org.geogebra.common.euclidian.EuclidianView; import org.geogebra.common.euclidian.GeneralPathClipped; import org.geogebra.common.euclidian.Previewable; import org.geogebra.common.factories.AwtFactory; import org.geogebra.common.kernel.ConstructionDefaults; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.geos.GeoPolyLine; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.plugin.EuclidianStyleConstants; /** * * @author Markus Hohenwarter */ public class DrawPolyLine extends Drawable implements Previewable { private GeoPolyLine poly; private boolean isVisible, labelVisible; private GeneralPathClipped gp; private double[] coords = new double[2]; private ArrayList<? extends GeoPointND> points; // list of single points created by pen private ArrayList<GPoint2D> pointList = new ArrayList<GPoint2D>(); private boolean startPointAdded = false; /** * @param view * view * @param poly * polyline */ public DrawPolyLine(EuclidianView view, GeoPolyLine poly) { this.view = view; this.poly = poly; geo = poly; update(); } /** * Creates a new DrawPolygon for preview. * * @param view * view * @param points * preview points */ public DrawPolyLine(EuclidianView view, ArrayList<? extends GeoPointND> points) { this.view = view; this.points = points; geo = view.getKernel().getConstruction().getConstructionDefaults() .getDefaultGeo(ConstructionDefaults.DEFAULT_POLYLINE); updatePreview(); } @Override final public void update() { isVisible = geo.isEuclidianVisible(); if (isVisible) { labelVisible = geo.isLabelVisible(); updateStrokes(poly); // build general path for this polygon addPointsToPath(poly.getPointsND()); // polygon on screen? if (!gp.intersects(0, 0, view.getWidth(), view.getHeight())) { isVisible = false; // don't return here to make sure that getBounds() works for // offscreen points too } // draw trace if (poly.getTrace()) { isTracing = true; GGraphics2D g2 = view.getBackgroundGraphics(); if (g2 != null) { drawTrace(g2); } } else { if (isTracing) { isTracing = false; // view.updateBackground(); } } } } @Override protected final void drawTrace(GGraphics2D g2) { if (isVisible) { g2.setPaint(getObjectColor()); g2.setStroke(objStroke); g2.draw(gp); } } private void addPointsToPath(GeoPointND[] pts) { if (gp == null) { gp = new GeneralPathClipped(view); } else { gp.reset(); } pointList.clear(); // for centroid calculation (needed for label pos) double xsum = 0; double ysum = 0; boolean skipNextPoint = true; Coords v; for (int i = 0; i < pts.length; i++) { v = getCoords(i); if (pts[i].isDefined() && Kernel.isZero(v.getZ())) { coords[0] = v.getX(); coords[1] = v.getY(); view.toScreenCoords(coords); if (labelVisible) { xsum += coords[0]; ysum += coords[1]; } if (skipNextPoint) { skipNextPoint = false; // collect start points // one point if (pts.length == 1 || // last point (i - 1 >= 0 && i + 1 == pts.length && !pts[i - 1].isDefined()) // between undef points || (i - 1 >= 0 && i + 1 < pts.length && !pts[i - 1].isDefined() && !pts[i + 1].isDefined()) // first point || (i == 0 && i + 1 < pts.length && !pts[i + 1].isDefined())) { // do not collect points remained after erasing if ((i - 2 >= 0 && pts[i - 2].isDefined()) || (i + 2 < pts.length && pts[i + 2].isDefined()) || i == 0 || i == pts.length - 1) { if (!pointList.contains(convertPoint(pts[i]))) { pointList.add(convertPoint(pts[i])); startPointAdded = true; } } } gp.moveTo(coords[0], coords[1]); } else { gp.lineTo(coords[0], coords[1]); // if point was added as start of segment // then remove it if (!pointList.isEmpty() && startPointAdded && view .getEuclidianController() .getMode() != EuclidianConstants.MODE_ERASER) { pointList.remove(pointList.size() - 1); startPointAdded = false; } } } else { // undefined point -> hole in polyline skipNextPoint = true; } } if (labelVisible) { labelDesc = geo.getLabelDescription(); xLabel = (int) (xsum / pts.length); yLabel = (int) (ysum / pts.length); addLabelOffset(); } } @Override final public void draw(GGraphics2D g2) { // draw single points for (int i = 0; i < pointList.size(); i++) { GPoint2D v = pointList.get(i); if (poly.getLineType() == EuclidianStyleConstants.LINE_TYPE_FULL) { drawEllipse(g2, v); } else { drawRectangle(g2, v); } } if (isVisible) { g2.setPaint(getObjectColor()); g2.setStroke(objStroke); g2.draw(gp); if (geo.doHighlighting()) { g2.setPaint(poly.getSelColor()); g2.setStroke(selStroke); g2.draw(gp); } if (labelVisible) { g2.setPaint(poly.getLabelColor()); g2.setFont(view.getFontPoint()); drawLabel(g2); } } } // method to draw ellipse for point created by pen tool // for full line type private void drawEllipse(GGraphics2D g2D, GPoint2D point) { GEllipse2DDouble ellipse = AwtFactory.getPrototype() .newEllipse2DDouble(); ellipse.setFrameFromCenter(point.getX(), point.getY(), point.getX() + getLineThicknessForPoint(), point.getY() + getLineThicknessForPoint()); GColor lineDrawingColor = getObjectColor() .deriveWithAlpha(poly.getLineOpacity()); g2D.setPaint(lineDrawingColor); g2D.fill(ellipse); g2D.setStroke(EuclidianStatic.getDefaultStroke()); g2D.draw(AwtFactory.getPrototype().newArea(ellipse)); } // method to draw rectangle for point created by pen tool // for other line types private void drawRectangle(GGraphics2D g2D, GPoint2D point) { GRectangle rectangle = AwtFactory.getPrototype().newRectangle(); rectangle.setRect(point.getX(), point.getY(), getLineThicknessForPoint() * 1.5, getLineThicknessForPoint() * 1.5); GColor lineDrawingColor = getObjectColor() .deriveWithAlpha(poly.getLineOpacity()); g2D.setPaint(lineDrawingColor); g2D.fill(rectangle); g2D.setStroke(EuclidianStatic.getDefaultStroke()); g2D.draw(AwtFactory.getPrototype().newArea(rectangle)); } private float getLineThicknessForPoint() { return poly.getLineThickness() / 4.0f; } @Override final public void updatePreview() { int size = points.size(); isVisible = size > 0; if (isVisible) { GeoPointND[] pointsArray = new GeoPointND[size]; for (int i = 0; i < size; i++) { pointsArray[i] = points.get(i); } addPointsToPath(pointsArray); } } private GPoint2D endPoint = AwtFactory.getPrototype().newPoint2D(); @Override final public void updateMousePos(double mouseRWx, double mouseRWy) { double xRW = mouseRWx; double yRW = mouseRWy; if (isVisible) { // double xRW = view.toRealWorldCoordX(mx); // double yRW = view.toRealWorldCoordY(my); int mx = view.toScreenCoordX(xRW); int my = view.toScreenCoordY(yRW); // round angle to nearest 15 degrees if alt pressed if (view.getEuclidianController().isAltDown()) { GeoPoint p = (GeoPoint) points.get(points.size() - 1); double px = p.inhomX; double py = p.inhomY; double angle = Math.atan2(yRW - py, xRW - px) * 180 / Math.PI; double radius = Math.sqrt( (py - yRW) * (py - yRW) + (px - xRW) * (px - xRW)); // round angle to nearest 15 degrees angle = Math.round(angle / 15) * 15; xRW = px + radius * Math.cos(angle * Math.PI / 180); yRW = py + radius * Math.sin(angle * Math.PI / 180); mx = view.toScreenCoordX(xRW); my = view.toScreenCoordY(yRW); endPoint.setX(xRW); endPoint.setY(yRW); view.getEuclidianController().setLineEndPoint(endPoint); gp.lineTo(mx, my); } else { view.getEuclidianController().setLineEndPoint(null); } gp.lineTo(view.toScreenCoordX(xRW), view.toScreenCoordY(yRW)); } } @Override final public void drawPreview(GGraphics2D g2) { if (isVisible) { g2.setPaint(getObjectColor()); updateStrokes(geo); g2.setStroke(objStroke); g2.draw(gp); } } @Override public void disposePreview() { // do nothing } @Override final public boolean hit(int x, int y, int hitThreshold) { if (gp == null) { return false; } // hit points of polyline GPathIterator it = gp.getGeneralPath().getPathIterator(null); it.currentSegment(coords); it.next(); if (it.isDone()) { if (pointList != null) { for (int i = 0; i < pointList.size(); i++) { GPoint2D p = pointList.get(i); GRectangle rect = AwtFactory.getPrototype().newRectangle(0, 0, 100, 100); rect.setBounds(x - hitThreshold, y - hitThreshold, 2 * hitThreshold, 2 * hitThreshold); if (rect.contains(p)) { return true; } } return false; } } if (isVisible) { if (strokedShape == null) { strokedShape = objStroke.createStrokedShape(gp); } boolean intersects = strokedShape.intersects(x - hitThreshold, y - hitThreshold, 2 * hitThreshold, 2 * hitThreshold); // if no hit of polyline // try single points of polyline if (!intersects && pointList != null) { for (int i = 0; i < pointList.size(); i++) { GPoint2D p = pointList.get(i); GRectangle rect = AwtFactory.getPrototype().newRectangle(0, 0, 100, 100); rect.setBounds(x - hitThreshold, y - hitThreshold, 2 * hitThreshold, 2 * hitThreshold); if (rect.contains(p)) { return true; } } } return intersects; } return false; } private GPoint2D convertPoint(GeoPointND point) { Coords v = point.getInhomCoordsInD3(); coords[0] = v.getX(); coords[1] = v.getY(); view.toScreenCoords(coords); GPoint2D p = AwtFactory.getPrototype().newPoint2D(); p.setLocation(coords[0], coords[1]); return p; } @Override public boolean intersectsRectangle(GRectangle rect) { GPathIterator it = gp.getGeneralPath().getPathIterator(null); it.currentSegment(coords); it.next(); if (it.isDone()) { if (pointList != null) { for (int i = 0; i < pointList.size(); i++) { GPoint2D p = pointList.get(i); if (rect.contains(p)) { return true; } } return false; } } if (isVisible) { if (strokedShape == null) { strokedShape = objStroke.createStrokedShape(gp); } boolean intersects = strokedShape.intersects(rect); if (!intersects && pointList != null) { for (int i = 0; i < pointList.size(); i++) { GPoint2D p = pointList.get(i); if (rect.contains(p)) { return true; } } } return intersects; } return false; } @Override final public boolean isInside(GRectangle rect) { return gp != null && rect.contains(gp.getBounds()); } @Override public GeoElement getGeoElement() { return geo; } @Override public void setGeoElement(GeoElement geo) { this.geo = geo; } /** * Returns the bounding box of this Drawable in screen coordinates. */ @Override final public GRectangle getBounds() { if (!geo.isDefined() || !geo.isEuclidianVisible()) { return null; } return gp.getBounds(); } private Coords getCoords(int i) { if (poly != null) { return view .getCoordsForView(poly.getPointND(i).getInhomCoordsInD3()); } return view.getCoordsForView(points.get(i).getInhomCoordsInD3()); } @Override public BoundingBox getBoundingBox() { // TODO Auto-generated method stub return null; } @Override public void updateBoundingBox() { // TODO Auto-generated method stub } }