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