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