Mercurial > hg > ccmieditor
comparison java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeNode.java @ 0:9418ab7b7f3f
Initial import
author | Fiore Martin <fiore@eecs.qmul.ac.uk> |
---|---|
date | Fri, 16 Dec 2011 17:35:51 +0000 |
parents | |
children | 2c67ac862920 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:9418ab7b7f3f |
---|---|
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 reshape(); | |
95 super.notifyChange(evt); | |
96 } | |
97 | |
98 @Override | |
99 public void setId(long id){ | |
100 super.setId(id); | |
101 /* when they are given an id nodes change name into "new <type> node <id>" * | |
102 * where <type> is the actual type of the node and <id> is the given id * | |
103 * therefore a reshape is necessary to display the new name */ | |
104 Rectangle2D boundsBeforeReshape = getBounds(); | |
105 reshape(); | |
106 /* the reshape might change the bounds, so the shape is translated so that the top-left * | |
107 * point is at the same position as before just to keep it more consistent */ | |
108 Rectangle2D boundsAfterReshape = getBounds(); | |
109 translateImplementation( | |
110 new Point2D.Double(), | |
111 boundsBeforeReshape.getX() - boundsAfterReshape.getX(), | |
112 boundsBeforeReshape.getY() - boundsAfterReshape.getY() | |
113 ); | |
114 } | |
115 | |
116 @Override | |
117 public boolean contains(Point2D p) { | |
118 if (getShape().contains(p)) | |
119 return true; | |
120 for(List<PropertyNode> pnList : propertyNodesMap.values()) | |
121 for(PropertyNode pn : pnList) | |
122 if(pn.contains(p)) | |
123 return true; | |
124 return false; | |
125 } | |
126 | |
127 protected void reshape(){ | |
128 Pair<List<String>, List<String>> splitPropertyTypes = splitPropertyTypes(); | |
129 /* properties displayed internally */ | |
130 reshapeInnerProperties(splitPropertyTypes.first); | |
131 /* properties displayed externally */ | |
132 reshapeOuterProperties(splitPropertyTypes.second); | |
133 } | |
134 | |
135 protected Pair<List<String>, List<String>> splitPropertyTypes(){ | |
136 List<String> types = getProperties().getTypes(); | |
137 ArrayList<String> insidePropertyTypes = new ArrayList<String>(types.size()); | |
138 ArrayList<String> outsidePropertyTypes = new ArrayList<String>(types.size()); | |
139 for(String type : types){ | |
140 if(((PropertyView)getProperties().getView(type)).getPosition() == Position.Inside) | |
141 insidePropertyTypes.add(type); | |
142 else | |
143 outsidePropertyTypes.add(type); | |
144 } | |
145 | |
146 return new Pair<List<String>, List<String>> (insidePropertyTypes,outsidePropertyTypes); | |
147 } | |
148 | |
149 protected void reshapeOuterProperties(List<String> outsidePropertyTypes){ | |
150 for(String type : outsidePropertyTypes){ | |
151 List<PropertyNode> propertyNodes = propertyNodesMap.get(type); | |
152 List<String> propertyValues = getProperties().getValues(type); | |
153 int diff = propertyNodes.size()-propertyValues.size(); | |
154 if(diff > 0) // properties have been removed | |
155 for(int i=0; i < diff; i++) | |
156 propertyNodes.remove(propertyNodes.size() - 1); | |
157 else if(diff < 0){ // properties have been added. We need more properties node. | |
158 for(int i=0; i < -diff; i++){ | |
159 PropertyNode propertyNode = new PropertyNode(((PropertyView)getProperties().getView(type)).getShapeType()); | |
160 Rectangle2D bounds = getBounds(); | |
161 double x = bounds.getCenterX() - bounds.getWidth()/2 - PROP_NODE_DIST; | |
162 double y = bounds.getCenterX() - PROP_NODE_DIST * i; | |
163 propertyNode.translate(x, y); | |
164 propertyNodes.add(propertyNode); | |
165 } | |
166 } | |
167 /* set the text on all the property nodes */ | |
168 int i = 0; | |
169 for(String text : propertyValues){ | |
170 NodeProperties.Modifiers modifiers = getProperties().getModifiers(type); | |
171 Set<Integer> viewIndexes = modifiers.getIndexes(i); | |
172 ModifierView[] views = new ModifierView[viewIndexes.size()]; | |
173 int j =0; | |
174 for(Integer I : viewIndexes){ | |
175 views[j] = (ModifierView) getProperties().getModifiers(type).getView(modifiers.getTypes().get(I)); | |
176 j++; | |
177 } | |
178 propertyNodes.get(i).setText(text,views); | |
179 i++; | |
180 } | |
181 } | |
182 } | |
183 | |
184 protected void reshapeInnerProperties(List<String> insidePropertyTypes){ | |
185 /* set the bounds for each multiline string and the resulting bound of the node */ | |
186 nameLabel = new MultiLineString(); | |
187 nameLabel.setText(getName().isEmpty() ? " " : getName()); | |
188 nameLabel.setBold(true); | |
189 Rectangle2D r = nameLabel.getBounds(); | |
190 | |
191 for(int i=0; i<insidePropertyTypes.size();i++){ | |
192 propertyLabels[i] = new MultiLineString(); | |
193 String propertyType = insidePropertyTypes.get(i); | |
194 if(getProperties().getValues(propertyType).size() == 0){ | |
195 propertyLabels[i].setText(" "); | |
196 }else{ | |
197 propertyLabels[i].setJustification(MultiLineString.LEFT); | |
198 String[] array = new String[getProperties().getValues(propertyType).size()]; | |
199 propertyLabels[i].setText(getProperties().getValues(propertyType).toArray(array), getProperties().getModifiers(propertyType)); | |
200 } | |
201 r.add(new Rectangle2D.Double(r.getX(),r.getMaxY(),propertyLabels[i].getBounds().getWidth(),propertyLabels[i].getBounds().getHeight())); | |
202 } | |
203 /* set a gap to uniformly distribute the extra space among property rectangles to reach the minimum bound's height */ | |
204 boundsGap = 0; | |
205 Rectangle2D.Double minBounds = (Rectangle2D.Double)getMinBounds(); | |
206 if(r.getHeight() < minBounds.height){ | |
207 boundsGap = minBounds.height - r.getHeight(); | |
208 boundsGap /= insidePropertyTypes.size(); | |
209 } | |
210 r.add(minBounds); //make sure it's at least as big as the minimum bounds | |
211 dataDisplayBounds.setFrame(new Rectangle2D.Double(dataDisplayBounds.x,dataDisplayBounds.y,r.getWidth(),r.getHeight())); | |
212 } | |
213 | |
214 /** | |
215 Draw the node from the Shape with shadow | |
216 @param g2 the graphics context | |
217 */ | |
218 @Override | |
219 public void draw(Graphics2D g2){ | |
220 /* draw the external shape */ | |
221 Shape shape = getShape(); | |
222 if (shape == null) return; | |
223 Color oldColor = g2.getColor(); | |
224 g2.translate(SHADOW_GAP, SHADOW_GAP); | |
225 g2.setColor(SHADOW_COLOR); | |
226 g2.fill(shape); | |
227 g2.translate(-SHADOW_GAP, -SHADOW_GAP); | |
228 g2.setColor(g2.getBackground()); | |
229 g2.fill(shape); | |
230 g2.setColor(Color.BLACK); | |
231 g2.draw(shape); | |
232 g2.setColor(oldColor); | |
233 | |
234 /* if there ain't any property to display inside, then display the name in the middle of the data Display bounds */ | |
235 if(!anyInsideProperties()){ | |
236 nameLabel.draw(g2, dataDisplayBounds); | |
237 }else{ | |
238 /* draw name */ | |
239 Rectangle2D currentBounds = new Rectangle2D.Double( | |
240 dataDisplayBounds.x, | |
241 dataDisplayBounds.y, | |
242 dataDisplayBounds.getWidth(), | |
243 nameLabel.getBounds().getHeight()); | |
244 if(drawPropertySeparators){ | |
245 Shape oldClip = g2.getClip(); | |
246 g2.setClip(getShape()); | |
247 g2.draw(new Rectangle2D.Double( | |
248 getBounds().getX(), | |
249 dataDisplayBounds.y, | |
250 getBounds().getWidth(), | |
251 nameLabel.getBounds().getHeight()) | |
252 ); | |
253 g2.setClip(oldClip); | |
254 } | |
255 nameLabel.draw(g2, currentBounds); | |
256 | |
257 /* draw internal properties */ | |
258 Rectangle2D previousBounds; | |
259 for(int i=0;i<propertyLabels.length;i++){ | |
260 previousBounds = currentBounds; | |
261 currentBounds = new Rectangle2D.Double( | |
262 previousBounds.getX(), | |
263 previousBounds.getMaxY(), | |
264 dataDisplayBounds.getWidth(), | |
265 propertyLabels[i].getBounds().getHeight()+boundsGap); | |
266 if(drawPropertySeparators){ | |
267 Shape oldClip = g2.getClip(); | |
268 g2.setClip(getShape()); | |
269 g2.draw(new Rectangle2D.Double( | |
270 getBounds().getX(), | |
271 currentBounds.getY(), | |
272 getBounds().getWidth(), | |
273 currentBounds.getHeight()) | |
274 ); | |
275 g2.setClip(oldClip); | |
276 } | |
277 propertyLabels[i].draw(g2, currentBounds); | |
278 } | |
279 } | |
280 | |
281 /* draw external properties */ | |
282 for(List<PropertyNode> pnList : propertyNodesMap.values()) | |
283 for(PropertyNode pn : pnList){ | |
284 pn.draw(g2); | |
285 Direction d = new Direction( getBounds().getCenterX() - pn.getCenter().getX(), getBounds().getCenterY() - pn.getCenter().getY()); | |
286 g2.draw(new Line2D.Double(pn.getConnectionPoint(d), getConnectionPoint(d.turn(180)))); | |
287 } | |
288 /* draw visual cue for bookmarks and notes */ | |
289 super.draw(g2); | |
290 } | |
291 | |
292 protected Rectangle2D getMinBounds(){ | |
293 return (Rectangle2D)minBounds.clone(); | |
294 } | |
295 | |
296 public abstract ShapeType getShapeType(); | |
297 | |
298 @Override | |
299 public void encode(Document doc, Element parent){ | |
300 super.encode(doc, parent); | |
301 if(getProperties().isEmpty()) | |
302 return; | |
303 NodeList propTagList = doc.getElementsByTagName(PersistenceManager.PROPERTY); | |
304 | |
305 /* scan all the PROPERTY tags to add the position tag */ | |
306 for(int i = 0 ; i< propTagList.getLength(); i++){ | |
307 Element propertyTag = (Element)propTagList.item(i); | |
308 Element typeTag = (Element)propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0); | |
309 String type = typeTag.getTextContent(); | |
310 | |
311 /* a property of another node, continue */ | |
312 if(!getProperties().getTypes().contains(type)) | |
313 continue; | |
314 | |
315 if(((PropertyView)getProperties().getView(type)).getPosition() == SimpleShapeNode.Position.Inside) | |
316 continue; | |
317 | |
318 List<String> values = getProperties().getValues(type); | |
319 if(values.isEmpty()) | |
320 continue; | |
321 | |
322 NodeList elementTagList = propertyTag.getElementsByTagName(PersistenceManager.ELEMENT); | |
323 List<PropertyNode> pnList = propertyNodesMap.get(type); | |
324 for(int j=0; j<elementTagList.getLength();j++){ | |
325 Element elementTag = (Element)elementTagList.item(j); | |
326 Element positionTag = doc.createElement(SimpleShapePrototypePersistenceDelegate.POSITION); | |
327 positionTag.setAttribute(PersistenceManager.X, String.valueOf(pnList.get(j).getX())); | |
328 positionTag.setAttribute(PersistenceManager.Y, String.valueOf(pnList.get(j).getY())); | |
329 elementTag.appendChild(positionTag); | |
330 } | |
331 } | |
332 } | |
333 | |
334 @Override | |
335 public void decode(Document doc, Element nodeTag) throws IOException{ | |
336 super.decode(doc, nodeTag); | |
337 | |
338 NodeList propTagList = nodeTag.getElementsByTagName(PersistenceManager.PROPERTY); | |
339 | |
340 /* split the property types into internal and external, properties have been set by super.decodeXMLInstance */ | |
341 ArrayList<String> insidePropertyTypes = new ArrayList<String>(getProperties().getTypes().size()); | |
342 ArrayList<String> outsidePropertyTypes = new ArrayList<String>(getProperties().getTypes().size()); | |
343 for(String type : getProperties().getTypes()){ | |
344 if(((PropertyView)getProperties().getView(type)).getPosition() == SimpleShapeNode.Position.Inside) | |
345 insidePropertyTypes.add(type); | |
346 else | |
347 outsidePropertyTypes.add(type); | |
348 } | |
349 | |
350 /* set the multi-line string bounds for the properties which are displayed internally */ | |
351 reshapeInnerProperties(insidePropertyTypes); | |
352 | |
353 /* scan all the PROPERTY tags to decode the position tag of the properties which are displayed externally */ | |
354 for(int i = 0 ; i< propTagList.getLength(); i++){ | |
355 Element propertyTag = (Element)propTagList.item(i); | |
356 if(propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0) == null) | |
357 throw new IOException(); | |
358 Element typeTag = (Element)propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0); | |
359 | |
360 String type = typeTag.getTextContent(); | |
361 /* (check on whether type exists in the node type definition is done in super.decode */ | |
362 | |
363 if(((PropertyView)getProperties().getView(type)).getPosition() == SimpleShapeNode.Position.Inside) | |
364 continue; | |
365 /* this will create external nodes and assign them their position */ | |
366 NodeList elementTagList = propertyTag.getElementsByTagName(PersistenceManager.ELEMENT); | |
367 List<PropertyNode> pnList = new LinkedList<PropertyNode>(); | |
368 for(int j=0; j<elementTagList.getLength();j++){ | |
369 Element elementTag = (Element)elementTagList.item(j); | |
370 if(elementTag.getElementsByTagName(SimpleShapePrototypePersistenceDelegate.POSITION).item(0) == null || | |
371 elementTag.getElementsByTagName(PersistenceManager.VALUE).item(0) == null) | |
372 throw new IOException(); | |
373 Element positionTag = (Element)elementTag.getElementsByTagName(SimpleShapePrototypePersistenceDelegate.POSITION).item(0); | |
374 Element valueTag = (Element)elementTag.getElementsByTagName(PersistenceManager.VALUE).item(0); | |
375 double dx,dy; | |
376 try{ | |
377 dx = Double.parseDouble(positionTag.getAttribute(PersistenceManager.X)); | |
378 dy = Double.parseDouble(positionTag.getAttribute(PersistenceManager.Y)); | |
379 }catch(NumberFormatException nfe){ | |
380 throw new IOException(); | |
381 } | |
382 PropertyNode pn = new PropertyNode(((PropertyView)getProperties().getView(type)).getShapeType()); | |
383 pn.translate(dx, dy); | |
384 pn.setText(valueTag.getTextContent(),null); | |
385 pnList.add(pn); | |
386 } | |
387 propertyNodesMap.put(type, pnList); | |
388 /* this will apply the modifier format to the properties */ | |
389 reshapeOuterProperties(outsidePropertyTypes); | |
390 } | |
391 } | |
392 | |
393 @Override | |
394 protected void translateImplementation(Point2D p, double dx, double dy){ | |
395 dataDisplayBounds.setFrame(dataDisplayBounds.getX() + dx, | |
396 dataDisplayBounds.getY() + dy, | |
397 dataDisplayBounds.getWidth(), | |
398 dataDisplayBounds.getHeight()); | |
399 /* translate all the external property nodes */ | |
400 for(List<PropertyNode> pnList : propertyNodesMap.values()) | |
401 for(PropertyNode pn : pnList) | |
402 pn.translate(dx, dy); | |
403 } | |
404 | |
405 @Override | |
406 public Object clone(){ | |
407 SimpleShapeNode n = (SimpleShapeNode)super.clone(); | |
408 n.propertyLabels = new MultiLineString[propertyLabels.length]; | |
409 n.nameLabel = new MultiLineString(); | |
410 n.propertyNodesMap = new LinkedHashMap<String, List<PropertyNode>>(); | |
411 for(String s : propertyNodesMap.keySet()) | |
412 n.propertyNodesMap.put(s, new LinkedList<PropertyNode>()); | |
413 n.dataDisplayBounds = (Rectangle2D.Double)dataDisplayBounds.clone(); | |
414 return n; | |
415 } | |
416 | |
417 protected boolean anyInsideProperties(){ | |
418 boolean propInside = false; | |
419 for(String type : getProperties().getTypes()){ | |
420 if(((PropertyView)getProperties().getView(type)).getPosition() == Position.Inside){ | |
421 if(!getProperties().getValues(type).isEmpty()){ | |
422 propInside = true; | |
423 break; | |
424 } | |
425 } | |
426 } | |
427 return propInside; | |
428 } | |
429 | |
430 protected Rectangle2D.Double dataDisplayBounds; | |
431 protected double boundsGap; | |
432 protected boolean drawPropertySeparators = true; | |
433 protected MultiLineString[] propertyLabels; | |
434 protected MultiLineString nameLabel; | |
435 protected Map<String,List<PropertyNode>> propertyNodesMap; | |
436 | |
437 public static enum ShapeType {Circle, Ellipse, Rectangle, Square, Triangle, Transparent}; | |
438 public static enum Position {Inside, Outside}; | |
439 | |
440 private static final int DEFAULT_WIDTH = 100; | |
441 private static final int DEFAULT_HEIGHT = 60; | |
442 private static final Rectangle2D.Double minBounds = new Rectangle2D.Double(0,0,DEFAULT_WIDTH,DEFAULT_HEIGHT); | |
443 private static final int PROP_NODE_DIST = 50; | |
444 | |
445 protected static class PropertyNode{ | |
446 public PropertyNode(ShapeType aShape){ | |
447 /* add a little padding in the shape holding the label */ | |
448 label = new MultiLineString(){ | |
449 public Rectangle2D getBounds(){ | |
450 Rectangle2D bounds = super.getBounds(); | |
451 if(bounds.getWidth() != 0 || bounds.getHeight() != 0){ | |
452 bounds.setFrame( | |
453 bounds.getX(), | |
454 bounds.getY(), | |
455 bounds.getWidth() + PADDING, | |
456 bounds.getHeight() + PADDING); | |
457 } | |
458 return bounds; | |
459 } | |
460 }; | |
461 label.setJustification(MultiLineString.CENTER); | |
462 shapeType = aShape; | |
463 shape = label.getBounds(); | |
464 } | |
465 | |
466 public void setText(String text, ModifierView[] views){ | |
467 label.setText(text,views); | |
468 | |
469 switch(shapeType){ | |
470 case Circle : | |
471 Rectangle2D circleBounds = EllipticalNode.getOutBounds(label.getBounds()); | |
472 shape = new Ellipse2D.Double( | |
473 circleBounds.getX(), | |
474 circleBounds.getY(), | |
475 Math.max(circleBounds.getWidth(),circleBounds.getHeight()), | |
476 Math.max(circleBounds.getWidth(),circleBounds.getHeight()) | |
477 ); | |
478 break; | |
479 case Ellipse : | |
480 Rectangle2D ellipseBounds = EllipticalNode.getOutBounds(label.getBounds()); | |
481 shape = new Ellipse2D.Double( | |
482 ellipseBounds.getX(), | |
483 ellipseBounds.getY(), | |
484 ellipseBounds.getWidth(), | |
485 ellipseBounds.getHeight() | |
486 ); | |
487 break; | |
488 case Triangle : | |
489 shape = TriangularNode.getOutShape(label.getBounds()); | |
490 break; | |
491 default : // Rectangle, Square and Transparent | |
492 shape = label.getBounds();; | |
493 break; | |
494 } | |
495 | |
496 /* a new shape, placed at (0,0) has been created as a result of set text, therefore * | |
497 * we must put it back where the old shape was, since the translation is performed * | |
498 * by adding the translate args to x and y, x and y must first be set to 0 */ | |
499 double currentX = x; | |
500 double currentY = y; | |
501 x = 0; | |
502 y = 0; | |
503 translate(currentX,currentY); | |
504 } | |
505 | |
506 public void draw(Graphics2D g){ | |
507 Color oldColor = g.getColor(); | |
508 if(shapeType != ShapeType.Transparent){ | |
509 g.translate(SHADOW_GAP, SHADOW_GAP); | |
510 g.setColor(SHADOW_COLOR); | |
511 g.fill(shape); | |
512 g.translate(-SHADOW_GAP, -SHADOW_GAP); | |
513 | |
514 g.setColor(g.getBackground()); | |
515 g.fill(shape); | |
516 g.setColor(Color.BLACK); | |
517 g.draw(shape); | |
518 } | |
519 | |
520 label.draw(g, shape.getBounds2D()); | |
521 g.setColor(oldColor); | |
522 } | |
523 | |
524 public void translate(double dx, double dy){ | |
525 x += dx; | |
526 y += dy; | |
527 | |
528 if(shape instanceof Path2D){ //it's a triangle | |
529 Rectangle2D labelBounds = label.getBounds(); | |
530 labelBounds.setFrame( | |
531 x, | |
532 y, | |
533 labelBounds.getWidth(), | |
534 labelBounds.getHeight() | |
535 ); | |
536 shape = TriangularNode.getOutShape(labelBounds); | |
537 }else{ | |
538 Rectangle2D bounds = shape.getBounds2D(); | |
539 ((RectangularShape)shape).setFrame( | |
540 x, | |
541 y, | |
542 bounds.getWidth(), | |
543 bounds.getHeight() | |
544 ); | |
545 } | |
546 } | |
547 | |
548 public Point2D getConnectionPoint(Direction d) { | |
549 switch(shapeType){ | |
550 case Circle : | |
551 case Ellipse : | |
552 return EllipticalNode.calculateConnectionPoint(d, shape.getBounds2D()); | |
553 case Triangle : | |
554 return TriangularNode.calculateConnectionPoint(d, shape.getBounds2D()); | |
555 default : | |
556 return RectangularNode.calculateConnectionPoint(d, shape.getBounds2D()); | |
557 } | |
558 } | |
559 | |
560 public boolean contains(Point2D p){ | |
561 return shape.contains(p); | |
562 } | |
563 | |
564 public Point2D getCenter(){ | |
565 Rectangle2D bounds = shape.getBounds2D() ; | |
566 return new Point2D.Double(bounds.getCenterX(), bounds.getCenterY()); | |
567 } | |
568 | |
569 double getX(){ | |
570 return x; | |
571 } | |
572 | |
573 double getY(){ | |
574 return y; | |
575 } | |
576 | |
577 private static final int PADDING = 5; | |
578 private MultiLineString label; | |
579 private ShapeType shapeType; | |
580 private Shape shape; | |
581 private double x; | |
582 private double y; | |
583 } | |
584 } |