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