package ui.view; import classes.*; import classes.comparator.UnitGraphPointComperator; import interfaces.GraphEditable; import interfaces.GraphEditable.Graphtype; import interfaces.IGraphedElement; import sun.reflect.generics.reflectiveObjects.NotImplementedException; import ui.controller.Control; import ui.controller.SingletonControl; import ui.model.Model; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.LinkedList; import java.util.ListIterator; /** * This Class represents a Graph where the User can model the behavior of * elements and switches over time. * * @author Tom Troppmann */ public class UnitGraph extends JPanel implements MouseListener, MouseMotionListener, ComponentListener { private static final long serialVersionUID = 1L; // Normal Settings private int border = 4; private int clickThreshholdSquared = 25; // Display Settings /** * The size of a dot in the graph. * It should be at least 1. * */ int dotSize = 8; /** The Color of a dot in the graph. */ Color dotColor = Color.blue; Color editDotColor = new Color(255, 119, 0); //Intern Variables //TODO: JavaDoc private LinkedList actualGraphPoints = new LinkedList(); private Graphtype actualGraphType; private GraphEditable actualElement; Position editPosition; boolean editMode = false; private enum pointType {Normal, StartPoint, EndPoint}; pointType editPoint = pointType.Normal; //Maybe Needed private Model model; private Control controller; private int widthWithBorder, heightWithBorder; /** * Constructor. * * @param model the Model * @param control the Controller */ public UnitGraph(final Model model, Control control) { setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); this.controller = control; this.model = model; this.setBackground(Color.WHITE); this.addMouseListener(this); this.addMouseMotionListener(this); this.addComponentListener(this); } /** * Paints all Components on the Canvas. * * @param g Graphics */ public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2D = (Graphics2D) g; drawGrid(g2D); g2D.setColor(Color.BLACK); g2D.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)); g2D.setStroke(new BasicStroke(2)); drawUnitGraph(g2D); g2D.setColor(dotColor); if(editMode) { drawUnitGraphPointsReleased(g2D); }else { drawUnitGraphPoints(g2D); } g2D.setColor(dotColor); g2D.setStroke(new BasicStroke(1)); drawCurrentIterartionLine(g2D); } private void drawGrid(Graphics2D g2D) { g2D.setStroke(new BasicStroke(1)); g2D.setColor(Color.lightGray); int amountOfLines = 10; int width = widthWithBorder + 2 * border; int height = heightWithBorder; for(int i = 0; i<=amountOfLines; i++) { int linehieght = (int) (((double)i/ (double) amountOfLines) * (double) height) + border; g2D.drawLine(0, linehieght, width, linehieght); } } private void drawCurrentIterartionLine(Graphics2D g) { int cur = model.getCurIteration(); int max = model.getIterations(); double where = ((double) cur)/((double) max - 1); Position oben = new Position(border + (int)(where * widthWithBorder), 0); Position unten = new Position(border + (int)(where * widthWithBorder), 2 * border + heightWithBorder); drawLine(g,oben,unten); } private void drawLine(Graphics2D g, Position start, Position end) { Path2D.Double path = new Path2D.Double(); path.moveTo(start.x, start.y); path.lineTo(end.x, end.y); g.draw(path); } private Path2D.Double initBezier(Position start) { //Good Source for basic understanding for Bezier Curves //http://www.theappguruz.com/blog/bezier-curve-in-games Path2D.Double path = new Path2D.Double(); path.moveTo(start.x, start.y); return path; } private void curveTo(Path2D.Double path, Position actual, Position target) { double mitte = (actual.x + target.x)* 0.5; path.curveTo(mitte, actual.y, mitte, target.y, target.x, target.y); } private void drawDot(Graphics2D g, Position p) { g.fillOval(p.x -dotSize/2, p.y-dotSize/2, dotSize, dotSize); } private void drawUnitGraph(Graphics2D g) { switch(actualGraphType) { case boolGraph: if(editMode) drawBoolGraphWithEditPosition(g); else drawBoolGraph(g); break; case doubleGraph: if(editMode) drawDoubleGraphWithEditPosition(g); else drawDoubleGraph(g); break; default: throw new UnsupportedOperationException(); } } private void drawUnitGraphPoints(Graphics2D g) { g.setColor(dotColor); for(UnitGraphPoint p : actualGraphPoints){ drawDot(g, p.displayedPosition); } } private void saveGraph() { LinkedList actual = actualElement.getStateGraph(); actual.clear(); for(UnitGraphPoint p: actualGraphPoints) { actual.add(p.getPoint()); } actualElement.sampleGraph(); } private void drawUnitGraphPointsReleased(Graphics2D g) { drawUnitGraphPoints(g); g.setColor(editDotColor); drawDot(g, editPosition); } private void drawBoolGraph(Graphics2D g) { System.out.println("DoImplement"); if(actualGraphPoints.size() <= 1) return; LinkedList cornerPoints = new LinkedList(); ListIterator iter = actualGraphPoints.listIterator(); Position actual = actualGraphPoints.getFirst().displayedPosition; Path2D.Double path = new Path2D.Double(); path.moveTo(actual.x, actual.y); while (iter.hasNext()) { Position target = iter.next().displayedPosition; //BooleanConnection path.lineTo(target.x, actual.y); //line to corner cornerPoints.add(new Position(target.x, actual.y)); //save corner path.lineTo(target.x, target.y); //line to next Point actual = target; } g.draw(path); //Draw the Points on the Corner that dont exist in Data but should be visual g.setColor(dotColor); for(Position p: cornerPoints) { drawDot(g, p); } } private void drawBoolGraphWithEditPosition(Graphics2D g) { LinkedList before = new LinkedList(); LinkedList after = new LinkedList(); for(UnitGraphPoint p: actualGraphPoints) { if(p.displayedPosition.x < editPosition.x) before.add(p.displayedPosition); else after.add(p.displayedPosition); } g.setColor(Color.BLACK); drawBoolGraphFromList(g, before); g.setColor(Color.BLACK); drawBoolGraphFromList(g, after); //EditGraph LinkedList middle = new LinkedList(); if(!before.isEmpty()) middle.add(before.getLast()); middle.add(editPosition); if(!after.isEmpty()) middle.add(after.getFirst()); g.setColor(editDotColor); drawBoolGraphFromList(g, middle); drawSnappingHint(g); } private void drawSnappingHint(Graphics2D g) { //ColorHint g.setColor(Color.RED); //Threshhold Line final float dash1[] = {10.0f}; final BasicStroke dashed =new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash1, 0.0f); g.setStroke(dashed); int halfheight = border + heightWithBorder / 2; g.drawLine(0, halfheight , widthWithBorder + 2 * border, halfheight); //Threshhold Text g.drawString("Snapping Threshold", 10, halfheight - 2); } private void drawBoolGraphFromList(Graphics2D g, LinkedList list) { if(list.size() <= 1) return; ListIterator iter = list.listIterator(); LinkedList cornerPoints = new LinkedList(); Position actual = list.getFirst(); Path2D.Double path = new Path2D.Double(); path.moveTo(actual.x, actual.y); while (iter.hasNext()) { Position target = iter.next(); //BooleanConnection path.lineTo(target.x, actual.y); //line to corner cornerPoints.add(new Position(target.x, actual.y)); //save corner path.lineTo(target.x, target.y); //line to next Point actual = target; } g.draw(path); g.setColor(dotColor); for(Position p: cornerPoints) { drawDot(g, p); } } private void drawDoubleGraph(Graphics2D g) { if(actualGraphPoints.isEmpty()) throw new IndexOutOfBoundsException("A Graph Without Points is not supportet jet"); ListIterator iter = actualGraphPoints.listIterator(); Position actual = iter.next().displayedPosition; Path2D.Double path = this.initBezier(actual); while (iter.hasNext()) { Position target = iter.next().displayedPosition; this.curveTo(path, actual, target); actual = target; } g.draw(path); } private void drawDoubleGraphWithEditPosition(Graphics2D g) { LinkedList before = new LinkedList(); LinkedList after = new LinkedList(); for(UnitGraphPoint p: actualGraphPoints) { if(p.displayedPosition.x < editPosition.x) before.add(p.displayedPosition); else after.add(p.displayedPosition); } drawUnitGraphFromList(g, before); drawUnitGraphFromList(g, after); //EditGraph LinkedList middle = new LinkedList(); if(!before.isEmpty()) middle.add(before.getLast()); middle.add(editPosition); if(!after.isEmpty()) middle.add(after.getFirst()); g.setColor(editDotColor); drawUnitGraphFromList(g, middle); } private void drawUnitGraphFromList(Graphics2D g, LinkedList list) { if(list.size() <= 1) return; ListIterator iter = list.listIterator(); Position actual = list.getFirst(); Path2D.Double path = this.initBezier(actual); while (iter.hasNext()) { Position target = iter.next(); curveTo(path, actual, target); actual = target; } g.draw(path); } private void updateRepresentativePositions() { for(UnitGraphPoint p : actualGraphPoints) { p.calcDisplayedPosition(border, widthWithBorder, heightWithBorder); } } private void overrideUnitGraph(LinkedList stateCurve) { actualGraphPoints.clear(); for(Point2D.Double p: stateCurve){ actualGraphPoints.add(new UnitGraphPoint(p)); } updateRepresentativePositions(); } private void calculateWidthHeight() { widthWithBorder = this.getWidth() - 2 * border; heightWithBorder = this.getHeight() - 2 * border; } public void initNewElement(GraphEditable element) { overrideUnitGraph(element.getStateGraph()); actualGraphType = element.getGraphType(); actualElement = element; repaint(); } private void removePointNearPosition(Position mPosition) { ListIterator iter = actualGraphPoints.listIterator(); while (iter.hasNext()) { if(near(mPosition,iter.next().displayedPosition)) { iter.remove(); break; } } } private void detectStartEndPoint(Position mPosition) { UnitGraphPoint first = actualGraphPoints.getFirst(); UnitGraphPoint last = actualGraphPoints.getLast(); if(near(mPosition, first.displayedPosition)) editPoint = pointType.StartPoint; else if(near(mPosition, last.displayedPosition)) editPoint = pointType.EndPoint; else editPoint = pointType.Normal; } private boolean near(Position actual, Position target) { switch(actualGraphType) { case boolGraph: //Distance only with X int xDis = target.x - actual.x; return xDis * xDis < clickThreshholdSquared; case doubleGraph: return actual.squareDistance(target) < clickThreshholdSquared; default: return false; } } private void updateEditPointPosition(Position newPosition) { //make it in the bounds of the UnitGraph no Point out of the Border Position currentPosition = setInBounds(newPosition); if(editPoint != pointType.Normal) attachToBorder(currentPosition); if(actualGraphType == Graphtype.boolGraph) snapBoolean(currentPosition); this.editPosition = currentPosition; } private Position setInBounds(Position p) { p.clampX(border, border + widthWithBorder); p.clampY(border, border + heightWithBorder); return p; } private Position snapBoolean(Position p) { if (p.y < border + heightWithBorder / 2) { p.y = border; } else { p.y = border + heightWithBorder; } return p; } private Point.Double getBezierPoint(double t, Point.Double p0, Point.Double p1,Point.Double p2,Point.Double p3) { /* * Calculate Beziér: * B(t) = (1-t)^3 * P0 + 3*(1-t)^2 * t * P1 + 3*(1-t)*t^2 * P2 + t^3 * P3 , 0 < t < 1 * * Source: //http://www.theappguruz.com/blog/bezier-curve-in-games */ Point.Double bezier = new Point.Double(); double OneSubT = 1-t; double OneSubT2 = Math.pow(OneSubT, 2); double OneSubT3 = Math.pow(OneSubT, 3); double t2 = Math.pow(t , 2); double t3 = Math.pow(t , 3); bezier.x = OneSubT3 * p0.x + 3 * OneSubT2 * t * p1.x + 3 * OneSubT * t2 * p2.x + t3 * p3.x; bezier.y = OneSubT3 * p0.y + 3 * OneSubT2 * t * p1.y + 3 * OneSubT * t2 * p2.y + t3 * p3.y; return bezier; } private double getYBetweenTwoPoints(double t, Point.Double start, Point.Double end) { double mitte = (start.x + end.x)* 0.5; Point.Double bezier = getBezierPoint(t, start, new Point.Double(mitte, start.y), new Point.Double(mitte, end.y), end); return bezier.y; } private float[] sampleGraph(int sampleLength) { ListIterator iter = actualGraphPoints.listIterator(); Point.Double before = iter.next().getPoint(); Point.Double after = iter.next().getPoint(); float [] sampleCurve = new float[sampleLength]; for(int i = 0; i after.x) { before = after; after = iter.next().getPoint(); } //inverseLerp(valueBetween, min, max) (valueBetween - min) / (max - min) // e.g. old.x = 0.4, actual.x = 0.8 and graphX = 0.6 then t is 0.5 double t = (after.x -before.x > 0)? (graphX - before.x) / (after.x -before.x) : 0.0; sampleCurve[i] = (float) getYBetweenTwoPoints(t, before, after); } return sampleCurve; } private Position attachToBorder(Position p) { switch(editPoint) { case StartPoint: p.x = border; break; case EndPoint: p.x = border + widthWithBorder; break; default: break; } return p; } private void insertNewGraphPoint(Position pos) { System.out.println("insertNewGraphPoint"); setInBounds(pos); ListIterator iter2 = actualGraphPoints.listIterator(); while (iter2.hasNext()) { Position tempPosition = iter2.next().displayedPosition; if(pos.x <= tempPosition.x) { //previous to go back a position to make the new point before the the Position with greater X iter2.previous(); iter2.add(generateUnitGraphPoint(pos)); break; } } if(!iter2.hasNext()) //if behind last point { iter2.add(generateUnitGraphPoint(pos)); } } private UnitGraphPoint generateUnitGraphPoint(Position pos) { UnitGraphPoint temp = new UnitGraphPoint((double) (pos.x - border) / (double) widthWithBorder, 1 - (double) (pos.y - border) / (double) heightWithBorder, true); temp.displayedPosition = pos; return temp; } @Override public void mouseDragged(MouseEvent e) { System.out.println("MouseDragged"); updateEditPointPosition(new Position(e.getPoint())); repaint(); } @Override public void mouseMoved(MouseEvent e) { } @Override public void mouseClicked(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void mousePressed(MouseEvent e) { System.out.println("mousePressed"); Position mPosition = new Position(e.getPoint()); if (e.getButton() == MouseEvent.BUTTON3) { // RightMouseButtonEvent detectStartEndPoint(mPosition); if (editPoint == pointType.Normal) { removePointNearPosition(mPosition); repaint(); } editMode = false; } else if (e.getButton() == MouseEvent.BUTTON1) { // LeftMouseButtonEvent detectStartEndPoint(mPosition); removePointNearPosition(mPosition); updateEditPointPosition(mPosition); editMode = true; repaint(); } } @Override public void mouseReleased(MouseEvent e) { System.out.println("mouseReleased"); if(editMode) { this.insertNewGraphPoint(editPosition); editMode = false; repaint(); } saveGraph(); } /** * When the Component is Resized. * * @param e ComponentEvent */ public void componentResized(ComponentEvent e) { System.out.println("componentResized"); calculateWidthHeight(); updateRepresentativePositions(); repaint(); } @Override public void componentHidden(ComponentEvent e) { } @Override public void componentMoved(ComponentEvent e) { } @Override public void componentShown(ComponentEvent e) { } public void reset() { System.out.println("reset"); if(this.actualElement != null) { actualElement.reset(); overrideUnitGraph(actualElement.getStateGraph()); repaint(); } } public void update(ArrayList obj) { System.out.println("update"); } public void setStretching(boolean selected) { System.out.println("setStretching"); } public void setLocalPeriod(int localLength) { System.out.println("setLocalPeriod"); } public void repaintGraph(AbstractCpsObject cps) { // TODO Auto-generated method stub System.out.println("repaintGraph"); } }