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