Java tutorial
/* Copyright (c) 2010, Carl Burch. License information is located in the * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */ package com.cburch.draw.shapes; import java.awt.Graphics; import java.awt.geom.GeneralPath; import java.util.Arrays; import java.util.List; import java.util.Random; import org.apache.commons.collections15.list.UnmodifiableList; import org.w3c.dom.Document; import org.w3c.dom.Element; import com.cburch.draw.model.CanvasObject; import com.cburch.draw.model.Handle; import com.cburch.draw.model.HandleGesture; import com.cburch.logisim.data.Attribute; import com.cburch.logisim.data.Bounds; import com.cburch.logisim.data.Location; import static com.cburch.logisim.util.LocaleString.*; public class Poly extends FillableCanvasObject { private boolean closed; // "handles" should be immutable - create a new array and change using // setHandles rather than changing contents private Handle[] handles; private GeneralPath path; private double[] lens; private Bounds bounds; public Poly(boolean closed, List<Location> locations) { Handle[] hs = new Handle[locations.size()]; int i = -1; for (Location loc : locations) { i++; hs[i] = new Handle(this, loc.getX(), loc.getY()); } this.closed = closed; handles = hs; recomputeBounds(); } @Override public Poly clone() { Poly ret = (Poly) super.clone(); Handle[] hs = this.handles.clone(); for (int i = 0, n = hs.length; i < n; i++) { Handle oldHandle = hs[i]; hs[i] = new Handle(ret, oldHandle.getX(), oldHandle.getY()); } ret.handles = hs; return ret; } @Override public boolean matches(CanvasObject other) { if (other instanceof Poly) { Poly that = (Poly) other; Handle[] a = this.handles; Handle[] b = that.handles; if (this.closed != that.closed || a.length != b.length) { return false; } else { for (int i = 0, n = a.length; i < n; i++) { if (!a[i].equals(b[i])) return false; } return super.matches(that); } } else { return false; } } @Override public int matchesHashCode() { int ret = super.matchesHashCode(); ret = ret * 3 + (closed ? 1 : 0); Handle[] hs = handles; for (int i = 0, n = hs.length; i < n; i++) { ret = ret * 31 + hs[i].hashCode(); } return ret; } @Override public String getDisplayName() { if (closed) { return _("shapePolygon"); } else { return _("shapePolyline"); } } @Override public Element toSvgElement(Document doc) { return SvgCreator.createPoly(doc, this); } @Override public List<Attribute<?>> getAttributes() { return DrawAttr.getFillAttributes(getPaintType()); } @Override public final boolean contains(Location loc, boolean assumeFilled) { Object type = getPaintType(); if (assumeFilled && type == DrawAttr.PAINT_STROKE) { type = DrawAttr.PAINT_STROKE_FILL; } if (type == DrawAttr.PAINT_STROKE) { int thresh = Math.max(Line.ON_LINE_THRESH, getStrokeWidth() / 2); PolyUtil.ClosestResult result = PolyUtil.getClosestPoint(loc, closed, handles); return result.getDistanceSq() < thresh * thresh; } else if (type == DrawAttr.PAINT_FILL) { GeneralPath path = getPath(); return path.contains(loc.getX(), loc.getY()); } else { // fill and stroke GeneralPath path = getPath(); if (path.contains(loc.getX(), loc.getY())) return true; int width = getStrokeWidth(); PolyUtil.ClosestResult result = PolyUtil.getClosestPoint(loc, closed, handles); return result.getDistanceSq() < (width * width) / 4; } } @Override public final Location getRandomPoint(Bounds bds, Random rand) { if (getPaintType() == DrawAttr.PAINT_STROKE) { Location ret = getRandomBoundaryPoint(bds, rand); int w = getStrokeWidth(); if (w > 1) { int dx = rand.nextInt(w) - w / 2; int dy = rand.nextInt(w) - w / 2; ret = ret.translate(dx, dy); } return ret; } else { return super.getRandomPoint(bds, rand); } } private Location getRandomBoundaryPoint(Bounds bds, Random rand) { Handle[] hs = handles; double[] ls = lens; if (ls == null) { ls = new double[hs.length + (closed ? 1 : 0)]; double total = 0.0; for (int i = 0; i < ls.length; i++) { int j = (i + 1) % hs.length; total += LineUtil.distance(hs[i].getX(), hs[i].getY(), hs[j].getX(), hs[j].getY()); ls[i] = total; } lens = ls; } double pos = ls[ls.length - 1] * rand.nextDouble(); for (int i = 0; true; i++) { if (pos < ls[i]) { Handle p = hs[i]; Handle q = hs[(i + 1) % hs.length]; double u = Math.random(); int x = (int) Math.round(p.getX() + u * (q.getX() - p.getX())); int y = (int) Math.round(p.getY() + u * (q.getY() - p.getY())); return Location.create(x, y); } } } @Override public Bounds getBounds() { return bounds; } @Override public void translate(int dx, int dy) { Handle[] hs = handles; Handle[] is = new Handle[hs.length]; for (int i = 0; i < hs.length; i++) { is[i] = new Handle(this, hs[i].getX() + dx, hs[i].getY() + dy); } setHandles(is); } public boolean isClosed() { return closed; } @Override public List<Handle> getHandles(HandleGesture gesture) { Handle[] hs = handles; if (gesture == null) { return UnmodifiableList.decorate(Arrays.asList(hs)); } else { Handle g = gesture.getHandle(); Handle[] ret = new Handle[hs.length]; for (int i = 0, n = hs.length; i < n; i++) { Handle h = hs[i]; if (h.equals(g)) { int x = h.getX() + gesture.getDeltaX(); int y = h.getY() + gesture.getDeltaY(); Location r; if (gesture.isShiftDown()) { Location prev = hs[(i + n - 1) % n].getLocation(); Location next = hs[(i + 1) % n].getLocation(); if (!closed) { if (i == 0) prev = null; if (i == n - 1) next = null; } if (prev == null) { r = LineUtil.snapTo8Cardinals(next, x, y); } else if (next == null) { r = LineUtil.snapTo8Cardinals(prev, x, y); } else { Location to = Location.create(x, y); Location a = LineUtil.snapTo8Cardinals(prev, x, y); Location b = LineUtil.snapTo8Cardinals(next, x, y); int ad = a.manhattanDistanceTo(to); int bd = b.manhattanDistanceTo(to); r = ad < bd ? a : b; } } else { r = Location.create(x, y); } ret[i] = new Handle(this, r); } else { ret[i] = h; } } return UnmodifiableList.decorate(Arrays.asList(ret)); } } @Override public boolean canMoveHandle(Handle handle) { return true; } @Override public Handle moveHandle(HandleGesture gesture) { List<Handle> hs = getHandles(gesture); Handle[] is = new Handle[hs.size()]; Handle ret = null; int i = -1; for (Handle h : hs) { i++; is[i] = h; } setHandles(is); return ret; } @Override public Handle canInsertHandle(Location loc) { PolyUtil.ClosestResult result = PolyUtil.getClosestPoint(loc, closed, handles); int thresh = Math.max(Line.ON_LINE_THRESH, getStrokeWidth() / 2); if (result.getDistanceSq() < thresh * thresh) { Location resLoc = result.getLocation(); if (result.getPreviousHandle().isAt(resLoc) || result.getNextHandle().isAt(resLoc)) { return null; } else { return new Handle(this, result.getLocation()); } } else { return null; } } @Override public Handle canDeleteHandle(Location loc) { int minHandles = closed ? 3 : 2; Handle[] hs = handles; if (hs.length <= minHandles) { return null; } else { int qx = loc.getX(); int qy = loc.getY(); int w = Math.max(Line.ON_LINE_THRESH, getStrokeWidth() / 2); for (Handle h : hs) { int hx = h.getX(); int hy = h.getY(); if (LineUtil.distance(qx, qy, hx, hy) < w * w) { return h; } } return null; } } @Override public void insertHandle(Handle desired, Handle previous) { Location loc = desired.getLocation(); Handle[] hs = handles; Handle prev; if (previous == null) { PolyUtil.ClosestResult result = PolyUtil.getClosestPoint(loc, closed, hs); prev = result.getPreviousHandle(); } else { prev = previous; } Handle[] is = new Handle[hs.length + 1]; boolean inserted = false; for (int i = 0; i < hs.length; i++) { if (inserted) { is[i + 1] = hs[i]; } else if (hs[i].equals(prev)) { inserted = true; is[i] = hs[i]; is[i + 1] = desired; } else { is[i] = hs[i]; } } if (!inserted) { throw new IllegalArgumentException("no such handle"); } setHandles(is); } @Override public Handle deleteHandle(Handle handle) { Handle[] hs = handles; int n = hs.length; Handle[] is = new Handle[n - 1]; Handle previous = null; boolean deleted = false; for (int i = 0; i < n; i++) { if (deleted) { is[i - 1] = hs[i]; } else if (hs[i].equals(handle)) { if (previous == null) { previous = hs[n - 1]; } deleted = true; } else { previous = hs[i]; is[i] = hs[i]; } } setHandles(is); return previous; } @Override public void paint(Graphics g, HandleGesture gesture) { List<Handle> hs = getHandles(gesture); int[] xs = new int[hs.size()]; int[] ys = new int[hs.size()]; int i = -1; for (Handle h : hs) { i++; xs[i] = h.getX(); ys[i] = h.getY(); } if (setForFill(g)) { g.fillPolygon(xs, ys, xs.length); } if (setForStroke(g)) { if (closed) g.drawPolygon(xs, ys, xs.length); else g.drawPolyline(xs, ys, xs.length); } } private void setHandles(Handle[] hs) { handles = hs; lens = null; path = null; recomputeBounds(); } private void recomputeBounds() { Handle[] hs = handles; int x0 = hs[0].getX(); int y0 = hs[0].getY(); int x1 = x0; int y1 = y0; for (int i = 1; i < hs.length; i++) { int x = hs[i].getX(); int y = hs[i].getY(); if (x < x0) x0 = x; if (x > x1) x1 = x; if (y < y0) y0 = y; if (y > y1) y1 = y; } Bounds bds = Bounds.create(x0, y0, x1 - x0 + 1, y1 - y0 + 1); int stroke = getStrokeWidth(); bounds = stroke < 2 ? bds : bds.expand(stroke / 2); } private GeneralPath getPath() { GeneralPath p = path; if (p == null) { p = new GeneralPath(); Handle[] hs = handles; if (hs.length > 0) { boolean first = true; for (Handle h : hs) { if (first) { p.moveTo(h.getX(), h.getY()); first = false; } else { p.lineTo(h.getX(), h.getY()); } } } path = p; } return p; } }