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