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