diff java/src/uk/ac/qmul/eecs/ccmi/gui/Node.java @ 0:78b7fc5391a2

first import, outcome of NIME 2014 hackaton
author Fiore Martin <f.martin@qmul.ac.uk>
date Tue, 08 Jul 2014 16:28:59 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Node.java	Tue Jul 08 16:28:59 2014 +0100
@@ -0,0 +1,371 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+	  
+ Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com)
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+package uk.ac.qmul.eecs.ccmi.gui;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramEdge;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers;
+import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
+
+/**
+ * An node in a graph. {@code Node} objects are used in a {@code GraphPanel} to render diagram nodes visually. 
+ * {@code Node} objects are used in the tree representation of the diagram as well, as they're
+ * subclasses of {@link DiagramNode}    
+ * 
+ */
+@SuppressWarnings("serial")
+public abstract class Node extends DiagramNode implements GraphElement{
+
+	/**
+	 * Constructor to be called by sub classes 
+	 * 
+	 * @param type the type of the new node. All nodes with this type will be 
+	 * put under the same tree node in the tree representation 
+	 * @param properties the properties of this node 
+	 */
+	public Node(String type, NodeProperties properties){
+		super(type,properties);
+		attachedEdges = new ArrayList<Edge>();
+	}
+
+	/* --- DiagramNode abstract methods implementation --- */
+	@Override
+	public int getEdgesNum(){
+		return attachedEdges.size();
+	}
+
+	@Override
+	public Edge getEdgeAt(int index){
+		return attachedEdges.get(index);
+	}
+	
+	@Override
+	public boolean addEdge(DiagramEdge e){
+		return attachedEdges.add((Edge)e);
+	}
+
+	@Override
+	public boolean removeEdge(DiagramEdge e){
+		return attachedEdges.remove((Edge)e);
+	}
+
+	@Override
+	public Node getExternalNode(){
+		return null; 
+	}
+
+	@Override
+	public void setExternalNode(DiagramNode node){
+		throw new UnsupportedOperationException(); 
+	}
+
+	@Override
+	public  Node getInternalNodeAt(int i){
+		throw new UnsupportedOperationException(); 
+	}
+	
+	@Override
+	public int getInternalNodesNum(){
+		return 0;
+	}
+
+	@Override
+	public void addInternalNode(DiagramNode node){
+		throw new UnsupportedOperationException(); 
+	}
+
+	@Override
+	public void removeInternalNode(DiagramNode node){
+		throw new UnsupportedOperationException(); 
+	}
+
+	@Override
+	public void stopMove(Object source){
+		notifyChange(new ElementChangedEvent(this,this,"stop_move",source));
+		/* edges can change as a result of nodes motion thus we call the method for all the edges
+		 * of the node regardless what the mouse point is */
+		for(int i = 0; i < getEdgesNum();i++){
+			getEdgeAt(i).stopMove(source);
+		}
+	}
+
+	@Override
+	public void translate( Point2D p , double dx, double dy, Object source){
+		translateImplementation( p, dx, dy);
+		for(int i=0; i< getInternalNodesNum();i++){
+			getInternalNodeAt(i).translate(p, dx, dy,source);
+		}
+		notifyChange(new ElementChangedEvent(this, this, "translate", source));
+	}
+
+	@Override
+	protected void setNotes(String notes,Object source){
+		this.notes = notes;
+		notifyChange(new ElementChangedEvent(this,this,"notes",source));
+	}
+	
+	/**
+	 *  The actual implementation of {@code translate()}. The {@code translate} method
+	 *  when called will, in turn, call this method, and then call all the registered
+	 *  change listeners in order to notify them that the node has been translated.
+	 *    
+	 *	@param p the point we are translating from
+	 *	@param dx the amount to translate in the x-direction
+	 *	@param dy the amount to translate in the y-direction
+	 */
+	protected abstract void translateImplementation(Point2D p , double dx, double dy);
+		
+	/**
+	 *	Tests whether the node contains a point.
+	 *	@param aPoint the point to test
+	 *	@return true if this node contains aPoint
+	 */
+	public abstract boolean contains(Point2D aPoint);
+
+	@Override
+	public abstract Rectangle2D getBounds();
+
+	@Override
+	public void startMove(Point2D p,Object source){
+		/* useless, here just to comply with the GraphElement interface */
+	}
+
+	@Override
+	public abstract Point2D getConnectionPoint(Direction d);
+
+	@Override
+	public void draw(Graphics2D g2){
+		if(!"".equals(getNotes())){
+			Rectangle2D bounds = getBounds();
+			Color oldColor = g2.getColor();
+			g2.setColor(GraphPanel.GRABBER_COLOR);
+			g2.fill(new Rectangle2D.Double(bounds.getX() - MARKER_SIZE / 2, bounds.getY() - MARKER_SIZE / 2, MARKER_SIZE, MARKER_SIZE));
+			g2.setColor(oldColor);
+		}
+	}
+
+	/**
+	 * Returns the geometric shape of this node  
+	 * 
+	 * @return the shape of this node
+	 */
+	public abstract Shape getShape();
+
+	/**
+	 * Encodes the internal data of this node (position, name, properties, modifiers) in XML format. 
+	 * 
+	 * The saved data can be retrieved and set back via {@code decode}. 
+	 *  
+	 * @param doc An XMl document
+	 * @param parent the parent XML tag this node tag will be nested in  
+	 */
+	public void encode(Document doc, Element parent){
+		parent.setAttribute(PersistenceManager.NAME,getName());
+
+		Element positionTag = doc.createElement(PersistenceManager.POSITION);
+		Rectangle2D bounds = getBounds();
+		positionTag.setAttribute(PersistenceManager.X, String.valueOf(bounds.getX()));
+		positionTag.setAttribute(PersistenceManager.Y, String.valueOf(bounds.getY()));
+		parent.appendChild(positionTag);
+
+		if(getProperties().isEmpty())
+			return;
+
+		Element propertiesTag = doc.createElement(PersistenceManager.PROPERTIES);
+		parent.appendChild(propertiesTag);
+		for(String type : getProperties().getTypes()){
+			List<String> values = getProperties().getValues(type);
+			if(values.isEmpty())
+				continue;
+			Element propertyTag = doc.createElement(PersistenceManager.PROPERTY);
+			propertiesTag.appendChild(propertyTag);
+
+			Element typeTag = doc.createElement(PersistenceManager.TYPE);
+			typeTag.appendChild(doc.createTextNode(type));
+			propertyTag.appendChild(typeTag);
+
+			int index = 0;
+			for(String value : values){
+				Element elementTag = doc.createElement(PersistenceManager.ELEMENT);
+				propertyTag.appendChild(elementTag);
+
+				Element valueTag = doc.createElement(PersistenceManager.VALUE);
+				valueTag.appendChild(doc.createTextNode(value));
+				elementTag.appendChild(valueTag);
+
+
+				Set<Integer> modifierIndexes = getProperties().getModifiers(type).getIndexes(index);
+				if(!modifierIndexes.isEmpty()){
+					Element modifiersTag = doc.createElement(PersistenceManager.MODIFIERS);
+					StringBuilder builder = new StringBuilder();
+					for(Integer i : modifierIndexes )
+						builder.append(i).append(' ');
+					builder.deleteCharAt(builder.length()-1);//remove last space
+					modifiersTag.appendChild(doc.createTextNode(builder.toString()));
+					elementTag.appendChild(modifiersTag);
+				}
+				index++;
+			}
+		}
+	}
+
+	/**
+	 * Sets the internal data of this node (position, name, properties, modifiers) from an XML file
+	 * node tag previously encoded via {@code encode}  
+	 * 
+	 * @param doc An XMl document
+	 * @param nodeTag the XML {@code PersistenceManager.NODE } tag with data for this node
+	 * @throws IOException if something goes wrong when reading the document. E.g. when the file is corrupted 
+	 * 
+	 * @see uk.ac.qmul.eecs.ccmi.gui.persistence
+	 */
+	public void decode(Document doc, Element nodeTag) throws IOException{
+		setName(nodeTag.getAttribute(PersistenceManager.NAME),DiagramEventSource.PERS);
+		try{
+			setId(Integer.parseInt(nodeTag.getAttribute(PersistenceManager.ID)));
+		}catch(NumberFormatException nfe){
+			throw new IOException();
+		}
+
+		if(nodeTag.getElementsByTagName(PersistenceManager.POSITION).item(0) == null)
+			throw new IOException();
+		Element positionTag = (Element)nodeTag.getElementsByTagName(PersistenceManager.POSITION).item(0);
+		double dx,dy;
+		try{
+			dx = Double.parseDouble(positionTag.getAttribute(PersistenceManager.X));
+			dy = Double.parseDouble(positionTag.getAttribute(PersistenceManager.Y));
+		}catch(NumberFormatException nfe){
+			throw new IOException();
+		}
+		Rectangle2D bounds = getBounds();
+		translate(new Point2D.Double(0,0), dx - bounds.getX(), dy - bounds.getY(),DiagramEventSource.PERS);
+
+		NodeList propList = nodeTag.getElementsByTagName(PersistenceManager.PROPERTY);
+		NodeProperties properties = getProperties();
+		for(int j=0; j<propList.getLength();j++){
+			Element propertyTag = (Element)propList.item(j);
+
+			if(propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0) == null)
+				throw new IOException();
+			Element pTypeTag = (Element)propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0);
+			String propertyType = pTypeTag.getTextContent();
+
+			/* scan all the <Element> of the current <Property>*/
+			NodeList elemValueList = propertyTag.getElementsByTagName(PersistenceManager.ELEMENT);
+			for(int h=0; h<elemValueList.getLength(); h++){
+
+				Element elemTag = (Element)elemValueList.item(h);
+				/* get the <value> */
+				if(elemTag.getElementsByTagName(PersistenceManager.VALUE).item(0) == null)
+					throw new IOException();
+				Element valueTag = (Element)elemTag.getElementsByTagName(PersistenceManager.VALUE).item(0);
+				String value = valueTag.getTextContent();
+
+				/* <modifiers>. need to go back on the prototypes because the content of <modifier> is a list */
+				/* of int index pointing to the modifiers type, defined just in the prototypes                */
+				Element prototypesTag = (Element)doc.getElementsByTagName(PersistenceManager.PROTOTYPES).item(0);
+				Modifiers modifiers = null;
+				try {
+					modifiers = properties.getModifiers(propertyType);
+				}catch(IllegalArgumentException iae){
+					throw new IOException(iae);
+				}
+				if(!modifiers.isNull()){
+					Element modifiersTag = (Element)((Element)elemValueList.item(h)).getElementsByTagName(PersistenceManager.MODIFIERS).item(0);
+					if(modifiersTag != null){ //else there are no modifiers specified for this property value
+						Set<Integer> indexesToAdd = new LinkedHashSet<Integer>();
+						String indexesString = modifiersTag.getTextContent();
+						String[] indexes = indexesString.split(" ");
+						for(String s : indexes){
+							try{
+								int index = Integer.parseInt(s);
+								NodeList templatePropList = prototypesTag.getElementsByTagName(PersistenceManager.PROPERTY);
+								String modifiersType = null;
+								/* look at the property prototypes to see which modifier the index is referring to.      *  
+								 * The index is in fact the id attribute of the <Modifier> tag in the prototypes section */
+								for(int k=0; k<templatePropList.getLength();k++){
+									Element prototypePropTag = (Element)templatePropList.item(k);
+									Element prototypePropTypeTag = (Element)prototypePropTag.getElementsByTagName(PersistenceManager.TYPE).item(0);
+	
+									if(propertyType.equals(prototypePropTypeTag.getTextContent())){
+										NodeList proptotypeModifierList = prototypePropTag.getElementsByTagName(PersistenceManager.MODIFIER);
+										for(int m = 0 ; m<proptotypeModifierList.getLength();m++){
+											if(index == Integer.parseInt(((Element)proptotypeModifierList.item(m)).getAttribute(PersistenceManager.ID))){
+												Element modifierTypeTag = (Element)((Element)proptotypeModifierList.item(m)).getElementsByTagName(PersistenceManager.TYPE).item(0);
+												modifiersType = modifierTypeTag.getTextContent();
+											}
+										}
+									}
+								}
+								if(modifiersType == null) // the index must point to a valid modifier's id
+									throw new IOException();
+								indexesToAdd.add(Integer.valueOf(modifiers.getTypes().indexOf(modifiersType)));
+							}catch(NumberFormatException nfe){
+								throw new IOException(nfe);
+							}
+						}
+						addProperty(propertyType, value,DiagramEventSource.PERS);//whether propertyType actually exist in the prototypes has been already checked  
+						setModifierIndexes(propertyType, h, indexesToAdd,DiagramEventSource.PERS);
+					}else
+						addProperty(propertyType, value,DiagramEventSource.PERS);
+				}else
+					addProperty(propertyType, value,DiagramEventSource.PERS);
+			}
+		}
+	}
+	
+	@SuppressWarnings("unchecked")
+	@Override
+	public Object clone(){
+		Node clone = (Node)super.clone();
+		clone.attachedEdges = (ArrayList<Edge>) attachedEdges.clone(); 
+		return clone;
+	}
+
+	/**
+	 * An array of references to the edges attached to this node 
+	 */
+	protected ArrayList<Edge> attachedEdges;
+
+	private final int MARKER_SIZE = 7;
+	/**
+	 * The shadow color of nodes
+	 */
+	protected static final Color SHADOW_COLOR = Color.LIGHT_GRAY;
+	public static final int SHADOW_GAP = 2;
+
+}