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.Color;
|
fiore@0
|
23 import java.awt.Graphics2D;
|
fiore@0
|
24 import java.awt.Shape;
|
fiore@0
|
25 import java.awt.geom.Ellipse2D;
|
fiore@0
|
26 import java.awt.geom.Line2D;
|
fiore@0
|
27 import java.awt.geom.Path2D;
|
fiore@0
|
28 import java.awt.geom.Point2D;
|
fiore@0
|
29 import java.awt.geom.Rectangle2D;
|
fiore@0
|
30 import java.awt.geom.RectangularShape;
|
fiore@0
|
31 import java.io.IOException;
|
fiore@0
|
32 import java.util.ArrayList;
|
fiore@0
|
33 import java.util.LinkedHashMap;
|
fiore@0
|
34 import java.util.LinkedList;
|
fiore@0
|
35 import java.util.List;
|
fiore@0
|
36 import java.util.Map;
|
fiore@0
|
37 import java.util.Set;
|
fiore@0
|
38
|
fiore@0
|
39 import org.w3c.dom.Document;
|
fiore@0
|
40 import org.w3c.dom.Element;
|
fiore@0
|
41 import org.w3c.dom.NodeList;
|
fiore@0
|
42
|
fiore@0
|
43 import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
|
fiore@0
|
44 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
|
fiore@0
|
45 import uk.ac.qmul.eecs.ccmi.gui.Direction;
|
fiore@0
|
46 import uk.ac.qmul.eecs.ccmi.gui.Node;
|
fiore@0
|
47 import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
|
fiore@0
|
48 import uk.ac.qmul.eecs.ccmi.utils.Pair;
|
fiore@0
|
49
|
fiore@0
|
50 /**
|
fiore@0
|
51 *
|
fiore@0
|
52 * A diagram node that can be represented visually as a simple shape such as
|
fiore@0
|
53 * a rectangle, square, circle, ellipse or triangle.
|
fiore@0
|
54 *
|
fiore@0
|
55 */
|
fiore@0
|
56 @SuppressWarnings("serial")
|
fiore@0
|
57 public abstract class SimpleShapeNode extends Node {
|
fiore@0
|
58
|
fiore@0
|
59 public static SimpleShapeNode getInstance(ShapeType shapeType, String typeName, NodeProperties properties){
|
fiore@0
|
60 switch(shapeType){
|
fiore@0
|
61 case Rectangle :
|
fiore@0
|
62 return new RectangularNode(typeName, properties);
|
fiore@0
|
63 case Square :
|
fiore@0
|
64 return new SquareNode(typeName, properties);
|
fiore@0
|
65 case Circle :
|
fiore@0
|
66 return new CircleNode(typeName, properties);
|
fiore@0
|
67 case Ellipse :
|
fiore@0
|
68 return new EllipticalNode(typeName, properties);
|
fiore@0
|
69 case Triangle :
|
fiore@0
|
70 return new TriangularNode(typeName, properties);
|
fiore@0
|
71 }
|
fiore@0
|
72 return null;
|
fiore@0
|
73 }
|
fiore@0
|
74
|
fiore@0
|
75 protected SimpleShapeNode(String typeName, NodeProperties properties){
|
fiore@0
|
76 super(typeName, properties);
|
fiore@0
|
77 dataDisplayBounds = (Rectangle2D.Double)getMinBounds();
|
fiore@0
|
78 /* Initialise the data structures for displaying the properties inside and outside */
|
fiore@0
|
79 propertyNodesMap = new LinkedHashMap<String,List<PropertyNode>>();
|
fiore@0
|
80 int numInsideProperties = 0;
|
fiore@0
|
81 for(String type : getProperties().getTypes()){
|
fiore@0
|
82 if(((PropertyView)getProperties().getView(type)).getPosition() == Position.Inside)
|
fiore@0
|
83 numInsideProperties++;
|
fiore@0
|
84 else
|
fiore@0
|
85 propertyNodesMap.put(type, new LinkedList<PropertyNode>());
|
fiore@0
|
86 }
|
fiore@0
|
87 propertyLabels = new MultiLineString[numInsideProperties];
|
fiore@0
|
88 nameLabel = new MultiLineString();
|
fiore@0
|
89 }
|
fiore@0
|
90
|
fiore@0
|
91 @Override
|
fiore@0
|
92 protected void notifyChange(ElementChangedEvent evt){
|
fiore@4
|
93 if(!evt.getChangeType().equals("translate")&&!evt.getChangeType().equals("stop_move")){ //don't reshape for just moving
|
fiore@4
|
94 Rectangle2D boundsBeforeReshape = getBounds();
|
fiore@4
|
95 reshape();
|
fiore@4
|
96 Rectangle2D boundsAfterReshape = getBounds();
|
fiore@4
|
97 /* after renaming or setting properties the boundaries can change resulting in a slight shift of the *
|
fiore@4
|
98 * node centre from its original position. the next line is to place it back to the right position */
|
fiore@4
|
99 Point2D start = new Point2D.Double(boundsAfterReshape.getCenterX(),boundsAfterReshape.getCenterY());
|
fiore@4
|
100 translateImplementation(start,
|
fiore@5
|
101 boundsBeforeReshape.getCenterX() - boundsAfterReshape.getCenterX(),
|
fiore@5
|
102 boundsBeforeReshape.getCenterY() - boundsAfterReshape.getCenterY());
|
fiore@4
|
103 }
|
fiore@0
|
104 super.notifyChange(evt);
|
fiore@0
|
105 }
|
fiore@0
|
106
|
fiore@0
|
107 @Override
|
fiore@0
|
108 public void setId(long id){
|
fiore@0
|
109 super.setId(id);
|
fiore@0
|
110 /* when they are given an id nodes change name into "new <type> node <id>" *
|
fiore@0
|
111 * where <type> is the actual type of the node and <id> is the given id *
|
fiore@0
|
112 * therefore a reshape is necessary to display the new name */
|
fiore@0
|
113 Rectangle2D boundsBeforeReshape = getBounds();
|
fiore@0
|
114 reshape();
|
fiore@0
|
115 /* the reshape might change the bounds, so the shape is translated so that the top-left *
|
fiore@0
|
116 * point is at the same position as before just to keep it more consistent */
|
fiore@0
|
117 Rectangle2D boundsAfterReshape = getBounds();
|
fiore@0
|
118 translateImplementation(
|
fiore@5
|
119 new Point2D.Double(),
|
fiore@5
|
120 boundsBeforeReshape.getX() - boundsAfterReshape.getX(),
|
fiore@5
|
121 boundsBeforeReshape.getY() - boundsAfterReshape.getY()
|
fiore@4
|
122 );
|
fiore@0
|
123 }
|
fiore@0
|
124
|
fiore@0
|
125 @Override
|
fiore@0
|
126 public boolean contains(Point2D p) {
|
fiore@0
|
127 if (getShape().contains(p))
|
fiore@0
|
128 return true;
|
fiore@0
|
129 for(List<PropertyNode> pnList : propertyNodesMap.values())
|
fiore@0
|
130 for(PropertyNode pn : pnList)
|
fiore@0
|
131 if(pn.contains(p))
|
fiore@0
|
132 return true;
|
fiore@0
|
133 return false;
|
fiore@0
|
134 }
|
fiore@0
|
135
|
fiore@0
|
136 protected void reshape(){
|
fiore@0
|
137 Pair<List<String>, List<String>> splitPropertyTypes = splitPropertyTypes();
|
fiore@0
|
138 /* properties displayed internally */
|
fiore@0
|
139 reshapeInnerProperties(splitPropertyTypes.first);
|
fiore@0
|
140 /* properties displayed externally */
|
fiore@0
|
141 reshapeOuterProperties(splitPropertyTypes.second);
|
fiore@0
|
142 }
|
fiore@0
|
143
|
fiore@0
|
144 protected Pair<List<String>, List<String>> splitPropertyTypes(){
|
fiore@0
|
145 List<String> types = getProperties().getTypes();
|
fiore@0
|
146 ArrayList<String> insidePropertyTypes = new ArrayList<String>(types.size());
|
fiore@0
|
147 ArrayList<String> outsidePropertyTypes = new ArrayList<String>(types.size());
|
fiore@0
|
148 for(String type : types){
|
fiore@0
|
149 if(((PropertyView)getProperties().getView(type)).getPosition() == Position.Inside)
|
fiore@0
|
150 insidePropertyTypes.add(type);
|
fiore@0
|
151 else
|
fiore@0
|
152 outsidePropertyTypes.add(type);
|
fiore@0
|
153 }
|
fiore@0
|
154
|
fiore@0
|
155 return new Pair<List<String>, List<String>> (insidePropertyTypes,outsidePropertyTypes);
|
fiore@0
|
156 }
|
fiore@0
|
157
|
fiore@0
|
158 protected void reshapeOuterProperties(List<String> outsidePropertyTypes){
|
fiore@0
|
159 for(String type : outsidePropertyTypes){
|
fiore@0
|
160 List<PropertyNode> propertyNodes = propertyNodesMap.get(type);
|
fiore@0
|
161 List<String> propertyValues = getProperties().getValues(type);
|
fiore@0
|
162 int diff = propertyNodes.size()-propertyValues.size();
|
fiore@0
|
163 if(diff > 0) // properties have been removed
|
fiore@0
|
164 for(int i=0; i < diff; i++)
|
fiore@0
|
165 propertyNodes.remove(propertyNodes.size() - 1);
|
fiore@0
|
166 else if(diff < 0){ // properties have been added. We need more properties node.
|
fiore@0
|
167 for(int i=0; i < -diff; i++){
|
fiore@0
|
168 PropertyNode propertyNode = new PropertyNode(((PropertyView)getProperties().getView(type)).getShapeType());
|
fiore@0
|
169 Rectangle2D bounds = getBounds();
|
fiore@0
|
170 double x = bounds.getCenterX() - bounds.getWidth()/2 - PROP_NODE_DIST;
|
fiore@0
|
171 double y = bounds.getCenterX() - PROP_NODE_DIST * i;
|
fiore@0
|
172 propertyNode.translate(x, y);
|
fiore@0
|
173 propertyNodes.add(propertyNode);
|
fiore@0
|
174 }
|
fiore@0
|
175 }
|
fiore@0
|
176 /* set the text on all the property nodes */
|
fiore@0
|
177 int i = 0;
|
fiore@0
|
178 for(String text : propertyValues){
|
fiore@0
|
179 NodeProperties.Modifiers modifiers = getProperties().getModifiers(type);
|
fiore@0
|
180 Set<Integer> viewIndexes = modifiers.getIndexes(i);
|
fiore@0
|
181 ModifierView[] views = new ModifierView[viewIndexes.size()];
|
fiore@0
|
182 int j =0;
|
fiore@0
|
183 for(Integer I : viewIndexes){
|
fiore@0
|
184 views[j] = (ModifierView) getProperties().getModifiers(type).getView(modifiers.getTypes().get(I));
|
fiore@0
|
185 j++;
|
fiore@0
|
186 }
|
fiore@0
|
187 propertyNodes.get(i).setText(text,views);
|
fiore@0
|
188 i++;
|
fiore@0
|
189 }
|
fiore@0
|
190 }
|
fiore@0
|
191 }
|
fiore@0
|
192
|
fiore@0
|
193 protected void reshapeInnerProperties(List<String> insidePropertyTypes){
|
fiore@0
|
194 /* set the bounds for each multiline string and the resulting bound of the node */
|
fiore@0
|
195 nameLabel = new MultiLineString();
|
fiore@0
|
196 nameLabel.setText(getName().isEmpty() ? " " : getName());
|
fiore@0
|
197 nameLabel.setBold(true);
|
fiore@0
|
198 Rectangle2D r = nameLabel.getBounds();
|
fiore@0
|
199
|
fiore@0
|
200 for(int i=0; i<insidePropertyTypes.size();i++){
|
fiore@0
|
201 propertyLabels[i] = new MultiLineString();
|
fiore@0
|
202 String propertyType = insidePropertyTypes.get(i);
|
fiore@0
|
203 if(getProperties().getValues(propertyType).size() == 0){
|
fiore@0
|
204 propertyLabels[i].setText(" ");
|
fiore@0
|
205 }else{
|
fiore@0
|
206 propertyLabels[i].setJustification(MultiLineString.LEFT);
|
fiore@0
|
207 String[] array = new String[getProperties().getValues(propertyType).size()];
|
fiore@0
|
208 propertyLabels[i].setText(getProperties().getValues(propertyType).toArray(array), getProperties().getModifiers(propertyType));
|
fiore@0
|
209 }
|
fiore@0
|
210 r.add(new Rectangle2D.Double(r.getX(),r.getMaxY(),propertyLabels[i].getBounds().getWidth(),propertyLabels[i].getBounds().getHeight()));
|
fiore@0
|
211 }
|
fiore@0
|
212 /* set a gap to uniformly distribute the extra space among property rectangles to reach the minimum bound's height */
|
fiore@0
|
213 boundsGap = 0;
|
fiore@0
|
214 Rectangle2D.Double minBounds = (Rectangle2D.Double)getMinBounds();
|
fiore@0
|
215 if(r.getHeight() < minBounds.height){
|
fiore@0
|
216 boundsGap = minBounds.height - r.getHeight();
|
fiore@0
|
217 boundsGap /= insidePropertyTypes.size();
|
fiore@0
|
218 }
|
fiore@0
|
219 r.add(minBounds); //make sure it's at least as big as the minimum bounds
|
fiore@0
|
220 dataDisplayBounds.setFrame(new Rectangle2D.Double(dataDisplayBounds.x,dataDisplayBounds.y,r.getWidth(),r.getHeight()));
|
fiore@0
|
221 }
|
fiore@0
|
222
|
fiore@0
|
223 /**
|
fiore@0
|
224 Draw the node from the Shape with shadow
|
fiore@0
|
225 @param g2 the graphics context
|
fiore@0
|
226 */
|
fiore@0
|
227 @Override
|
fiore@0
|
228 public void draw(Graphics2D g2){
|
fiore@0
|
229 /* draw the external shape */
|
fiore@0
|
230 Shape shape = getShape();
|
fiore@0
|
231 if (shape == null) return;
|
fiore@0
|
232 Color oldColor = g2.getColor();
|
fiore@0
|
233 g2.translate(SHADOW_GAP, SHADOW_GAP);
|
fiore@0
|
234 g2.setColor(SHADOW_COLOR);
|
fiore@0
|
235 g2.fill(shape);
|
fiore@0
|
236 g2.translate(-SHADOW_GAP, -SHADOW_GAP);
|
fiore@0
|
237 g2.setColor(g2.getBackground());
|
fiore@0
|
238 g2.fill(shape);
|
fiore@0
|
239 g2.setColor(Color.BLACK);
|
fiore@0
|
240 g2.draw(shape);
|
fiore@0
|
241 g2.setColor(oldColor);
|
fiore@0
|
242
|
fiore@0
|
243 /* if there ain't any property to display inside, then display the name in the middle of the data Display bounds */
|
fiore@0
|
244 if(!anyInsideProperties()){
|
fiore@0
|
245 nameLabel.draw(g2, dataDisplayBounds);
|
fiore@0
|
246 }else{
|
fiore@0
|
247 /* draw name */
|
fiore@0
|
248 Rectangle2D currentBounds = new Rectangle2D.Double(
|
fiore@0
|
249 dataDisplayBounds.x,
|
fiore@0
|
250 dataDisplayBounds.y,
|
fiore@0
|
251 dataDisplayBounds.getWidth(),
|
fiore@0
|
252 nameLabel.getBounds().getHeight());
|
fiore@0
|
253 if(drawPropertySeparators){
|
fiore@0
|
254 Shape oldClip = g2.getClip();
|
fiore@0
|
255 g2.setClip(getShape());
|
fiore@0
|
256 g2.draw(new Rectangle2D.Double(
|
fiore@0
|
257 getBounds().getX(),
|
fiore@0
|
258 dataDisplayBounds.y,
|
fiore@0
|
259 getBounds().getWidth(),
|
fiore@0
|
260 nameLabel.getBounds().getHeight())
|
fiore@0
|
261 );
|
fiore@0
|
262 g2.setClip(oldClip);
|
fiore@0
|
263 }
|
fiore@0
|
264 nameLabel.draw(g2, currentBounds);
|
fiore@0
|
265
|
fiore@0
|
266 /* draw internal properties */
|
fiore@0
|
267 Rectangle2D previousBounds;
|
fiore@0
|
268 for(int i=0;i<propertyLabels.length;i++){
|
fiore@0
|
269 previousBounds = currentBounds;
|
fiore@0
|
270 currentBounds = new Rectangle2D.Double(
|
fiore@0
|
271 previousBounds.getX(),
|
fiore@0
|
272 previousBounds.getMaxY(),
|
fiore@0
|
273 dataDisplayBounds.getWidth(),
|
fiore@0
|
274 propertyLabels[i].getBounds().getHeight()+boundsGap);
|
fiore@0
|
275 if(drawPropertySeparators){
|
fiore@0
|
276 Shape oldClip = g2.getClip();
|
fiore@0
|
277 g2.setClip(getShape());
|
fiore@0
|
278 g2.draw(new Rectangle2D.Double(
|
fiore@0
|
279 getBounds().getX(),
|
fiore@0
|
280 currentBounds.getY(),
|
fiore@0
|
281 getBounds().getWidth(),
|
fiore@0
|
282 currentBounds.getHeight())
|
fiore@0
|
283 );
|
fiore@0
|
284 g2.setClip(oldClip);
|
fiore@0
|
285 }
|
fiore@0
|
286 propertyLabels[i].draw(g2, currentBounds);
|
fiore@0
|
287 }
|
fiore@0
|
288 }
|
fiore@0
|
289
|
fiore@0
|
290 /* draw external properties */
|
fiore@0
|
291 for(List<PropertyNode> pnList : propertyNodesMap.values())
|
fiore@0
|
292 for(PropertyNode pn : pnList){
|
fiore@0
|
293 pn.draw(g2);
|
fiore@0
|
294 Direction d = new Direction( getBounds().getCenterX() - pn.getCenter().getX(), getBounds().getCenterY() - pn.getCenter().getY());
|
fiore@0
|
295 g2.draw(new Line2D.Double(pn.getConnectionPoint(d), getConnectionPoint(d.turn(180))));
|
fiore@0
|
296 }
|
fiore@0
|
297 /* draw visual cue for bookmarks and notes */
|
fiore@0
|
298 super.draw(g2);
|
fiore@0
|
299 }
|
fiore@0
|
300
|
fiore@0
|
301 protected Rectangle2D getMinBounds(){
|
fiore@0
|
302 return (Rectangle2D)minBounds.clone();
|
fiore@0
|
303 }
|
fiore@0
|
304
|
fiore@0
|
305 public abstract ShapeType getShapeType();
|
fiore@0
|
306
|
fiore@0
|
307 @Override
|
fiore@0
|
308 public void encode(Document doc, Element parent){
|
fiore@0
|
309 super.encode(doc, parent);
|
fiore@0
|
310 if(getProperties().isEmpty())
|
fiore@0
|
311 return;
|
fiore@0
|
312 NodeList propTagList = doc.getElementsByTagName(PersistenceManager.PROPERTY);
|
fiore@0
|
313
|
fiore@0
|
314 /* scan all the PROPERTY tags to add the position tag */
|
fiore@0
|
315 for(int i = 0 ; i< propTagList.getLength(); i++){
|
fiore@0
|
316 Element propertyTag = (Element)propTagList.item(i);
|
fiore@0
|
317 Element typeTag = (Element)propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0);
|
fiore@0
|
318 String type = typeTag.getTextContent();
|
fiore@0
|
319
|
fiore@0
|
320 /* a property of another node, continue */
|
fiore@0
|
321 if(!getProperties().getTypes().contains(type))
|
fiore@0
|
322 continue;
|
fiore@0
|
323
|
fiore@0
|
324 if(((PropertyView)getProperties().getView(type)).getPosition() == SimpleShapeNode.Position.Inside)
|
fiore@0
|
325 continue;
|
fiore@0
|
326
|
fiore@0
|
327 List<String> values = getProperties().getValues(type);
|
fiore@0
|
328 if(values.isEmpty())
|
fiore@0
|
329 continue;
|
fiore@0
|
330
|
fiore@0
|
331 NodeList elementTagList = propertyTag.getElementsByTagName(PersistenceManager.ELEMENT);
|
fiore@0
|
332 List<PropertyNode> pnList = propertyNodesMap.get(type);
|
fiore@0
|
333 for(int j=0; j<elementTagList.getLength();j++){
|
fiore@0
|
334 Element elementTag = (Element)elementTagList.item(j);
|
fiore@0
|
335 Element positionTag = doc.createElement(SimpleShapePrototypePersistenceDelegate.POSITION);
|
fiore@0
|
336 positionTag.setAttribute(PersistenceManager.X, String.valueOf(pnList.get(j).getX()));
|
fiore@0
|
337 positionTag.setAttribute(PersistenceManager.Y, String.valueOf(pnList.get(j).getY()));
|
fiore@0
|
338 elementTag.appendChild(positionTag);
|
fiore@0
|
339 }
|
fiore@0
|
340 }
|
fiore@0
|
341 }
|
fiore@0
|
342
|
fiore@0
|
343 @Override
|
fiore@0
|
344 public void decode(Document doc, Element nodeTag) throws IOException{
|
fiore@0
|
345 super.decode(doc, nodeTag);
|
fiore@0
|
346
|
fiore@0
|
347 NodeList propTagList = nodeTag.getElementsByTagName(PersistenceManager.PROPERTY);
|
fiore@0
|
348
|
fiore@0
|
349 /* split the property types into internal and external, properties have been set by super.decodeXMLInstance */
|
fiore@0
|
350 ArrayList<String> insidePropertyTypes = new ArrayList<String>(getProperties().getTypes().size());
|
fiore@0
|
351 ArrayList<String> outsidePropertyTypes = new ArrayList<String>(getProperties().getTypes().size());
|
fiore@0
|
352 for(String type : getProperties().getTypes()){
|
fiore@0
|
353 if(((PropertyView)getProperties().getView(type)).getPosition() == SimpleShapeNode.Position.Inside)
|
fiore@0
|
354 insidePropertyTypes.add(type);
|
fiore@0
|
355 else
|
fiore@0
|
356 outsidePropertyTypes.add(type);
|
fiore@0
|
357 }
|
fiore@0
|
358
|
fiore@0
|
359 /* set the multi-line string bounds for the properties which are displayed internally */
|
fiore@0
|
360 reshapeInnerProperties(insidePropertyTypes);
|
fiore@0
|
361
|
fiore@0
|
362 /* scan all the PROPERTY tags to decode the position tag of the properties which are displayed externally */
|
fiore@0
|
363 for(int i = 0 ; i< propTagList.getLength(); i++){
|
fiore@0
|
364 Element propertyTag = (Element)propTagList.item(i);
|
fiore@0
|
365 if(propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0) == null)
|
fiore@0
|
366 throw new IOException();
|
fiore@0
|
367 Element typeTag = (Element)propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0);
|
fiore@0
|
368
|
fiore@0
|
369 String type = typeTag.getTextContent();
|
fiore@0
|
370 /* (check on whether type exists in the node type definition is done in super.decode */
|
fiore@0
|
371
|
fiore@0
|
372 if(((PropertyView)getProperties().getView(type)).getPosition() == SimpleShapeNode.Position.Inside)
|
fiore@0
|
373 continue;
|
fiore@0
|
374 /* this will create external nodes and assign them their position */
|
fiore@0
|
375 NodeList elementTagList = propertyTag.getElementsByTagName(PersistenceManager.ELEMENT);
|
fiore@0
|
376 List<PropertyNode> pnList = new LinkedList<PropertyNode>();
|
fiore@0
|
377 for(int j=0; j<elementTagList.getLength();j++){
|
fiore@0
|
378 Element elementTag = (Element)elementTagList.item(j);
|
fiore@0
|
379 if(elementTag.getElementsByTagName(SimpleShapePrototypePersistenceDelegate.POSITION).item(0) == null ||
|
fiore@0
|
380 elementTag.getElementsByTagName(PersistenceManager.VALUE).item(0) == null)
|
fiore@0
|
381 throw new IOException();
|
fiore@0
|
382 Element positionTag = (Element)elementTag.getElementsByTagName(SimpleShapePrototypePersistenceDelegate.POSITION).item(0);
|
fiore@0
|
383 Element valueTag = (Element)elementTag.getElementsByTagName(PersistenceManager.VALUE).item(0);
|
fiore@0
|
384 double dx,dy;
|
fiore@0
|
385 try{
|
fiore@0
|
386 dx = Double.parseDouble(positionTag.getAttribute(PersistenceManager.X));
|
fiore@0
|
387 dy = Double.parseDouble(positionTag.getAttribute(PersistenceManager.Y));
|
fiore@0
|
388 }catch(NumberFormatException nfe){
|
fiore@0
|
389 throw new IOException();
|
fiore@0
|
390 }
|
fiore@0
|
391 PropertyNode pn = new PropertyNode(((PropertyView)getProperties().getView(type)).getShapeType());
|
fiore@0
|
392 pn.translate(dx, dy);
|
fiore@0
|
393 pn.setText(valueTag.getTextContent(),null);
|
fiore@0
|
394 pnList.add(pn);
|
fiore@0
|
395 }
|
fiore@0
|
396 propertyNodesMap.put(type, pnList);
|
fiore@0
|
397 /* this will apply the modifier format to the properties */
|
fiore@0
|
398 reshapeOuterProperties(outsidePropertyTypes);
|
fiore@0
|
399 }
|
fiore@0
|
400 }
|
fiore@0
|
401
|
fiore@0
|
402 @Override
|
fiore@0
|
403 protected void translateImplementation(Point2D p, double dx, double dy){
|
fiore@0
|
404 dataDisplayBounds.setFrame(dataDisplayBounds.getX() + dx,
|
fiore@0
|
405 dataDisplayBounds.getY() + dy,
|
fiore@0
|
406 dataDisplayBounds.getWidth(),
|
fiore@0
|
407 dataDisplayBounds.getHeight());
|
fiore@0
|
408 /* translate all the external property nodes */
|
fiore@0
|
409 for(List<PropertyNode> pnList : propertyNodesMap.values())
|
fiore@0
|
410 for(PropertyNode pn : pnList)
|
fiore@0
|
411 pn.translate(dx, dy);
|
fiore@0
|
412 }
|
fiore@0
|
413
|
fiore@0
|
414 @Override
|
fiore@0
|
415 public Object clone(){
|
fiore@0
|
416 SimpleShapeNode n = (SimpleShapeNode)super.clone();
|
fiore@0
|
417 n.propertyLabels = new MultiLineString[propertyLabels.length];
|
fiore@0
|
418 n.nameLabel = new MultiLineString();
|
fiore@0
|
419 n.propertyNodesMap = new LinkedHashMap<String, List<PropertyNode>>();
|
fiore@0
|
420 for(String s : propertyNodesMap.keySet())
|
fiore@0
|
421 n.propertyNodesMap.put(s, new LinkedList<PropertyNode>());
|
fiore@0
|
422 n.dataDisplayBounds = (Rectangle2D.Double)dataDisplayBounds.clone();
|
fiore@0
|
423 return n;
|
fiore@0
|
424 }
|
fiore@0
|
425
|
fiore@0
|
426 protected boolean anyInsideProperties(){
|
fiore@0
|
427 boolean propInside = false;
|
fiore@0
|
428 for(String type : getProperties().getTypes()){
|
fiore@0
|
429 if(((PropertyView)getProperties().getView(type)).getPosition() == Position.Inside){
|
fiore@0
|
430 if(!getProperties().getValues(type).isEmpty()){
|
fiore@0
|
431 propInside = true;
|
fiore@0
|
432 break;
|
fiore@0
|
433 }
|
fiore@0
|
434 }
|
fiore@0
|
435 }
|
fiore@0
|
436 return propInside;
|
fiore@0
|
437 }
|
fiore@0
|
438
|
fiore@0
|
439 protected Rectangle2D.Double dataDisplayBounds;
|
fiore@0
|
440 protected double boundsGap;
|
fiore@0
|
441 protected boolean drawPropertySeparators = true;
|
fiore@0
|
442 protected MultiLineString[] propertyLabels;
|
fiore@0
|
443 protected MultiLineString nameLabel;
|
fiore@0
|
444 protected Map<String,List<PropertyNode>> propertyNodesMap;
|
fiore@0
|
445
|
fiore@0
|
446 public static enum ShapeType {Circle, Ellipse, Rectangle, Square, Triangle, Transparent};
|
fiore@0
|
447 public static enum Position {Inside, Outside};
|
fiore@0
|
448
|
fiore@0
|
449 private static final int DEFAULT_WIDTH = 100;
|
fiore@0
|
450 private static final int DEFAULT_HEIGHT = 60;
|
fiore@0
|
451 private static final Rectangle2D.Double minBounds = new Rectangle2D.Double(0,0,DEFAULT_WIDTH,DEFAULT_HEIGHT);
|
fiore@0
|
452 private static final int PROP_NODE_DIST = 50;
|
fiore@0
|
453
|
fiore@6
|
454 /**
|
fiore@6
|
455 * When properties are configure to appear outside, the values are represented as small nodes
|
fiore@6
|
456 * connected (with a straight line) to the node they belong to. This class represents such
|
fiore@6
|
457 * small nodes. The possible shapes are: triangle, rectangle, square, circle, ellipse and no shape in
|
fiore@6
|
458 * which case only the string with the property value and the line connecting it to the node is shown.
|
fiore@6
|
459 *
|
fiore@6
|
460 */
|
fiore@0
|
461 protected static class PropertyNode{
|
fiore@0
|
462 public PropertyNode(ShapeType aShape){
|
fiore@0
|
463 /* add a little padding in the shape holding the label */
|
fiore@0
|
464 label = new MultiLineString(){
|
fiore@0
|
465 public Rectangle2D getBounds(){
|
fiore@0
|
466 Rectangle2D bounds = super.getBounds();
|
fiore@0
|
467 if(bounds.getWidth() != 0 || bounds.getHeight() != 0){
|
fiore@0
|
468 bounds.setFrame(
|
fiore@0
|
469 bounds.getX(),
|
fiore@0
|
470 bounds.getY(),
|
fiore@0
|
471 bounds.getWidth() + PADDING,
|
fiore@0
|
472 bounds.getHeight() + PADDING);
|
fiore@0
|
473 }
|
fiore@0
|
474 return bounds;
|
fiore@0
|
475 }
|
fiore@0
|
476 };
|
fiore@0
|
477 label.setJustification(MultiLineString.CENTER);
|
fiore@0
|
478 shapeType = aShape;
|
fiore@0
|
479 shape = label.getBounds();
|
fiore@0
|
480 }
|
fiore@0
|
481
|
fiore@0
|
482 public void setText(String text, ModifierView[] views){
|
fiore@0
|
483 label.setText(text,views);
|
fiore@0
|
484
|
fiore@0
|
485 switch(shapeType){
|
fiore@0
|
486 case Circle :
|
fiore@0
|
487 Rectangle2D circleBounds = EllipticalNode.getOutBounds(label.getBounds());
|
fiore@0
|
488 shape = new Ellipse2D.Double(
|
fiore@0
|
489 circleBounds.getX(),
|
fiore@0
|
490 circleBounds.getY(),
|
fiore@0
|
491 Math.max(circleBounds.getWidth(),circleBounds.getHeight()),
|
fiore@0
|
492 Math.max(circleBounds.getWidth(),circleBounds.getHeight())
|
fiore@0
|
493 );
|
fiore@0
|
494 break;
|
fiore@0
|
495 case Ellipse :
|
fiore@0
|
496 Rectangle2D ellipseBounds = EllipticalNode.getOutBounds(label.getBounds());
|
fiore@0
|
497 shape = new Ellipse2D.Double(
|
fiore@0
|
498 ellipseBounds.getX(),
|
fiore@0
|
499 ellipseBounds.getY(),
|
fiore@0
|
500 ellipseBounds.getWidth(),
|
fiore@0
|
501 ellipseBounds.getHeight()
|
fiore@0
|
502 );
|
fiore@0
|
503 break;
|
fiore@0
|
504 case Triangle :
|
fiore@0
|
505 shape = TriangularNode.getOutShape(label.getBounds());
|
fiore@0
|
506 break;
|
fiore@0
|
507 default : // Rectangle, Square and Transparent
|
fiore@0
|
508 shape = label.getBounds();;
|
fiore@0
|
509 break;
|
fiore@0
|
510 }
|
fiore@0
|
511
|
fiore@0
|
512 /* a new shape, placed at (0,0) has been created as a result of set text, therefore *
|
fiore@0
|
513 * we must put it back where the old shape was, since the translation is performed *
|
fiore@0
|
514 * by adding the translate args to x and y, x and y must first be set to 0 */
|
fiore@0
|
515 double currentX = x;
|
fiore@0
|
516 double currentY = y;
|
fiore@0
|
517 x = 0;
|
fiore@0
|
518 y = 0;
|
fiore@0
|
519 translate(currentX,currentY);
|
fiore@0
|
520 }
|
fiore@0
|
521
|
fiore@0
|
522 public void draw(Graphics2D g){
|
fiore@0
|
523 Color oldColor = g.getColor();
|
fiore@0
|
524 if(shapeType != ShapeType.Transparent){
|
fiore@0
|
525 g.translate(SHADOW_GAP, SHADOW_GAP);
|
fiore@0
|
526 g.setColor(SHADOW_COLOR);
|
fiore@0
|
527 g.fill(shape);
|
fiore@0
|
528 g.translate(-SHADOW_GAP, -SHADOW_GAP);
|
fiore@0
|
529
|
fiore@0
|
530 g.setColor(g.getBackground());
|
fiore@0
|
531 g.fill(shape);
|
fiore@0
|
532 g.setColor(Color.BLACK);
|
fiore@0
|
533 g.draw(shape);
|
fiore@0
|
534 }
|
fiore@0
|
535
|
fiore@0
|
536 label.draw(g, shape.getBounds2D());
|
fiore@0
|
537 g.setColor(oldColor);
|
fiore@0
|
538 }
|
fiore@0
|
539
|
fiore@0
|
540 public void translate(double dx, double dy){
|
fiore@0
|
541 x += dx;
|
fiore@0
|
542 y += dy;
|
fiore@0
|
543
|
fiore@0
|
544 if(shape instanceof Path2D){ //it's a triangle
|
fiore@0
|
545 Rectangle2D labelBounds = label.getBounds();
|
fiore@0
|
546 labelBounds.setFrame(
|
fiore@0
|
547 x,
|
fiore@0
|
548 y,
|
fiore@0
|
549 labelBounds.getWidth(),
|
fiore@0
|
550 labelBounds.getHeight()
|
fiore@0
|
551 );
|
fiore@0
|
552 shape = TriangularNode.getOutShape(labelBounds);
|
fiore@0
|
553 }else{
|
fiore@0
|
554 Rectangle2D bounds = shape.getBounds2D();
|
fiore@0
|
555 ((RectangularShape)shape).setFrame(
|
fiore@0
|
556 x,
|
fiore@0
|
557 y,
|
fiore@0
|
558 bounds.getWidth(),
|
fiore@0
|
559 bounds.getHeight()
|
fiore@0
|
560 );
|
fiore@0
|
561 }
|
fiore@0
|
562 }
|
fiore@0
|
563
|
fiore@0
|
564 public Point2D getConnectionPoint(Direction d) {
|
fiore@0
|
565 switch(shapeType){
|
fiore@0
|
566 case Circle :
|
fiore@0
|
567 case Ellipse :
|
fiore@0
|
568 return EllipticalNode.calculateConnectionPoint(d, shape.getBounds2D());
|
fiore@0
|
569 case Triangle :
|
fiore@0
|
570 return TriangularNode.calculateConnectionPoint(d, shape.getBounds2D());
|
fiore@0
|
571 default :
|
fiore@0
|
572 return RectangularNode.calculateConnectionPoint(d, shape.getBounds2D());
|
fiore@0
|
573 }
|
fiore@0
|
574 }
|
fiore@0
|
575
|
fiore@0
|
576 public boolean contains(Point2D p){
|
fiore@0
|
577 return shape.contains(p);
|
fiore@0
|
578 }
|
fiore@0
|
579
|
fiore@0
|
580 public Point2D getCenter(){
|
fiore@0
|
581 Rectangle2D bounds = shape.getBounds2D() ;
|
fiore@0
|
582 return new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
|
fiore@0
|
583 }
|
fiore@0
|
584
|
fiore@0
|
585 double getX(){
|
fiore@0
|
586 return x;
|
fiore@0
|
587 }
|
fiore@0
|
588
|
fiore@0
|
589 double getY(){
|
fiore@0
|
590 return y;
|
fiore@0
|
591 }
|
fiore@0
|
592
|
fiore@0
|
593 private static final int PADDING = 5;
|
fiore@0
|
594 private MultiLineString label;
|
fiore@0
|
595 private ShapeType shapeType;
|
fiore@0
|
596 private Shape shape;
|
fiore@0
|
597 private double x;
|
fiore@0
|
598 private double y;
|
fiore@0
|
599 }
|
fiore@0
|
600 }
|