comparison java/src/uk/ac/qmul/eecs/ccmi/gui/Edge.java @ 0:78b7fc5391a2

first import, outcome of NIME 2014 hackaton
author Fiore Martin <f.martin@qmul.ac.uk>
date Tue, 08 Jul 2014 16:28:59 +0100
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:78b7fc5391a2
1 /*
2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
3
4 Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com)
5 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
6
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 package uk.ac.qmul.eecs.ccmi.gui;
22
23 import java.awt.BasicStroke;
24 import java.awt.Color;
25 import java.awt.Graphics2D;
26 import java.awt.Shape;
27 import java.awt.geom.Line2D;
28 import java.awt.geom.Point2D;
29 import java.awt.geom.Rectangle2D;
30 import java.io.IOException;
31 import java.util.ArrayList;
32 import java.util.BitSet;
33 import java.util.LinkedHashMap;
34 import java.util.LinkedHashSet;
35 import java.util.LinkedList;
36 import java.util.List;
37 import java.util.ListIterator;
38 import java.util.Map;
39 import java.util.ResourceBundle;
40
41 import org.w3c.dom.Document;
42 import org.w3c.dom.Element;
43 import org.w3c.dom.NodeList;
44
45 import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException;
46 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramEdge;
47 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
48 import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
49 import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
50
51 /**
52 * An edge in a graph. Edge objects are used in a GraphPanel to render a diagram edge visually.
53 * Edge objects are used in the tree representation of the diagram as well, as they're
54 * subclasses of {@link DiagramEdge}
55 *
56 */
57 @SuppressWarnings("serial")
58 public abstract class Edge extends DiagramEdge implements GraphElement{
59
60 /**
61 * Creates a new Edge
62 *
63 * @param type the type of the edge. The type is just a string that the user assign to the edge.
64 * All the edges created via clonation from this edge will have the same type.
65 * @param availableEndDescriptions all the possible end description ends of this edge can be
66 * associated to. An end description is a text associated to a possible arrow head of an edge end.
67 * It's used to give awareness of arrow heads via speech.
68 * @param minAttachedNodes the minimum number of nodes that can be attached to this edge
69 * @param maxAttachedNodes the minimum number of nodes that can be attached to this edge
70 * @param style the style of the edge: whether it's solid, dotted or dashed
71 */
72 public Edge(String type, String[] availableEndDescriptions, int minAttachedNodes, int maxAttachedNodes,LineStyle style){
73 super(type,availableEndDescriptions);
74 this.minAttachedNodes = minAttachedNodes;
75 this.maxAttachedNodes = maxAttachedNodes;
76 this.style = style;
77 nodes = new ArrayList<Node>();
78 }
79
80 /* --- Methods inherited from DiagramEdge --- */
81 @Override
82 public Node getNodeAt(int index){
83 return nodes.get(index);
84 }
85
86 @Override
87 public int getNodesNum(){
88 return nodes.size();
89 }
90
91 @Override
92 public boolean removeNode(DiagramNode diagramNode, Object source){
93 Node n = (Node)diagramNode;
94 if(nodes.size() == 2)
95 throw new RuntimeException("Cannot remove a node from a two ends edge");
96 else{
97 for(InnerPoint p : points)
98 if(p.hasNeighbour(n)){
99 p.neighbours.remove(n);
100 if(p.neighbours.size() == 1)
101 removePoint(p);
102 break;
103 }
104 boolean nodeRemoved = nodes.remove(n);
105 /* for update in the haptic device */
106 notifyChange(new ElementChangedEvent(this,n,"remove_node",source));
107 return nodeRemoved;
108 }
109 }
110
111 @Override
112 public void connect(List<DiagramNode> nodes) throws ConnectNodesException{
113 assert(getNodesNum() == 0);
114 /* this is to eliminate duplicates */
115 LinkedHashSet<Node> nodeSet = new LinkedHashSet<Node>();
116 for(DiagramNode n : nodes)
117 nodeSet.add((Node)n);
118
119 /* checks on connection consistency */
120 if((nodeSet == null)||(nodeSet.size() < minAttachedNodes))
121 throw new ConnectNodesException("You must select at least "+ minAttachedNodes + "nodes");
122 if((nodeSet.size() > maxAttachedNodes))
123 throw new ConnectNodesException("You must select at most " + maxAttachedNodes +" nodes");
124
125 points = new ArrayList<InnerPoint>();
126 if(nodeSet.size() > 2){
127 /* there are more than three nodes. compute the central inner point *
128 * which will connect all the nodes, as the middle points of the edge bound */
129 Rectangle2D bounds = new Rectangle2D.Double();
130 for(Node n : nodeSet){
131 bounds.add(n.getBounds());
132 }
133 InnerPoint p = new InnerPoint();
134 p.translate(new Point2D.Double(0,0), bounds.getCenterX(), bounds.getCenterY(),DiagramEventSource.NONE);
135 p.neighbours.addAll(nodeSet);
136 points.add(p);
137 }
138 this.nodes.addAll(nodeSet);
139
140 if(!points.isEmpty())// points is empty when the edge has two nodes only
141 masterInnerPoint = points.get(0);
142 }
143
144 @Override
145 public abstract void draw(Graphics2D g2);
146
147 @Override
148 public void translate(Point2D p, double dx, double dy, Object source){
149 for(InnerPoint ip : points)
150 ip.translate(p, dx, dy,source);
151 }
152
153 /**
154 * To be called before {@code bend}, determines from {@code downPoint} whether
155 * a line is going to be break into two lines (with the creation of a new inner point)
156 * or if the bending is determined by an already existing inner point being translated
157 */
158 @Override
159 public void startMove(Point2D downPoint,Object source){
160 this.downPoint = downPoint;
161 newInnerPoint = null;
162 for(InnerPoint itrPoint : points)
163 if(itrPoint.contains(downPoint)){
164 /* clicked on an already existing EdgePoint */
165 newInnerPoint = itrPoint;
166 newPointCreated = false;
167 }
168 if(newInnerPoint == null){
169 /* no point under the click, create a new one */
170 newInnerPoint = new InnerPoint();
171 newInnerPoint.translate(downPoint, downPoint.getX() - newInnerPoint.getBounds().getCenterX(),
172 downPoint.getY() - newInnerPoint.getBounds().getCenterY(),DiagramEventSource.NONE);
173 newPointCreated = true;
174 /* this methods checks for segments of the edge which are aligned and makes a unique edge out of them */
175 }
176 }
177
178 /**
179 * If this edge is made out of several lines and two or more of them becomes
180 * aligned then they are blended in one single line and the inner point that was
181 * at the joint is removed.
182 *
183 * @param source the source of the {@code stopMove} action
184 */
185 @Override
186 public void stopMove(Object source){
187 for(ListIterator<InnerPoint> pItr = points.listIterator(); pItr.hasNext(); ){
188 InnerPoint ePoint = pItr.next();
189 if(ePoint.neighbours.size() > 2)
190 continue;
191 Rectangle2D startBounds = ePoint.getBounds();
192 Rectangle2D endBounds = ePoint.neighbours.get(0).getBounds();
193 Direction d1 = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY());
194 endBounds = ePoint.neighbours.get(1).getBounds();
195 Direction d2 = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY());
196 if(d1.isParallel(d2)){
197 InnerPoint p = null;
198 GraphElement q = null;
199 if(ePoint.neighbours.get(0) instanceof InnerPoint){
200 p = (InnerPoint)ePoint.neighbours.get(0);
201 q = ePoint.neighbours.get(1);
202 p.neighbours.add(q);
203 p.neighbours.remove(ePoint);
204 }
205 if(ePoint.neighbours.get(1) instanceof InnerPoint){
206 p = (InnerPoint)ePoint.neighbours.get(1);
207 q = ePoint.neighbours.get(0);
208 p.neighbours.add(q);
209 p.neighbours.remove(ePoint);
210 }
211 pItr.remove();
212 }
213 }
214 notifyChange(new ElementChangedEvent(this,this,"stop_move",source));
215 }
216
217 @Override
218 public abstract Rectangle2D getBounds();
219
220 @Override
221 public Point2D getConnectionPoint(Direction d){return null;}
222
223 @Override
224 public boolean contains(Point2D aPoint){
225 if(points.isEmpty()){
226 return fatStrokeContains (nodes.get(0), nodes.get(1), aPoint);
227 }
228 for(InnerPoint p : points){
229 for(GraphElement ge : p.neighbours){
230 if(fatStrokeContains(p,ge,aPoint))
231 return true;
232 }
233 }
234 return false;
235 }
236
237 /**
238 * Look for the node attached to this edge which lays at the minimum distance
239 * from the point passed as argument. The distance cannot be lower than the
240 * value passed as argument.
241 *
242 * @param aPoint the point the distance is measured from
243 * @param distanceLimit the limit from the distance between the nodes and the point
244 * @return the closest node or null if the node lays at an higher distance than distanceLimit
245 */
246 public Node getClosestNode(Point2D aPoint, double distanceLimit){
247 Node closestNode = null;
248 double minDist = distanceLimit;
249
250 if(points.isEmpty()){
251 Line2D line = getSegment(nodes.get(0),nodes.get(1));
252 if(line.getP1().distance(aPoint) < minDist){
253 minDist = line.getP1().distance(aPoint);
254 closestNode = nodes.get(0);
255 }
256 if(line.getP2().distance(aPoint) < minDist){
257 minDist = line.getP2().distance(aPoint);
258 closestNode = nodes.get(1);
259 }
260 return closestNode;
261 }else{
262 for(InnerPoint p : points){
263 for(GraphElement ge : p.getNeighbours())
264 if(ge instanceof Node){
265 Node n = (Node)ge;
266 Direction d = new Direction(p.getBounds().getCenterX() - n.getBounds().getCenterX(),p.getBounds().getCenterY() - n.getBounds().getCenterY());
267 if(n.getConnectionPoint(d).distance(aPoint) < minDist){
268 minDist = n.getConnectionPoint(d).distance(aPoint);
269 closestNode = n;
270 }
271 }
272 }
273 return closestNode;
274 }
275 }
276
277 private void removePoint(InnerPoint p){
278 /* we assume at this moment p has one neighbour only */
279 InnerPoint neighbour = (InnerPoint)p.neighbours.get(0);
280 points.remove(p);
281 neighbour.neighbours.remove(p);
282 if(neighbour.neighbours.size() == 1)
283 removePoint(neighbour);
284 }
285
286 /* checks if a point belongs to a shape with a margin of MAX_DIST*/
287 private boolean fatStrokeContains(GraphElement ge1, GraphElement ge2, Point2D p){
288 BasicStroke fatStroke = new BasicStroke((float) (2 * MAX_DIST));
289 Line2D line = new Line2D.Double(
290 ge1.getBounds().getCenterX(),
291 ge1.getBounds().getCenterY(),
292 ge2.getBounds().getCenterX(),
293 ge2.getBounds().getCenterY());
294 Shape fatPath = fatStroke.createStrokedShape(line);
295 return fatPath.contains(p);
296 }
297
298 /**
299 * Returns a line connecting the centre of the graph elements passed as argument.
300 *
301 * @param start the first graph element
302 * @param end the second graph element
303 *
304 * @return a line connecting {@code start} and {@code end}
305 */
306 protected Line2D.Double getSegment(GraphElement start, GraphElement end){
307 Rectangle2D startBounds = start.getBounds();
308 Rectangle2D endBounds = end.getBounds();
309 Direction d = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY());
310 return new Line2D.Double(start.getConnectionPoint(d), end.getConnectionPoint(d.turn(180)));
311 }
312
313 /**
314 * Returns a list of the points where this edge and the nodes it connects come to a contact
315 *
316 * @return a list of points
317 */
318 public List<Point2D> getConnectionPoints(){
319 List<Point2D> list = new LinkedList<Point2D>();
320 if(points.isEmpty()){
321 Line2D line = getSegment(nodes.get(0),nodes.get(1));
322 list.add(line.getP1());
323 list.add(line.getP2());
324 }else{
325 for(InnerPoint p : points){
326 for(GraphElement ge : p.neighbours)
327 if(ge instanceof Node){
328 Direction d = new Direction(p.getBounds().getCenterX() - ge.getBounds().getCenterX(),p.getBounds().getCenterY() - ge.getBounds().getCenterY());
329 list.add(((Node)ge).getConnectionPoint(d));
330 }
331 }
332 }
333 return list;
334 }
335
336 /**
337 * Gets the stipple pattern of this edge line style
338 *
339 * @see LineStyle#getStipplePattern()
340 *
341 * @return an int representing the stipple pattern of this edge
342 */
343 public int getStipplePattern(){
344 return getStyle().getStipplePattern();
345 }
346
347 /**
348 * Bends one of the lines forming this edge.
349 *
350 * When an line is bent, if the location where the bending happens is a line then a new
351 * inner point is created and the line is broken into two sub lines. If the location is an
352 * already existing inner point, then the point is translated.
353 *
354 * @param p the starting point of the bending
355 * @param source the source of the bending action
356 */
357 public void bend(Point2D p,Object source) {
358 boolean found = false;
359 if(points.isEmpty()){
360 newInnerPoint.neighbours.addAll(nodes);
361 points.add(newInnerPoint);
362 newPointCreated = false;
363 }else if(newPointCreated){
364 /* find the segment closest to where the new point lays */
365 InnerPoint closestP1 = null;
366 GraphElement closestP2 = null;
367 double minDist = 0;
368 for(ListIterator<InnerPoint> pItr = points.listIterator(); pItr.hasNext(); ){
369 InnerPoint ePoint = pItr.next();
370 for(ListIterator<GraphElement> geItr = ePoint.neighbours.listIterator(); geItr.hasNext() && !found;){
371 /* find the neighbour of the current edge point whose line the new point lays on */
372 GraphElement next = geItr.next();
373 double dist = Line2D.ptSegDist(
374 ePoint.getBounds().getCenterX(),
375 ePoint.getBounds().getCenterY(),
376 next.getBounds().getCenterX(),
377 next.getBounds().getCenterY(),
378 downPoint.getX(),
379 downPoint.getY()
380 );
381
382 if(closestP1 == null || dist < minDist){
383 closestP1 = ePoint;
384 closestP2 = next;
385 minDist = dist;
386 continue;
387 }
388 }
389 }
390
391 if(closestP2 instanceof InnerPoint ){
392 /* remove current edge point from the neighbour's neighbours */
393 ((InnerPoint)closestP2).neighbours.remove(closestP1);
394 /* add the new inner point */
395 ((InnerPoint)closestP2).neighbours.add(newInnerPoint);
396 }
397 /*remove old neighbour from edge inner point neighbours */
398 closestP1.neighbours.remove(closestP2);
399 newInnerPoint.neighbours.add(closestP1);
400 newInnerPoint.neighbours.add(closestP2);
401 /* add the new node to the list of EdgeNodes of this edge */
402 points.add(newInnerPoint);
403 closestP1.neighbours.add(newInnerPoint);
404 found = true;
405 newPointCreated = false;
406 }
407 newInnerPoint.translate(p, p.getX() - newInnerPoint.getBounds().getCenterX(),
408 p.getY() - newInnerPoint.getBounds().getCenterY(),DiagramEventSource.NONE);
409 notifyChange(new ElementChangedEvent(this,this,"bend",source));
410 }
411
412 /**
413 * Returns the line where the edge name will be painted. If the edge is only made out
414 * of a straight line then this will be returned.
415 *
416 * If the edge has been broken into several segments (by bending it) then the central
417 * line is returned. If the edge connects more than two nodes then a line (not necessarily
418 * matching the edge) that has the central point in its centre is returned. Note that a
419 * edge connecting more than two nodes is painted as a central point connected to all the nodes.
420 *
421 * @return the line where the name will be painted
422 */
423 public Line2D getNameLine(){
424 if(points.isEmpty()){ // straight line
425 return getSegment(nodes.get(0),nodes.get(1));
426 }else{
427 if(masterInnerPoint != null){/* multiended edge */
428 Rectangle2D bounds = masterInnerPoint.getBounds();
429 Point2D p = new Point2D.Double(bounds.getCenterX() - 1,bounds.getCenterY());
430 Point2D q = new Point2D.Double(bounds.getCenterX() + 1,bounds.getCenterY());
431 return new Line2D.Double(p, q);
432 }else{
433 GraphElement ge1 = nodes.get(0);
434 GraphElement ge2 = nodes.get(1);
435 InnerPoint c1 = null;
436 InnerPoint c2 = null;
437
438 for(InnerPoint innp : points){
439 if(innp.getNeighbours().contains(ge1)){
440 c1 = innp;
441 }
442 if(innp.getNeighbours().contains(ge2)){
443 c2 = innp;
444 }
445 }
446
447 /* we only have two nodes but the edge has been bended */
448 while((c1 != c2)&&(!c2.getNeighbours().contains(c1))){
449 if(c1.getNeighbours().get(0) == ge1){
450 ge1 = c1;
451 c1 = (InnerPoint)c1.getNeighbours().get(1);
452 }
453 else{
454 ge1 = c1;
455 c1 = (InnerPoint)c1.getNeighbours().get(0);
456 }
457 if(c2.getNeighbours().get(0) == ge2){
458 ge2 = c2;
459 c2 = (InnerPoint)c2.getNeighbours().get(1);
460 }
461 else{
462 ge2 = c2;
463 c2 = (InnerPoint)c2.getNeighbours().get(0);
464 }
465 }
466
467 Point2D p = new Point2D.Double();
468 Point2D q = new Point2D.Double();
469 if(c1 == c2){
470 Rectangle2D bounds = c1.getBounds();
471 p.setLocation( bounds.getCenterX() - 1,bounds.getCenterY());
472 q.setLocation( bounds.getCenterX() + 1,bounds.getCenterY());
473 }else{
474 Rectangle2D bounds = c1.getBounds();
475 p.setLocation( bounds.getCenterX(),bounds.getCenterY());
476 bounds = c2.getBounds();
477 q.setLocation(bounds.getCenterX(),bounds.getCenterY());
478
479 }
480 return new Line2D.Double(p,q);
481 }
482 }
483 }
484
485 /**
486 * Encodes all the relevant data of this object in XML format.
487 *
488 * @param doc an XML document
489 * @param parent the parent XML element, where tag about this edge will be inserted
490 * @param nodes a list of all nodes of the diagram
491 */
492 public void encode(Document doc, Element parent, List<Node> nodes){
493 parent.setAttribute(PersistenceManager.TYPE,getType());
494 parent.setAttribute(PersistenceManager.NAME, getName());
495 parent.setAttribute(PersistenceManager.ID, String.valueOf(getId()));
496
497 int numNodes = getNodesNum();
498 if(numNodes > 0){
499 Element nodesTag = doc.createElement(PersistenceManager.NODES);
500 parent.appendChild(nodesTag);
501 for(int i=0; i<numNodes;i++){
502 Element nodeTag = doc.createElement(PersistenceManager.NODE);
503 nodeTag.setAttribute(PersistenceManager.ID, String.valueOf(getNodeAt(i).getId()));
504 nodeTag.setAttribute(PersistenceManager.LABEL, getEndLabel(getNodeAt(i)));
505 nodesTag.appendChild(nodeTag);
506 }
507 }
508
509 if(!points.isEmpty()){
510 Element pointsTag = doc.createElement(PersistenceManager.POINTS);
511 parent.appendChild(pointsTag);
512 for(InnerPoint point : points){
513 Element pointTag = doc.createElement(PersistenceManager.POINT);
514 pointsTag.appendChild(pointTag);
515 pointTag.setAttribute(PersistenceManager.ID, String.valueOf(-(points.indexOf(point)+1)));
516
517 Element positionTag = doc.createElement(PersistenceManager.POSITION);
518 pointTag.appendChild(positionTag);
519 Rectangle2D bounds = point.getBounds();
520 positionTag.setAttribute(PersistenceManager.X,String.valueOf(bounds.getX()));
521 positionTag.setAttribute(PersistenceManager.Y,String.valueOf(bounds.getY()));
522
523 Element neighboursTag = doc.createElement(PersistenceManager.NEIGHBOURS);
524 pointTag.appendChild(neighboursTag);
525 StringBuilder builder = new StringBuilder();
526 for(GraphElement ge : point.getNeighbours()){
527 if(ge instanceof Node){
528 builder.append(((Node)ge).getId());
529 }else{
530 builder.append(-(points.indexOf(ge)+1));
531 }
532 builder.append(" ");
533 }
534 builder.deleteCharAt(builder.length()-1);
535 neighboursTag.setTextContent(builder.toString());
536 }
537 }
538 }
539
540
541 /**
542 * Decodes an edge from the XML representation.
543 *
544 * @see #encode(Document, Element, List)
545 *
546 * @param doc an XML document
547 * @param edgeTag the tag in the XML file related to this edge
548 * @param nodesId a map linking node ids in the XML file to the {@code Node} objects they represent
549 * @throws IOException if something goes wrong while reading the XML file
550 */
551 public void decode(Document doc, Element edgeTag, Map<String,Node> nodesId) throws IOException{
552 setName(edgeTag.getAttribute(PersistenceManager.NAME),DiagramEventSource.PERS);
553 if(getName().isEmpty())
554 throw new IOException();
555 try{
556 setId(Integer.parseInt(edgeTag.getAttribute(PersistenceManager.ID)));
557 }catch(NumberFormatException nfe){
558 throw new IOException(nfe);
559 }
560
561 NodeList nodeList = edgeTag.getElementsByTagName(PersistenceManager.NODE);
562 List<DiagramNode> attachedNodes = new ArrayList<DiagramNode>(nodeList.getLength());
563 List<String> labels = new ArrayList<String>(nodeList.getLength());
564 for(int i=0; i<nodeList.getLength();i++){
565 String id = ((Element)nodeList.item(i)).getAttribute(PersistenceManager.ID);
566 if(!nodesId.containsKey(id))
567 throw new IOException();
568 attachedNodes.add(nodesId.get(id));
569 labels.add(((Element)nodeList.item(i)).getAttribute(PersistenceManager.LABEL));
570 }
571
572 try {
573 connect(attachedNodes);
574 } catch (ConnectNodesException e) {
575 throw new IOException(e);
576 }
577
578 for(int i=0; i < labels.size(); i++){
579 setEndLabel(attachedNodes.get(i), labels.get(i),DiagramEventSource.PERS);
580 }
581
582 Map<String, InnerPoint> pointsId = new LinkedHashMap<String, InnerPoint>();
583 NodeList pointTagList = edgeTag.getElementsByTagName(PersistenceManager.POINT);
584
585 for(int i=0; i<pointTagList.getLength(); i++){
586 InnerPoint point = new InnerPoint();
587 Element pointTag = (Element)pointTagList.item(i);
588 String id = pointTag.getAttribute(PersistenceManager.ID);
589 /* id of property nodes must be a negative value */
590 try{
591 if(Integer.parseInt(id) >= 0)
592 throw new IOException();
593 }catch(NumberFormatException nfe){
594 throw new IOException(nfe);
595 }
596
597 pointsId.put(id, point);
598
599 if(pointTag.getElementsByTagName(PersistenceManager.POSITION).item(0) == null)
600 throw new IOException();
601 Element pointPositionTag = (Element)pointTag.getElementsByTagName(PersistenceManager.POSITION).item(0);
602 double dx = 0,dy = 0;
603 try{
604 dx = Double.parseDouble(pointPositionTag.getAttribute(PersistenceManager.X));
605 dy = Double.parseDouble(pointPositionTag.getAttribute(PersistenceManager.Y));
606 }catch(NumberFormatException nfe){
607 throw new IOException();
608 }
609 point.translate(new Point2D.Double(), dx, dy,DiagramEventSource.PERS);
610 }
611
612 /* remove the master inner point eventually created by connect */
613 /* we're going to replace it with the one in the XML file */
614 points.clear();
615 /* re do the cycle when all the points id have been Map-ped */
616 for(int i=0; i<pointTagList.getLength(); i++){
617 Element pointTag = (Element)pointTagList.item(i);
618 InnerPoint point = pointsId.get(pointTag.getAttribute(PersistenceManager.ID));
619
620 if(pointTag.getElementsByTagName(PersistenceManager.NEIGHBOURS).item(0) == null)
621 throw new IOException();
622 Element pointNeighboursTag = (Element)pointTag.getElementsByTagName(PersistenceManager.NEIGHBOURS).item(0);
623 String pointNeighboursTagContent = pointNeighboursTag.getTextContent();
624 String[] neighboursId = pointNeighboursTagContent.split(" ");
625
626 for(String neighbourId : neighboursId){
627 GraphElement ge = nodesId.get(neighbourId);
628 if(ge == null) // it ain't a node
629 ge = pointsId.get(neighbourId);
630 if(ge == null)
631 throw new IOException();
632 point.neighbours.add(ge);
633 }
634 points.add(point);
635 if(i==0)
636 masterInnerPoint = point;
637 }
638 }
639
640 /**
641 * Returns the minimum number of nodes that edge of this type can connect
642 *
643 * @return the minimum nodes for edges of this type
644 */
645 public int getMinAttachedNodes(){
646 return minAttachedNodes;
647 }
648
649 /**
650 * Returns the maximum number of nodes that edge of this type can connect
651 *
652 * @return the maximum nodes for edges of this type
653 */
654 public int getMaxAttachedNodes(){
655 return maxAttachedNodes;
656 }
657
658 /**
659 * Return the line style of this edge
660 *
661 * @return the line style of this edge
662 */
663 public LineStyle getStyle(){
664 return style;
665 }
666
667 protected Point2D downPoint;
668 private List<Node> nodes;
669
670 /* list containing the vertex of the edge which are not nodes */
671 /**
672 * The list of the inner points of this edge
673 */
674 protected List<InnerPoint> points;
675 /**
676 * For edges connecting more than two nodes, this is the central inner point where
677 * all the lines from the nodes join
678 */
679 protected InnerPoint masterInnerPoint;
680
681 private boolean newPointCreated;
682 private InnerPoint newInnerPoint;
683 private int minAttachedNodes;
684 private int maxAttachedNodes;
685
686 private LineStyle style;
687
688
689 private static final double MAX_DIST = 5;
690 private static final Color POINT_COLOR = Color.GRAY;
691 /**
692 * The end description for an end that has no hand description set by the user
693 */
694 public static final String NO_ENDDESCRIPTION_STRING = ResourceBundle.getBundle(EditorFrame.class.getName()).getString("no_arrow_string");
695
696
697 /**
698 * When an edge's (straight) line is bent it breaks into two sub lines. At the point where this two
699 * sub lines join a square shaped point is painted. This class represent that point. Objects of this class
700 * are graph elements and the user can click on them and translate them along the graph.
701 *
702 */
703 protected static class InnerPoint implements GraphElement{
704 /**
705 * Creates a new inner point
706 */
707 public InnerPoint(){
708 bounds = new Rectangle2D.Double(0,0,DIM,DIM);
709 neighbours = new LinkedList<GraphElement>();
710 }
711
712 @Override
713 public void startMove(Point2D p, Object source){}
714
715 @Override
716 public void stopMove(Object source){}
717
718 @Override
719 public void draw(Graphics2D g2){
720 Color oldColor = g2.getColor();
721 g2.setColor(POINT_COLOR);
722 g2.fill(bounds);
723 g2.setColor(oldColor);
724 }
725
726 @Override
727 public boolean contains(Point2D p){
728 return bounds.contains(p);
729 }
730
731 @Override
732 public Point2D getConnectionPoint(Direction d){
733 return new Point2D.Double(bounds.getCenterX(),bounds.getCenterY());
734 }
735
736 @Override
737 public void translate(Point2D p, double dx, double dy, Object source){
738 bounds.setFrame(bounds.getX() + dx,
739 bounds.getY() + dy,
740 bounds.getWidth(),
741 bounds.getHeight());
742 }
743
744 @Override
745 public Rectangle2D getBounds(){
746 return (Rectangle2D)bounds.clone();
747 }
748
749 /**
750 * Neighbours are the graph elements (either nodes or other inner points) this inner point is
751 * directly connected to. Directly connected means there is a straight line from this node
752 * and the neighbour.
753 *
754 * @return a list of neighbours of this inner node
755 */
756 public List<GraphElement> getNeighbours(){
757 return neighbours;
758 }
759
760 /**
761 * Returns true if this inner node and {@code ge} are neighbours.
762 *
763 * @see #getNeighbours()
764 *
765 * @param ge the graph element to be tested
766 * @return {@code true} if {@code ge} is a neighbour of this graph element, {@code false} otherwise
767 *
768 */
769 public boolean hasNeighbour(GraphElement ge){
770 return neighbours.contains(ge);
771 }
772
773 @Override
774 public String toString(){
775 return "EdgePoint: "+bounds.getCenterX()+"-"+bounds.getCenterY();
776 }
777
778 private Rectangle2D bounds;
779 private List<GraphElement> neighbours;
780 private static final int DIM = 7;
781 }
782
783 /**
784 * A representation of this edge as a set of 2D points.
785 * Every node the edge connects and every inner point are represented as a pair with coordinates
786 * of their centre. Furthermore an adjacency matrix holds the information
787 * about which point is connected to which is. This representation of the edge is
788 * used in the haptic space, being more suitable for haptic devices.
789 *
790 */
791 public static class PointRepresentation {
792 public PointRepresentation(int size){
793 xs = new double[size];
794 ys = new double[size];
795 adjMatrix = new BitSet[size];
796 for(int i=0; i<size; i++){
797 adjMatrix[i] = new BitSet(size);
798 }
799 }
800 /**
801 * An array with all the x coordinate of the edge's points (nodes and inner points)
802 */
803 public double xs[];
804 /**
805 * An array with all the y coordinate of the edge's points (nodes and inner points)
806 */
807 public double ys[];
808 /**
809 * The adjacency matrix. If the i-th bit of {@code adjMatrix[j]} is set to {@code true}
810 * it means there is a direct line connecting the i-th point (coordinates {@code (xs[i],ys[i])}
811 * to the j-th point (coordinates {@code (xs[j],ys[j])}. Note that connection are represented only
812 * once to avoid double paintings by the haptic engine. So if the i-th bit of {@code adjMatrix[j]}
813 * is set to {@code true}, the j-th bit of {@code adjMatrix[i]} and information redundancy is avoided.
814 */
815 public BitSet adjMatrix[];
816 /**
817 * The index of the beginning of the nodes (after inner points) in {@code adjMatrix}.
818 * So if the edge has three nodes and two inner points. The inner points
819 * will be at {@code adjMatrix[0]} and {@code adjMatrix[1]} and the nodes at the following indexes.
820 * In this case {@code nodeStart} is equal to 2.
821 */
822 public int nodeStart;
823 }
824
825 /**
826 * Returns a new {@code PointRepresentation} of this edge
827 *
828 * @return a new {@code PointRepresentation} of this edge
829 */
830 public PointRepresentation getPointRepresentation(){
831 PointRepresentation pr = new PointRepresentation(points.size()+nodes.size());
832 if(points.isEmpty()){ // two ended edge
833 pr.xs[0] = nodes.get(0).getBounds().getCenterX();
834 pr.ys[0] = nodes.get(0).getBounds().getCenterY();
835 pr.xs[1] = nodes.get(1).getBounds().getCenterX();
836 pr.ys[1] = nodes.get(1).getBounds().getCenterY();
837 // we only need one edge, else it would be painted twice
838 pr.adjMatrix[0].set(1);
839 pr.nodeStart = 0;
840 }else{
841 //[ point 1, point 2, point 3, ... , point n, node, 1 node 2, ... , node n ]
842 int pSize = points.size();
843 pr.nodeStart = pSize; // the first node starts after the points
844 for(int i=0; i<pSize;i++){
845 pr.xs[i] = points.get(i).getBounds().getCenterX();
846 pr.ys[i] = points.get(i).getBounds().getCenterY();
847 for(GraphElement ge : points.get(i).neighbours){
848 if(ge instanceof InnerPoint)
849 pr.adjMatrix[i].set(points.indexOf(ge));
850 else //Node
851 pr.adjMatrix[i].set(pSize+nodes.indexOf(ge));
852 }
853 }
854 /* set the coordinates of the nodes, no adj matrix needed as the inner points are enough */
855 for(int i=0 ; i<nodes.size(); i++){
856 pr.xs[pSize+i] = nodes.get(i).getBounds().getCenterX();
857 pr.ys[pSize+i] = nodes.get(i).getBounds().getCenterY();
858 }
859 }
860 return pr;
861 }
862
863 }
864