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