f@0: /* f@0: CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool f@0: f@0: Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com) f@0: Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) f@0: f@0: This program is free software: you can redistribute it and/or modify f@0: it under the terms of the GNU General Public License as published by f@0: the Free Software Foundation, either version 3 of the License, or f@0: (at your option) any later version. f@0: f@0: This program is distributed in the hope that it will be useful, f@0: but WITHOUT ANY WARRANTY; without even the implied warranty of f@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the f@0: GNU General Public License for more details. f@0: f@0: You should have received a copy of the GNU General Public License f@0: along with this program. If not, see . f@0: */ f@0: f@0: package uk.ac.qmul.eecs.ccmi.gui; f@0: f@0: import java.awt.BasicStroke; f@0: import java.awt.Color; f@0: import java.awt.Graphics2D; f@0: import java.awt.Shape; f@0: import java.awt.geom.Line2D; f@0: import java.awt.geom.Point2D; f@0: import java.awt.geom.Rectangle2D; f@0: import java.io.IOException; f@0: import java.util.ArrayList; f@0: import java.util.BitSet; f@0: import java.util.LinkedHashMap; f@0: import java.util.LinkedHashSet; f@0: import java.util.LinkedList; f@0: import java.util.List; f@0: import java.util.ListIterator; f@0: import java.util.Map; f@0: import java.util.ResourceBundle; f@0: f@0: import org.w3c.dom.Document; f@0: import org.w3c.dom.Element; f@0: import org.w3c.dom.NodeList; f@0: f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramEdge; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent; f@0: import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager; f@0: f@0: /** f@0: * An edge in a graph. Edge objects are used in a GraphPanel to render a diagram edge visually. f@0: * Edge objects are used in the tree representation of the diagram as well, as they're f@0: * subclasses of {@link DiagramEdge} f@0: * f@0: */ f@0: @SuppressWarnings("serial") f@0: public abstract class Edge extends DiagramEdge implements GraphElement{ f@0: f@0: /** f@0: * Creates a new Edge f@0: * f@0: * @param type the type of the edge. The type is just a string that the user assign to the edge. f@0: * All the edges created via clonation from this edge will have the same type. f@0: * @param availableEndDescriptions all the possible end description ends of this edge can be f@0: * associated to. An end description is a text associated to a possible arrow head of an edge end. f@0: * It's used to give awareness of arrow heads via speech. f@0: * @param minAttachedNodes the minimum number of nodes that can be attached to this edge f@0: * @param maxAttachedNodes the minimum number of nodes that can be attached to this edge f@0: * @param style the style of the edge: whether it's solid, dotted or dashed f@0: */ f@0: public Edge(String type, String[] availableEndDescriptions, int minAttachedNodes, int maxAttachedNodes,LineStyle style){ f@0: super(type,availableEndDescriptions); f@0: this.minAttachedNodes = minAttachedNodes; f@0: this.maxAttachedNodes = maxAttachedNodes; f@0: this.style = style; f@0: nodes = new ArrayList(); f@0: } f@0: f@0: /* --- Methods inherited from DiagramEdge --- */ f@0: @Override f@0: public Node getNodeAt(int index){ f@0: return nodes.get(index); f@0: } f@0: f@0: @Override f@0: public int getNodesNum(){ f@0: return nodes.size(); f@0: } f@0: f@0: @Override f@0: public boolean removeNode(DiagramNode diagramNode, Object source){ f@0: Node n = (Node)diagramNode; f@0: if(nodes.size() == 2) f@0: throw new RuntimeException("Cannot remove a node from a two ends edge"); f@0: else{ f@0: for(InnerPoint p : points) f@0: if(p.hasNeighbour(n)){ f@0: p.neighbours.remove(n); f@0: if(p.neighbours.size() == 1) f@0: removePoint(p); f@0: break; f@0: } f@0: boolean nodeRemoved = nodes.remove(n); f@0: /* for update in the haptic device */ f@0: notifyChange(new ElementChangedEvent(this,n,"remove_node",source)); f@0: return nodeRemoved; f@0: } f@0: } f@0: f@0: @Override f@0: public void connect(List nodes) throws ConnectNodesException{ f@0: assert(getNodesNum() == 0); f@0: /* this is to eliminate duplicates */ f@0: LinkedHashSet nodeSet = new LinkedHashSet(); f@0: for(DiagramNode n : nodes) f@0: nodeSet.add((Node)n); f@0: f@0: /* checks on connection consistency */ f@0: if((nodeSet == null)||(nodeSet.size() < minAttachedNodes)) f@0: throw new ConnectNodesException("You must select at least "+ minAttachedNodes + "nodes"); f@0: if((nodeSet.size() > maxAttachedNodes)) f@0: throw new ConnectNodesException("You must select at most " + maxAttachedNodes +" nodes"); f@0: f@0: points = new ArrayList(); f@0: if(nodeSet.size() > 2){ f@0: /* there are more than three nodes. compute the central inner point * f@0: * which will connect all the nodes, as the middle points of the edge bound */ f@0: Rectangle2D bounds = new Rectangle2D.Double(); f@0: for(Node n : nodeSet){ f@0: bounds.add(n.getBounds()); f@0: } f@0: InnerPoint p = new InnerPoint(); f@0: p.translate(new Point2D.Double(0,0), bounds.getCenterX(), bounds.getCenterY(),DiagramEventSource.NONE); f@0: p.neighbours.addAll(nodeSet); f@0: points.add(p); f@0: } f@0: this.nodes.addAll(nodeSet); f@0: f@0: if(!points.isEmpty())// points is empty when the edge has two nodes only f@0: masterInnerPoint = points.get(0); f@0: } f@0: f@0: @Override f@0: public abstract void draw(Graphics2D g2); f@0: f@0: @Override f@0: public void translate(Point2D p, double dx, double dy, Object source){ f@0: for(InnerPoint ip : points) f@0: ip.translate(p, dx, dy,source); f@0: } f@0: f@0: /** f@0: * To be called before {@code bend}, determines from {@code downPoint} whether f@0: * a line is going to be break into two lines (with the creation of a new inner point) f@0: * or if the bending is determined by an already existing inner point being translated f@0: */ f@0: @Override f@0: public void startMove(Point2D downPoint,Object source){ f@0: this.downPoint = downPoint; f@0: newInnerPoint = null; f@0: for(InnerPoint itrPoint : points) f@0: if(itrPoint.contains(downPoint)){ f@0: /* clicked on an already existing EdgePoint */ f@0: newInnerPoint = itrPoint; f@0: newPointCreated = false; f@0: } f@0: if(newInnerPoint == null){ f@0: /* no point under the click, create a new one */ f@0: newInnerPoint = new InnerPoint(); f@0: newInnerPoint.translate(downPoint, downPoint.getX() - newInnerPoint.getBounds().getCenterX(), f@0: downPoint.getY() - newInnerPoint.getBounds().getCenterY(),DiagramEventSource.NONE); f@0: newPointCreated = true; f@0: /* this methods checks for segments of the edge which are aligned and makes a unique edge out of them */ f@0: } f@0: } f@0: f@0: /** f@0: * If this edge is made out of several lines and two or more of them becomes f@0: * aligned then they are blended in one single line and the inner point that was f@0: * at the joint is removed. f@0: * f@0: * @param source the source of the {@code stopMove} action f@0: */ f@0: @Override f@0: public void stopMove(Object source){ f@0: for(ListIterator pItr = points.listIterator(); pItr.hasNext(); ){ f@0: InnerPoint ePoint = pItr.next(); f@0: if(ePoint.neighbours.size() > 2) f@0: continue; f@0: Rectangle2D startBounds = ePoint.getBounds(); f@0: Rectangle2D endBounds = ePoint.neighbours.get(0).getBounds(); f@0: Direction d1 = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY()); f@0: endBounds = ePoint.neighbours.get(1).getBounds(); f@0: Direction d2 = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY()); f@0: if(d1.isParallel(d2)){ f@0: InnerPoint p = null; f@0: GraphElement q = null; f@0: if(ePoint.neighbours.get(0) instanceof InnerPoint){ f@0: p = (InnerPoint)ePoint.neighbours.get(0); f@0: q = ePoint.neighbours.get(1); f@0: p.neighbours.add(q); f@0: p.neighbours.remove(ePoint); f@0: } f@0: if(ePoint.neighbours.get(1) instanceof InnerPoint){ f@0: p = (InnerPoint)ePoint.neighbours.get(1); f@0: q = ePoint.neighbours.get(0); f@0: p.neighbours.add(q); f@0: p.neighbours.remove(ePoint); f@0: } f@0: pItr.remove(); f@0: } f@0: } f@0: notifyChange(new ElementChangedEvent(this,this,"stop_move",source)); f@0: } f@0: f@0: @Override f@0: public abstract Rectangle2D getBounds(); f@0: f@0: @Override f@0: public Point2D getConnectionPoint(Direction d){return null;} f@0: f@0: @Override f@0: public boolean contains(Point2D aPoint){ f@0: if(points.isEmpty()){ f@0: return fatStrokeContains (nodes.get(0), nodes.get(1), aPoint); f@0: } f@0: for(InnerPoint p : points){ f@0: for(GraphElement ge : p.neighbours){ f@0: if(fatStrokeContains(p,ge,aPoint)) f@0: return true; f@0: } f@0: } f@0: return false; f@0: } f@0: f@0: /** f@0: * Look for the node attached to this edge which lays at the minimum distance f@0: * from the point passed as argument. The distance cannot be lower than the f@0: * value passed as argument. f@0: * f@0: * @param aPoint the point the distance is measured from f@0: * @param distanceLimit the limit from the distance between the nodes and the point f@0: * @return the closest node or null if the node lays at an higher distance than distanceLimit f@0: */ f@0: public Node getClosestNode(Point2D aPoint, double distanceLimit){ f@0: Node closestNode = null; f@0: double minDist = distanceLimit; f@0: f@0: if(points.isEmpty()){ f@0: Line2D line = getSegment(nodes.get(0),nodes.get(1)); f@0: if(line.getP1().distance(aPoint) < minDist){ f@0: minDist = line.getP1().distance(aPoint); f@0: closestNode = nodes.get(0); f@0: } f@0: if(line.getP2().distance(aPoint) < minDist){ f@0: minDist = line.getP2().distance(aPoint); f@0: closestNode = nodes.get(1); f@0: } f@0: return closestNode; f@0: }else{ f@0: for(InnerPoint p : points){ f@0: for(GraphElement ge : p.getNeighbours()) f@0: if(ge instanceof Node){ f@0: Node n = (Node)ge; f@0: Direction d = new Direction(p.getBounds().getCenterX() - n.getBounds().getCenterX(),p.getBounds().getCenterY() - n.getBounds().getCenterY()); f@0: if(n.getConnectionPoint(d).distance(aPoint) < minDist){ f@0: minDist = n.getConnectionPoint(d).distance(aPoint); f@0: closestNode = n; f@0: } f@0: } f@0: } f@0: return closestNode; f@0: } f@0: } f@0: f@0: private void removePoint(InnerPoint p){ f@0: /* we assume at this moment p has one neighbour only */ f@0: InnerPoint neighbour = (InnerPoint)p.neighbours.get(0); f@0: points.remove(p); f@0: neighbour.neighbours.remove(p); f@0: if(neighbour.neighbours.size() == 1) f@0: removePoint(neighbour); f@0: } f@0: f@0: /* checks if a point belongs to a shape with a margin of MAX_DIST*/ f@0: private boolean fatStrokeContains(GraphElement ge1, GraphElement ge2, Point2D p){ f@0: BasicStroke fatStroke = new BasicStroke((float) (2 * MAX_DIST)); f@0: Line2D line = new Line2D.Double( f@0: ge1.getBounds().getCenterX(), f@0: ge1.getBounds().getCenterY(), f@0: ge2.getBounds().getCenterX(), f@0: ge2.getBounds().getCenterY()); f@0: Shape fatPath = fatStroke.createStrokedShape(line); f@0: return fatPath.contains(p); f@0: } f@0: f@0: /** f@0: * Returns a line connecting the centre of the graph elements passed as argument. f@0: * f@0: * @param start the first graph element f@0: * @param end the second graph element f@0: * f@0: * @return a line connecting {@code start} and {@code end} f@0: */ f@0: protected Line2D.Double getSegment(GraphElement start, GraphElement end){ f@0: Rectangle2D startBounds = start.getBounds(); f@0: Rectangle2D endBounds = end.getBounds(); f@0: Direction d = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY()); f@0: return new Line2D.Double(start.getConnectionPoint(d), end.getConnectionPoint(d.turn(180))); f@0: } f@0: f@0: /** f@0: * Returns a list of the points where this edge and the nodes it connects come to a contact f@0: * f@0: * @return a list of points f@0: */ f@0: public List getConnectionPoints(){ f@0: List list = new LinkedList(); f@0: if(points.isEmpty()){ f@0: Line2D line = getSegment(nodes.get(0),nodes.get(1)); f@0: list.add(line.getP1()); f@0: list.add(line.getP2()); f@0: }else{ f@0: for(InnerPoint p : points){ f@0: for(GraphElement ge : p.neighbours) f@0: if(ge instanceof Node){ f@0: Direction d = new Direction(p.getBounds().getCenterX() - ge.getBounds().getCenterX(),p.getBounds().getCenterY() - ge.getBounds().getCenterY()); f@0: list.add(((Node)ge).getConnectionPoint(d)); f@0: } f@0: } f@0: } f@0: return list; f@0: } f@0: f@0: /** f@0: * Gets the stipple pattern of this edge line style f@0: * f@0: * @see LineStyle#getStipplePattern() f@0: * f@0: * @return an int representing the stipple pattern of this edge f@0: */ f@0: public int getStipplePattern(){ f@0: return getStyle().getStipplePattern(); f@0: } f@0: f@0: /** f@0: * Bends one of the lines forming this edge. f@0: * f@0: * When an line is bent, if the location where the bending happens is a line then a new f@0: * inner point is created and the line is broken into two sub lines. If the location is an f@0: * already existing inner point, then the point is translated. f@0: * f@0: * @param p the starting point of the bending f@0: * @param source the source of the bending action f@0: */ f@0: public void bend(Point2D p,Object source) { f@0: boolean found = false; f@0: if(points.isEmpty()){ f@0: newInnerPoint.neighbours.addAll(nodes); f@0: points.add(newInnerPoint); f@0: newPointCreated = false; f@0: }else if(newPointCreated){ f@0: /* find the segment closest to where the new point lays */ f@0: InnerPoint closestP1 = null; f@0: GraphElement closestP2 = null; f@0: double minDist = 0; f@0: for(ListIterator pItr = points.listIterator(); pItr.hasNext(); ){ f@0: InnerPoint ePoint = pItr.next(); f@0: for(ListIterator geItr = ePoint.neighbours.listIterator(); geItr.hasNext() && !found;){ f@0: /* find the neighbour of the current edge point whose line the new point lays on */ f@0: GraphElement next = geItr.next(); f@0: double dist = Line2D.ptSegDist( f@0: ePoint.getBounds().getCenterX(), f@0: ePoint.getBounds().getCenterY(), f@0: next.getBounds().getCenterX(), f@0: next.getBounds().getCenterY(), f@0: downPoint.getX(), f@0: downPoint.getY() f@0: ); f@0: f@0: if(closestP1 == null || dist < minDist){ f@0: closestP1 = ePoint; f@0: closestP2 = next; f@0: minDist = dist; f@0: continue; f@0: } f@0: } f@0: } f@0: f@0: if(closestP2 instanceof InnerPoint ){ f@0: /* remove current edge point from the neighbour's neighbours */ f@0: ((InnerPoint)closestP2).neighbours.remove(closestP1); f@0: /* add the new inner point */ f@0: ((InnerPoint)closestP2).neighbours.add(newInnerPoint); f@0: } f@0: /*remove old neighbour from edge inner point neighbours */ f@0: closestP1.neighbours.remove(closestP2); f@0: newInnerPoint.neighbours.add(closestP1); f@0: newInnerPoint.neighbours.add(closestP2); f@0: /* add the new node to the list of EdgeNodes of this edge */ f@0: points.add(newInnerPoint); f@0: closestP1.neighbours.add(newInnerPoint); f@0: found = true; f@0: newPointCreated = false; f@0: } f@0: newInnerPoint.translate(p, p.getX() - newInnerPoint.getBounds().getCenterX(), f@0: p.getY() - newInnerPoint.getBounds().getCenterY(),DiagramEventSource.NONE); f@0: notifyChange(new ElementChangedEvent(this,this,"bend",source)); f@0: } f@0: f@0: /** f@0: * Returns the line where the edge name will be painted. If the edge is only made out f@0: * of a straight line then this will be returned. f@0: * f@0: * If the edge has been broken into several segments (by bending it) then the central f@0: * line is returned. If the edge connects more than two nodes then a line (not necessarily f@0: * matching the edge) that has the central point in its centre is returned. Note that a f@0: * edge connecting more than two nodes is painted as a central point connected to all the nodes. f@0: * f@0: * @return the line where the name will be painted f@0: */ f@0: public Line2D getNameLine(){ f@0: if(points.isEmpty()){ // straight line f@0: return getSegment(nodes.get(0),nodes.get(1)); f@0: }else{ f@0: if(masterInnerPoint != null){/* multiended edge */ f@0: Rectangle2D bounds = masterInnerPoint.getBounds(); f@0: Point2D p = new Point2D.Double(bounds.getCenterX() - 1,bounds.getCenterY()); f@0: Point2D q = new Point2D.Double(bounds.getCenterX() + 1,bounds.getCenterY()); f@0: return new Line2D.Double(p, q); f@0: }else{ f@0: GraphElement ge1 = nodes.get(0); f@0: GraphElement ge2 = nodes.get(1); f@0: InnerPoint c1 = null; f@0: InnerPoint c2 = null; f@0: f@0: for(InnerPoint innp : points){ f@0: if(innp.getNeighbours().contains(ge1)){ f@0: c1 = innp; f@0: } f@0: if(innp.getNeighbours().contains(ge2)){ f@0: c2 = innp; f@0: } f@0: } f@0: f@0: /* we only have two nodes but the edge has been bended */ f@0: while((c1 != c2)&&(!c2.getNeighbours().contains(c1))){ f@0: if(c1.getNeighbours().get(0) == ge1){ f@0: ge1 = c1; f@0: c1 = (InnerPoint)c1.getNeighbours().get(1); f@0: } f@0: else{ f@0: ge1 = c1; f@0: c1 = (InnerPoint)c1.getNeighbours().get(0); f@0: } f@0: if(c2.getNeighbours().get(0) == ge2){ f@0: ge2 = c2; f@0: c2 = (InnerPoint)c2.getNeighbours().get(1); f@0: } f@0: else{ f@0: ge2 = c2; f@0: c2 = (InnerPoint)c2.getNeighbours().get(0); f@0: } f@0: } f@0: f@0: Point2D p = new Point2D.Double(); f@0: Point2D q = new Point2D.Double(); f@0: if(c1 == c2){ f@0: Rectangle2D bounds = c1.getBounds(); f@0: p.setLocation( bounds.getCenterX() - 1,bounds.getCenterY()); f@0: q.setLocation( bounds.getCenterX() + 1,bounds.getCenterY()); f@0: }else{ f@0: Rectangle2D bounds = c1.getBounds(); f@0: p.setLocation( bounds.getCenterX(),bounds.getCenterY()); f@0: bounds = c2.getBounds(); f@0: q.setLocation(bounds.getCenterX(),bounds.getCenterY()); f@0: f@0: } f@0: return new Line2D.Double(p,q); f@0: } f@0: } f@0: } f@0: f@0: /** f@0: * Encodes all the relevant data of this object in XML format. f@0: * f@0: * @param doc an XML document f@0: * @param parent the parent XML element, where tag about this edge will be inserted f@0: * @param nodes a list of all nodes of the diagram f@0: */ f@0: public void encode(Document doc, Element parent, List nodes){ f@0: parent.setAttribute(PersistenceManager.TYPE,getType()); f@0: parent.setAttribute(PersistenceManager.NAME, getName()); f@0: parent.setAttribute(PersistenceManager.ID, String.valueOf(getId())); f@0: f@0: int numNodes = getNodesNum(); f@0: if(numNodes > 0){ f@0: Element nodesTag = doc.createElement(PersistenceManager.NODES); f@0: parent.appendChild(nodesTag); f@0: for(int i=0; i nodesId) throws IOException{ f@0: setName(edgeTag.getAttribute(PersistenceManager.NAME),DiagramEventSource.PERS); f@0: if(getName().isEmpty()) f@0: throw new IOException(); f@0: try{ f@0: setId(Integer.parseInt(edgeTag.getAttribute(PersistenceManager.ID))); f@0: }catch(NumberFormatException nfe){ f@0: throw new IOException(nfe); f@0: } f@0: f@0: NodeList nodeList = edgeTag.getElementsByTagName(PersistenceManager.NODE); f@0: List attachedNodes = new ArrayList(nodeList.getLength()); f@0: List labels = new ArrayList(nodeList.getLength()); f@0: for(int i=0; i pointsId = new LinkedHashMap(); f@0: NodeList pointTagList = edgeTag.getElementsByTagName(PersistenceManager.POINT); f@0: f@0: for(int i=0; i= 0) f@0: throw new IOException(); f@0: }catch(NumberFormatException nfe){ f@0: throw new IOException(nfe); f@0: } f@0: f@0: pointsId.put(id, point); f@0: f@0: if(pointTag.getElementsByTagName(PersistenceManager.POSITION).item(0) == null) f@0: throw new IOException(); f@0: Element pointPositionTag = (Element)pointTag.getElementsByTagName(PersistenceManager.POSITION).item(0); f@0: double dx = 0,dy = 0; f@0: try{ f@0: dx = Double.parseDouble(pointPositionTag.getAttribute(PersistenceManager.X)); f@0: dy = Double.parseDouble(pointPositionTag.getAttribute(PersistenceManager.Y)); f@0: }catch(NumberFormatException nfe){ f@0: throw new IOException(); f@0: } f@0: point.translate(new Point2D.Double(), dx, dy,DiagramEventSource.PERS); f@0: } f@0: f@0: /* remove the master inner point eventually created by connect */ f@0: /* we're going to replace it with the one in the XML file */ f@0: points.clear(); f@0: /* re do the cycle when all the points id have been Map-ped */ f@0: for(int i=0; i nodes; f@0: f@0: /* list containing the vertex of the edge which are not nodes */ f@0: /** f@0: * The list of the inner points of this edge f@0: */ f@0: protected List points; f@0: /** f@0: * For edges connecting more than two nodes, this is the central inner point where f@0: * all the lines from the nodes join f@0: */ f@0: protected InnerPoint masterInnerPoint; f@0: f@0: private boolean newPointCreated; f@0: private InnerPoint newInnerPoint; f@0: private int minAttachedNodes; f@0: private int maxAttachedNodes; f@0: f@0: private LineStyle style; f@0: f@0: f@0: private static final double MAX_DIST = 5; f@0: private static final Color POINT_COLOR = Color.GRAY; f@0: /** f@0: * The end description for an end that has no hand description set by the user f@0: */ f@0: public static final String NO_ENDDESCRIPTION_STRING = ResourceBundle.getBundle(EditorFrame.class.getName()).getString("no_arrow_string"); f@0: f@0: f@0: /** f@0: * When an edge's (straight) line is bent it breaks into two sub lines. At the point where this two f@0: * sub lines join a square shaped point is painted. This class represent that point. Objects of this class f@0: * are graph elements and the user can click on them and translate them along the graph. f@0: * f@0: */ f@0: protected static class InnerPoint implements GraphElement{ f@0: /** f@0: * Creates a new inner point f@0: */ f@0: public InnerPoint(){ f@0: bounds = new Rectangle2D.Double(0,0,DIM,DIM); f@0: neighbours = new LinkedList(); f@0: } f@0: f@0: @Override f@0: public void startMove(Point2D p, Object source){} f@0: f@0: @Override f@0: public void stopMove(Object source){} f@0: f@0: @Override f@0: public void draw(Graphics2D g2){ f@0: Color oldColor = g2.getColor(); f@0: g2.setColor(POINT_COLOR); f@0: g2.fill(bounds); f@0: g2.setColor(oldColor); f@0: } f@0: f@0: @Override f@0: public boolean contains(Point2D p){ f@0: return bounds.contains(p); f@0: } f@0: f@0: @Override f@0: public Point2D getConnectionPoint(Direction d){ f@0: return new Point2D.Double(bounds.getCenterX(),bounds.getCenterY()); f@0: } f@0: f@0: @Override f@0: public void translate(Point2D p, double dx, double dy, Object source){ f@0: bounds.setFrame(bounds.getX() + dx, f@0: bounds.getY() + dy, f@0: bounds.getWidth(), f@0: bounds.getHeight()); f@0: } f@0: f@0: @Override f@0: public Rectangle2D getBounds(){ f@0: return (Rectangle2D)bounds.clone(); f@0: } f@0: f@0: /** f@0: * Neighbours are the graph elements (either nodes or other inner points) this inner point is f@0: * directly connected to. Directly connected means there is a straight line from this node f@0: * and the neighbour. f@0: * f@0: * @return a list of neighbours of this inner node f@0: */ f@0: public List getNeighbours(){ f@0: return neighbours; f@0: } f@0: f@0: /** f@0: * Returns true if this inner node and {@code ge} are neighbours. f@0: * f@0: * @see #getNeighbours() f@0: * f@0: * @param ge the graph element to be tested f@0: * @return {@code true} if {@code ge} is a neighbour of this graph element, {@code false} otherwise f@0: * f@0: */ f@0: public boolean hasNeighbour(GraphElement ge){ f@0: return neighbours.contains(ge); f@0: } f@0: f@0: @Override f@0: public String toString(){ f@0: return "EdgePoint: "+bounds.getCenterX()+"-"+bounds.getCenterY(); f@0: } f@0: f@0: private Rectangle2D bounds; f@0: private List neighbours; f@0: private static final int DIM = 7; f@0: } f@0: f@0: /** f@0: * A representation of this edge as a set of 2D points. f@0: * Every node the edge connects and every inner point are represented as a pair with coordinates f@0: * of their centre. Furthermore an adjacency matrix holds the information f@0: * about which point is connected to which is. This representation of the edge is f@0: * used in the haptic space, being more suitable for haptic devices. f@0: * f@0: */ f@0: public static class PointRepresentation { f@0: public PointRepresentation(int size){ f@0: xs = new double[size]; f@0: ys = new double[size]; f@0: adjMatrix = new BitSet[size]; f@0: for(int i=0; i