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