package com.jpaulmorrison.graphics; import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.util.*; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import com.jpaulmorrison.graphics.DrawFBP.Side; public class Arrow implements ActionListener { DrawFBP driver; int fromX, fromY, toX, toY; int lastX, lastY; // "last" x and y int fromId, toId, id = 0; boolean endsAtBlock, endsAtLine; LinkedList<Bend> bends; String upStreamPort, downStreamPort; //String uspMod; // upstream port after lowercasing String dspMod; // downStreamPort after lowercasing //DrawFBP.Side fromSide, toSide; boolean deleteOnSave = false; static Color FOREST_GREEN = new Color(34, 139, 34); static Color ORANGE_RED = new Color(255, 69, 0); boolean headMarked, tailMarked; boolean dropOldest; int capacity; //LegendBlock capLegend; //Legend block associated with Arrow //Arrowhead tipArrowhead = null; Arrowhead extraArrowhead = null; enum Status { UNCHECKED, COMPATIBLE, INCOMPATIBLE } Status checkStatus = Status.UNCHECKED; Diagram diag; Arrow(Diagram d) { super(); endsAtBlock = false; endsAtLine = false; bends = null; upStreamPort = null; downStreamPort = null; toX = -1; //OK! toY = -1; toId = -1; diag = d; driver = d.driver; diag.maxArrowNo++; id = diag.maxArrowNo; //uspMod = null; dspMod = null; capacity = -1; } void draw(Graphics2D g) { int endX, endY; Block from = null; if (fromId > -1) { from = diag.blocks.get(new Integer(fromId)); } Block to = null; if (!endsAtLine && toId > -1) { to = diag.blocks.get(new Integer(toId)); } if (toX == -1) endX = diag.xa; else endX = toX; if (toY == -1) endY = diag.ya; else endY = toY; g.setColor(Color.GRAY); Stroke stroke = g.getStroke(); ZigzagStroke zzstroke = new ZigzagStroke(stroke, 2, 4); if (toX == -1) { g.drawRect(fromX - 3, fromY - 3, 6, 6); return; } if (driver.selArrow == this) g.setColor(Color.BLUE); else if ((from instanceof ProcessBlock || from instanceof ExtPortBlock || from instanceof Enclosure) && (to instanceof ProcessBlock || to instanceof ExtPortBlock || to instanceof Enclosure || endsAtLine)) if (checkStatus == Status.UNCHECKED) g.setColor(Color.BLACK); else if (checkStatus == Status.COMPATIBLE) g.setColor(FOREST_GREEN); else g.setColor(ORANGE_RED); else if (from instanceof LegendBlock || to instanceof LegendBlock) g.setColor(Color.GRAY); int fx, fy, tx, ty; fx = fromX; fy = fromY; //tx = toX; //ty = toY; //int autoX = -1, autoY = -1; // only used for automatic ports if (bends != null) { for (Bend bend : bends) { tx = bend.x; ty = bend.y; if (!dropOldest) g.drawLine(fx, fy, tx, ty); else { Shape shape = new Line2D.Double(fx, fy, tx, ty); shape = zzstroke.createStrokedShape(shape); g.draw(shape); } if (capacity > 0) { int x = (fx + tx) / 2; int y = (fy + ty) / 2; String s = "(" + capacity + ")"; x -= s.length() / 2; g.drawString(s, x, y + 12); } if (bend.marked) { Color col = g.getColor(); g.setColor(Color.RED); g.drawOval(tx - 5, ty - 5, 10, 10); g.setColor(col); } calcLimits(fx, tx, fy, ty); fx = tx; fy = ty; } } else { if (capacity > 0) { int x = (fx + endX) / 2; int y = (fy + endY) / 2; String s = "(" + capacity + ")"; x -= s.length() * driver.gFontWidth / 2; g.drawString(s, x, y + 12); calcLimits(x, x + s.length() * driver.gFontWidth, y + 12, y + 12 + driver.gFontHeight); } } tx = endX; ty = endY; calcLimits(fx, tx, fy, ty); int x = endX; if (to != null && endsAtBlock && to.multiplex) { String s = to.mpxfactor; if (s == null) s = " "; int i = s.length() * driver.gFontWidth + 10; x -= i; } if (headMarked) { Color col = g.getColor(); g.setColor(Color.RED); g.drawOval(x - 5, toY - 5, 10, 10); g.setColor(col); } if (!dropOldest) g.drawLine(fx, fy, tx, ty); else { Shape shape = new Line2D.Double(fx, fy, tx, ty); shape = zzstroke.createStrokedShape(shape); g.draw(shape); // g.setStroke(stroke); } if (tailMarked) { Color col = g.getColor(); g.setColor(Color.RED); g.drawOval(fromX - 5, fromY - 5, 10, 10); g.setColor(col); } calcLimits(fx, x, fy, toY); if (!endsAtBlock && !endsAtLine) { //g.drawRect(fromX - 3, fromY - 3, 6, 6); g.drawRect(x - 3, toY - 3, 6, 6); } else if (endsAtBlock) { if ((from instanceof ProcessBlock || from instanceof ExtPortBlock || from instanceof Enclosure || from instanceof IIPBlock) && to != null && (to instanceof ProcessBlock || to instanceof ExtPortBlock || to instanceof Enclosure)) { Arrowhead ah = new Arrowhead(fx, fy, toX, toY); ah.draw(g); } } else if (endsAtLine) { drawCircleTo(g, fx, fy, x, toY, Color.BLACK, 4); // g.drawOval(toX - 2, toY - 2, 4, 4); // g.fillOval(toX - 2, toY - 2, 4, 4); } if (toX != -1 && (endsAtBlock || endsAtLine)) { if (upStreamPort != null && (from instanceof ProcessBlock || from instanceof Enclosure)) { if (upStreamPort.equals("*")) { drawCircleFrom(g, fromX, fromY, endX, endY, Color.BLUE, 8); } else if (from.visible) { g.setColor(Color.BLUE); int y = fromY + driver.gFontHeight; int x2 = fromX + driver.gFontWidth; g.drawString(upStreamPort, x2, y); } g.setColor(Color.BLACK); } if (downStreamPort != null && !endsAtLine && to != null && (to instanceof ProcessBlock || to instanceof Enclosure)) { if (downStreamPort.equals("*")) { drawCircleTo(g, fx, fy, toX, toY, Color.BLUE, 8); } else if (to.visible) { g.setColor(Color.BLUE); int y = toY - driver.gFontHeight / 2; x = toX - driver.gFontWidth * (downStreamPort.length() + 1); if (!endsAtLine && to != null && to.multiplex) x -= 20; g.drawString(downStreamPort, x, y); } g.setColor(Color.BLACK); } } if (extraArrowhead != null) extraArrowhead.draw(g); } void calcLimits(int x1, int x2, int y1, int y2) { if (x1 < x2) { diag.maxX = Math.max(x2 + 20, diag.maxX); diag.minX = Math.min(x1 - 20, diag.minX); } else { diag.maxX = Math.max(x1 + 20, diag.maxX); diag.minX = Math.min(x2 - 20, diag.minX); } if (y1 < y2) { diag.maxY = Math.max(y2 + 20, diag.maxY); diag.minY = Math.min(y1 - 20, diag.minY); } else { diag.maxY = Math.max(y1 + 20, diag.maxY); diag.minY = Math.min(y2 - 20, diag.minY); } } void drawCircleFrom(Graphics2D g, int fx, int fy, int tx, int ty, Color color, int size) { Color col = g.getColor(); g.setColor(color); int x, y; if (fx == tx) { // vertical line x = fx; if (ty > fy) y = fy + size / 2; else y = fy - size / 2; } else { // assume horizontal for now y = fy; if (tx > fx) x = fx + size / 2; else x = fx - size / 2; } // x and y are the centre of the circle // adjust to top left corner x -= size / 2; y -= size / 2; g.drawOval(x, y, size, size); g.fillOval(x, y, size, size); g.setColor(col); } void drawCircleTo(Graphics2D g, int fx, int fy, int tx, int ty, Color color, int size) { Color col = g.getColor(); g.setColor(color); int x, y; x = tx; y = ty; if (color == Color.BLUE) { if (fx == tx) { // vertical line if (ty > fy) y = ty - size / 2; else y = ty + size / 2; } else { if (tx > fx) x = tx - size / 2; else x = tx + size / 2; } } // x and y are the centre of the circle // adjust to top left corner x -= size / 2; y -= size / 2; g.drawOval(x, y, size, size); g.fillOval(x, y, size, size); g.setColor(col); } String serialize() { String s = "<connection> <fromx>" + fromX + "</fromx> " + "<fromy>" + fromY + "</fromy> " + "<tox>" + toX + "</tox> " + "<toy>" + toY + "</toy> " + "<fromid>" + fromId + "</fromid> " + "<toid>" + toId + "</toid> " + "<id>" + id + "</id> "; //if (endsAtLine) s += "<endsatline>" + (endsAtLine?"true":"false") + "</endsatline>"; if (upStreamPort != null) { s += "<upstreamport>" + upStreamPort + "</upstreamport>"; } if (downStreamPort != null) { s += "<downstreamport>" + downStreamPort + "</downstreamport>"; } if (dropOldest) { s += "<dropoldest>" + (dropOldest?"true":"false") + "</dropOldest>"; } if (capacity > 0) s += "<capacity>" + capacity + "</capacity>"; if (bends != null) { s += "<bends> "; for (Bend bend : bends) { s += "<bend> <x>" + bend.x + "</x> <y> " + bend.y + "</y> </bend>\n "; } s += "</bends> "; } s += "</connection> \n"; return s; } void buildArrow(HashMap<String, String> item) { String s; s = item.get("fromx").trim(); fromX = Integer.parseInt(s); s = item.get("fromy").trim(); fromY = Integer.parseInt(s); s = item.get("tox").trim(); toX = Integer.parseInt(s); s = item.get("toy").trim(); toY = Integer.parseInt(s); upStreamPort = item.get("upstreamport"); downStreamPort = item.get("downstreamport"); s = item.get("endsatline"); if (s != null && s.equals("true")) endsAtLine = true; endsAtBlock = !endsAtLine; s = item.get("dropoldest"); if (s != null && s.equals("true")) dropOldest = true; s = item.get("capacity"); if (s != null) capacity = Integer.parseInt(s); s = item.get("fromid").trim(); fromId = Integer.parseInt(s); s = item.get("toid").trim(); toId = Integer.parseInt(s); s = item.get("id"); if (s == null) id = 0; else id = Integer.parseInt(s.trim()); if (id == 0) id = diag.maxArrowNo + 1; diag.maxArrowNo = Math.max(id, diag.maxArrowNo); } void buildArrowPopupMenu() { diag.jpm = new JPopupMenu(" Arrow-related Actions"); // driver.curPopup = jpm; //diag.jpm.setLocation(fromX + 100, fromY + 100); diag.jpm.setVisible(true); JLabel label2 = new JLabel(); label2.setText(diag.jpm.getLabel()); label2.setFont(driver.fontg); // label2.setForeground(Color.BLUE); diag.jpm.add(label2); diag.jpm.addSeparator(); JMenuItem menuItem; Block from = diag.blocks.get(new Integer(fromId)); Block to = diag.blocks.get(new Integer(toId)); if (!(from instanceof FileBlock || from instanceof PersonBlock || from instanceof ReportBlock || from instanceof LegendBlock || to instanceof FileBlock || to instanceof PersonBlock || to instanceof ReportBlock || to instanceof LegendBlock ) ) { if (!(from instanceof ExtPortBlock) && !(from instanceof IIPBlock)) { menuItem = new JMenuItem("Edit Upstream Port Name"); menuItem.addActionListener(this); diag.jpm.add(menuItem); } if (!(to instanceof ExtPortBlock)) { menuItem = new JMenuItem("Edit Downstream Port Name"); menuItem.addActionListener(this); diag.jpm.add(menuItem); } diag.jpm.addSeparator(); menuItem = new JMenuItem("Toggle Upstream Port Automatic / Normal"); menuItem.addActionListener(this); diag.jpm.add(menuItem); menuItem = new JMenuItem("Toggle Downstream Port Automatic / Normal"); menuItem.addActionListener(this); diag.jpm.add(menuItem); } diag.jpm.addSeparator(); menuItem = new JMenuItem("Set Capacity"); menuItem.addActionListener(this); diag.jpm.add(menuItem); menuItem = new JMenuItem("Remove Capacity"); menuItem.addActionListener(this); diag.jpm.add(menuItem); diag.jpm.addSeparator(); menuItem = new JMenuItem("Toggle DropOldest"); menuItem.addActionListener(this); diag.jpm.add(menuItem); diag.jpm.addSeparator(); menuItem = new JMenuItem("Drag Tail"); menuItem.addActionListener(this); diag.jpm.add(menuItem); menuItem = new JMenuItem("Drag Head"); menuItem.addActionListener(this); diag.jpm.add(menuItem); menuItem = new JMenuItem("Drag New or Existing Bend"); menuItem.addActionListener(this); diag.jpm.add(menuItem); diag.jpm.addSeparator(); menuItem = new JMenuItem("Add Extra Arrowhead"); menuItem.addActionListener(this); diag.jpm.add(menuItem); menuItem = new JMenuItem("Remove Extra Arrowhead"); menuItem.addActionListener(this); diag.jpm.add(menuItem); diag.jpm.addSeparator(); menuItem = new JMenuItem("Delete"); diag.jpm.add(menuItem); menuItem.addActionListener(this); } public void actionPerformed(ActionEvent e) { String s = e.getActionCommand(); diag.jpm = null; if (s.equals("Edit Upstream Port Name")) { String ans = (String) MyOptionPane.showInputDialog(driver.frame, "Enter or change text", "Edit upstream port name", MyOptionPane.PLAIN_MESSAGE, null, null, upStreamPort); if (ans != null /* && ans.length() > 0*/ ) { Block b = diag.blocks.get(new Integer(fromId)); // upStreamPort = ans; diag.changed = true; boolean found = false; for (Arrow a : diag.arrows.values()) { if (a.fromId == fromId && a.upStreamPort != null && a.upStreamPort.equals(ans) && !(upStreamPort.equals(ans)) || a.toId == fromId && a.downStreamPort != null && a.downStreamPort.equals(ans)) found = true; } if (found) { String proc = driver.curDiag.blocks.get(fromId).description; MyOptionPane.showMessageDialog(driver.frame, "Duplicate port name: " + proc + "." + ans, MyOptionPane.WARNING_MESSAGE); upStreamPort = ""; return; } upStreamPort = ans; if (b.type.equals(Block.Types.EXTPORT_IN_BLOCK) || b instanceof IIPBlock) { MyOptionPane.showMessageDialog(driver.frame, "Upstream port must be blank", MyOptionPane.ERROR_MESSAGE); upStreamPort = ""; } } driver.frame.repaint(); } else if (s.equals("Edit Downstream Port Name") && endsAtBlock) { String ans = (String) MyOptionPane.showInputDialog(driver.frame, "Enter or change text", "Edit downstream port name", MyOptionPane.PLAIN_MESSAGE, null, null, downStreamPort); if (ans != null /* && ans.length() > 0 */) { Block b = diag.blocks.get(new Integer(toId)); diag.changed = true; boolean found = false; for (Arrow a : diag.arrows.values()) { if (a.fromId == toId && a.upStreamPort != null && a.upStreamPort.equals(ans) || a.toId == toId && a.downStreamPort != null && a.downStreamPort.equals(ans) && !(downStreamPort.equals(ans))) found = true; } if (found) { String proc = driver.curDiag.blocks.get(toId).description; MyOptionPane.showMessageDialog(driver.frame, "Duplicate port name: " + proc + "." + ans, MyOptionPane.WARNING_MESSAGE); downStreamPort = ""; return; } downStreamPort = ans; if (b.type.equals(Block.Types.EXTPORT_OUT_BLOCK)) { MyOptionPane.showMessageDialog(driver.frame, "Downstream port must be blank", MyOptionPane.ERROR_MESSAGE); downStreamPort = ""; } } driver.frame.repaint(); } else if (s.equals("Set Capacity")) { String capString = null; if (capacity < 1) capString = ""; else capString = Integer.toString(capacity); String ans = (String) MyOptionPane.showInputDialog(driver.frame, "Enter or change text", "Set Capacity", MyOptionPane.PLAIN_MESSAGE, null, null, capString); if ((ans != null) && (ans.length() > 0)) { try { capacity = Integer.parseInt(ans); } catch (NumberFormatException e2) { MyOptionPane.showMessageDialog(driver.frame, "Capacity must be numeric", MyOptionPane.ERROR_MESSAGE); return; } /* if (capLegend == null) { diag.xa = 2; // get around fudge in DrawFBP diag.ya = 2; // get around fudge in DrawFBP capLegend = (LegendBlock) diag.driver.createBlock( Block.Types.LEGEND_BLOCK, false); int x = toX; int y = toY; if (bends != null) { Bend bd = bends.peek(); x = bd.x; y = bd.y; } capLegend.cx = (fromX + x) / 2; capLegend.cy = (fromY + y) / 2 + 20; if (fromX == x) capLegend.cx -= 20; } capLegend.description = "(" + capacity + ")"; */ } driver.frame.repaint(); diag.changed = true; } else if (s.equals("Remove Capacity")) { capacity = -1; driver.frame.repaint(); diag.changed = true; } else if (s.equals("Toggle Upstream Port Automatic / Normal")) { if (upStreamPort == null || !upStreamPort.equals("*")) upStreamPort = "*"; else upStreamPort = null; driver.frame.repaint(); diag.changed = true; } else if (s.equals("Toggle Downstream Port Automatic / Normal")) { if (downStreamPort == null || !downStreamPort.equals("*")) downStreamPort = "*"; else downStreamPort = null; driver.frame.repaint(); diag.changed = true; } else if (s.equals("Toggle DropOldest")) { dropOldest = !dropOldest; } else if (s.equals("Drag Tail")) { tailMarked = true; driver.arrowEndForDragging = this; } else if (s.equals("Drag Head")) { headMarked = true; driver.arrowEndForDragging = this; } else if (s.equals("Drag New or Existing Bend")) { createBend(driver.curx, driver.cury); diag.changed = true; } else if (s.equals("Add Extra Arrowhead")) { Point p = new Point(driver.curx, driver.cury); int fx = fromX; int fy = fromY; int tx, ty; if (bends != null) { for (Bend bend : bends) { tx = bend.x; ty = bend.y; if (pointInLine(p, fx, fy, tx, ty)) { extraArrowhead = new Arrowhead(fx, fy, driver.curx, driver.cury); return; } fx = tx; fy = ty; } } tx = toX; ty = toY; if (pointInLine(p, fx, fy, tx, ty)) extraArrowhead = new Arrowhead(fx, fy, driver.curx, driver.cury); diag.changed = true; return; } else if (s.equals("Remove Extra Arrowhead")) { extraArrowhead = null; diag.changed = true; return; } else if (s.equals("Delete")) { if (MyOptionPane.YES_OPTION == MyOptionPane.showConfirmDialog( driver.frame, "Do you want to delete this arrow?", "Delete arrow", MyOptionPane.YES_NO_OPTION)) { diag.delArrow(this); diag.changed = true; diag.currentArrow = null; } driver.frame.repaint(); diag.foundArrow = null; diag.changed = true; } } void createBend(int bendx, int bendy) { Bend bn = null; int index = 0; if (bends == null) { if (driver.nearpln(bendx, bendy, fromX, fromY, toX, toY)) { bends = new LinkedList<Bend>(); bn = new Bend(bendx, bendy); if (fromX == toX) // if line vertical bn.x = fromX; if (fromY == toY) // if line horizontal bn.y = fromY; bends.add(bn); bn.marked = true; driver.bendForDragging = bn; return; } } else { int x = fromX; int y = fromY; Object[] oa = bends.toArray(); for (Object o : oa) { Bend b = (Bend) o; if (sameBend(bendx, bendy, b)) { bn = b; bn.marked = true; driver.bendForDragging = bn; return; } if (driver.nearpln(bendx, bendy, x, y, b.x, b.y)) { bn = new Bend(bendx, bendy); if (x == b.x) // if line vertical bn.x = x; if (y == b.y) // if line horizontal bn.y = y; bends.add(index, bn); bn.marked = true; driver.bendForDragging = bn; return; } x = b.x; y = b.y; index++; } if (driver.nearpln(bendx, bendy, x, y, toX, toY)) { bn = new Bend(bendx, bendy); if (x == toX) // if line vertical bn.x = x; if (y == toY) // if line horizontal bn.y = y; bends.add(bn); bn.marked = true; driver.bendForDragging = bn; } } } boolean sameBend(int x1, int y1, Bend b) { return ((x1 - b.x) * (x1 - b.x) + (y1 - b.y) * (y1 - b.y)) < 6 * 6; } boolean pointInLine(Point p, int fx, int fy, int tx, int ty){ double d = Line2D.ptLineDist((double) fx, (double) fy, (double) tx, (double) ty, (double) p.x, (double) p.y); return d <= 6.0; } Arrow findArrowEndingAtBlock() { if (endsAtBlock) return this; int id = toId; // not a block, so toId must be a line ID while (true) { for (Arrow arrow : diag.arrows.values()) { if (id == arrow.id) { if (arrow.endsAtBlock) return arrow; id = arrow.toId; break; } } if (id == toId) { //MyOptionPane.showMessageDialog(driver.frame, // "Can't find connecting arrow", MyOptionPane.ERROR_MESSAGE); return null; } } } boolean touches(Block b, int x, int y) { DrawFBP.Side side = null; if (driver.nearpln(x, y, b.cx - b.width / 2, b.cy - b.height / 2, b.cx - b.width / 2, b.cy + b.height / 2)) { side = Side.LEFT; } if (driver.nearpln(x, y, b.cx - b.width / 2, b.cy - b.height / 2, b.cx + b.width / 2, b.cy - b.height / 2)) { side = Side.TOP; } if (driver.nearpln(x, y, b.cx + b.width / 2, b.cy - b.height / 2, b.cx + b.width / 2, b.cy + b.height / 2)) { side = Side.RIGHT; } if (driver.nearpln(x, y, b.cx - b.width / 2, b.cy + b.height / 2, b.cx + b.width / 2, b.cy + b.height / 2)) { side = Side.BOTTOM; } if (side != null) { //if (tailMarked) // fromSide = side; //if (headMarked) // toSide = side; return true; } return false; } boolean checkSides() { Block from = diag.blocks.get(new Integer(fromId)); Block to = diag.blocks.get(new Integer(toId)); if (!(from instanceof ProcessBlock) && !(from instanceof ExtPortBlock)) return true; if (!(to instanceof ProcessBlock) && !(to instanceof ExtPortBlock)) return true; // if (fromSide == DrawFBP.Side.LEFT || fromSide == DrawFBP.Side.TOP) // return false; // if (toSide == DrawFBP.Side.BOTTOM) // return false; return true; } void reverseDirection() { int x, y, id; x = toX; toX = fromX; fromX = x; y = toY; toY = fromY; fromY = y; id = toId; toId = fromId; fromId = id; //DrawFBP.Side s = toSide; //toSide = fromSide; //fromSide = s; String st = upStreamPort; upStreamPort = downStreamPort; downStreamPort = st; } Arrow makeCopy(Diagram d) { Arrow arr = new Arrow(d); arr.fromX = this.fromX; arr.fromY = this.fromY; arr.toX = this.toX; arr.toY = this.toY; arr.lastX = this.lastX; arr.lastY = this.lastY; arr.fromId = this.fromId; arr.toId = this.toId; arr.id = this.id; arr.capacity = this.capacity; arr.endsAtBlock = this.endsAtBlock; arr.endsAtLine = this.endsAtLine; if (this.bends != null) { arr.bends = new LinkedList<Bend>(); for (Bend b : this.bends) { Bend b2 = new Bend(b.x, b.y); arr.bends.add(b2); } } arr.upStreamPort = this.upStreamPort; arr.downStreamPort = this.downStreamPort; //arr.fromSide = this.fromSide; //arr.toSide = this.toSide; arr.diag = d; return arr; } /* Thanks to Jerry Huxtable * http://www.jhlabs.com/java/java2d/strokes/ */ public class ZigzagStroke implements Stroke { private float amplitude = 10.0f; private float wavelength = 10.0f; private static final float FLATNESS = 1; public ZigzagStroke( Stroke stroke, float amplitude, float wavelength ) { this.amplitude = amplitude; this.wavelength = wavelength; } public Shape createStrokedShape( Shape shape ) { GeneralPath result = new GeneralPath(); PathIterator it = new FlatteningPathIterator( shape.getPathIterator( null ), FLATNESS ); float points[] = new float[6]; float moveX = 0, moveY = 0; float lastX = 0, lastY = 0; float thisX = 0, thisY = 0; int type = 0; float next = 0; int phase = 0; while ( !it.isDone() ) { type = it.currentSegment( points ); switch( type ){ case PathIterator.SEG_MOVETO: moveX = lastX = points[0]; moveY = lastY = points[1]; result.moveTo( moveX, moveY ); next = wavelength/2; break; case PathIterator.SEG_CLOSE: points[0] = moveX; points[1] = moveY; // Fall into.... case PathIterator.SEG_LINETO: thisX = points[0]; thisY = points[1]; float dx = thisX-lastX; float dy = thisY-lastY; float distance = (float)Math.sqrt( dx*dx + dy*dy ); if ( distance >= next ) { float r = 1.0f / distance; while ( distance >= next ) { float x = lastX + next*dx*r; float y = lastY + next*dy*r; if ( (phase & 1) == 0 ) result.lineTo( x+amplitude*dy*r, y-amplitude*dx*r ); else result.lineTo( x-amplitude*dy*r, y+amplitude*dx*r ); next += wavelength; phase++; } } next -= distance; lastX = thisX; lastY = thisY; if ( type == PathIterator.SEG_CLOSE ) result.closePath(); break; } it.next(); } //return stroke.createStrokedShape( result ); return result; } } class Arrowhead { int fx, fy, toX, toY; Arrowhead(int fx, int fy, int toX, int toY) { this.fx = fx; this.fy = fy; this.toX = toX; this.toY = toY; } void draw(Graphics2D g) { // toX/toY is tip of arrow and fx/fy is a point on the line - // fx/fy is used to determine direction & angle AffineTransform at = AffineTransform.getTranslateInstance(toX, toY); int b = 9; double theta = Math.toRadians(20); // The idea of using a GeneralPath is so we can // create the (three lines that make up the) arrow // (only) one time and then use AffineTransform to // place it anywhere we want. GeneralPath path = new GeneralPath(); // distance between line and the arrow mark <** not ** // Start a new line segment from the position of (0,0). path.moveTo(0, 0); // Create one of the two arrow head lines. int x = (int) (-b * Math.cos(theta)); int y = (int) (b * Math.sin(theta)); path.lineTo(x, y); // distance between line and the arrow mark <** not ** // Make the other arrow head line. int x2 = (int) (-b * Math.cos(-theta)); int y2 = (int) (b * Math.sin(-theta)); // path.moveTo(0,0); path.lineTo(x2, y2); path.closePath(); // theta is in radians double s, t; s = toY - fy; // calculate slopes. t = toX - fx; if (t != 0) { s = s / t; theta = Math.atan(s); if (t < 0) theta += Math.PI; } else if (s < 0) theta = -(Math.PI / 2); else theta = Math.PI / 2; at.rotate(theta); // at.rotate(theta,toX,toY); Shape shape = at.createTransformedShape(path); if (checkStatus == Status.UNCHECKED) g.setColor(Color.BLACK); else if (checkStatus == Status.COMPATIBLE) g.setColor(FOREST_GREEN); else g.setColor(ORANGE_RED); g.fill(shape); g.draw(shape); } } }