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: }