annotate java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeNode.java @ 8:ea7885bd9bff tip

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