annotate java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeNode.java @ 1:e3935c01cde2 tip

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