Mercurial > hg > accesspd
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; + +}