Source code: com/xerox/VTM/glyphs/VText.java
1 /* FILE: VText.java
2 * DATE OF CREATION: Nov 23 2000
3 * AUTHOR : Emmanuel Pietriga (emmanuel.pietriga@xrce.xerox.com)
4 * MODIF: Thu Jul 10 17:11:40 2003 by Emmanuel Pietriga (emmanuel@w3.org, emmanuel@claribole.net)
5 * Copyright (c) Xerox Corporation, XRCE/Contextual Computing, 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 java.awt.Color;
23 import java.awt.Graphics2D;
24 import java.awt.Stroke;
25 import java.awt.Font;
26 import java.awt.geom.AffineTransform;
27 import java.lang.Math;
28 import java.util.Vector;
29 import com.xerox.VTM.engine.*;
30
31 /**
32 * Standalone Text (font properties are set in the view, but can be changed for each VText using setSpecialFont())
33 * vx and vy are coordinates of lower-left corner of String because it would be too time-consuming to compute the String's center (needs to be computed at each repaint: it requires access to Graphics2D) (besides it makes the VTM unstable)
34 * @author Emmanuel Pietriga
35 */
36
37 public class VText extends Glyph implements Cloneable {
38
39 /**text alignment (for text anchor) used to align a VText relative to its (vx,vy coordinates coincides with start of String)*/
40 public static short TEXT_ANCHOR_START=0;
41 /**text alignment (for text anchor) used to align a VText relative to its (vx,vy coordinates coincides with middle of String)*/
42 public static short TEXT_ANCHOR_MIDDLE=1;
43 /**text alignment (for text anchor) used to align a VText relative to its (vx,vy coordinates coincides with end of String)*/
44 public static short TEXT_ANCHOR_END=2;
45
46 private short text_anchor=TEXT_ANCHOR_START;
47
48 AffineTransform at;
49
50 ProjText[] pc;
51
52 boolean zoomSensitive=true;
53
54 public VText(String t){
55 vx=0;
56 vy=0;
57 vz=0;
58 sensit=false;
59 text=t;
60 setColor(Color.white);
61 setBorderColor(Color.black);
62 }
63
64 /**
65 *@param x coordinate in virtual space
66 *@param y coordinate in virtual space
67 *@param z altitude
68 *@param c fill color
69 *@param t text string
70 */
71 public VText(long x,long y,float z,Color c,String t){
72 vx=x;
73 vy=y;
74 vz=z;
75 sensit=false;
76 text=t;
77 setColor(c);
78 setBorderColor(Color.black);
79 }
80
81 /**
82 *@param x coordinate in virtual space
83 *@param y coordinate in virtual space
84 *@param z altitude
85 *@param c fill color
86 *@param t text string
87 *@param ta text-anchor (for alignment: one of TEXT_ANCHOR_*)
88 */
89 public VText(long x,long y,float z,Color c,String t,short ta){
90 vx=x;
91 vy=y;
92 vz=z;
93 sensit=false;
94 text=t;
95 setColor(c);
96 setBorderColor(Color.black);
97 text_anchor=ta;
98 }
99
100 /**called when glyph is created in order to create the initial set of projected coordinates wrt the number of cameras in the space
101 *@param nbCam current number of cameras in the virtual space
102 */
103 public void initCams(int nbCam){
104 pc=new ProjText[nbCam];
105 for (int i=0;i<nbCam;i++){
106 pc[i]=new ProjText();
107 }
108 }
109
110 /**used internally to create new projected coordinates to use with the new camera
111 *@param verifIndex camera index, just to be sure that the number of projected coordinates is consistent with the number of cameras
112 */
113 public void addCamera(int verifIndex){
114 if (pc!=null){
115 if (verifIndex==pc.length){
116 ProjText[] ta=pc;
117 pc=new ProjText[ta.length+1];
118 for (int i=0;i<ta.length;i++){
119 pc[i]=ta[i];
120 }
121 pc[pc.length-1]=new ProjText();
122 }
123 else {System.err.println("VText:Error while adding camera "+verifIndex);}
124 }
125 else {
126 if (verifIndex==0){
127 pc=new ProjText[1];
128 pc[0]=new ProjText();
129 }
130 else {System.err.println("VText:Error while adding camera "+verifIndex);}
131 }
132 }
133
134 /**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*/
135 public void removeCamera(int index){
136 pc[index]=null;
137 }
138
139 /**reset prevMouseIn for projected coordinates nb i*/
140 public void resetMouseIn(int i){
141 if (pc[i]!=null){pc[i].prevMouseIn=false;}
142 }
143
144 /**set size (absolute) - has no effect*/
145 public void sizeTo(float factor){}
146
147 /**set size (relative) - has no effect*/
148 public void reSize(float factor){}
149
150 /**set size (absolute) - has no effect*/
151 // public void sizeToNS(float factor){}
152
153 /**set orientation (absolute) - has no effect*/
154 public void orientTo(float angle){}
155
156 /**set orientation (absolute) - has no effect*/
157 // public void orientToNS(float angle){}
158
159 /**get size (always =1)*/
160 public float getSize(){return 1.0f;}
161
162 /**get orientation*/
163 public float getOrient(){return orient;}
164
165 /**if false, text size is not sensitive to zoom*/
166 public void setZoomSensitive(boolean b){
167 if (zoomSensitive!=b){
168 zoomSensitive=b;
169 try{vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
170 }
171 }
172
173 /**if false, text size is not sensitive to zoom*/
174 public boolean isZoomSensitive(){
175 return zoomSensitive;
176 }
177
178 /**used to find out if it is necessary to project and draw the glyph in the current view*/
179 public boolean drawMe(long w1,long h1,long w2,long h2,int i){//it should be the camera's index
180 if ((vx>=w2) && (vx<=w1) && (vy>=h2) && (vy<=h1)){ //if glyph hotspot is in the region, it is obviously visible
181 return true;
182 }
183 else {
184 if (text_anchor==TEXT_ANCHOR_START){
185 if ((vx<=w1) && ((vx+pc[i].cw)>=w2) && (vy<=h1) && ((vy+pc[i].ch)>=h2)){
186 //if glyph is at least partially in region (we approximate using the glyph bounding circle, meaning that some
187 return true; //glyphs not actually visible can be projected and drawn (but they won't be displayed))
188 }
189 else return false; //otherwise the glyph is not visible
190 }
191 else if (text_anchor==TEXT_ANCHOR_MIDDLE){
192 if ((vx-pc[i].cw/2<=w1) && ((vx+pc[i].cw/2)>=w2) && (vy<=h1) && ((vy+pc[i].ch)>=h2)){
193 //if glyph is at least partially in region (we approximate using the glyph bounding circle, meaning that some
194 return true; //glyphs not actually visible can be projected and drawn (but they won't be displayed))
195 }
196 else return false; //otherwise the glyph is not visible
197 }
198 else {//TEXT_ANCHOR_END
199 if ((vx-pc[i].cw<=w1) && (vx>=w2) && (vy<=h1) && ((vy+pc[i].ch)>=h2)){
200 //if glyph is at least partially in region (we approximate using the glyph bounding circle, meaning that some
201 return true; //glyphs not actually visible can be projected and drawn (but they won't be displayed))
202 }
203 else return false; //otherwise the glyph is not visible
204 }
205 }
206 }
207
208 /**used to find out if glyph completely fills the view (in which case it is not necessary to repaint objects at a lower altitude)*/
209 public boolean fillsView(long w,long h,int camIndex){
210 return false;
211 }
212
213 /**detects whether the given point is inside this glyph or not
214 *@param x EXPECTS PROJECTED JPanel COORDINATE
215 *@param y EXPECTS PROJECTED JPanel COORDINATE
216 */
217 public boolean coordInside(int x,int y,int camIndex){
218 return false;
219 }
220
221 /**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)*/
222 public int mouseInOut(int x,int y,int camIndex){
223 return 0;
224 }
225
226 /**project shape in camera coord sys prior to actual painting*/
227 public void project(Camera c,ViewPanel v){
228 int i=c.getIndex();
229 coef=(float)(c.focal/(c.focal+c.altitude));
230 //find coordinates of object's geom center wrt to camera center and project
231 pc[i].cx=Math.round((vx-c.posx)*coef);
232 pc[i].cy=Math.round((vy-c.posy)*coef);
233 //translate in JPanel coords
234 pc[i].cx=(v.getSize().width/2)+pc[i].cx;
235 pc[i].cy=(v.getSize().height/2)-pc[i].cy;
236 }
237
238 /**draw glyph
239 *@param i camera index in the virtual space
240 */
241 public void draw(Graphics2D g,int vW,int vH,int i,Stroke stdS,AffineTransform stdT){
242 g.setColor(this.color);
243 if (coef*fontSize>vsm.getTextDisplayedAsSegCoef() || !zoomSensitive){//if this value is < to about 0.5, AffineTransform.scale does not work properly (anyway, font is too small to be readable)
244 if (font!=null){
245 g.setFont(font);
246 if (!pc[i].valid){
247 java.awt.geom.Rectangle2D r=g.getFontMetrics().getStringBounds(text,g);
248 pc[i].cw=(int)r.getWidth();
249 pc[i].ch=(int)r.getHeight();
250 pc[i].valid=true;
251 }
252 if (text_anchor==TEXT_ANCHOR_START){at=AffineTransform.getTranslateInstance(pc[i].cx,pc[i].cy);}
253 else if (text_anchor==TEXT_ANCHOR_MIDDLE){at=AffineTransform.getTranslateInstance(pc[i].cx-pc[i].cw*coef/2,pc[i].cy);}
254 else {at=AffineTransform.getTranslateInstance(pc[i].cx-pc[i].cw*coef,pc[i].cy);}
255 if (zoomSensitive){at.concatenate(AffineTransform.getScaleInstance(coef,coef));}
256 g.setTransform(at);
257 try {g.drawString(text,0.0f,0.0f);}
258 catch (NullPointerException ex){/*text could be null*/}
259 g.setFont(VirtualSpaceManager.getMainFont());
260 }
261 else {
262 if (!pc[i].valid){
263 java.awt.geom.Rectangle2D r=g.getFontMetrics().getStringBounds(text,g);
264 pc[i].cw=(int)r.getWidth();
265 pc[i].ch=(int)r.getHeight();
266 pc[i].valid=true;
267 }
268 if (text_anchor==TEXT_ANCHOR_START){at=AffineTransform.getTranslateInstance(pc[i].cx,pc[i].cy);}
269 else if (text_anchor==TEXT_ANCHOR_MIDDLE){at=AffineTransform.getTranslateInstance(pc[i].cx-pc[i].cw*coef/2,pc[i].cy);}
270 else {at=AffineTransform.getTranslateInstance(pc[i].cx-pc[i].cw*coef,pc[i].cy);}
271 if (zoomSensitive){at.concatenate(AffineTransform.getScaleInstance(coef,coef));}
272 g.setTransform(at);
273 try {g.drawString(text,0.0f,0.0f);}
274 catch(NullPointerException ex){/*text could be null*/}
275 }
276 g.setTransform(stdT);
277 }
278 else {
279 g.fillRect(pc[i].cx,pc[i].cy,1,1);
280 }
281 }
282
283 /**set text that should be painted with this glyph - override Glyph method to call invalidate*/
284 public void setText(String t){
285 super.setText(t);
286 invalidate();
287 }
288
289 /**force computing of text's bounding box at next paint call*/
290 public void invalidate(){
291 try {for (int i=0;i<pc.length;i++){pc[i].valid=false;}}
292 catch (NullPointerException ex){}
293 }
294
295 /**returns width and height of the bounding box as a Point (so this is not a real Point)
296 *@param i index of camera (Camera.getIndex())
297 */
298 public LongPoint getBounds(int i){
299 return new LongPoint(pc[i].cw,pc[i].ch);
300 }
301
302 /**tells whether the bounds of the text are valid at this time or not (can be invalid if the thread in charge of painting has not dealt with this glyph since invalidate() was called on it) - might be useful to test this before calling getBounds()*/
303 public boolean validBounds(int i){
304 return pc[i].valid;
305 }
306
307 /**change font for this specific text object - f=null to get back to default font*/
308 public void setSpecialFont(Font f){
309 super.setSpecialFont(f);
310 invalidate();
311 }
312
313
314 /** Set the text anchor
315 *@param ta one of TEXT_ANCHOR_START, TEXT_ANCHOR_MIDDLE, TEXT_ANCHOR_END
316 */
317 public void setTextAnchor(short ta){
318 text_anchor=ta;
319 }
320
321 /**
322 *get text anchor (one of TEXT_ANCHOR_START, TEXT_ANCHOR_MIDDLE, TEXT_ANCHOR_END)
323 */
324 public short getTextAnchor(){
325 return text_anchor;
326 }
327
328 /**returns a clone of this object (only basic information is cloned for now: shape, orientation, position, size)*/
329 public Object clone(){
330 VText res=new VText(vx,vy,0,color,(new StringBuffer(text)).toString(),text_anchor);
331 res.borderColor=this.borderColor;
332 res.selectedColor=this.selectedColor;
333 res.mouseInsideColor=this.mouseInsideColor;
334 res.bColor=this.bColor;
335 return res;
336 }
337
338 }