annotate java/src/uk/ac/qmul/eecs/ccmi/gui/Node.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) 2002 Cay S. Horstmann (http://horstmann.com)
fiore@0 5 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
fiore@0 6
fiore@0 7 This program is free software: you can redistribute it and/or modify
fiore@0 8 it under the terms of the GNU General Public License as published by
fiore@0 9 the Free Software Foundation, either version 3 of the License, or
fiore@0 10 (at your option) any later version.
fiore@0 11
fiore@0 12 This program is distributed in the hope that it will be useful,
fiore@0 13 but WITHOUT ANY WARRANTY; without even the implied warranty of
fiore@0 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
fiore@0 15 GNU General Public License for more details.
fiore@0 16
fiore@0 17 You should have received a copy of the GNU General Public License
fiore@0 18 along with this program. If not, see <http://www.gnu.org/licenses/>.
fiore@0 19 */
fiore@0 20 package uk.ac.qmul.eecs.ccmi.gui;
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.Point2D;
fiore@0 26 import java.awt.geom.Rectangle2D;
fiore@0 27 import java.io.IOException;
fiore@0 28 import java.util.ArrayList;
fiore@0 29 import java.util.LinkedHashSet;
fiore@0 30 import java.util.List;
fiore@0 31 import java.util.Set;
fiore@0 32
fiore@0 33 import org.w3c.dom.Document;
fiore@0 34 import org.w3c.dom.Element;
fiore@0 35 import org.w3c.dom.NodeList;
fiore@0 36
fiore@0 37 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramEdge;
fiore@0 38 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
fiore@0 39 import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
fiore@0 40 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
fiore@0 41 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers;
fiore@0 42 import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
fiore@0 43
fiore@0 44 /**
fiore@5 45 * An node in a graph. {@code Node} objects are used in a {@code GraphPanel} to render diagram nodes visually.
fiore@5 46 * {@code Node} objects are used in the tree representation of the diagram as well, as they're
fiore@0 47 * subclasses of {@link DiagramNode}
fiore@0 48 *
fiore@0 49 */
fiore@0 50 @SuppressWarnings("serial")
fiore@0 51 public abstract class Node extends DiagramNode implements GraphElement{
fiore@0 52
fiore@5 53 /**
fiore@5 54 * Constructor to be called by sub classes
fiore@5 55 *
fiore@5 56 * @param type the type of the new node. All nodes with this type will be
fiore@5 57 * put under the same tree node in the tree representation
fiore@5 58 * @param properties the properties of this node
fiore@5 59 */
fiore@0 60 public Node(String type, NodeProperties properties){
fiore@0 61 super(type,properties);
fiore@0 62 attachedEdges = new ArrayList<Edge>();
fiore@0 63 }
fiore@0 64
fiore@0 65 /* --- DiagramNode abstract methods implementation --- */
fiore@0 66 @Override
fiore@0 67 public int getEdgesNum(){
fiore@0 68 return attachedEdges.size();
fiore@0 69 }
fiore@0 70
fiore@0 71 @Override
fiore@0 72 public Edge getEdgeAt(int index){
fiore@0 73 return attachedEdges.get(index);
fiore@0 74 }
fiore@0 75
fiore@0 76 @Override
fiore@0 77 public boolean addEdge(DiagramEdge e){
fiore@0 78 return attachedEdges.add((Edge)e);
fiore@0 79 }
fiore@0 80
fiore@0 81 @Override
fiore@0 82 public boolean removeEdge(DiagramEdge e){
fiore@0 83 return attachedEdges.remove((Edge)e);
fiore@0 84 }
fiore@0 85
fiore@0 86 @Override
fiore@0 87 public Node getExternalNode(){
fiore@5 88 return null;
fiore@0 89 }
fiore@0 90
fiore@0 91 @Override
fiore@0 92 public void setExternalNode(DiagramNode node){
fiore@5 93 throw new UnsupportedOperationException();
fiore@0 94 }
fiore@0 95
fiore@0 96 @Override
fiore@0 97 public Node getInternalNodeAt(int i){
fiore@5 98 throw new UnsupportedOperationException();
fiore@0 99 }
fiore@0 100
fiore@0 101 @Override
fiore@0 102 public int getInternalNodesNum(){
fiore@5 103 return 0;
fiore@0 104 }
fiore@0 105
fiore@0 106 @Override
fiore@0 107 public void addInternalNode(DiagramNode node){
fiore@5 108 throw new UnsupportedOperationException();
fiore@0 109 }
fiore@0 110
fiore@0 111 @Override
fiore@0 112 public void removeInternalNode(DiagramNode node){
fiore@5 113 throw new UnsupportedOperationException();
fiore@0 114 }
fiore@0 115
fiore@0 116 @Override
fiore@3 117 public void stopMove(Object source){
fiore@3 118 notifyChange(new ElementChangedEvent(this,this,"stop_move",source));
fiore@0 119 /* edges can change as a result of nodes motion thus we call the method for all the edges
fiore@0 120 * of the node regardless what the mouse point is */
fiore@0 121 for(int i = 0; i < getEdgesNum();i++){
fiore@3 122 getEdgeAt(i).stopMove(source);
fiore@0 123 }
fiore@0 124 }
fiore@0 125
fiore@5 126 @Override
fiore@3 127 public void translate( Point2D p , double dx, double dy, Object source){
fiore@0 128 translateImplementation( p, dx, dy);
fiore@3 129 for(int i=0; i< getInternalNodesNum();i++){
fiore@3 130 getInternalNodeAt(i).translate(p, dx, dy,source);
fiore@3 131 }
fiore@3 132 notifyChange(new ElementChangedEvent(this, this, "translate", source));
fiore@0 133 }
fiore@0 134
fiore@3 135 @Override
fiore@3 136 protected void setNotes(String notes,Object source){
fiore@3 137 this.notes = notes;
fiore@3 138 notifyChange(new ElementChangedEvent(this,this,"notes",source));
fiore@0 139 }
fiore@3 140
fiore@4 141 /**
fiore@4 142 * The actual implementation of {@code translate()}. The {@code translate} method
fiore@4 143 * when called will, in turn, call this method, and then call all the registered
fiore@4 144 * change listeners in order to notify them that the node has been translated.
fiore@4 145 *
fiore@4 146 * @param p the point we are translating from
fiore@4 147 * @param dx the amount to translate in the x-direction
fiore@4 148 * @param dy the amount to translate in the y-direction
fiore@4 149 */
fiore@3 150 protected abstract void translateImplementation(Point2D p , double dx, double dy);
fiore@3 151
fiore@0 152 /**
fiore@0 153 * Tests whether the node contains a point.
fiore@0 154 * @param aPoint the point to test
fiore@0 155 * @return true if this node contains aPoint
fiore@0 156 */
fiore@0 157 public abstract boolean contains(Point2D aPoint);
fiore@0 158
fiore@0 159 @Override
fiore@0 160 public abstract Rectangle2D getBounds();
fiore@0 161
fiore@0 162 @Override
fiore@3 163 public void startMove(Point2D p,Object source){
fiore@0 164 /* useless, here just to comply with the GraphElement interface */
fiore@0 165 }
fiore@0 166
fiore@5 167 @Override
fiore@0 168 public abstract Point2D getConnectionPoint(Direction d);
fiore@0 169
fiore@0 170 @Override
fiore@0 171 public void draw(Graphics2D g2){
fiore@0 172 if(!"".equals(getNotes())){
fiore@0 173 Rectangle2D bounds = getBounds();
fiore@0 174 Color oldColor = g2.getColor();
fiore@0 175 g2.setColor(GraphPanel.GRABBER_COLOR);
fiore@0 176 g2.fill(new Rectangle2D.Double(bounds.getX() - MARKER_SIZE / 2, bounds.getY() - MARKER_SIZE / 2, MARKER_SIZE, MARKER_SIZE));
fiore@0 177 g2.setColor(oldColor);
fiore@0 178 }
fiore@0 179 }
fiore@0 180
fiore@5 181 /**
fiore@5 182 * Returns the geometric shape of this node
fiore@5 183 *
fiore@5 184 * @return the shape of this node
fiore@5 185 */
fiore@0 186 public abstract Shape getShape();
fiore@0 187
fiore@5 188 /**
fiore@5 189 * Encodes the internal data of this node (position, name, properties, modifiers) in XML format.
fiore@5 190 *
fiore@5 191 * The saved data can be retrieved and set back via {@code decode}.
fiore@5 192 *
fiore@5 193 * @param doc An XMl document
fiore@5 194 * @param parent the parent XML tag this node tag will be nested in
fiore@5 195 */
fiore@0 196 public void encode(Document doc, Element parent){
fiore@0 197 parent.setAttribute(PersistenceManager.NAME,getName());
fiore@0 198
fiore@0 199 Element positionTag = doc.createElement(PersistenceManager.POSITION);
fiore@0 200 Rectangle2D bounds = getBounds();
fiore@0 201 positionTag.setAttribute(PersistenceManager.X, String.valueOf(bounds.getX()));
fiore@0 202 positionTag.setAttribute(PersistenceManager.Y, String.valueOf(bounds.getY()));
fiore@0 203 parent.appendChild(positionTag);
fiore@0 204
fiore@0 205 if(getProperties().isEmpty())
fiore@0 206 return;
fiore@0 207
fiore@0 208 Element propertiesTag = doc.createElement(PersistenceManager.PROPERTIES);
fiore@0 209 parent.appendChild(propertiesTag);
fiore@0 210 for(String type : getProperties().getTypes()){
fiore@0 211 List<String> values = getProperties().getValues(type);
fiore@0 212 if(values.isEmpty())
fiore@0 213 continue;
fiore@0 214 Element propertyTag = doc.createElement(PersistenceManager.PROPERTY);
fiore@0 215 propertiesTag.appendChild(propertyTag);
fiore@0 216
fiore@0 217 Element typeTag = doc.createElement(PersistenceManager.TYPE);
fiore@0 218 typeTag.appendChild(doc.createTextNode(type));
fiore@0 219 propertyTag.appendChild(typeTag);
fiore@0 220
fiore@0 221 int index = 0;
fiore@0 222 for(String value : values){
fiore@0 223 Element elementTag = doc.createElement(PersistenceManager.ELEMENT);
fiore@0 224 propertyTag.appendChild(elementTag);
fiore@0 225
fiore@0 226 Element valueTag = doc.createElement(PersistenceManager.VALUE);
fiore@0 227 valueTag.appendChild(doc.createTextNode(value));
fiore@0 228 elementTag.appendChild(valueTag);
fiore@0 229
fiore@0 230
fiore@0 231 Set<Integer> modifierIndexes = getProperties().getModifiers(type).getIndexes(index);
fiore@0 232 if(!modifierIndexes.isEmpty()){
fiore@0 233 Element modifiersTag = doc.createElement(PersistenceManager.MODIFIERS);
fiore@0 234 StringBuilder builder = new StringBuilder();
fiore@0 235 for(Integer i : modifierIndexes )
fiore@0 236 builder.append(i).append(' ');
fiore@0 237 builder.deleteCharAt(builder.length()-1);//remove last space
fiore@0 238 modifiersTag.appendChild(doc.createTextNode(builder.toString()));
fiore@0 239 elementTag.appendChild(modifiersTag);
fiore@0 240 }
fiore@0 241 index++;
fiore@0 242 }
fiore@0 243 }
fiore@0 244 }
fiore@0 245
fiore@5 246 /**
fiore@5 247 * Sets the internal data of this node (position, name, properties, modifiers) from an XML file
fiore@5 248 * node tag previously encoded via {@code encode}
fiore@5 249 *
fiore@5 250 * @param doc An XMl document
fiore@5 251 * @param nodeTag the XML {@code PersistenceManager.NODE } tag with data for this node
fiore@6 252 * @throws IOException if something goes wrong when reading the document. E.g. when the file is corrupted
fiore@5 253 *
fiore@5 254 * @see uk.ac.qmul.eecs.ccmi.gui.persistence
fiore@5 255 */
fiore@0 256 public void decode(Document doc, Element nodeTag) throws IOException{
fiore@3 257 setName(nodeTag.getAttribute(PersistenceManager.NAME),DiagramEventSource.PERS);
fiore@0 258 try{
fiore@0 259 setId(Integer.parseInt(nodeTag.getAttribute(PersistenceManager.ID)));
fiore@0 260 }catch(NumberFormatException nfe){
fiore@0 261 throw new IOException();
fiore@0 262 }
fiore@0 263
fiore@0 264 if(nodeTag.getElementsByTagName(PersistenceManager.POSITION).item(0) == null)
fiore@0 265 throw new IOException();
fiore@0 266 Element positionTag = (Element)nodeTag.getElementsByTagName(PersistenceManager.POSITION).item(0);
fiore@0 267 double dx,dy;
fiore@0 268 try{
fiore@0 269 dx = Double.parseDouble(positionTag.getAttribute(PersistenceManager.X));
fiore@0 270 dy = Double.parseDouble(positionTag.getAttribute(PersistenceManager.Y));
fiore@0 271 }catch(NumberFormatException nfe){
fiore@0 272 throw new IOException();
fiore@0 273 }
fiore@0 274 Rectangle2D bounds = getBounds();
fiore@3 275 translate(new Point2D.Double(0,0), dx - bounds.getX(), dy - bounds.getY(),DiagramEventSource.PERS);
fiore@0 276
fiore@0 277 NodeList propList = nodeTag.getElementsByTagName(PersistenceManager.PROPERTY);
fiore@0 278 NodeProperties properties = getProperties();
fiore@0 279 for(int j=0; j<propList.getLength();j++){
fiore@0 280 Element propertyTag = (Element)propList.item(j);
fiore@0 281
fiore@0 282 if(propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0) == null)
fiore@0 283 throw new IOException();
fiore@0 284 Element pTypeTag = (Element)propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0);
fiore@0 285 String propertyType = pTypeTag.getTextContent();
fiore@0 286
fiore@0 287 /* scan all the <Element> of the current <Property>*/
fiore@0 288 NodeList elemValueList = propertyTag.getElementsByTagName(PersistenceManager.ELEMENT);
fiore@0 289 for(int h=0; h<elemValueList.getLength(); h++){
fiore@0 290
fiore@0 291 Element elemTag = (Element)elemValueList.item(h);
fiore@0 292 /* get the <value> */
fiore@0 293 if(elemTag.getElementsByTagName(PersistenceManager.VALUE).item(0) == null)
fiore@0 294 throw new IOException();
fiore@0 295 Element valueTag = (Element)elemTag.getElementsByTagName(PersistenceManager.VALUE).item(0);
fiore@0 296 String value = valueTag.getTextContent();
fiore@0 297
fiore@0 298 /* <modifiers>. need to go back on the prototypes because the content of <modifier> is a list */
fiore@0 299 /* of int index pointing to the modifiers type, defined just in the prototypes */
fiore@0 300 Element prototypesTag = (Element)doc.getElementsByTagName(PersistenceManager.PROTOTYPES).item(0);
fiore@0 301 Modifiers modifiers = null;
fiore@0 302 try {
fiore@0 303 modifiers = properties.getModifiers(propertyType);
fiore@0 304 }catch(IllegalArgumentException iae){
fiore@0 305 throw new IOException(iae);
fiore@0 306 }
fiore@0 307 if(!modifiers.isNull()){
fiore@0 308 Element modifiersTag = (Element)((Element)elemValueList.item(h)).getElementsByTagName(PersistenceManager.MODIFIERS).item(0);
fiore@0 309 if(modifiersTag != null){ //else there are no modifiers specified for this property value
fiore@0 310 Set<Integer> indexesToAdd = new LinkedHashSet<Integer>();
fiore@0 311 String indexesString = modifiersTag.getTextContent();
fiore@0 312 String[] indexes = indexesString.split(" ");
fiore@0 313 for(String s : indexes){
fiore@0 314 try{
fiore@0 315 int index = Integer.parseInt(s);
fiore@0 316 NodeList templatePropList = prototypesTag.getElementsByTagName(PersistenceManager.PROPERTY);
fiore@0 317 String modifiersType = null;
fiore@0 318 /* look at the property prototypes to see which modifier the index is referring to. *
fiore@0 319 * The index is in fact the id attribute of the <Modifier> tag in the prototypes section */
fiore@0 320 for(int k=0; k<templatePropList.getLength();k++){
fiore@0 321 Element prototypePropTag = (Element)templatePropList.item(k);
fiore@0 322 Element prototypePropTypeTag = (Element)prototypePropTag.getElementsByTagName(PersistenceManager.TYPE).item(0);
fiore@0 323
fiore@0 324 if(propertyType.equals(prototypePropTypeTag.getTextContent())){
fiore@0 325 NodeList proptotypeModifierList = prototypePropTag.getElementsByTagName(PersistenceManager.MODIFIER);
fiore@0 326 for(int m = 0 ; m<proptotypeModifierList.getLength();m++){
fiore@0 327 if(index == Integer.parseInt(((Element)proptotypeModifierList.item(m)).getAttribute(PersistenceManager.ID))){
fiore@0 328 Element modifierTypeTag = (Element)((Element)proptotypeModifierList.item(m)).getElementsByTagName(PersistenceManager.TYPE).item(0);
fiore@0 329 modifiersType = modifierTypeTag.getTextContent();
fiore@0 330 }
fiore@0 331 }
fiore@0 332 }
fiore@0 333 }
fiore@0 334 if(modifiersType == null) // the index must point to a valid modifier's id
fiore@0 335 throw new IOException();
fiore@0 336 indexesToAdd.add(Integer.valueOf(modifiers.getTypes().indexOf(modifiersType)));
fiore@0 337 }catch(NumberFormatException nfe){
fiore@0 338 throw new IOException(nfe);
fiore@0 339 }
fiore@0 340 }
fiore@3 341 addProperty(propertyType, value,DiagramEventSource.PERS);//whether propertyType actually exist in the prototypes has been already checked
fiore@3 342 setModifierIndexes(propertyType, h, indexesToAdd,DiagramEventSource.PERS);
fiore@0 343 }else
fiore@3 344 addProperty(propertyType, value,DiagramEventSource.PERS);
fiore@0 345 }else
fiore@3 346 addProperty(propertyType, value,DiagramEventSource.PERS);
fiore@0 347 }
fiore@0 348 }
fiore@0 349 }
fiore@0 350
fiore@0 351 @SuppressWarnings("unchecked")
fiore@0 352 @Override
fiore@0 353 public Object clone(){
fiore@0 354 Node clone = (Node)super.clone();
fiore@0 355 clone.attachedEdges = (ArrayList<Edge>) attachedEdges.clone();
fiore@0 356 return clone;
fiore@0 357 }
fiore@0 358
fiore@5 359 /**
fiore@5 360 * An array of references to the edges attached to this node
fiore@5 361 */
fiore@0 362 protected ArrayList<Edge> attachedEdges;
fiore@0 363
fiore@0 364 private final int MARKER_SIZE = 7;
fiore@5 365 /**
fiore@5 366 * The shadow color of nodes
fiore@5 367 */
fiore@0 368 protected static final Color SHADOW_COLOR = Color.LIGHT_GRAY;
fiore@0 369 public static final int SHADOW_GAP = 2;
fiore@0 370
fiore@0 371 }