fiore@0
|
1 /*
|
fiore@0
|
2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
|
fiore@0
|
3
|
fiore@0
|
4 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
|
fiore@0
|
5
|
fiore@0
|
6 This program is free software: you can redistribute it and/or modify
|
fiore@0
|
7 it under the terms of the GNU General Public License as published by
|
fiore@0
|
8 the Free Software Foundation, either version 3 of the License, or
|
fiore@0
|
9 (at your option) any later version.
|
fiore@0
|
10
|
fiore@0
|
11 This program is distributed in the hope that it will be useful,
|
fiore@0
|
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
fiore@0
|
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
fiore@0
|
14 GNU General Public License for more details.
|
fiore@0
|
15
|
fiore@0
|
16 You should have received a copy of the GNU General Public License
|
fiore@0
|
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
|
fiore@0
|
18 */
|
fiore@0
|
19
|
fiore@0
|
20 package uk.ac.qmul.eecs.ccmi.simpletemplate;
|
fiore@0
|
21
|
fiore@0
|
22 import java.awt.Graphics2D;
|
fiore@0
|
23 import java.awt.Stroke;
|
fiore@0
|
24 import java.awt.geom.Line2D;
|
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.io.InputStream;
|
fiore@0
|
29 import java.util.HashMap;
|
fiore@0
|
30 import java.util.List;
|
fiore@0
|
31 import java.util.Map;
|
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.DiagramNode;
|
fiore@3
|
38 import uk.ac.qmul.eecs.ccmi.gui.DiagramEventSource;
|
fiore@0
|
39 import uk.ac.qmul.eecs.ccmi.gui.Edge;
|
fiore@0
|
40 import uk.ac.qmul.eecs.ccmi.gui.GraphElement;
|
fiore@0
|
41 import uk.ac.qmul.eecs.ccmi.gui.LineStyle;
|
fiore@0
|
42 import uk.ac.qmul.eecs.ccmi.gui.Node;
|
fiore@0
|
43 import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
|
fiore@0
|
44 import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
|
fiore@0
|
45
|
fiore@5
|
46 /**
|
fiore@5
|
47 * An edge rendered as a straight, dotted or dashed line. The edge can have an arrow head
|
fiore@5
|
48 * at each end. Possible arrow heads are :
|
fiore@5
|
49 * <ul>
|
fiore@5
|
50 * <li> Triangle
|
fiore@5
|
51 * <li> Black Triangle
|
fiore@5
|
52 * <li> V
|
fiore@5
|
53 * <li> Half V
|
fiore@5
|
54 * <li> Diamond
|
fiore@5
|
55 * <li> Black Diamond
|
fiore@5
|
56 * <li> Tail
|
fiore@5
|
57 * </ul>
|
fiore@5
|
58 *
|
fiore@5
|
59 */
|
fiore@0
|
60 @SuppressWarnings("serial")
|
fiore@0
|
61 public class SimpleShapeEdge extends Edge {
|
fiore@0
|
62
|
fiore@0
|
63 public SimpleShapeEdge(String type, LineStyle style, ArrowHead[] heads, String[] availableEndDescriptions, int minAttachedNodes, int maxAttachedNodes) {
|
fiore@0
|
64 super(type,availableEndDescriptions,minAttachedNodes,maxAttachedNodes,style);
|
fiore@0
|
65 this.heads = heads;
|
fiore@0
|
66 currentHeads = new HashMap<Node,ArrowHead>();
|
fiore@0
|
67 }
|
fiore@0
|
68
|
fiore@0
|
69 @Override
|
fiore@5
|
70 public boolean removeNode(DiagramNode n, Object source){
|
fiore@0
|
71 currentHeads.remove(n);
|
fiore@4
|
72 return super.removeNode(n,source);
|
fiore@0
|
73 }
|
fiore@0
|
74
|
fiore@5
|
75
|
fiore@0
|
76 @Override
|
fiore@0
|
77 public void draw(Graphics2D g2) {
|
fiore@0
|
78 Stroke oldStroke = g2.getStroke();
|
fiore@0
|
79 g2.setStroke(getStyle().getStroke());
|
fiore@5
|
80
|
fiore@0
|
81 /* straight line */
|
fiore@0
|
82 if(points.isEmpty()){
|
fiore@0
|
83 Line2D line = getSegment(getNodeAt(0),getNodeAt(1));
|
fiore@0
|
84 g2.draw(line);
|
fiore@0
|
85
|
fiore@0
|
86 /* draw arrow heads if any */
|
fiore@0
|
87 ArrowHead h = currentHeads.get(getNodeAt(0));
|
fiore@0
|
88 if( h != null && h != ArrowHead.TAIL){
|
fiore@0
|
89 Line2D revLine = getSegment(getNodeAt(1),getNodeAt(0));
|
fiore@0
|
90 h.draw(g2, revLine.getP1(), revLine.getP2());
|
fiore@0
|
91 }
|
fiore@0
|
92 h = currentHeads.get(getNodeAt(1));
|
fiore@0
|
93 if( h != null && h != ArrowHead.TAIL){
|
fiore@0
|
94 h.draw(g2, line.getP1(), line.getP2());
|
fiore@0
|
95 }
|
fiore@0
|
96
|
fiore@0
|
97 /* draw labels if any */
|
fiore@0
|
98 String label;
|
fiore@0
|
99 if((label = getEndLabel(getNodeAt(0))) != null){
|
fiore@0
|
100 EdgeDrawSupport.drawString(g2, line.getP2(), line.getP1(), currentHeads.get(getNodeAt(0)), label, false);
|
fiore@0
|
101 }
|
fiore@0
|
102 if((label = getEndLabel(getNodeAt(1))) != null){
|
fiore@0
|
103 EdgeDrawSupport.drawString(g2, line.getP1(), line.getP2(), currentHeads.get(getNodeAt(1)), label, false);
|
fiore@0
|
104 }
|
fiore@0
|
105
|
fiore@0
|
106 /* draw name if any */
|
fiore@0
|
107 if(!getName().isEmpty()){
|
fiore@0
|
108 EdgeDrawSupport.drawString(g2, line.getP2(), line.getP1(), null, getName(), true);
|
fiore@0
|
109 }
|
fiore@0
|
110 if(!"".equals(getNotes()))
|
fiore@0
|
111 EdgeDrawSupport.drawMarker(g2,line.getP1(),line.getP2());
|
fiore@0
|
112 }else{
|
fiore@0
|
113 /* edge with inner points: it can be a multiended(eventually bended) edge or a straight bended edge */
|
fiore@0
|
114
|
fiore@0
|
115 /* arrow and labels are drawn in the same way in either case */
|
fiore@0
|
116 for(InnerPoint p : points){
|
fiore@0
|
117 for(GraphElement ge : p.getNeighbours()){
|
fiore@0
|
118 g2.draw(getSegment(p,ge));
|
fiore@0
|
119 if(ge instanceof Node){ // this is the inner point which is connected to a Node
|
fiore@0
|
120 /* draw arrow if any */
|
fiore@0
|
121 ArrowHead h = currentHeads.get((Node)ge);
|
fiore@0
|
122 if(h != null && h != ArrowHead.TAIL){
|
fiore@0
|
123 Line2D line = getSegment(p,ge);
|
fiore@0
|
124 h.draw(g2, line.getP1() , line.getP2());
|
fiore@0
|
125 }
|
fiore@0
|
126
|
fiore@0
|
127 /* draw label if any */
|
fiore@0
|
128 String label = getEndLabel((Node)ge);
|
fiore@0
|
129 if(label != null){
|
fiore@0
|
130 Line2D line = getSegment(p,ge);
|
fiore@0
|
131 EdgeDrawSupport.drawString(g2, line.getP1(), line.getP2(), currentHeads.get((Node)ge), label, false);
|
fiore@0
|
132 }
|
fiore@0
|
133 }
|
fiore@0
|
134 }
|
fiore@0
|
135 p.draw(g2);
|
fiore@0
|
136 }
|
fiore@0
|
137 /* name is drawn differently :
|
fiore@0
|
138 * for multiended edges name is drawn on the master inner point
|
fiore@0
|
139 * for two ends bended name is drawn in (about) the middle point of the edge
|
fiore@0
|
140 */
|
fiore@0
|
141
|
fiore@0
|
142 if(masterInnerPoint != null){/* multiended edge */
|
fiore@0
|
143 Rectangle2D bounds = masterInnerPoint.getBounds();
|
fiore@0
|
144 Point2D p = new Point2D.Double(bounds.getCenterX() - 1,bounds.getCenterY());
|
fiore@0
|
145 Point2D q = new Point2D.Double(bounds.getCenterX() + 1,bounds.getCenterY());
|
fiore@0
|
146 if(!getName().isEmpty())
|
fiore@0
|
147 EdgeDrawSupport.drawString(g2, p, q, null, getName(), true);
|
fiore@0
|
148 if(!"".equals(getNotes()))
|
fiore@0
|
149 EdgeDrawSupport.drawMarker(g2,p,q);
|
fiore@0
|
150 }else{
|
fiore@0
|
151 /* straight edge which has been bended */
|
fiore@0
|
152 GraphElement ge1 = getNodeAt(0);
|
fiore@0
|
153 GraphElement ge2 = getNodeAt(1);
|
fiore@0
|
154 InnerPoint c1 = null;
|
fiore@0
|
155 InnerPoint c2 = null;
|
fiore@0
|
156
|
fiore@0
|
157 for(InnerPoint innp : points){
|
fiore@0
|
158 if(innp.getNeighbours().contains(ge1)){
|
fiore@0
|
159 c1 = innp;
|
fiore@0
|
160 }
|
fiore@0
|
161 if(innp.getNeighbours().contains(ge2)){
|
fiore@0
|
162 c2 = innp;
|
fiore@0
|
163 }
|
fiore@0
|
164 }
|
fiore@0
|
165
|
fiore@0
|
166 /* draw name if any */
|
fiore@0
|
167 if(!getName().isEmpty()){
|
fiore@0
|
168 /* we only have two nodes but the edge has been bended */
|
fiore@0
|
169 while((c1 != c2)&&(!c2.getNeighbours().contains(c1))){
|
fiore@0
|
170 if(c1.getNeighbours().get(0) == ge1){
|
fiore@0
|
171 ge1 = c1;
|
fiore@0
|
172 c1 = (InnerPoint)c1.getNeighbours().get(1);
|
fiore@0
|
173 }
|
fiore@0
|
174 else{
|
fiore@0
|
175 ge1 = c1;
|
fiore@0
|
176 c1 = (InnerPoint)c1.getNeighbours().get(0);
|
fiore@0
|
177 }
|
fiore@0
|
178 if(c2.getNeighbours().get(0) == ge2){
|
fiore@0
|
179 ge2 = c2;
|
fiore@0
|
180 c2 = (InnerPoint)c2.getNeighbours().get(1);
|
fiore@0
|
181 }
|
fiore@0
|
182 else{
|
fiore@0
|
183 ge2 = c2;
|
fiore@0
|
184 c2 = (InnerPoint)c2.getNeighbours().get(0);
|
fiore@0
|
185 }
|
fiore@0
|
186 }
|
fiore@0
|
187
|
fiore@0
|
188 Point2D p = new Point2D.Double();
|
fiore@0
|
189 Point2D q = new Point2D.Double();
|
fiore@0
|
190 if(c1 == c2){
|
fiore@0
|
191 Rectangle2D bounds = c1.getBounds();
|
fiore@0
|
192 p.setLocation( bounds.getCenterX() - 1,bounds.getCenterY());
|
fiore@0
|
193 q.setLocation( bounds.getCenterX() + 1,bounds.getCenterY());
|
fiore@0
|
194 }else{
|
fiore@0
|
195 Rectangle2D bounds = c1.getBounds();
|
fiore@0
|
196 p.setLocation( bounds.getCenterX(),bounds.getCenterY());
|
fiore@0
|
197 bounds = c2.getBounds();
|
fiore@0
|
198 q.setLocation(bounds.getCenterX(),bounds.getCenterY());
|
fiore@0
|
199
|
fiore@0
|
200 }
|
fiore@0
|
201 if(!getName().isEmpty())
|
fiore@0
|
202 EdgeDrawSupport.drawString(g2, p, q, null, getName(), true);
|
fiore@0
|
203 if(!"".equals(getNotes()))
|
fiore@0
|
204 EdgeDrawSupport.drawMarker(g2,p,q);
|
fiore@0
|
205 }
|
fiore@0
|
206 }
|
fiore@0
|
207 }
|
fiore@0
|
208 g2.setStroke(oldStroke);
|
fiore@0
|
209 }
|
fiore@0
|
210
|
fiore@0
|
211 public Rectangle2D getBounds() {
|
fiore@0
|
212 if(points.isEmpty()){
|
fiore@0
|
213 return getSegment(getNodeAt(0), getNodeAt(1)).getBounds2D();
|
fiore@0
|
214 }else{
|
fiore@0
|
215 Rectangle2D bounds = points.get(0).getBounds();
|
fiore@0
|
216 for(InnerPoint p : points){
|
fiore@0
|
217 for(GraphElement ge : p.getNeighbours())
|
fiore@0
|
218 bounds.add(getSegment(p,ge).getBounds2D());
|
fiore@0
|
219 }
|
fiore@0
|
220 return bounds;
|
fiore@0
|
221 }
|
fiore@0
|
222 }
|
fiore@0
|
223
|
fiore@0
|
224 public ArrowHead[] getHeads() {
|
fiore@0
|
225 return heads;
|
fiore@0
|
226 }
|
fiore@0
|
227
|
fiore@0
|
228 @Override
|
fiore@0
|
229 public InputStream getSound(){
|
fiore@0
|
230 switch(getStyle()){
|
fiore@0
|
231 case Dashed : return dashedSound;
|
fiore@0
|
232 case Dotted : return dottedSound;
|
fiore@0
|
233 default : return straightSound;
|
fiore@0
|
234 }
|
fiore@0
|
235 }
|
fiore@0
|
236
|
fiore@0
|
237 @Override
|
fiore@3
|
238 public void setEndDescription(DiagramNode diagramNode, int index, Object source){
|
fiore@0
|
239 Node n = (Node)diagramNode;
|
fiore@0
|
240 if(index == NO_END_DESCRIPTION_INDEX){
|
fiore@0
|
241 currentHeads.remove(n);
|
fiore@3
|
242 super.setEndDescription(n, index,source);
|
fiore@0
|
243 }else{
|
fiore@0
|
244 ArrowHead h = heads[index];
|
fiore@0
|
245 currentHeads.put(n, h);
|
fiore@3
|
246 super.setEndDescription(n, index,source);
|
fiore@0
|
247 }
|
fiore@0
|
248 }
|
fiore@0
|
249
|
fiore@0
|
250 @Override
|
fiore@0
|
251 public void encode(Document doc, Element parent, List<Node> nodes){
|
fiore@0
|
252 super.encode(doc, parent, nodes);
|
fiore@0
|
253 /* add the head attribute to the NODE tag */
|
fiore@0
|
254 NodeList nodeTagList = parent.getElementsByTagName(PersistenceManager.NODE);
|
fiore@0
|
255 for(int i = 0 ; i< nodeTagList.getLength(); i++){
|
fiore@0
|
256 Element nodeTag = (Element)nodeTagList.item(i);
|
fiore@0
|
257 String nodeIdAsString = nodeTag.getAttribute(PersistenceManager.ID);
|
fiore@0
|
258 long nodeId = Long.parseLong(nodeIdAsString);
|
fiore@0
|
259 Node node = null;
|
fiore@0
|
260 /* find the node with the id of the tag */
|
fiore@0
|
261 for(Node n : nodes)
|
fiore@0
|
262 if(n.getId() == nodeId){
|
fiore@0
|
263 node = n;
|
fiore@0
|
264 break;
|
fiore@0
|
265 }
|
fiore@0
|
266 String head = (currentHeads.get(node) == null) ? "" : currentHeads.get(node).toString();
|
fiore@0
|
267 nodeTag.setAttribute(SimpleShapePrototypePersistenceDelegate.HEAD, head );
|
fiore@0
|
268 }
|
fiore@0
|
269 }
|
fiore@0
|
270
|
fiore@0
|
271 @Override
|
fiore@0
|
272 public void decode(Document doc, Element edgeTag, Map<String,Node> nodesId) throws IOException{
|
fiore@0
|
273 super.decode(doc, edgeTag, nodesId);
|
fiore@0
|
274 NodeList nodeList = edgeTag.getElementsByTagName(PersistenceManager.NODE);
|
fiore@0
|
275 for(int i=0; i<nodeList.getLength(); i++){
|
fiore@0
|
276 Element nodeTag = (Element)nodeList.item(i);
|
fiore@0
|
277 String id = nodeTag.getAttribute(PersistenceManager.ID);
|
fiore@0
|
278 if(id.isEmpty())
|
fiore@0
|
279 throw new IOException();
|
fiore@0
|
280 String head = nodeTag.getAttribute(SimpleShapePrototypePersistenceDelegate.HEAD);
|
fiore@0
|
281 if(!head.isEmpty()){
|
fiore@0
|
282 ArrowHead headShape = null;
|
fiore@0
|
283 try{
|
fiore@0
|
284 headShape = ArrowHead.getArrowHeadFromString(head);
|
fiore@0
|
285 }catch(IOException e){
|
fiore@0
|
286 throw e;
|
fiore@0
|
287 }
|
fiore@0
|
288 currentHeads.put(nodesId.get(id), headShape);
|
fiore@0
|
289 int headDescriptionIndex = Edge.NO_END_DESCRIPTION_INDEX;
|
fiore@0
|
290 for(int j=0; j<heads.length;j++){
|
fiore@0
|
291 if(heads[j].equals(headShape)){
|
fiore@0
|
292 headDescriptionIndex = j;
|
fiore@0
|
293 break;
|
fiore@0
|
294 }
|
fiore@0
|
295 }
|
fiore@3
|
296 setEndDescription(nodesId.get(id),headDescriptionIndex,DiagramEventSource.PERS);
|
fiore@0
|
297 }
|
fiore@0
|
298 }
|
fiore@0
|
299 }
|
fiore@0
|
300
|
fiore@0
|
301 @Override
|
fiore@0
|
302 public Object clone(){
|
fiore@0
|
303 return new SimpleShapeEdge(getType(), getStyle(), heads, getAvailableEndDescriptions(), getMinAttachedNodes(), getMaxAttachedNodes() );
|
fiore@0
|
304 }
|
fiore@0
|
305
|
fiore@0
|
306
|
fiore@0
|
307
|
fiore@0
|
308 private ArrowHead[] heads;
|
fiore@0
|
309 private Map<Node,ArrowHead> currentHeads;
|
fiore@0
|
310 private static InputStream straightSound;
|
fiore@0
|
311 private static InputStream dottedSound;
|
fiore@0
|
312 private static InputStream dashedSound;
|
fiore@0
|
313
|
fiore@0
|
314 static{
|
fiore@0
|
315 Class<SimpleShapeEdge> c = SimpleShapeEdge.class;
|
fiore@0
|
316 straightSound = c.getResourceAsStream("audio/straightLine.mp3");
|
fiore@0
|
317 dottedSound = c.getResourceAsStream("audio/dashedLine.mp3");
|
fiore@0
|
318 dashedSound = c.getResourceAsStream("audio/dottedLine.mp3");
|
fiore@0
|
319 SoundFactory.getInstance().loadSound(straightSound);
|
fiore@0
|
320 SoundFactory.getInstance().loadSound(dottedSound);
|
fiore@0
|
321 SoundFactory.getInstance().loadSound(dashedSound);
|
fiore@0
|
322 }
|
fiore@0
|
323 }
|