fiore@0
|
1 /*
|
fiore@0
|
2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
|
fiore@0
|
3
|
fiore@0
|
4 Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com)
|
fiore@0
|
5 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
|
fiore@0
|
6
|
fiore@0
|
7 This program is free software: you can redistribute it and/or modify
|
fiore@0
|
8 it under the terms of the GNU General Public License as published by
|
fiore@0
|
9 the Free Software Foundation, either version 3 of the License, or
|
fiore@0
|
10 (at your option) any later version.
|
fiore@0
|
11
|
fiore@0
|
12 This program is distributed in the hope that it will be useful,
|
fiore@0
|
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
fiore@0
|
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
fiore@0
|
15 GNU General Public License for more details.
|
fiore@0
|
16
|
fiore@0
|
17 You should have received a copy of the GNU General Public License
|
fiore@0
|
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
|
fiore@0
|
19 */
|
fiore@0
|
20 package uk.ac.qmul.eecs.ccmi.gui;
|
fiore@0
|
21
|
fiore@0
|
22 import java.awt.Color;
|
fiore@0
|
23 import java.awt.Graphics2D;
|
fiore@0
|
24 import java.awt.Shape;
|
fiore@0
|
25 import java.awt.geom.Point2D;
|
fiore@0
|
26 import java.awt.geom.Rectangle2D;
|
fiore@0
|
27 import java.io.IOException;
|
fiore@0
|
28 import java.util.ArrayList;
|
fiore@0
|
29 import java.util.LinkedHashSet;
|
fiore@0
|
30 import java.util.List;
|
fiore@0
|
31 import java.util.Set;
|
fiore@0
|
32
|
fiore@0
|
33 import org.w3c.dom.Document;
|
fiore@0
|
34 import org.w3c.dom.Element;
|
fiore@0
|
35 import org.w3c.dom.NodeList;
|
fiore@0
|
36
|
fiore@0
|
37 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramEdge;
|
fiore@0
|
38 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
|
fiore@0
|
39 import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
|
fiore@0
|
40 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
|
fiore@0
|
41 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers;
|
fiore@0
|
42 import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
|
fiore@0
|
43
|
fiore@0
|
44 /**
|
fiore@5
|
45 * An node in a graph. {@code Node} objects are used in a {@code GraphPanel} to render diagram nodes visually.
|
fiore@5
|
46 * {@code Node} objects are used in the tree representation of the diagram as well, as they're
|
fiore@0
|
47 * subclasses of {@link DiagramNode}
|
fiore@0
|
48 *
|
fiore@0
|
49 */
|
fiore@0
|
50 @SuppressWarnings("serial")
|
fiore@0
|
51 public abstract class Node extends DiagramNode implements GraphElement{
|
fiore@0
|
52
|
fiore@5
|
53 /**
|
fiore@5
|
54 * Constructor to be called by sub classes
|
fiore@5
|
55 *
|
fiore@5
|
56 * @param type the type of the new node. All nodes with this type will be
|
fiore@5
|
57 * put under the same tree node in the tree representation
|
fiore@5
|
58 * @param properties the properties of this node
|
fiore@5
|
59 */
|
fiore@0
|
60 public Node(String type, NodeProperties properties){
|
fiore@0
|
61 super(type,properties);
|
fiore@0
|
62 attachedEdges = new ArrayList<Edge>();
|
fiore@0
|
63 }
|
fiore@0
|
64
|
fiore@0
|
65 /* --- DiagramNode abstract methods implementation --- */
|
fiore@0
|
66 @Override
|
fiore@0
|
67 public int getEdgesNum(){
|
fiore@0
|
68 return attachedEdges.size();
|
fiore@0
|
69 }
|
fiore@0
|
70
|
fiore@0
|
71 @Override
|
fiore@0
|
72 public Edge getEdgeAt(int index){
|
fiore@0
|
73 return attachedEdges.get(index);
|
fiore@0
|
74 }
|
fiore@0
|
75
|
fiore@0
|
76 @Override
|
fiore@0
|
77 public boolean addEdge(DiagramEdge e){
|
fiore@0
|
78 return attachedEdges.add((Edge)e);
|
fiore@0
|
79 }
|
fiore@0
|
80
|
fiore@0
|
81 @Override
|
fiore@0
|
82 public boolean removeEdge(DiagramEdge e){
|
fiore@0
|
83 return attachedEdges.remove((Edge)e);
|
fiore@0
|
84 }
|
fiore@0
|
85
|
fiore@0
|
86 @Override
|
fiore@0
|
87 public Node getExternalNode(){
|
fiore@5
|
88 return null;
|
fiore@0
|
89 }
|
fiore@0
|
90
|
fiore@0
|
91 @Override
|
fiore@0
|
92 public void setExternalNode(DiagramNode node){
|
fiore@5
|
93 throw new UnsupportedOperationException();
|
fiore@0
|
94 }
|
fiore@0
|
95
|
fiore@0
|
96 @Override
|
fiore@0
|
97 public Node getInternalNodeAt(int i){
|
fiore@5
|
98 throw new UnsupportedOperationException();
|
fiore@0
|
99 }
|
fiore@0
|
100
|
fiore@0
|
101 @Override
|
fiore@0
|
102 public int getInternalNodesNum(){
|
fiore@5
|
103 return 0;
|
fiore@0
|
104 }
|
fiore@0
|
105
|
fiore@0
|
106 @Override
|
fiore@0
|
107 public void addInternalNode(DiagramNode node){
|
fiore@5
|
108 throw new UnsupportedOperationException();
|
fiore@0
|
109 }
|
fiore@0
|
110
|
fiore@0
|
111 @Override
|
fiore@0
|
112 public void removeInternalNode(DiagramNode node){
|
fiore@5
|
113 throw new UnsupportedOperationException();
|
fiore@0
|
114 }
|
fiore@0
|
115
|
fiore@0
|
116 @Override
|
fiore@3
|
117 public void stopMove(Object source){
|
fiore@3
|
118 notifyChange(new ElementChangedEvent(this,this,"stop_move",source));
|
fiore@0
|
119 /* edges can change as a result of nodes motion thus we call the method for all the edges
|
fiore@0
|
120 * of the node regardless what the mouse point is */
|
fiore@0
|
121 for(int i = 0; i < getEdgesNum();i++){
|
fiore@3
|
122 getEdgeAt(i).stopMove(source);
|
fiore@0
|
123 }
|
fiore@0
|
124 }
|
fiore@0
|
125
|
fiore@5
|
126 @Override
|
fiore@3
|
127 public void translate( Point2D p , double dx, double dy, Object source){
|
fiore@0
|
128 translateImplementation( p, dx, dy);
|
fiore@3
|
129 for(int i=0; i< getInternalNodesNum();i++){
|
fiore@3
|
130 getInternalNodeAt(i).translate(p, dx, dy,source);
|
fiore@3
|
131 }
|
fiore@3
|
132 notifyChange(new ElementChangedEvent(this, this, "translate", source));
|
fiore@0
|
133 }
|
fiore@0
|
134
|
fiore@3
|
135 @Override
|
fiore@3
|
136 protected void setNotes(String notes,Object source){
|
fiore@3
|
137 this.notes = notes;
|
fiore@3
|
138 notifyChange(new ElementChangedEvent(this,this,"notes",source));
|
fiore@0
|
139 }
|
fiore@3
|
140
|
fiore@4
|
141 /**
|
fiore@4
|
142 * The actual implementation of {@code translate()}. The {@code translate} method
|
fiore@4
|
143 * when called will, in turn, call this method, and then call all the registered
|
fiore@4
|
144 * change listeners in order to notify them that the node has been translated.
|
fiore@4
|
145 *
|
fiore@4
|
146 * @param p the point we are translating from
|
fiore@4
|
147 * @param dx the amount to translate in the x-direction
|
fiore@4
|
148 * @param dy the amount to translate in the y-direction
|
fiore@4
|
149 */
|
fiore@3
|
150 protected abstract void translateImplementation(Point2D p , double dx, double dy);
|
fiore@3
|
151
|
fiore@0
|
152 /**
|
fiore@0
|
153 * Tests whether the node contains a point.
|
fiore@0
|
154 * @param aPoint the point to test
|
fiore@0
|
155 * @return true if this node contains aPoint
|
fiore@0
|
156 */
|
fiore@0
|
157 public abstract boolean contains(Point2D aPoint);
|
fiore@0
|
158
|
fiore@0
|
159 @Override
|
fiore@0
|
160 public abstract Rectangle2D getBounds();
|
fiore@0
|
161
|
fiore@0
|
162 @Override
|
fiore@3
|
163 public void startMove(Point2D p,Object source){
|
fiore@0
|
164 /* useless, here just to comply with the GraphElement interface */
|
fiore@0
|
165 }
|
fiore@0
|
166
|
fiore@5
|
167 @Override
|
fiore@0
|
168 public abstract Point2D getConnectionPoint(Direction d);
|
fiore@0
|
169
|
fiore@0
|
170 @Override
|
fiore@0
|
171 public void draw(Graphics2D g2){
|
fiore@0
|
172 if(!"".equals(getNotes())){
|
fiore@0
|
173 Rectangle2D bounds = getBounds();
|
fiore@0
|
174 Color oldColor = g2.getColor();
|
fiore@0
|
175 g2.setColor(GraphPanel.GRABBER_COLOR);
|
fiore@0
|
176 g2.fill(new Rectangle2D.Double(bounds.getX() - MARKER_SIZE / 2, bounds.getY() - MARKER_SIZE / 2, MARKER_SIZE, MARKER_SIZE));
|
fiore@0
|
177 g2.setColor(oldColor);
|
fiore@0
|
178 }
|
fiore@0
|
179 }
|
fiore@0
|
180
|
fiore@5
|
181 /**
|
fiore@5
|
182 * Returns the geometric shape of this node
|
fiore@5
|
183 *
|
fiore@5
|
184 * @return the shape of this node
|
fiore@5
|
185 */
|
fiore@0
|
186 public abstract Shape getShape();
|
fiore@0
|
187
|
fiore@5
|
188 /**
|
fiore@5
|
189 * Encodes the internal data of this node (position, name, properties, modifiers) in XML format.
|
fiore@5
|
190 *
|
fiore@5
|
191 * The saved data can be retrieved and set back via {@code decode}.
|
fiore@5
|
192 *
|
fiore@5
|
193 * @param doc An XMl document
|
fiore@5
|
194 * @param parent the parent XML tag this node tag will be nested in
|
fiore@5
|
195 */
|
fiore@0
|
196 public void encode(Document doc, Element parent){
|
fiore@0
|
197 parent.setAttribute(PersistenceManager.NAME,getName());
|
fiore@0
|
198
|
fiore@0
|
199 Element positionTag = doc.createElement(PersistenceManager.POSITION);
|
fiore@0
|
200 Rectangle2D bounds = getBounds();
|
fiore@0
|
201 positionTag.setAttribute(PersistenceManager.X, String.valueOf(bounds.getX()));
|
fiore@0
|
202 positionTag.setAttribute(PersistenceManager.Y, String.valueOf(bounds.getY()));
|
fiore@0
|
203 parent.appendChild(positionTag);
|
fiore@0
|
204
|
fiore@0
|
205 if(getProperties().isEmpty())
|
fiore@0
|
206 return;
|
fiore@0
|
207
|
fiore@0
|
208 Element propertiesTag = doc.createElement(PersistenceManager.PROPERTIES);
|
fiore@0
|
209 parent.appendChild(propertiesTag);
|
fiore@0
|
210 for(String type : getProperties().getTypes()){
|
fiore@0
|
211 List<String> values = getProperties().getValues(type);
|
fiore@0
|
212 if(values.isEmpty())
|
fiore@0
|
213 continue;
|
fiore@0
|
214 Element propertyTag = doc.createElement(PersistenceManager.PROPERTY);
|
fiore@0
|
215 propertiesTag.appendChild(propertyTag);
|
fiore@0
|
216
|
fiore@0
|
217 Element typeTag = doc.createElement(PersistenceManager.TYPE);
|
fiore@0
|
218 typeTag.appendChild(doc.createTextNode(type));
|
fiore@0
|
219 propertyTag.appendChild(typeTag);
|
fiore@0
|
220
|
fiore@0
|
221 int index = 0;
|
fiore@0
|
222 for(String value : values){
|
fiore@0
|
223 Element elementTag = doc.createElement(PersistenceManager.ELEMENT);
|
fiore@0
|
224 propertyTag.appendChild(elementTag);
|
fiore@0
|
225
|
fiore@0
|
226 Element valueTag = doc.createElement(PersistenceManager.VALUE);
|
fiore@0
|
227 valueTag.appendChild(doc.createTextNode(value));
|
fiore@0
|
228 elementTag.appendChild(valueTag);
|
fiore@0
|
229
|
fiore@0
|
230
|
fiore@0
|
231 Set<Integer> modifierIndexes = getProperties().getModifiers(type).getIndexes(index);
|
fiore@0
|
232 if(!modifierIndexes.isEmpty()){
|
fiore@0
|
233 Element modifiersTag = doc.createElement(PersistenceManager.MODIFIERS);
|
fiore@0
|
234 StringBuilder builder = new StringBuilder();
|
fiore@0
|
235 for(Integer i : modifierIndexes )
|
fiore@0
|
236 builder.append(i).append(' ');
|
fiore@0
|
237 builder.deleteCharAt(builder.length()-1);//remove last space
|
fiore@0
|
238 modifiersTag.appendChild(doc.createTextNode(builder.toString()));
|
fiore@0
|
239 elementTag.appendChild(modifiersTag);
|
fiore@0
|
240 }
|
fiore@0
|
241 index++;
|
fiore@0
|
242 }
|
fiore@0
|
243 }
|
fiore@0
|
244 }
|
fiore@0
|
245
|
fiore@5
|
246 /**
|
fiore@5
|
247 * Sets the internal data of this node (position, name, properties, modifiers) from an XML file
|
fiore@5
|
248 * node tag previously encoded via {@code encode}
|
fiore@5
|
249 *
|
fiore@5
|
250 * @param doc An XMl document
|
fiore@5
|
251 * @param nodeTag the XML {@code PersistenceManager.NODE } tag with data for this node
|
fiore@6
|
252 * @throws IOException if something goes wrong when reading the document. E.g. when the file is corrupted
|
fiore@5
|
253 *
|
fiore@5
|
254 * @see uk.ac.qmul.eecs.ccmi.gui.persistence
|
fiore@5
|
255 */
|
fiore@0
|
256 public void decode(Document doc, Element nodeTag) throws IOException{
|
fiore@3
|
257 setName(nodeTag.getAttribute(PersistenceManager.NAME),DiagramEventSource.PERS);
|
fiore@0
|
258 try{
|
fiore@0
|
259 setId(Integer.parseInt(nodeTag.getAttribute(PersistenceManager.ID)));
|
fiore@0
|
260 }catch(NumberFormatException nfe){
|
fiore@0
|
261 throw new IOException();
|
fiore@0
|
262 }
|
fiore@0
|
263
|
fiore@0
|
264 if(nodeTag.getElementsByTagName(PersistenceManager.POSITION).item(0) == null)
|
fiore@0
|
265 throw new IOException();
|
fiore@0
|
266 Element positionTag = (Element)nodeTag.getElementsByTagName(PersistenceManager.POSITION).item(0);
|
fiore@0
|
267 double dx,dy;
|
fiore@0
|
268 try{
|
fiore@0
|
269 dx = Double.parseDouble(positionTag.getAttribute(PersistenceManager.X));
|
fiore@0
|
270 dy = Double.parseDouble(positionTag.getAttribute(PersistenceManager.Y));
|
fiore@0
|
271 }catch(NumberFormatException nfe){
|
fiore@0
|
272 throw new IOException();
|
fiore@0
|
273 }
|
fiore@0
|
274 Rectangle2D bounds = getBounds();
|
fiore@3
|
275 translate(new Point2D.Double(0,0), dx - bounds.getX(), dy - bounds.getY(),DiagramEventSource.PERS);
|
fiore@0
|
276
|
fiore@0
|
277 NodeList propList = nodeTag.getElementsByTagName(PersistenceManager.PROPERTY);
|
fiore@0
|
278 NodeProperties properties = getProperties();
|
fiore@0
|
279 for(int j=0; j<propList.getLength();j++){
|
fiore@0
|
280 Element propertyTag = (Element)propList.item(j);
|
fiore@0
|
281
|
fiore@0
|
282 if(propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0) == null)
|
fiore@0
|
283 throw new IOException();
|
fiore@0
|
284 Element pTypeTag = (Element)propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0);
|
fiore@0
|
285 String propertyType = pTypeTag.getTextContent();
|
fiore@0
|
286
|
fiore@0
|
287 /* scan all the <Element> of the current <Property>*/
|
fiore@0
|
288 NodeList elemValueList = propertyTag.getElementsByTagName(PersistenceManager.ELEMENT);
|
fiore@0
|
289 for(int h=0; h<elemValueList.getLength(); h++){
|
fiore@0
|
290
|
fiore@0
|
291 Element elemTag = (Element)elemValueList.item(h);
|
fiore@0
|
292 /* get the <value> */
|
fiore@0
|
293 if(elemTag.getElementsByTagName(PersistenceManager.VALUE).item(0) == null)
|
fiore@0
|
294 throw new IOException();
|
fiore@0
|
295 Element valueTag = (Element)elemTag.getElementsByTagName(PersistenceManager.VALUE).item(0);
|
fiore@0
|
296 String value = valueTag.getTextContent();
|
fiore@0
|
297
|
fiore@0
|
298 /* <modifiers>. need to go back on the prototypes because the content of <modifier> is a list */
|
fiore@0
|
299 /* of int index pointing to the modifiers type, defined just in the prototypes */
|
fiore@0
|
300 Element prototypesTag = (Element)doc.getElementsByTagName(PersistenceManager.PROTOTYPES).item(0);
|
fiore@0
|
301 Modifiers modifiers = null;
|
fiore@0
|
302 try {
|
fiore@0
|
303 modifiers = properties.getModifiers(propertyType);
|
fiore@0
|
304 }catch(IllegalArgumentException iae){
|
fiore@0
|
305 throw new IOException(iae);
|
fiore@0
|
306 }
|
fiore@0
|
307 if(!modifiers.isNull()){
|
fiore@0
|
308 Element modifiersTag = (Element)((Element)elemValueList.item(h)).getElementsByTagName(PersistenceManager.MODIFIERS).item(0);
|
fiore@0
|
309 if(modifiersTag != null){ //else there are no modifiers specified for this property value
|
fiore@0
|
310 Set<Integer> indexesToAdd = new LinkedHashSet<Integer>();
|
fiore@0
|
311 String indexesString = modifiersTag.getTextContent();
|
fiore@0
|
312 String[] indexes = indexesString.split(" ");
|
fiore@0
|
313 for(String s : indexes){
|
fiore@0
|
314 try{
|
fiore@0
|
315 int index = Integer.parseInt(s);
|
fiore@0
|
316 NodeList templatePropList = prototypesTag.getElementsByTagName(PersistenceManager.PROPERTY);
|
fiore@0
|
317 String modifiersType = null;
|
fiore@0
|
318 /* look at the property prototypes to see which modifier the index is referring to. *
|
fiore@0
|
319 * The index is in fact the id attribute of the <Modifier> tag in the prototypes section */
|
fiore@0
|
320 for(int k=0; k<templatePropList.getLength();k++){
|
fiore@0
|
321 Element prototypePropTag = (Element)templatePropList.item(k);
|
fiore@0
|
322 Element prototypePropTypeTag = (Element)prototypePropTag.getElementsByTagName(PersistenceManager.TYPE).item(0);
|
fiore@0
|
323
|
fiore@0
|
324 if(propertyType.equals(prototypePropTypeTag.getTextContent())){
|
fiore@0
|
325 NodeList proptotypeModifierList = prototypePropTag.getElementsByTagName(PersistenceManager.MODIFIER);
|
fiore@0
|
326 for(int m = 0 ; m<proptotypeModifierList.getLength();m++){
|
fiore@0
|
327 if(index == Integer.parseInt(((Element)proptotypeModifierList.item(m)).getAttribute(PersistenceManager.ID))){
|
fiore@0
|
328 Element modifierTypeTag = (Element)((Element)proptotypeModifierList.item(m)).getElementsByTagName(PersistenceManager.TYPE).item(0);
|
fiore@0
|
329 modifiersType = modifierTypeTag.getTextContent();
|
fiore@0
|
330 }
|
fiore@0
|
331 }
|
fiore@0
|
332 }
|
fiore@0
|
333 }
|
fiore@0
|
334 if(modifiersType == null) // the index must point to a valid modifier's id
|
fiore@0
|
335 throw new IOException();
|
fiore@0
|
336 indexesToAdd.add(Integer.valueOf(modifiers.getTypes().indexOf(modifiersType)));
|
fiore@0
|
337 }catch(NumberFormatException nfe){
|
fiore@0
|
338 throw new IOException(nfe);
|
fiore@0
|
339 }
|
fiore@0
|
340 }
|
fiore@3
|
341 addProperty(propertyType, value,DiagramEventSource.PERS);//whether propertyType actually exist in the prototypes has been already checked
|
fiore@3
|
342 setModifierIndexes(propertyType, h, indexesToAdd,DiagramEventSource.PERS);
|
fiore@0
|
343 }else
|
fiore@3
|
344 addProperty(propertyType, value,DiagramEventSource.PERS);
|
fiore@0
|
345 }else
|
fiore@3
|
346 addProperty(propertyType, value,DiagramEventSource.PERS);
|
fiore@0
|
347 }
|
fiore@0
|
348 }
|
fiore@0
|
349 }
|
fiore@0
|
350
|
fiore@0
|
351 @SuppressWarnings("unchecked")
|
fiore@0
|
352 @Override
|
fiore@0
|
353 public Object clone(){
|
fiore@0
|
354 Node clone = (Node)super.clone();
|
fiore@0
|
355 clone.attachedEdges = (ArrayList<Edge>) attachedEdges.clone();
|
fiore@0
|
356 return clone;
|
fiore@0
|
357 }
|
fiore@0
|
358
|
fiore@5
|
359 /**
|
fiore@5
|
360 * An array of references to the edges attached to this node
|
fiore@5
|
361 */
|
fiore@0
|
362 protected ArrayList<Edge> attachedEdges;
|
fiore@0
|
363
|
fiore@0
|
364 private final int MARKER_SIZE = 7;
|
fiore@5
|
365 /**
|
fiore@5
|
366 * The shadow color of nodes
|
fiore@5
|
367 */
|
fiore@0
|
368 protected static final Color SHADOW_COLOR = Color.LIGHT_GRAY;
|
fiore@0
|
369 public static final int SHADOW_GAP = 2;
|
fiore@0
|
370
|
fiore@0
|
371 }
|