package ui.view.main.old; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Cursor; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.util.LinkedList; import java.util.ListIterator; import javax.swing.JPanel; import classes.Position; import classes.UnitGraphPoint; import interfaces.GraphEditable; import interfaces.GraphEditable.Graphtype; import model.Model; import interfaces.LocalMode; import ui.controller.Control; /** * 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 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.model = model; this.setBackground(Color.WHITE); this.addMouseListener(this); this.addMouseMotionListener(this); this.addComponentListener(this); } /** * When the UnitGraph should represent a new GraphEditable Element. * Its Updates the Graph and give access to the Element. * @param element */ public void initNewElement(GraphEditable element) { overrideUnitGraph(element.getStateGraph()); actualGraphType = element.getGraphType(); actualElement = element; repaint(); } /** * Paints the Graph, the Grid, the actual Line fro the currentIteration * @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); } // Draw Methods only to let the User see the changes. Nothing its saved here or changed. /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Methods draws the UnitGraph whether its a boolGraph or a doubleGraph. * @param g to draw. */ private void drawUnitGraph(Graphics2D g) { switch(actualGraphType) { case boolGraph: if(editMode) drawBoolGraphInEditMode(g); else drawBoolGraph(g); break; case doubleGraph: if(editMode) drawDoubleGraphInEditMode(g); else drawDoubleGraph(g); break; default: throw new UnsupportedOperationException(); } } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Methods draws the UnitGraphPoints of the UnitGraph. * @param g to draw. */ private void drawUnitGraphPoints(Graphics2D g) { g.setColor(dotColor); for(UnitGraphPoint p : actualGraphPoints){ drawDot(g, p.displayedPosition); } } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Methods draws the UnitGraphPoints of the UnitGraph when its in EditMode. * @param g to draw. */ private void drawUnitGraphPointsReleased(Graphics2D g) { drawUnitGraphPoints(g); g.setColor(editDotColor); drawDot(g, editPosition); } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Methods draws the Grid on the Canvas. * @param g2D to draw. */ 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); } } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Method draws the CurrentIterationLine. * @param g2D to draw. */ private void drawCurrentIterartionLine(Graphics2D g) { int cur = model.getCurIteration(); int max = model.getIterations(); double where; if(!this.isUsingLocalPeriod()) { where = ((double) cur)/((double) max); } else { int lPeriod = this.getLocalPeriod(); where = ((double) cur%lPeriod)/((double) lPeriod); } Position oben = new Position(border + (int)(where * widthWithBorder), 0); Position unten = new Position(border + (int)(where * widthWithBorder), 2 * border + heightWithBorder); drawLine(g,oben,unten); } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Method draws a line between two Positions on the Canvas. * @param g2D to draw. * @param start the Position of one end of the line to draw. * @param end the other Ends Position of the Line to draw. */ 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); } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* Initialize a Cubic BezierCurve. * @param start The Position to start the Curve. */ 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; } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* Calculate the Path of a the Cubic BezierCurve with the special controlPoints to make the wanted behavior. * @param path the path of the Bezier. * @param actaul the actual Position of the Path. * @param target the end Position of the Curve. */ 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); } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* Draws a Dot at a Position. * @param g to draw. * @param p the position of the Dot. */ private void drawDot(Graphics2D g, Position p) { g.fillOval(p.x -dotSize/2, p.y-dotSize/2, dotSize, dotSize); } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Method draws the UnitGraph as BoolGraph. * @param g2D to draw. */ private void drawBoolGraph(Graphics2D g) { 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); } } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Method draws the UnitGraph as BoolGraph in EditMode. * @param g2D to draw. */ private void drawBoolGraphInEditMode(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); } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Method draws a red Hint to signal the User the snapping of the hovered Point under the Cursor in EditMode. * @param g2D to draw. */ 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); } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Method draws a partial Graph from a Position List as BoolGraph. * @param g2D to draw. * @param list the PositionList to draw a BoolGraph */ 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); } } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Method draws the UnitGraph as DoubleGraph. * @param g2D to draw. */ 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); } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Method draws the UnitGraph as DoubleGraph in EditMode. * @param g2D to draw. */ private void drawDoubleGraphInEditMode(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); } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Method draws a partial Graph from a Position List as DoubleGraph. * @param g2D to draw. * @param list the PositionList to draw a DoubleGraph */ 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); } //Under the hood functions to calculate and function the /** * A unitgraphpoint have a x and y position to store the data of a graph point. * Also it have a displayposition to store the Position of the GraphPoints on the Canvas. * e.g. when the canvas has 500 width and 200 height a GraphPoint with the X=0.5 and Y=1.0 should have a displayposition of (250,3) when border is 3. */ private void updateRepresentativePositions() { for(UnitGraphPoint p : actualGraphPoints) { p.calcDisplayedPosition(border, widthWithBorder, heightWithBorder); } } /** * Takes a List of GraphPoints and convert it to the actual UnitGraphPoints with displayposition in the {@link #actualGraphPoints} * @param stateCurve the list of GraphPoint */ private void overrideUnitGraph(LinkedList stateCurve) { actualGraphPoints.clear(); for(Point2D.Double p: stateCurve){ actualGraphPoints.add(new UnitGraphPoint(p)); } updateRepresentativePositions(); } /** * When the PanelSize Change the width and height to calculate the drawings have to be adjusted. */ private void calculateWidthHeight() { widthWithBorder = this.getWidth() - 2 * border; heightWithBorder = this.getHeight() - 2 * border; } /** * Save the actualGraphPoint List to the GraphEditable Element. */ private void saveGraph() { LinkedList actual = actualElement.getStateGraph(); actual.clear(); for(UnitGraphPoint p: actualGraphPoints) { actual.add(p.getPoint()); } actualElement.sampleGraph(); } /** * Remove a UnitGraphPoint from the UnitGraphPoint list ({@link #actualGraphPoints} when its near a given Position. * @param mPosition */ private void removePointNearPosition(Position mPosition) { ListIterator iter = actualGraphPoints.listIterator(); while (iter.hasNext()) { if(near(mPosition,iter.next().displayedPosition)) { iter.remove(); break; } } } /** * Determine if the Point is a StartPoint , EndPoint or a NormalPoint a.k.a. in between Points. * @param mPosition The Position to check. */ 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; } /** * Determine if a Point is near the Cursor (depends on Mode what near means). To detect if it should grab the Point or create a new Point. * @param actual * @param target * @return */ 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; } } /** * When the Mouse Drag a Point it updates each time the position. * @param newPosition */ 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; } /** * No Point on the UnitGraph should exit the UnitGraph. * @param p the Position * @return the updated Position. */ private Position setInBounds(Position p) { p.clampX(border, border + widthWithBorder); p.clampY(border, border + heightWithBorder); return p; } /** * For Switches the Point have to be Snap to the Top or the Bottem. * @param p the Position * @return the updated Position. */ private Position snapBoolean(Position p) { if (p.y < border + heightWithBorder / 2) { p.y = border; } else { p.y = border + heightWithBorder; } return p; } /** * The First Point has to be at 0(LeftSide) and Last Point has to be at 1(RightSide). * @param p the Position * @return the updated Position. */ private Position attachToBorder(Position p) { switch(editPoint) { case StartPoint: p.x = border; break; case EndPoint: p.x = border + widthWithBorder; break; default: break; } return p; } /** * Insert a Position in the UnitGraphList at the right order. * Its sorted based on the xValues. * @param pos The new UnitGraphPoints Position */ private void insertNewGraphPoint(Position pos) { 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)); } } /** * Generate a UnitGraphPoint from a normal Position in the UnitGraph. * @param pos the normal pos with xValues from 0..Width and yValues from 0..Height * @return a UnitGraphPoint */ 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; } /** * Update the Point Position */ @Override public void mouseDragged(MouseEvent e) { 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) { } /** * The First Step. * When LeftMouseButton its checks if a point is to grab under the cursor or create a new Point. Then enter EditMode. * When RightMouseButton its delete a point if its under the Curser. */ @Override public void mousePressed(MouseEvent e) { 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(); } } /** * The last step to save the Changes. * Its insert the Hovering Point and exit EditMode. */ @Override public void mouseReleased(MouseEvent e) { if(editMode) { this.insertNewGraphPoint(editPosition); editMode = false; repaint(); } saveGraph(); } /** * When the Component is Resized. * * @param e ComponentEvent */ public void componentResized(ComponentEvent e) { calculateWidthHeight(); updateRepresentativePositions(); repaint(); } @Override public void componentHidden(ComponentEvent e) { } @Override public void componentMoved(ComponentEvent e) { } @Override public void componentShown(ComponentEvent e) { } /** * Resets the graph to normal. */ public void reset() { if(this.actualElement != null) { actualElement.reset(); overrideUnitGraph(actualElement.getStateGraph()); repaint(); } } //LocalMode access methods... //To access a element from the GUI for the LocalMode public void setUseLocalPeriod(boolean state) { if(this.actualElement != null) { ((LocalMode) actualElement).setUseLocalPeriod(state); } } public void setLocalPeriod(int localLength) { if(this.actualElement != null) { ((LocalMode) actualElement).setLocalPeriod(localLength); } } public int getLocalPeriod() { if(this.actualElement != null) { return ((LocalMode) actualElement).getLocalPeriod(); } return -1; } public boolean isUsingLocalPeriod() { if(this.actualElement != null) { return ((LocalMode) actualElement).isUsingLocalPeriod(); } return false; } }