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