// File: FigConstEdge.java // Classes: FigConstEdge // Original Author: Kedar Patankar // $Id: FigConstEdge.java,v 1.1.1.1 1997/02/27 20:52:34 chandra Exp $ package uci.graphedit; import java.awt.Point; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Polygon; import java.awt.Color; import java.awt.FontMetrics; import java.util.Hashtable; /** Class to implement lines in diagrams. */ public class FigConstEdge extends Fig { public static final int FUDGE_FACTOR = 4; private Point _destIntersection,_sourceIntersection; private Point _destShaftend,_sourceShaftend,_diagonalEnd; private String _sourceTowards,_arrowEdge; double ArrowAngle=Math.PI/6.0; double ArrowShaft = 12; private double angle=0.0; private String _edge_label; private String _card_label; public FigConstEdge(Integer x, Integer y, Integer r_width, Integer r_height, Color l_color, String label, String cardinality) { this(x.intValue(), y.intValue(), r_width.intValue(), r_height.intValue(), l_color,label,cardinality); } public FigConstEdge(int x, int y, int r_width, int r_height, Color l_color, String label, String cardinality) { super(x, y, r_width, r_height, l_color, null, 2); _card_label = cardinality; _edge_label = label; } /** Construct a new FigConstEdge with the given coordinates and attributes. */ public FigConstEdge(int x,int y,int r_width,int r_height, Hashtable gAttrs){ super(x, y, r_width, r_height, Color.black, null, 2); setGraphicAttributes(gAttrs); } public void set_label(String name) { _edge_label = name;} public void set_cardinality(String card){_card_label = card;} public String get_label(){ return _edge_label;} public String get_cardinality(){ return _card_label;} public void set_position(Point src, Point dst) { position().x = src.x; position().y = src.y; objectWidth = dst.x; objectHeight = dst.y; } public void draw (Graphics g, Color l_color,Point s,Point d) { Color old = objectLineColor; objectLineColor = l_color; draw(g,s,d); objectLineColor = old; } public void draw(Graphics g,Point sourceSize,Point destSize) { Point source=position(); Point dest=new Point(objectWidth,objectHeight); _destIntersection=findIntersection(source,dest,destSize); if (_destIntersection==null) { return;} _arrowEdge=_sourceTowards; int x,y; if((source.x-dest.x)>=0) { x=(int)(_destIntersection.x+(ArrowShaft*Math.cos(angle))); y=(int)(_destIntersection.y-(ArrowShaft*Math.sin(angle))); _destShaftend=new Point(x,y); } else { x=(int)(_destIntersection.x-(ArrowShaft*Math.cos(angle))); y=(int)(_destIntersection.y+(ArrowShaft*Math.sin(angle))); _destShaftend=new Point(x,y); } _sourceIntersection=findIntersection(dest,source,sourceSize); if (_sourceIntersection==null) { return;} if((dest.x-source.x)>=0) { x=(int)(_sourceIntersection.x+(ArrowShaft*Math.cos(angle))); y=(int)(_sourceIntersection.y-(ArrowShaft*Math.sin(angle))); _sourceShaftend=new Point(x,y); } else { x=(int)(_sourceIntersection.x-(ArrowShaft*Math.cos(angle))); y=(int)(_sourceIntersection.y+(ArrowShaft*Math.sin(angle))); _sourceShaftend=new Point(x,y); } double diagonal=2*ArrowShaft*Math.cos(ArrowAngle); if((dest.x-source.x)>=0) { x=(int)(_sourceIntersection.x+(diagonal*Math.cos(angle))); y=(int)(_sourceIntersection.y-(diagonal*Math.sin(angle))); _diagonalEnd=new Point(x,y); } else { x=(int)(_sourceIntersection.x-(diagonal*Math.cos(angle))); y=(int)(_sourceIntersection.y+(diagonal*Math.sin(angle))); _diagonalEnd=new Point(x,y); } draw(g); } private Point findIntersection(Point source,Point dest,Point destSize) { double dX = source.x - dest.x; double dY = dest.y - source.y; double x1 = dest.x; double y1 = dest.y; double width = destSize.x/2.0; double height = destSize.y/2.0; Point intersection=null; if((dX==0) && (dY==0)) return intersection; if(dX!=0) angle=Math.atan(dY/dX); else { if(dY > 0) angle=Math.PI/2.0; else angle= -Math.PI/2.0; } double limit = Math.atan(height/width); if (dX>=0) // The right half of the rectangle { int x,y; if(Math.abs(angle)<=limit) // covers -limit<= angle <=limit { x=(int)(x1+width); y=(int)(y1-(width*dY/dX)); intersection = new Point(x,y); _sourceTowards="Right"; } else if(angle > limit) // covers angle <= 90 { x=(int)(x1+(height*dX/dY)); y=(int)(y1-height); intersection = new Point(x,y); _sourceTowards="Up"; } else // covers -90 <= angle { x=(int)(x1-(height*dX/dY)); y=(int)(y1+height); intersection = new Point(x,y); _sourceTowards="Down"; } return intersection; } else // The left half of the rectangle { int x,y; if(Math.abs(angle)<=limit) // covers -limit<= angle <=limit { x=(int)(x1-width); y=(int)(y1+(width*dY/dX)); intersection = new Point(x,y); _sourceTowards="Left"; } else if(angle > limit) // covers angle <= 90 { x=(int)(x1-(height*dX/dY)); y=(int)(y1+height); intersection = new Point(x,y); _sourceTowards="Down"; } else // covers -90 <= angle { x=(int)(x1+(height*dX/dY)); y=(int)(y1-height); intersection = new Point(x,y); _sourceTowards="Up"; } return intersection; } } /** draw this line object */ public void draw(Graphics g) { g.setColor(objectLineColor); g.drawLine(_sourceIntersection.x, _sourceIntersection.y, _destIntersection.x,_destIntersection.y); FontMetrics fm =g.getFontMetrics(); Point p=new Point(0,0); p.x= (int)(_destIntersection.x + ((_sourceIntersection.x - _destIntersection.x)/2.0)); p.y= (int)(_destIntersection.y + ((_sourceIntersection.y - _destIntersection.y)/2.0)); if(angle>0) p.y+=fm.getMaxAscent(); else if(Math.abs(angle)==(Math.PI/2.0)) p.x+=2; else p.y-=fm.getMaxDescent()+1; g.drawString(_edge_label, p.x,p.y); Point p1=new Point(0,0); Point p2=new Point(0,0); p1=rotateLine(_destIntersection,_destShaftend,ArrowAngle); p2=rotateLine(_destIntersection,_destShaftend,-ArrowAngle); g.drawLine(_destIntersection.x,_destIntersection.y,p1.x,p1.y); g.drawLine(_destIntersection.x,_destIntersection.y,p2.x,p2.y); Point q; if(_arrowEdge.equals("Up")) q=new Point(p1.x+1,p1.y); else if(_arrowEdge.equals("Down")) q=new Point(p2.x+1,p2.y+fm.getMaxAscent()); else if(_arrowEdge.equals("Left")) q=new Point(p2.x-fm.stringWidth(_card_label),p2.y+1+fm.getMaxAscent()); else q=new Point(p1.x,p1.y+1+fm.getMaxAscent()); g.drawString(_card_label, q.x,q.y); p1=rotateLine(_sourceIntersection,_sourceShaftend,ArrowAngle); p2=rotateLine(_sourceIntersection,_sourceShaftend,-ArrowAngle); Polygon diamond = new Polygon(); diamond.addPoint(_sourceIntersection.x,_sourceIntersection.y); diamond.addPoint(p1.x,p1.y); diamond.addPoint(_diagonalEnd.x,_diagonalEnd.y); diamond.addPoint(p2.x,p2.y); g.fillPolygon(diamond); } private Point rotateLine(Point end1,Point end2,double angle) { int x1=(int) ( (end1.x )+ (end2.x - end1.x)*Math.cos(angle) - (end2.y - end1.y)*Math.sin(angle) ); int y1=(int) ( ( end1.y )+ (end2.x - end1.x)*Math.sin(angle) + (end2.y - end1.y)*Math.cos(angle) ); return new Point(x1,y1); } /** draw this line when it is selected. */ public void drawSelected(Graphics g) { _handleRects[0].x = position().x - 3; _handleRects[0].y = position().y - 3; _handleRects[1].x = objectWidth - 3; _handleRects[1].y = objectHeight - 3; drawHandles(g); } /** When this Fig is selected, the Editor should use an instance of * SelectionHandles to record that fact. */ public Selection selectionObject() { return new SelectionHandles(this); } /** Reply true iff the given point is "near" the line. Nearness * allows the user to more easily select the line with the * mouse. Needs-More-Work: I should probably have two functions * inside() which gives a strict geometric version, and near() which * is for selection by mouse clicks. */ public boolean inside(int x, int y) { return intersects(new Rectangle(x - GRIP_MARGIN, y - GRIP_MARGIN, 2 * GRIP_MARGIN, 2 * GRIP_MARGIN)); } /** Reply true if the given point is counter-clockwise from the * vector defined by the position of this line and its endpoint. This * is used as in determining intersection between lines and * rectangles. Taken from Algorithms in C by Sedgewick, page * 350. */ int counterClockWise(int x, int y) { Point p0 = position(); Point p1 = new Point(objectWidth, objectHeight); Point p2 = new Point(x, y); /* the point to test */ int dx1 = p1.x - p0.x; int dy1 = p1.y - p0.y; int dx2 = p2.x - p0.x; int dy2 = p2.y - p0.y; if (dx1*dy2 > dy1*dx2) return +1; if (dx1*dy2 < dy1*dx2) return -1; if ((dx1*dx2 < 0) || (dy1*dy2 < 0)) return -1; if ((dx1*dx1+dy1*dy1) < (dx2*dx2+dy2*dy2)) return +1; return 0; } /** Reply true iff this line passes through, or is even partly * inside the given rectangle, or if any corner of the rect is on the * line. What happens if the line runs along one edge of the rect, * but not all the way to either corner? */ public boolean intersects(Rectangle r) { if (! r.intersects(getBoundingBox())) return false; int ccw1 = counterClockWise(r.x, r.y); int ccw2 = counterClockWise(r.x, r.y + r.height); int ccw3 = counterClockWise(r.x + r.width, r.y); int ccw4 = counterClockWise(r.x + r.width, r.y + r.height); // reply true iff any of the points are on opposite sides of the // line, or if any of them are on the line return ((ccw1 == 1 || ccw2 == 1 || ccw3 == 1 || ccw4 == 1) && (ccw1 == -1 || ccw2 == -1 || ccw3 == -1 || ccw4 == -1) || ccw1 == 0 || ccw2 == 0 || ccw3 == 0 || ccw4 == 0); } /** Reply a rect that encloses this line. I add a few pixels on * either side so that the rect will have non-zero area. */ public Rectangle getBoundingBox() { Point p = position(); return new Rectangle ( Math.min(p.x, objectWidth) - 1, Math.min(p.y, objectHeight) - 1, Math.max(p.x, objectWidth) - Math.min(p.x, objectWidth) + 2, Math.max(p.y, objectHeight) - Math.min(p.y, objectHeight) + 2 ); } } /* end class FigConstEdge */