Source code: com/xerox/VTM/glyphs/Glyph.java
1 /* FILE: Glyph.java
2 * DATE OF CREATION: Jul 11 2000
3 * AUTHOR : Emmanuel Pietriga (emmanuel.pietriga@xrce.xerox.com)
4 * MODIF: Wed Aug 06 14:22:52 2003 by Emmanuel Pietriga (emmanuel@w3.org, emmanuel@claribole.net)
5 * Copyright (c) Xerox Corporation, XRCE/Contextual Computing, and Emmanuel Pietriga, 2002. All Rights Reserved
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * For full terms see the file COPYING.
18 */
19
20 package com.xerox.VTM.glyphs;
21
22 import com.xerox.VTM.engine.*;
23 import java.awt.Graphics2D;
24 import java.awt.Color;
25 import java.awt.Polygon;
26 import java.util.Vector;
27 import java.awt.geom.AffineTransform;
28 import java.awt.Stroke;
29 import java.awt.BasicStroke;
30 import java.awt.Font;
31 import java.awt.geom.Rectangle2D;
32 import java.util.Enumeration;
33 import net.claribole.zvtm.glyphs.*;
34
35 /**glyph - parent class of all graphical objects
36 * @author Emmanuel Pietriga
37 */
38
39 public abstract class Glyph implements Cloneable {
40
41 /**Glyph ID*/
42 Long ID;
43 /**coordinate in virtual space (geometric center of object)*/
44 public long vx,vy;
45 /**altitude (in virtual space)*/
46 public float vz;
47 /**radius of bounding circle*/
48 float size;
49 /**object orientation [0:2Pi[ */
50 float orient=0.0f;
51 /**tells whether this glyph is selected or not (default is false)*/
52 boolean selected=false;
53 /**tells whether this glyph is visible or not (default is true)*/
54 boolean visible=true;
55 /**tells whether we should detect entry/exit in this glyph*/
56 boolean sensit=true;
57 /**set sensitivity of this glyph*/
58 public void setSensitivity(boolean b){
59 sensit=b;
60 }
61 /**tells whether mouse sends events related to entry/exit in this glyph or not*/
62 public boolean isSensitive(){return sensit;}
63
64 /**text that should be drawn with glyph*/
65 String text;
66 /**1=always draw text 0=draw text when approx fits inside object -1=never draw text 2=as VText*/
67 int fontSizePolicy=1;
68 /**tells if string should be painted inside glyph (=0), on top of glyph (=1), or under glyph (=-1)*/
69 int textPos=0;
70 /**width of text in pixels (only available in textDraw())*/
71 int textWidth;
72 /**height of text in pixels (only available in textDraw())*/
73 int textHeight;
74 /**constant: default font used in the VTM (must be the same as the one in View)*/
75 // public static Font defaultFont=new Font("Dialog",0,10);
76 /**font size in pixels*/
77 public static float fontSize=VirtualSpaceManager.getMainFont().getSize2D();
78 /**special font used in this object only (null if default font)*/
79 Font font;
80
81 /**ref to the object this glyph represents*/
82 Object owner;
83 /**type of object (can be any string)*/
84 String type="";
85
86 /**composite glyph associated with this glyph (meaning that this glyph is either a primary or secondary glyph inside a CGlyph)*/
87 CGlyph cGlyph=null;
88 /**set the composite glyph associated with this glyph (meaning that this glyph is either a primary or secondary glyph inside a CGlyph) - do not call this method manually ; called autolatically when adding the glyph in the cglyph*/
89 public void setCGlyph(CGlyph c){cGlyph=c;}
90 /**returns the composite glyph associated with this glyph (meaning that this glyph is either a primary or secondary glyph inside a CGlyph) - returns null if none*/
91 public CGlyph getCGlyph(){return cGlyph;}
92
93
94 Vector dependants;
95 /**add a glyph to the list of glyphs depending on this one - put glyphs that depend on this one here (for instance segments to hide when hiding this object...). What should be done with dependants depends on the application (the programmer has to specify what to do with dependants when something happens to this glyph manually - we just store them here) - this list has to be maintained manually (if a glyph is destroyed, it is not removed from dependant lists in which it might appear)*/
96 public void addDependant(Glyph g){
97 if (dependants==null){dependants=new Vector();}
98 dependants.add(g);
99 }
100 /**remove a glyph frmo the list of glyphs depending on this one (nothing happens if g is not in the list)*/
101 public void removeDependant(Glyph g){
102 if (dependants!=null){dependants.remove(g);}
103 if (dependants.isEmpty()){dependants=null;}
104 }
105 /**get list of glyphs depending on this one*/
106 public Vector getDependants(){
107 return dependants;
108 }
109
110 /**projection coef*/
111 public float coef=1.0f;
112
113 /**ref to VSM*/
114 public VirtualSpaceManager vsm;
115
116 /**color of interior*/
117 public Color color;
118 /**current color of border*/
119 public Color borderColor;
120 /**color of border when glyph is selected (can be different fom color when cursor is inside glyph)*/
121 public Color selectedColor;
122 /**color of border when cursor is inside glyph*/
123 public Color mouseInsideColor;
124 /**standard color of border*/
125 public Color bColor=Color.black;
126
127 /**HSV coordinates of interior color in range 0.0-1.0*/
128 protected float[] HSV=new float[3];
129 /**HSV coordinates of border color*/
130 protected float[] HSVb=new float[3];
131
132 //Stroke originalStroke; //used to store original graphics context's stroke
133 /**first is width of line, last is offset*/
134 public static float DEFAULT_STROKE_WIDTH=1.0f;
135 BasicStroke stroke=null;
136 boolean dashedContour=false;
137 float strokeWidth=DEFAULT_STROKE_WIDTH;
138 boolean paintBorder=true;
139
140 /**
141 *@param b true -> draw a discontinuous contour for this glyph
142 */
143 public void setDashed(boolean b){
144 dashedContour=b;
145 strokeWidth=(stroke!=null) ? stroke.getLineWidth() : DEFAULT_STROKE_WIDTH;
146 int cap=(stroke!=null) ? stroke.getEndCap() : BasicStroke.CAP_BUTT;
147 int join=(stroke!=null) ? stroke.getLineJoin() : BasicStroke.JOIN_MITER;
148 float miterlimit=(stroke!=null) ? stroke.getMiterLimit() : 4.0f;
149 if (dashedContour){
150 float[] dasharray={10.0f};
151 float dashphase=(stroke!=null) ? stroke.getDashPhase() : 0.0f;
152 stroke=new BasicStroke(strokeWidth,cap,join,miterlimit,dasharray,dashphase);
153 }
154 else {
155 stroke=new BasicStroke(strokeWidth,cap,join,miterlimit);
156 }
157 try{vsm.repaintNow();}catch(NullPointerException e){}
158 }
159
160 /**
161 *@param w stroke width - does not change the dashed property
162 */
163 public void setStrokeWidth(float w){
164 strokeWidth=w;
165 int cap=(stroke!=null) ? stroke.getEndCap() : BasicStroke.CAP_BUTT;
166 int join=(stroke!=null) ? stroke.getLineJoin() : BasicStroke.JOIN_MITER;
167 float miterlimit=(stroke!=null) ? stroke.getMiterLimit() : 4.0f;
168 if (dashedContour){
169 float[] dasharray={10.0f};
170 float dashphase=(stroke!=null) ? stroke.getDashPhase() : 0.0f;
171 stroke=new BasicStroke(strokeWidth,cap,join,miterlimit,dasharray,dashphase);
172 }
173 else {
174 stroke=new BasicStroke(strokeWidth,cap,join,miterlimit);
175 }
176 try{vsm.repaintNow();}catch(NullPointerException e){}
177 }
178
179 /**
180 *@param s basic stroke - has to be built by user - if null, get back to standard stroke
181 */
182 public void setStroke(BasicStroke b){
183 if (b!=null){stroke=b;strokeWidth=stroke.getLineWidth();}
184 else {stroke=null;strokeWidth=1.0f;}
185 try{vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
186 }
187
188 /**
189 *returns the stroke used to paint the border of this glyph (null if none)
190 */
191 public BasicStroke getStroke(){
192 return stroke;
193 }
194
195 /**
196 *returns the stroke width used to paint the border of this glyph (default is 1.0)
197 */
198 public float getStrokeWidth(){
199 if (stroke!=null){return stroke.getLineWidth();}
200 else return strokeWidth;
201 }
202
203 /**
204 *@param b draw border with border color (default is true)
205 */
206 public void setPaintBorder(boolean b){
207 if (b!=paintBorder){
208 paintBorder=b;
209 try{vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
210 }
211 }
212
213 /**tells whether a glyph's border is painted or not*/
214 public boolean getPaintBorderStatus(){return paintBorder;}
215
216 /**set border color when glyph is selected
217 *@param c color used for selection
218 */
219 public void setSelectedColor(Color c){
220 this.selectedColor=c;
221 }
222
223 /**set border color when cursor is inside glyph
224 *@param c color used for selction
225 */
226 public void setMouseInsideColor(Color c){
227 this.mouseInsideColor=c;
228 }
229
230 /**select this glyph
231 *@param b true to select glyph, false to unselect it
232 */
233 public void select(boolean b){
234 selected=b;
235 if (b){if (selectedColor!=null){borderColor=selectedColor;}else{borderColor=color;}}
236 else{borderColor=bColor;}
237 try{vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
238 }
239
240 /**get this glyph's selection state (returns true if selected)*/
241 public boolean isSelected(){
242 return selected;
243 }
244
245 /**make this glyph (in)visible
246 *@param b true to make glyph visible, false to make it invisible
247 */
248 public void setVisible(boolean b){
249 if (b!=visible){
250 visible=b;
251 try{vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
252 }
253 }
254
255 /**get this glyph's visibility state (returns true if visible)*/
256 public boolean isVisible(){
257 return visible;
258 }
259
260 boolean filled=true;
261 /**
262 *@param b false -> do not paint interior of glyph (only paint contour)
263 */
264 public void setFill(boolean b){
265 if (b!=filled){
266 filled=b;
267 try{vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
268 }
269 }
270
271 /**tells whether this glyph is filled or not*/
272 public boolean getFillStatus(){return filled;}
273
274 /**glyphs sticked to this one*/
275 Vector stickedGlyphs;
276
277 /**object to which this glyph is sticked to (could be a VCursor or a Glyph)*/
278 public Object stickedTo;
279
280 /**draw his glyph
281 *@param g graphic context in which the glyph should be drawn
282 *@param vW associated view width (used to determine if border should be drawn)
283 *@param vH associated view height (used to determine if border should be drawn)
284 * right now only VRectangle and VRectangleOr(/Or=0) use this
285 *@param i camera index in the virtual space
286 */
287 public abstract void draw(Graphics2D g,int vW,int vH,int i,Stroke stdS,AffineTransform stdT);
288
289 /**project shape in camera coord sys prior to actual painting*/
290 public abstract void project(Camera c,ViewPanel v);
291
292 /**relative translation (offset)*/
293 public void move(long x,long y){
294 vx+=x;
295 vy+=y;
296 propagateMove(x,y); //take care of sticked glyphs
297 try{vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
298 //try{vsm.constMgr.suggestAPos(this.ID,vx,vy);}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
299 }
300
301 /**absolute translation*/
302 public void moveTo(long x,long y){
303 propagateMove(x-vx,y-vy); //take care of sticked glyphs
304 vx=x;
305 vy=y;
306 try{vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
307 //try{vsm.constMgr.suggestAPos(this.ID,vx,vy);}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
308 }
309
310 /**absolute translation DO NOT SUGGEST NEW VALUE TO SOLVER*/
311 // public void moveToNS(long x,long y){
312 // propagateMove(x-vx,y-vy); //take care of sticked glyphs
313 // vx=x;
314 // vy=y;
315 // }
316
317 /**propagate this glyph's movement to all glyphs constrained by this one (pos)*/
318 public void propagateMove(long x,long y){
319 if (stickedGlyphs!=null){
320 for (Enumeration e=stickedGlyphs.elements();e.hasMoreElements();){
321 ((Glyph)(e.nextElement())).move(x,y);
322 }
323 }
324 }
325
326 /**get size of object (radius of bounding circle)*/
327 public abstract float getSize();
328
329 /**set size of object by setting its bounding circle's radius*/
330 public abstract void sizeTo(float radius);
331
332 /**multiply bounding circle radius by factor*/
333 public abstract void reSize(float factor);
334
335 /**set size of object by setting its bounding circle's radius*/
336 // public abstract void sizeToNS(float radius);
337
338 /**get orientation*/
339 public abstract float getOrient();
340
341 /**set absolute orientation*/
342 public abstract void orientTo(float angle);
343
344 /**set absolute orientation*/
345 // public abstract void orientToNS(float angle);
346
347 /**called when glyph is created in order to create the initial set of projected coordinates wrt the number of cameras in the space
348 *@param nbCam current number of cameras in the virtual space
349 */
350 public abstract void initCams(int nbCam);
351
352 /**used internally to create new projected coordinates to use with the new camera
353 *@param verifIndex camera index, just to be sure that the number of projected coordinates is consistent with the number of cameras
354 */
355 public abstract void addCamera(int verifIndex);
356
357 /**if a camera is removed from the virtual space, we should delete the corresponding projected coordinates, but do not modify the array it self because we do not want to change other cameras' index - just point to null*/
358 public abstract void removeCamera(int index);
359
360 /**detects whether the given point is inside this glyph or not
361 *@param x EXPECTS PROJECTED JPanel COORDINATE
362 *@param y EXPECTS PROJECTED JPanel COORDINATE
363 */
364 public abstract boolean coordInside(int x,int y,int camIndex);
365
366 /**reset prevMouseIn for projected coordinates nb i*/
367 public abstract void resetMouseIn(int i);
368
369 /**used to find out if it is necessary to project and draw the glyph in the current view*/
370 public boolean drawMe(long w1,long h1,long w2,long h2,int i){//i should be the camera's index (used only by some glyph classes redefining this method)
371 if ((vx>=w2) && (vx<=w1) && (vy>=h2) && (vy<=h1)){ //if glyph hotspot is in the region, it is obviously visible
372 return true;
373 }
374 else {
375 if (((vx-size)<=w1) && ((vx+size)>=w2) && ((vy-size)<=h1) && ((vy+size)>=h2)){
376 //if glyph is at least partially in region (we approximate using the glyph
377 // bounding circle, meaning that some glyphs not actually visible can
378 return true; //be projected and drawn (but they won't be displayed))
379 }
380 else return false; //otherwise the glyph is not visible
381 }
382 }
383
384 /**used to find out if glyph completely fills the view (in which case it is not necessary to repaint objects at a lower altitude)*/
385 public abstract boolean fillsView(long w,long h,int camIndex);
386
387 /**returns 1 if mouse has entered the glyph, -1 if it has exited the glyph, 0 if nothing has changed (meaning it was already inside or outside it)*/
388 public abstract int mouseInOut(int x,int y,int camIndex);
389
390 /**used by glyph constructor to initialize color*/
391 public void setColor(Color c){
392 color=c;
393 HSV=Color.RGBtoHSB(c.getRed(),c.getGreen(),c.getBlue(),(new float[3]));
394 try{vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
395 }
396
397 /**used by glyph constructor to initialize color*/
398 public void setBorderColor(Color c){
399 borderColor=c;
400 HSVb=Color.RGBtoHSB(borderColor.getRed(),borderColor.getGreen(),borderColor.getBlue(),(new float[3]));
401 bColor=borderColor;
402 try{vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
403 }
404
405 /**set absolute fill color in HSV coord sys*/
406 public void setHSVColor(float h,float s,float v){ //color [0.0,1.0]
407 HSV[0]=h;
408 if (HSV[0]>1) {HSV[0]=1.0f;} else {if (HSV[0]<0) {HSV[0]=0;}}
409 HSV[1]=s;
410 if (HSV[1]>1) {HSV[1]=1.0f;} else {if (HSV[1]<0) {HSV[1]=0;}}
411 HSV[2]=v;
412 if (HSV[2]>1) {HSV[2]=1.0f;} else {if (HSV[2]<0) {HSV[2]=0;}}
413 this.color=Color.getHSBColor(HSV[0],HSV[1],HSV[2]);
414 try{/*vsm.constMgr.suggestAColor(this.ID,HSV[0],HSV[1],HSV[2]);*/vsm.repaintNow();}catch(NullPointerException e){}
415 }
416
417 // /**set absolute fill color in HSV coord sys*/
418 // public void setHSVColorNS(float h,float s,float v){ //color [0.0,1.0]
419 // HSV[0]=h;
420 // if (HSV[0]>1) {HSV[0]=1.0f;} else {if (HSV[0]<0) {HSV[0]=0;}}
421 // HSV[1]=s;
422 // if (HSV[1]>1) {HSV[1]=1.0f;} else {if (HSV[1]<0) {HSV[1]=0;}}
423 // HSV[2]=v;
424 // if (HSV[2]>1) {HSV[2]=1.0f;} else {if (HSV[2]<0) {HSV[2]=0;}}
425 // this.color=Color.getHSBColor(HSV[0],HSV[1],HSV[2]);
426 // try{vsm.repaintNow();}catch(NullPointerException e){}
427 // }
428
429 /**set relative fill color in HSV coord sys*/
430 public void addHSVColor(float h,float s,float v){ //color [-1.0,1.0]
431 HSV[0]=HSV[0]+h;
432 if (HSV[0]>1) {HSV[0]=1.0f;} else {if (HSV[0]<0) {HSV[0]=0;}}
433 HSV[1]=HSV[1]+s;
434 if (HSV[1]>1) {HSV[1]=1.0f;} else {if (HSV[1]<0) {HSV[1]=0;}}
435 HSV[2]=HSV[2]+v;
436 if (HSV[2]>1) {HSV[2]=1.0f;} else {if (HSV[2]<0) {HSV[2]=0;}}
437 this.color=Color.getHSBColor(HSV[0],HSV[1],HSV[2]);
438 try{/*vsm.constMgr.suggestAColor(this.ID,HSV[0],HSV[1],HSV[2]);*/vsm.repaintNow();}catch(NullPointerException e){}
439 }
440
441 /**set absolute border color in HSV coord sys*/
442 public void setHSVbColor(float h,float s,float v){//color [0.0,1.0]
443 HSVb[0]=h;
444 if (HSVb[0]>1) {HSVb[0]=1.0f;} else {if (HSVb[0]<0) {HSVb[0]=0;}}
445 HSVb[1]=s;
446 if (HSVb[1]>1) {HSVb[1]=1.0f;} else {if (HSVb[1]<0) {HSVb[1]=0;}}
447 HSVb[2]=v;
448 if (HSVb[2]>1) {HSVb[2]=1.0f;} else {if (HSVb[2]<0) {HSVb[2]=0;}}
449 this.borderColor=Color.getHSBColor(HSVb[0],HSVb[1],HSVb[2]);
450 this.bColor=this.borderColor;
451 try{vsm.repaintNow();}catch(NullPointerException e){}
452 }
453
454 /**set relative border color in HSV coord sys*/
455 public void addHSVbColor(float h,float s,float v){//color [-1.0,1.0]
456 HSVb[0]=HSVb[0]+h;
457 if (HSVb[0]>1) {HSVb[0]=1.0f;} else {if (HSVb[0]<0) {HSVb[0]=0;}}
458 HSVb[1]=HSVb[1]+s;
459 if (HSVb[1]>1) {HSVb[1]=1.0f;} else {if (HSVb[1]<0) {HSVb[1]=0;}}
460 HSVb[2]=HSVb[2]+v;
461 if (HSVb[2]>1) {HSVb[2]=1.0f;} else {if (HSVb[2]<0) {HSVb[2]=0;}}
462 this.borderColor=Color.getHSBColor(HSVb[0],HSVb[1],HSVb[2]);
463 this.bColor=this.borderColor;
464 try{vsm.repaintNow();}catch(NullPointerException e){}
465 }
466
467 /**get fill color*/
468 public float[] getHSVColor(){ //color [0.0-1.0]
469 return this.HSV;
470 }
471
472 /**get border color*/
473 public float[] getHSVbColor(){ //border color [0.0-1.0]
474 return this.HSVb;
475 }
476
477 /**get fill color as an object*/
478 public Color getColor(){
479 return this.color;
480 }
481
482 /**get border color as an object*/
483 public Color getColorb(){
484 return this.borderColor;
485 }
486
487 /**get text associated with this glyph*/
488 public String getText(){return text;}
489
490 /**set text that should be painted with this glyph*/
491 public void setText(String t){
492 text=t;
493 try{vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
494 }
495
496 /**change font for this specific text object - f=null to get back to default font*/
497 public void setSpecialFont(Font f){
498 if (f!=null){font=f;fontSize=font.getSize2D();}else{font=null;fontSize=VirtualSpaceManager.getMainFont().getSize2D();}
499 try{vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
500 }
501
502 /**returns the font used for this glyph's text*/
503 public Font getFont(){
504 if (font!=null){return font;}
505 else return VirtualSpaceManager.getMainFont();
506 }
507
508 /**tells whether this glyph is using a special font or not (note: using a special font does not necessarily means that this font is different from the default font (although it should, but this is at programer's prerogative))*/
509 public boolean usesSpecialFont(){
510 if (font==null){return false;}
511 else {return true;}
512 }
513
514 /**
515 *set the font size display policy
516 *@param policy 1=always draw text 0=draw text when approx fits inside object -1=never draw text 2=shrink text
517 */
518 public void setFontSizePolicy(int policy){
519 this.fontSizePolicy=policy; //1=always draw text ; 0=draw text when approx fits inside object ; -1=never draw text ; 2=shrink text
520 if ((this.fontSizePolicy<(-1)) || (this.fontSizePolicy>2)){this.fontSizePolicy=1;}
521 try {vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
522 }
523
524 /**
525 *
526 *@param p -1=text is drawn under glyph ; 0=text is drawn inside glyph ; 1=text is drawn on top of glyph
527 */
528 public void setTextPos(int p){
529 this.textPos=p;
530 try{vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
531 }
532
533 /**get glyph ID*/
534 public Long getID(){
535 return ID;
536 }
537
538 /**set glyph ID (make sure there is no conflict)*/
539 public void setID(Long ident){
540 ID=ident;
541 }
542
543 /**get object this glyph represents*/
544 public Object getOwner(){
545 return owner;
546 }
547
548 /**associate an application object with this glyph*/
549 public void setOwner(Object o){
550 this.owner=o;
551 }
552
553 /**get glyph type*/
554 public String getType(){
555 return type;
556 }
557
558 /**
559 *set glyph type
560 *@param t any string
561 */
562 public void setType(String t){
563 this.type=t;
564 }
565
566
567 /**set a ref to the virtual space manager*/
568 public void setVSM(VirtualSpaceManager v){this.vsm=v;}
569
570 /**
571 *attach glyph to this one
572 *@param g glyph to be attached to this one
573 */
574 public void stick(Glyph g){
575 if (stickedGlyphs==null){
576 stickedGlyphs=new Vector();
577 stickedGlyphs.add(g);
578 g.stickedTo=this;
579 }
580 else {
581 if (!stickedGlyphs.contains(g)){
582 stickedGlyphs.add(g);
583 g.stickedTo=this;
584 }
585 else {
586 if (this.vsm.debugModeON()){System.err.println("Warning: trying to stick Glyph "+g+" to Glyph "+this+" while they are already sticked.");}
587 }
588 }
589 }
590
591 /**
592 *detach glyph from this one
593 *@param g glyph to be detached
594 */
595 public void unstick(Glyph g){
596 if (stickedGlyphs!=null){
597 stickedGlyphs.remove(g);
598 if (stickedGlyphs.isEmpty()){stickedGlyphs=null;}
599 g.stickedTo=null;
600 }
601 }
602
603 /**
604 * return the list of glyphs sticked to this one (empty vector if none)
605 */
606 public Vector getStickedGlyphs(){
607 if (stickedGlyphs==null){return new Vector();}
608 else return stickedGlyphs;
609 }
610
611 /***returns coordinates of the glyph's geom center as a LongPoint*/
612 public LongPoint getLocation(){return new LongPoint(vx,vy);}
613
614 /**
615 * returns a String with ID, position, and altitude
616 */
617 public String toString(){
618 return new String(super.toString()+" Glyph ID "+ID+" pos ("+vx+","+vy+","+vz+") "+type);
619 }
620
621 public abstract Object clone();
622
623 }