Mercurial > hg > accesspd
comparison java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeNode.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) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) | |
5 | |
6 This program is free software: you can redistribute it and/or modify | |
7 it under the terms of the GNU General Public License as published by | |
8 the Free Software Foundation, either version 3 of the License, or | |
9 (at your option) any later version. | |
10 | |
11 This program is distributed in the hope that it will be useful, | |
12 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 GNU General Public License for more details. | |
15 | |
16 You should have received a copy of the GNU General Public License | |
17 along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 */ | |
19 | |
20 package uk.ac.qmul.eecs.ccmi.simpletemplate; | |
21 | |
22 import java.awt.Color; | |
23 import java.awt.Graphics2D; | |
24 import java.awt.Shape; | |
25 import java.awt.geom.Ellipse2D; | |
26 import java.awt.geom.Line2D; | |
27 import java.awt.geom.Path2D; | |
28 import java.awt.geom.Point2D; | |
29 import java.awt.geom.Rectangle2D; | |
30 import java.awt.geom.RectangularShape; | |
31 import java.io.IOException; | |
32 import java.util.ArrayList; | |
33 import java.util.LinkedHashMap; | |
34 import java.util.LinkedList; | |
35 import java.util.List; | |
36 import java.util.Map; | |
37 import java.util.Set; | |
38 | |
39 import org.w3c.dom.Document; | |
40 import org.w3c.dom.Element; | |
41 import org.w3c.dom.NodeList; | |
42 | |
43 import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent; | |
44 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties; | |
45 import uk.ac.qmul.eecs.ccmi.gui.Direction; | |
46 import uk.ac.qmul.eecs.ccmi.gui.Node; | |
47 import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager; | |
48 import uk.ac.qmul.eecs.ccmi.utils.Pair; | |
49 | |
50 /** | |
51 * | |
52 * A diagram node that can be represented visually as a simple shape such as | |
53 * a rectangle, square, circle, ellipse or triangle. | |
54 * | |
55 */ | |
56 @SuppressWarnings("serial") | |
57 public abstract class SimpleShapeNode extends Node { | |
58 | |
59 public static SimpleShapeNode getInstance(ShapeType shapeType, String typeName, NodeProperties properties){ | |
60 switch(shapeType){ | |
61 case Rectangle : | |
62 return new RectangularNode(typeName, properties); | |
63 case Square : | |
64 return new SquareNode(typeName, properties); | |
65 case Circle : | |
66 return new CircleNode(typeName, properties); | |
67 case Ellipse : | |
68 return new EllipticalNode(typeName, properties); | |
69 case Triangle : | |
70 return new TriangularNode(typeName, properties); | |
71 } | |
72 return null; | |
73 } | |
74 | |
75 protected SimpleShapeNode(String typeName, NodeProperties properties){ | |
76 super(typeName, properties); | |
77 dataDisplayBounds = (Rectangle2D.Double)getMinBounds(); | |
78 /* Initialise the data structures for displaying the properties inside and outside */ | |
79 propertyNodesMap = new LinkedHashMap<String,List<PropertyNode>>(); | |
80 int numInsideProperties = 0; | |
81 for(String type : getProperties().getTypes()){ | |
82 if(((PropertyView)getProperties().getView(type)).getPosition() == Position.Inside) | |
83 numInsideProperties++; | |
84 else | |
85 propertyNodesMap.put(type, new LinkedList<PropertyNode>()); | |
86 } | |
87 propertyLabels = new MultiLineString[numInsideProperties]; | |
88 nameLabel = new MultiLineString(); | |
89 } | |
90 | |
91 @Override | |
92 protected void notifyChange(ElementChangedEvent evt){ | |
93 if(!evt.getChangeType().equals("translate")&&!evt.getChangeType().equals("stop_move")){ //don't reshape for just moving | |
94 Rectangle2D boundsBeforeReshape = getBounds(); | |
95 reshape(); | |
96 Rectangle2D boundsAfterReshape = getBounds(); | |
97 /* after renaming or setting properties the boundaries can change resulting in a slight shift of the * | |
98 * node centre from its original position. the next line is to place it back to the right position */ | |
99 Point2D start = new Point2D.Double(boundsAfterReshape.getCenterX(),boundsAfterReshape.getCenterY()); | |
100 translateImplementation(start, | |
101 boundsBeforeReshape.getCenterX() - boundsAfterReshape.getCenterX(), | |
102 boundsBeforeReshape.getCenterY() - boundsAfterReshape.getCenterY()); | |
103 } | |
104 super.notifyChange(evt); | |
105 } | |
106 | |
107 @Override | |
108 public void setId(long id){ | |
109 super.setId(id); | |
110 /* when they are given an id nodes change name into "new <type> node <id>" * | |
111 * where <type> is the actual type of the node and <id> is the given id * | |
112 * therefore a reshape is necessary to display the new name */ | |
113 Rectangle2D boundsBeforeReshape = getBounds(); | |
114 reshape(); | |
115 /* the reshape might change the bounds, so the shape is translated so that the top-left * | |
116 * point is at the same position as before just to keep it more consistent */ | |
117 Rectangle2D boundsAfterReshape = getBounds(); | |
118 translateImplementation( | |
119 new Point2D.Double(), | |
120 boundsBeforeReshape.getX() - boundsAfterReshape.getX(), | |
121 boundsBeforeReshape.getY() - boundsAfterReshape.getY() | |
122 ); | |
123 } | |
124 | |
125 @Override | |
126 public boolean contains(Point2D p) { | |
127 if (getShape().contains(p)) | |
128 return true; | |
129 for(List<PropertyNode> pnList : propertyNodesMap.values()) | |
130 for(PropertyNode pn : pnList) | |
131 if(pn.contains(p)) | |
132 return true; | |
133 return false; | |
134 } | |
135 | |
136 protected void reshape(){ | |
137 Pair<List<String>, List<String>> splitPropertyTypes = splitPropertyTypes(); | |
138 /* properties displayed internally */ | |
139 reshapeInnerProperties(splitPropertyTypes.first); | |
140 /* properties displayed externally */ | |
141 reshapeOuterProperties(splitPropertyTypes.second); | |
142 } | |
143 | |
144 protected Pair<List<String>, List<String>> splitPropertyTypes(){ | |
145 List<String> types = getProperties().getTypes(); | |
146 ArrayList<String> insidePropertyTypes = new ArrayList<String>(types.size()); | |
147 ArrayList<String> outsidePropertyTypes = new ArrayList<String>(types.size()); | |
148 for(String type : types){ | |
149 if(((PropertyView)getProperties().getView(type)).getPosition() == Position.Inside) | |
150 insidePropertyTypes.add(type); | |
151 else | |
152 outsidePropertyTypes.add(type); | |
153 } | |
154 | |
155 return new Pair<List<String>, List<String>> (insidePropertyTypes,outsidePropertyTypes); | |
156 } | |
157 | |
158 protected void reshapeOuterProperties(List<String> outsidePropertyTypes){ | |
159 for(String type : outsidePropertyTypes){ | |
160 List<PropertyNode> propertyNodes = propertyNodesMap.get(type); | |
161 List<String> propertyValues = getProperties().getValues(type); | |
162 int diff = propertyNodes.size()-propertyValues.size(); | |
163 if(diff > 0) // properties have been removed | |
164 for(int i=0; i < diff; i++) | |
165 propertyNodes.remove(propertyNodes.size() - 1); | |
166 else if(diff < 0){ // properties have been added. We need more properties node. | |
167 for(int i=0; i < -diff; i++){ | |
168 PropertyNode propertyNode = new PropertyNode(((PropertyView)getProperties().getView(type)).getShapeType()); | |
169 Rectangle2D bounds = getBounds(); | |
170 double x = bounds.getCenterX() - bounds.getWidth()/2 - PROP_NODE_DIST; | |
171 double y = bounds.getCenterX() - PROP_NODE_DIST * i; | |
172 propertyNode.translate(x, y); | |
173 propertyNodes.add(propertyNode); | |
174 } | |
175 } | |
176 /* set the text on all the property nodes */ | |
177 int i = 0; | |
178 for(String text : propertyValues){ | |
179 NodeProperties.Modifiers modifiers = getProperties().getModifiers(type); | |
180 Set<Integer> viewIndexes = modifiers.getIndexes(i); | |
181 ModifierView[] views = new ModifierView[viewIndexes.size()]; | |
182 int j =0; | |
183 for(Integer I : viewIndexes){ | |
184 views[j] = (ModifierView) getProperties().getModifiers(type).getView(modifiers.getTypes().get(I)); | |
185 j++; | |
186 } | |
187 propertyNodes.get(i).setText(text,views); | |
188 i++; | |
189 } | |
190 } | |
191 } | |
192 | |
193 protected void reshapeInnerProperties(List<String> insidePropertyTypes){ | |
194 /* set the bounds for each multiline string and the resulting bound of the node */ | |
195 nameLabel = new MultiLineString(); | |
196 nameLabel.setText(getName().isEmpty() ? " " : getName()); | |
197 nameLabel.setBold(true); | |
198 Rectangle2D r = nameLabel.getBounds(); | |
199 | |
200 for(int i=0; i<insidePropertyTypes.size();i++){ | |
201 propertyLabels[i] = new MultiLineString(); | |
202 String propertyType = insidePropertyTypes.get(i); | |
203 if(getProperties().getValues(propertyType).size() == 0){ | |
204 propertyLabels[i].setText(" "); | |
205 }else{ | |
206 propertyLabels[i].setJustification(MultiLineString.LEFT); | |
207 String[] array = new String[getProperties().getValues(propertyType).size()]; | |
208 propertyLabels[i].setText(getProperties().getValues(propertyType).toArray(array), getProperties().getModifiers(propertyType)); | |
209 } | |
210 r.add(new Rectangle2D.Double(r.getX(),r.getMaxY(),propertyLabels[i].getBounds().getWidth(),propertyLabels[i].getBounds().getHeight())); | |
211 } | |
212 /* set a gap to uniformly distribute the extra space among property rectangles to reach the minimum bound's height */ | |
213 boundsGap = 0; | |
214 Rectangle2D.Double minBounds = (Rectangle2D.Double)getMinBounds(); | |
215 if(r.getHeight() < minBounds.height){ | |
216 boundsGap = minBounds.height - r.getHeight(); | |
217 boundsGap /= insidePropertyTypes.size(); | |
218 } | |
219 r.add(minBounds); //make sure it's at least as big as the minimum bounds | |
220 dataDisplayBounds.setFrame(new Rectangle2D.Double(dataDisplayBounds.x,dataDisplayBounds.y,r.getWidth(),r.getHeight())); | |
221 } | |
222 | |
223 /** | |
224 Draw the node from the Shape with shadow | |
225 @param g2 the graphics context | |
226 */ | |
227 @Override | |
228 public void draw(Graphics2D g2){ | |
229 /* draw the external shape */ | |
230 Shape shape = getShape(); | |
231 if (shape == null) return; | |
232 Color oldColor = g2.getColor(); | |
233 g2.translate(SHADOW_GAP, SHADOW_GAP); | |
234 g2.setColor(SHADOW_COLOR); | |
235 g2.fill(shape); | |
236 g2.translate(-SHADOW_GAP, -SHADOW_GAP); | |
237 g2.setColor(g2.getBackground()); | |
238 g2.fill(shape); | |
239 g2.setColor(Color.BLACK); | |
240 g2.draw(shape); | |
241 g2.setColor(oldColor); | |
242 | |
243 /* if there ain't any property to display inside, then display the name in the middle of the data Display bounds */ | |
244 if(!anyInsideProperties()){ | |
245 nameLabel.draw(g2, dataDisplayBounds); | |
246 }else{ | |
247 /* draw name */ | |
248 Rectangle2D currentBounds = new Rectangle2D.Double( | |
249 dataDisplayBounds.x, | |
250 dataDisplayBounds.y, | |
251 dataDisplayBounds.getWidth(), | |
252 nameLabel.getBounds().getHeight()); | |
253 if(drawPropertySeparators){ | |
254 Shape oldClip = g2.getClip(); | |
255 g2.setClip(getShape()); | |
256 g2.draw(new Rectangle2D.Double( | |
257 getBounds().getX(), | |
258 dataDisplayBounds.y, | |
259 getBounds().getWidth(), | |
260 nameLabel.getBounds().getHeight()) | |
261 ); | |
262 g2.setClip(oldClip); | |
263 } | |
264 nameLabel.draw(g2, currentBounds); | |
265 | |
266 /* draw internal properties */ | |
267 Rectangle2D previousBounds; | |
268 for(int i=0;i<propertyLabels.length;i++){ | |
269 previousBounds = currentBounds; | |
270 currentBounds = new Rectangle2D.Double( | |
271 previousBounds.getX(), | |
272 previousBounds.getMaxY(), | |
273 dataDisplayBounds.getWidth(), | |
274 propertyLabels[i].getBounds().getHeight()+boundsGap); | |
275 if(drawPropertySeparators){ | |
276 Shape oldClip = g2.getClip(); | |
277 g2.setClip(getShape()); | |
278 g2.draw(new Rectangle2D.Double( | |
279 getBounds().getX(), | |
280 currentBounds.getY(), | |
281 getBounds().getWidth(), | |
282 currentBounds.getHeight()) | |
283 ); | |
284 g2.setClip(oldClip); | |
285 } | |
286 propertyLabels[i].draw(g2, currentBounds); | |
287 } | |
288 } | |
289 | |
290 /* draw external properties */ | |
291 for(List<PropertyNode> pnList : propertyNodesMap.values()) | |
292 for(PropertyNode pn : pnList){ | |
293 pn.draw(g2); | |
294 Direction d = new Direction( getBounds().getCenterX() - pn.getCenter().getX(), getBounds().getCenterY() - pn.getCenter().getY()); | |
295 g2.draw(new Line2D.Double(pn.getConnectionPoint(d), getConnectionPoint(d.turn(180)))); | |
296 } | |
297 /* draw visual cue for bookmarks and notes */ | |
298 super.draw(g2); | |
299 } | |
300 | |
301 protected Rectangle2D getMinBounds(){ | |
302 return (Rectangle2D)minBounds.clone(); | |
303 } | |
304 | |
305 public abstract ShapeType getShapeType(); | |
306 | |
307 @Override | |
308 public void encode(Document doc, Element parent){ | |
309 super.encode(doc, parent); | |
310 if(getProperties().isEmpty()) | |
311 return; | |
312 NodeList propTagList = doc.getElementsByTagName(PersistenceManager.PROPERTY); | |
313 | |
314 /* scan all the PROPERTY tags to add the position tag */ | |
315 for(int i = 0 ; i< propTagList.getLength(); i++){ | |
316 Element propertyTag = (Element)propTagList.item(i); | |
317 Element typeTag = (Element)propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0); | |
318 String type = typeTag.getTextContent(); | |
319 | |
320 /* a property of another node, continue */ | |
321 if(!getProperties().getTypes().contains(type)) | |
322 continue; | |
323 | |
324 if(((PropertyView)getProperties().getView(type)).getPosition() == SimpleShapeNode.Position.Inside) | |
325 continue; | |
326 | |
327 List<String> values = getProperties().getValues(type); | |
328 if(values.isEmpty()) | |
329 continue; | |
330 | |
331 NodeList elementTagList = propertyTag.getElementsByTagName(PersistenceManager.ELEMENT); | |
332 List<PropertyNode> pnList = propertyNodesMap.get(type); | |
333 for(int j=0; j<elementTagList.getLength();j++){ | |
334 Element elementTag = (Element)elementTagList.item(j); | |
335 Element positionTag = doc.createElement(SimpleShapePrototypePersistenceDelegate.POSITION); | |
336 positionTag.setAttribute(PersistenceManager.X, String.valueOf(pnList.get(j).getX())); | |
337 positionTag.setAttribute(PersistenceManager.Y, String.valueOf(pnList.get(j).getY())); | |
338 elementTag.appendChild(positionTag); | |
339 } | |
340 } | |
341 } | |
342 | |
343 @Override | |
344 public void decode(Document doc, Element nodeTag) throws IOException{ | |
345 super.decode(doc, nodeTag); | |
346 | |
347 NodeList propTagList = nodeTag.getElementsByTagName(PersistenceManager.PROPERTY); | |
348 | |
349 /* split the property types into internal and external, properties have been set by super.decodeXMLInstance */ | |
350 ArrayList<String> insidePropertyTypes = new ArrayList<String>(getProperties().getTypes().size()); | |
351 ArrayList<String> outsidePropertyTypes = new ArrayList<String>(getProperties().getTypes().size()); | |
352 for(String type : getProperties().getTypes()){ | |
353 if(((PropertyView)getProperties().getView(type)).getPosition() == SimpleShapeNode.Position.Inside) | |
354 insidePropertyTypes.add(type); | |
355 else | |
356 outsidePropertyTypes.add(type); | |
357 } | |
358 | |
359 /* set the multi-line string bounds for the properties which are displayed internally */ | |
360 reshapeInnerProperties(insidePropertyTypes); | |
361 | |
362 /* scan all the PROPERTY tags to decode the position tag of the properties which are displayed externally */ | |
363 for(int i = 0 ; i< propTagList.getLength(); i++){ | |
364 Element propertyTag = (Element)propTagList.item(i); | |
365 if(propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0) == null) | |
366 throw new IOException(); | |
367 Element typeTag = (Element)propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0); | |
368 | |
369 String type = typeTag.getTextContent(); | |
370 /* (check on whether type exists in the node type definition is done in super.decode */ | |
371 | |
372 if(((PropertyView)getProperties().getView(type)).getPosition() == SimpleShapeNode.Position.Inside) | |
373 continue; | |
374 /* this will create external nodes and assign them their position */ | |
375 NodeList elementTagList = propertyTag.getElementsByTagName(PersistenceManager.ELEMENT); | |
376 List<PropertyNode> pnList = new LinkedList<PropertyNode>(); | |
377 for(int j=0; j<elementTagList.getLength();j++){ | |
378 Element elementTag = (Element)elementTagList.item(j); | |
379 if(elementTag.getElementsByTagName(SimpleShapePrototypePersistenceDelegate.POSITION).item(0) == null || | |
380 elementTag.getElementsByTagName(PersistenceManager.VALUE).item(0) == null) | |
381 throw new IOException(); | |
382 Element positionTag = (Element)elementTag.getElementsByTagName(SimpleShapePrototypePersistenceDelegate.POSITION).item(0); | |
383 Element valueTag = (Element)elementTag.getElementsByTagName(PersistenceManager.VALUE).item(0); | |
384 double dx,dy; | |
385 try{ | |
386 dx = Double.parseDouble(positionTag.getAttribute(PersistenceManager.X)); | |
387 dy = Double.parseDouble(positionTag.getAttribute(PersistenceManager.Y)); | |
388 }catch(NumberFormatException nfe){ | |
389 throw new IOException(); | |
390 } | |
391 PropertyNode pn = new PropertyNode(((PropertyView)getProperties().getView(type)).getShapeType()); | |
392 pn.translate(dx, dy); | |
393 pn.setText(valueTag.getTextContent(),null); | |
394 pnList.add(pn); | |
395 } | |
396 propertyNodesMap.put(type, pnList); | |
397 /* this will apply the modifier format to the properties */ | |
398 reshapeOuterProperties(outsidePropertyTypes); | |
399 } | |
400 } | |
401 | |
402 @Override | |
403 protected void translateImplementation(Point2D p, double dx, double dy){ | |
404 dataDisplayBounds.setFrame(dataDisplayBounds.getX() + dx, | |
405 dataDisplayBounds.getY() + dy, | |
406 dataDisplayBounds.getWidth(), | |
407 dataDisplayBounds.getHeight()); | |
408 /* translate all the external property nodes */ | |
409 for(List<PropertyNode> pnList : propertyNodesMap.values()) | |
410 for(PropertyNode pn : pnList) | |
411 pn.translate(dx, dy); | |
412 } | |
413 | |
414 @Override | |
415 public Object clone(){ | |
416 SimpleShapeNode n = (SimpleShapeNode)super.clone(); | |
417 n.propertyLabels = new MultiLineString[propertyLabels.length]; | |
418 n.nameLabel = new MultiLineString(); | |
419 n.propertyNodesMap = new LinkedHashMap<String, List<PropertyNode>>(); | |
420 for(String s : propertyNodesMap.keySet()) | |
421 n.propertyNodesMap.put(s, new LinkedList<PropertyNode>()); | |
422 n.dataDisplayBounds = (Rectangle2D.Double)dataDisplayBounds.clone(); | |
423 return n; | |
424 } | |
425 | |
426 protected boolean anyInsideProperties(){ | |
427 boolean propInside = false; | |
428 for(String type : getProperties().getTypes()){ | |
429 if(((PropertyView)getProperties().getView(type)).getPosition() == Position.Inside){ | |
430 if(!getProperties().getValues(type).isEmpty()){ | |
431 propInside = true; | |
432 break; | |
433 } | |
434 } | |
435 } | |
436 return propInside; | |
437 } | |
438 | |
439 protected Rectangle2D.Double dataDisplayBounds; | |
440 protected double boundsGap; | |
441 protected boolean drawPropertySeparators = true; | |
442 protected MultiLineString[] propertyLabels; | |
443 protected MultiLineString nameLabel; | |
444 protected Map<String,List<PropertyNode>> propertyNodesMap; | |
445 | |
446 public static enum ShapeType {Circle, Ellipse, Rectangle, Square, Triangle, Transparent}; | |
447 public static enum Position {Inside, Outside}; | |
448 | |
449 private static final int DEFAULT_WIDTH = 100; | |
450 private static final int DEFAULT_HEIGHT = 60; | |
451 private static final Rectangle2D.Double minBounds = new Rectangle2D.Double(0,0,DEFAULT_WIDTH,DEFAULT_HEIGHT); | |
452 private static final int PROP_NODE_DIST = 50; | |
453 | |
454 /** | |
455 * When properties are configure to appear outside, the values are represented as small nodes | |
456 * connected (with a straight line) to the node they belong to. This class represents such | |
457 * small nodes. The possible shapes are: triangle, rectangle, square, circle, ellipse and no shape in | |
458 * which case only the string with the property value and the line connecting it to the node is shown. | |
459 * | |
460 */ | |
461 protected static class PropertyNode{ | |
462 public PropertyNode(ShapeType aShape){ | |
463 /* add a little padding in the shape holding the label */ | |
464 label = new MultiLineString(){ | |
465 public Rectangle2D getBounds(){ | |
466 Rectangle2D bounds = super.getBounds(); | |
467 if(bounds.getWidth() != 0 || bounds.getHeight() != 0){ | |
468 bounds.setFrame( | |
469 bounds.getX(), | |
470 bounds.getY(), | |
471 bounds.getWidth() + PADDING, | |
472 bounds.getHeight() + PADDING); | |
473 } | |
474 return bounds; | |
475 } | |
476 }; | |
477 label.setJustification(MultiLineString.CENTER); | |
478 shapeType = aShape; | |
479 shape = label.getBounds(); | |
480 } | |
481 | |
482 public void setText(String text, ModifierView[] views){ | |
483 label.setText(text,views); | |
484 | |
485 switch(shapeType){ | |
486 case Circle : | |
487 Rectangle2D circleBounds = EllipticalNode.getOutBounds(label.getBounds()); | |
488 shape = new Ellipse2D.Double( | |
489 circleBounds.getX(), | |
490 circleBounds.getY(), | |
491 Math.max(circleBounds.getWidth(),circleBounds.getHeight()), | |
492 Math.max(circleBounds.getWidth(),circleBounds.getHeight()) | |
493 ); | |
494 break; | |
495 case Ellipse : | |
496 Rectangle2D ellipseBounds = EllipticalNode.getOutBounds(label.getBounds()); | |
497 shape = new Ellipse2D.Double( | |
498 ellipseBounds.getX(), | |
499 ellipseBounds.getY(), | |
500 ellipseBounds.getWidth(), | |
501 ellipseBounds.getHeight() | |
502 ); | |
503 break; | |
504 case Triangle : | |
505 shape = TriangularNode.getOutShape(label.getBounds()); | |
506 break; | |
507 default : // Rectangle, Square and Transparent | |
508 shape = label.getBounds();; | |
509 break; | |
510 } | |
511 | |
512 /* a new shape, placed at (0,0) has been created as a result of set text, therefore * | |
513 * we must put it back where the old shape was, since the translation is performed * | |
514 * by adding the translate args to x and y, x and y must first be set to 0 */ | |
515 double currentX = x; | |
516 double currentY = y; | |
517 x = 0; | |
518 y = 0; | |
519 translate(currentX,currentY); | |
520 } | |
521 | |
522 public void draw(Graphics2D g){ | |
523 Color oldColor = g.getColor(); | |
524 if(shapeType != ShapeType.Transparent){ | |
525 g.translate(SHADOW_GAP, SHADOW_GAP); | |
526 g.setColor(SHADOW_COLOR); | |
527 g.fill(shape); | |
528 g.translate(-SHADOW_GAP, -SHADOW_GAP); | |
529 | |
530 g.setColor(g.getBackground()); | |
531 g.fill(shape); | |
532 g.setColor(Color.BLACK); | |
533 g.draw(shape); | |
534 } | |
535 | |
536 label.draw(g, shape.getBounds2D()); | |
537 g.setColor(oldColor); | |
538 } | |
539 | |
540 public void translate(double dx, double dy){ | |
541 x += dx; | |
542 y += dy; | |
543 | |
544 if(shape instanceof Path2D){ //it's a triangle | |
545 Rectangle2D labelBounds = label.getBounds(); | |
546 labelBounds.setFrame( | |
547 x, | |
548 y, | |
549 labelBounds.getWidth(), | |
550 labelBounds.getHeight() | |
551 ); | |
552 shape = TriangularNode.getOutShape(labelBounds); | |
553 }else{ | |
554 Rectangle2D bounds = shape.getBounds2D(); | |
555 ((RectangularShape)shape).setFrame( | |
556 x, | |
557 y, | |
558 bounds.getWidth(), | |
559 bounds.getHeight() | |
560 ); | |
561 } | |
562 } | |
563 | |
564 public Point2D getConnectionPoint(Direction d) { | |
565 switch(shapeType){ | |
566 case Circle : | |
567 case Ellipse : | |
568 return EllipticalNode.calculateConnectionPoint(d, shape.getBounds2D()); | |
569 case Triangle : | |
570 return TriangularNode.calculateConnectionPoint(d, shape.getBounds2D()); | |
571 default : | |
572 return RectangularNode.calculateConnectionPoint(d, shape.getBounds2D()); | |
573 } | |
574 } | |
575 | |
576 public boolean contains(Point2D p){ | |
577 return shape.contains(p); | |
578 } | |
579 | |
580 public Point2D getCenter(){ | |
581 Rectangle2D bounds = shape.getBounds2D() ; | |
582 return new Point2D.Double(bounds.getCenterX(), bounds.getCenterY()); | |
583 } | |
584 | |
585 double getX(){ | |
586 return x; | |
587 } | |
588 | |
589 double getY(){ | |
590 return y; | |
591 } | |
592 | |
593 private static final int PADDING = 5; | |
594 private MultiLineString label; | |
595 private ShapeType shapeType; | |
596 private Shape shape; | |
597 private double x; | |
598 private double y; | |
599 } | |
600 } |