001: /*************************************************************************
002: * *
003: * 1) This source code file, in unmodified form, and compiled classes *
004: * derived from it can be used and distributed without restriction, *
005: * including for commercial use. (Attribution is not required *
006: * but is appreciated.) *
007: * *
008: * 2) Modified versions of this file can be made and distributed *
009: * provided: the modified versions are put into a Java package *
010: * different from the original package, edu.hws; modified *
011: * versions are distributed under the same terms as the original; *
012: * and the modifications are documented in comments. (Modification *
013: * here does not include simply making subclasses that belong to *
014: * a package other than edu.hws, which can be done without any *
015: * restriction.) *
016: * *
017: * David J. Eck *
018: * Department of Mathematics and Computer Science *
019: * Hobart and William Smith Colleges *
020: * Geneva, New York 14456, USA *
021: * Email: eck@hws.edu WWW: http://math.hws.edu/eck/ *
022: * *
023: *************************************************************************/package edu.hws.jcm.draw;
024:
025: import java.awt.*;
026: import edu.hws.jcm.data.*;
027: import edu.hws.jcm.awt.*;
028: import java.util.StringTokenizer;
029:
030: /**
031: * A DrawString object displays a string, possibly multi-line, in a DisplayCanvas,
032: * inside the rectangular region of a CoordinateRect. The location of the string
033: * can be specified in two ways. First, by giving the coordinates of a reference
034: * point together with a constant that says how the string is positioned with
035: * respect to that reference point. The coordintes are given as Value objects
036: * and the values are interepreted in the coordinate system of the CoordinateRect.
037: * The positioning object is one of the constants TOP_LEFT, TOP_CENTER, ...,
038: * BOTTOM_RIGHT defined in this class. This says where the REFERENCE POINT
039: * is -- at the top left of the string, at the top center, etc.
040: * <p>The second way to specify the position of the string is to set the reference
041: * point coordinates to null. In that case, the postioning constant gives
042: * the location of the STRING in the CorrdinateRect. A value of TOP_LEFT
043: * says that the string is in the top left corner of the rect, etc.
044: *
045: * <p>An array of Value objects can be specified to be displayed in the string.
046: * Their values are substituted for #'s in the string. (A double # in the string,
047: * however, is displayed as a literal single #.)
048: *
049: * <p>It is possible to set the color, font and justification of the string.
050: *
051: * <p>A DisplayString implements the Computable interface, so it can be added to
052: * a Controller. The values of the Value objects used by the string are recomputed
053: * only when its compute() method is called.
054: */
055:
056: public class DrawString extends Drawable implements Computable {
057:
058: /**
059: * Specify string location in rect
060: */
061: public static final int TOP_LEFT = 4 * 0 + 0,
062: TOP_CENTER = 4 * 0 + 1,
063: //value/4 gives vertical position
064: TOP_RIGHT = 4 * 0 + 2,
065: // value%4 gives horizontal position
066: CENTER_LEFT = 4 * 1 + 0, CENTER_CENTER = 4 * 1 + 1,
067: CENTER_RIGHT = 4 * 1 + 2, BOTTOM_LEFT = 4 * 2 + 0,
068: BOTTOM_CENTER = 4 * 2 + 1, BOTTOM_RIGHT = 4 * 2 + 2;
069:
070: /**
071: * For specifying justification of lines in multiline strings.
072: * (But can also be used as a synonym for CENTER_CENTER to specify the position of the string).
073: */
074: public static final int CENTER = CENTER_CENTER;
075:
076: /**
077: * For specifying justification of lines in multiline strings.
078: * (But can also be used as a synonym for TOP_LEFT to specify the position of the string).
079: */
080: public static final int LEFT = TOP_LEFT;
081:
082: /**
083: * For specifying justification of lines in multiline strings.
084: * (But can also be used as a synonym for TOP_RIGHT to specify the position of the string).
085: */
086: public static final int RIGHT = TOP_RIGHT;
087:
088: /**
089: * one of the constants defined in this class for specifying position
090: */
091: protected int position;
092:
093: /**
094: * String, possibly with \n and #'s. This is used as a base to get the actual string that is drawn.
095: */
096: protected String baseString;
097:
098: /**
099: * The actual lines to draw, derived from baseString.
100: */
101: protected String[] strings;
102:
103: /**
104: * Values to be substituted for #'s in the baseString.
105: */
106: protected Value[] values;
107:
108: /**
109: * xy-coords for drawing the string. If non-null then relative positioning is used.
110: * If null, then positioning is absolute.
111: */
112: protected Value xPos, yPos;
113:
114: /**
115: * Color of string. If null, black is used as the default.
116: */
117: protected Color color;
118:
119: /**
120: * Font for drawing string. If null, get font from graphics context.
121: */
122: protected Font font;
123:
124: /**
125: * If absolute positioning is used, then this gives a gap between the string and edge of rect.
126: * For relative positioning, this gives an offset from the value of xPos yPos.
127: */
128: protected int offset = 3;
129:
130: /**
131: * If true, the string is clamped to lie within the CoordinateRect.
132: */
133: protected boolean clamp = true;
134:
135: /**
136: * Left, right, or center justification of lines in the text.
137: */
138: protected int justification = LEFT;
139:
140: /**
141: * Maximum number of characters desired in numbers; actual number might actually be larger.
142: */
143: protected int numSize = 10;
144:
145: /**
146: * If backgroundColor is non-null, then a rectangle of this color is filled
147: * as a background for the string;
148: */
149: protected Color backgroundColor;
150:
151: /**
152: * If frameWidth is greater than zero, then a frame of this width is drawn around the
153: * string in the color given by frameColor.
154: */
155: protected int frameWidth;
156:
157: /**
158: * If frameWidth is greate than zero, then a frame is drawn around the string in this
159: * color. If the value is null, then the color will be the same as the color of the string.
160: */
161: protected Color frameColor;
162:
163: // In the following, note that str can contain \n to break up the
164: // string into multiple lines.
165:
166: private double xRef, yRef; //Coordinates of reference point where string is drawn.
167: private boolean changed = true; // set to true when strings need to be recomputed
168:
169: /**
170: * Create a DrawString object that initially has no string to draw.
171: */
172: public DrawString() {
173: this (null, TOP_LEFT, (Value[]) null);
174: }
175:
176: /**
177: * Create a DrawString for drawing a black string in the top left corner of the coordinate rect.
178: *
179: * @param str The string to draw, which can contain \n's to indicate line breaks.
180: */
181: public DrawString(String str) {
182: this (str, TOP_LEFT, (Value[]) null);
183: }
184:
185: /**
186: * Create a DrawString for drawing a black string in the position specified.
187: *
188: * @param str The string to draw, which can contain \n's to indicate line breaks.
189: * @param pos The positioning of the string in the coordinate rect. One of the positioning constants such as TOP_LEFT or BOTTOM_RIGHT.
190: */
191: public DrawString(String str, int pos) {
192: this (str, pos, (Value[]) null);
193: }
194:
195: /**
196: * Create a DrawString for drawing a black string in the specified position.
197: * The number of #'s in the string should match values.length. The values
198: * are computed and substituted for the #'s.
199: *
200: * @param str The string to draw, which can contain \n's to indicate line breaks and #'s to be replaced by numeric values.
201: * @param pos The positioning of the string in the coordinate rect. One of the positioning constants such as TOP_LEFT or BOTTOM_RIGHT.
202: * @param values Value objects associated with #'s in the string.
203: */
204: public DrawString(String str, int pos, Value[] values) {
205: position = pos;
206: this .values = values;
207: setString(str);
208: }
209:
210: /**
211: * Create a string that is displayed at the reference point (xPos,yPos);
212: * The positioning constant, pos, gives the positioning relative to this point, if xPos or yPos is non-null.
213: *
214: * @param str The string to draw, which can contain \n's to indicate line breaks and #'s to be replaced by numeric values.
215: * @param pos The positioning of the string. One of the positioning constants such as TOP_LEFT or BOTTOM_RIGHT.
216: If xPos or yPos is non-nul, this is interpreted relative to their values.
217: * @param xPos x-coordinate relative to which the string is drawn (or null for absolute hoizontal positioning).
218: * @param yPos y-coordinate relative to which the string is drawn (or null for absolute vertical positioning).
219: * @param values Value objects associated with #'s in the string.
220: */
221: public DrawString(String str, int pos, Value xPos, Value yPos,
222: Value[] values) {
223: setReferencePoint(xPos, yPos);
224: position = pos;
225: this .values = values;
226: setString(str);
227: }
228:
229: /**
230: * Set the color for the string. If c is null, Color.black is used.
231: *
232: */
233: public void setColor(Color c) {
234: color = c;
235: needsRedraw();
236: }
237:
238: /**
239: * Get the non-null color that is used for drawing the string.
240: *
241: */
242: public Color getColor() {
243: return (color == null) ? Color.black : color;
244: }
245:
246: /**
247: * Set the font that is used for drawing this string. If f is null,
248: * then the font is obtained from the Graphics context in which the
249: * string is drawn.
250: *
251: */
252: public void setFont(Font f) {
253: font = f;
254: needsRedraw();
255: }
256:
257: /**
258: * Return the font that is used for drawing the string. If the return
259: * value is null, then the font is taken from the Graphics context.
260: *
261: */
262: public Font getFont() {
263: return font;
264: }
265:
266: /**
267: * Set the Values that are substituted for (single) #'s in the string.
268: * If the array of Values is null, then no substitution is done. The length of the array should match
269: * the number of #'s, but it is not an error if they do not match.
270: * Extra values will be ignored; extra #'s will be shown as "undefined".
271: *
272: */
273: public void setValues(Value[] v) {
274: values = v;
275: changed = true;
276: needsRedraw();
277: }
278:
279: /**
280: * Return the array of values that are substituted for #'s in the string.
281: *
282: */
283: public Value[] getValues() {
284: return values;
285: }
286:
287: /**
288: * Set the positioning of the string. The parameter should be one of the positioning
289: * contstants defined in this class, such as TOP_LEFT. (If it is not,
290: * TOP_LEFT is used by default.)
291: *
292: */
293: public void setPositioning(int pos) {
294: position = pos;
295: needsRedraw();
296: }
297:
298: /**
299: * Return the positioning, as set by setPositioning().
300: *
301: */
302: public int getPositioning() {
303: return position;
304: }
305:
306: /**
307: * Set the values of the (x,y) coordinates of the
308: * reference point for the stirng. If a value is null,
309: * absolute positioning is used. If a value is
310: * undefined, the string is not drawn.
311: *
312: */
313: public void setReferencePoint(Value x, Value y) {
314: xPos = x;
315: yPos = y;
316: try {
317: if (xPos != null)
318: xRef = xPos.getVal();
319: if (yPos != null)
320: yRef = yPos.getVal();
321: } catch (RuntimeException e) {
322: }
323: needsRedraw();
324: }
325:
326: /**
327: * Return the Value object that gives the x-coordinate of the reference
328: * point of this string.
329: *
330: */
331: public Value getXPos() {
332: return xPos;
333: }
334:
335: /**
336: * Return the Value object that gives the y-coordinate of the reference
337: * point of this string.point of this string.
338: *
339: */
340: public Value getYPos() {
341: return yPos;
342: }
343:
344: /**
345: * Set the string that is displayed. Note that it can include '\n' to
346: * represent a line break, and it can contain #'s which will be replaced
347: * by computed values.
348: *
349: */
350: public void setString(String str) {
351: baseString = str;
352: strings = null;
353: changed = true;
354: needsRedraw();
355: }
356:
357: /**
358: * Get a copy of the display string (with \n's #'s, not with substitued values.)
359: *
360: */
361: public String getString() {
362: return baseString;
363: }
364:
365: /**
366: * Set the distance of the bounding box of the string from the reference
367: * point where it is drawn. The default value is 3.
368: *
369: */
370: public void setOffset(int b) {
371: offset = b;
372: needsRedraw();
373: }
374:
375: /**
376: * Get the distance of the bounding box of the string from the reference
377: * point where it is drawn.
378: *
379: */
380: public int getOffset() {
381: return offset;
382: }
383:
384: /**
385: * Set the "clamp" property of the DrawString.
386: * If set to true, the string will be clamped to lie entirely within the CoordinateRect
387: * (unless it doens't fit -- then it can stick out on the right and bottom).
388: * The default value is true.
389: *
390: */
391: public void setClamp(boolean clamp) {
392: this .clamp = clamp;
393: needsRedraw();
394: }
395:
396: /**
397: * Returns true if the string is set to be clamped to lie within the CoordinateRect.
398: *
399: */
400: public boolean getClamp() {
401: return clamp;
402: }
403:
404: /**
405: * Set the justification to be used if there are multiple lins in the string.
406: * Possible value are DrawString.LEFT, DrawString.RIGHT, and DrawString.CENTER.
407: *
408: */
409: public void setJustification(int j) {
410: if (j == RIGHT || j == CENTER)
411: justification = j;
412: else
413: justification = LEFT;
414: needsRedraw();
415: }
416:
417: /**
418: * Get the justification that is used for a multiple-line string. The value
419: * is one of the constants DrawString.LEFT, DrawString.RIGHT, or DrawString.CENTER
420: */
421: public int getJustification() {
422: return justification;
423: }
424:
425: /**
426: * Set the desired maximum number of characters in displayed numbers.
427: * Actual size might be larger. Value is clamped to the range
428: * 6 to 25.
429: *
430: */
431: public void setNumSize(int size) {
432: numSize = Math.min(Math.max(size, 6), 25);
433: changed = true;
434: needsRedraw();
435: }
436:
437: /**
438: * Return the desired maximum number of characters in displayed numbers.
439: *
440: */
441: public int getNumSize() {
442: return numSize;
443: }
444:
445: /**
446: * Get the color that is used to fill a rectangle on which the string is drawn. Null
447: * indicates that no rectangle is filled so the stuff in back of the string shows though.
448: * The default value is null.
449: */
450: public Color getBackgroundColor() {
451: return backgroundColor;
452: }
453:
454: /**
455: * Set the color that is used to fill a rectangle on which the string is drawn. If the
456: * value is null, no rectangle is filled and the string just overlays whatever is in back
457: * of it on the canvas.
458: */
459: public void setBackgroundColor(Color color) {
460: backgroundColor = color;
461: needsRedraw();
462: }
463:
464: /**
465: * Get the color that is used to draw a frame around the string. This is only done if the
466: * frameWidth property is greater than zero. If the value is null, the frame is the same color
467: * as the string.
468: */
469: public Color getFrameColor() {
470: return frameColor;
471: }
472:
473: /**
474: * Set the color that is used to draw a frame around the string. This is only done if the
475: * frameWidth property is greater than zero. If the value is null, the frame is the same color
476: * as the string.
477: */
478: public void setFrameColor(Color color) {
479: frameColor = color;
480: needsRedraw();
481: }
482:
483: /**
484: * Get the width, in pixels, of the frame that is drawn around the string.
485: * The default width is zero. The largest possible value is 25.
486: */
487: public int getFrameWidth() {
488: return frameWidth;
489: }
490:
491: /**
492: * Set the width, in pixels, of a frame to draw around the string. If the value is zero,
493: * no frame is drawn. The default value is zero. The the value is clamped
494: * to the range 0 to 25.
495: */
496: public void setFrameWidth(int width) {
497: if (width < 0)
498: frameWidth = 0;
499: else if (width > 25)
500: frameWidth = 25;
501: else
502: frameWidth = width;
503: needsRedraw();
504: }
505:
506: /**
507: * The compute method sets up the array of strings that is actually displayed.
508: * This is required by the Computable interface and is usually called by a
509: * Controller rather than directly.
510: */
511: public void compute() {
512: changed = true;
513: needsRedraw();
514: }
515:
516: private void getSubstitutedText() { //Get the strings obtained by substituting values for #'s in text.
517: changed = false;
518: if (xPos != null)
519: xRef = xPos.getVal();
520: if (yPos != null)
521: yRef = yPos.getVal();
522: if (values == null && strings != null) // no need to recompute, since there is no #-substitution to do.
523: return;
524: if (baseString == null || baseString.trim().length() == 0) {
525: strings = null;
526: return;
527: }
528: StringTokenizer tok = new StringTokenizer(baseString, "\n");
529: int count = tok.countTokens();
530: strings = new String[count];
531: if (values == null) {
532: for (int i = 0; i < count; i++)
533: strings[i] = tok.nextToken();
534: return;
535: }
536: StringBuffer b = new StringBuffer();
537: int expCt = 0;
538: for (int strNum = 0; strNum < count; strNum++) {
539: String text = tok.nextToken();
540: for (int i = 0; i < text.length(); i++) {
541: if (text.charAt(i) == '#') {
542: if (i != text.length() - 1
543: && text.charAt(i + 1) == '#') {
544: b.append('#');
545: i++;
546: } else {
547: if (expCt < values.length) {
548: try {
549: b.append(NumUtils
550: .realToString(values[expCt]
551: .getVal(), numSize));
552: } catch (RuntimeException e) {
553: b.append("(error)");
554: }
555: expCt++;
556: } else
557: b.append("undefined");
558: }
559: } else
560: b.append(text.charAt(i));
561: }
562: strings[strNum] = b.toString();
563: b.setLength(0);
564: }
565: }
566:
567: /**
568: * Draws the string.
569: */
570: public void draw(Graphics g, boolean coordsChanged) {
571:
572: if (changed)
573: getSubstitutedText();
574:
575: if (strings == null)
576: return;
577:
578: if (xPos != null
579: && (Double.isNaN(xRef) || Double.isInfinite(xRef)))
580: return;
581: if (yPos != null
582: && (Double.isNaN(yRef) || Double.isInfinite(yRef)))
583: return;
584:
585: int trueOffset = offset; // offset allowing for frame and background
586: if (backgroundColor != null || frameWidth > 0)
587: trueOffset += 3;
588: trueOffset += frameWidth;
589:
590: Font saveFont = null;
591: FontMetrics fm;
592: if (font != null) {
593: saveFont = g.getFont();
594: g.setFont(font);
595: fm = g.getFontMetrics(font);
596: } else
597: fm = g.getFontMetrics(g.getFont());
598: int lineHeight = fm.getHeight();
599:
600: int xmin = coords.getLeft();
601: int width = coords.getWidth();
602: int ymin = coords.getTop();
603: int height = coords.getHeight();
604: int xmax = xmin + width;
605: int ymax = ymin + height;
606: int stringWidth = 0;
607: for (int i = 0; i < strings.length; i++)
608: stringWidth = Math.max(stringWidth, fm
609: .stringWidth(strings[i]));
610: int stringHeight = strings.length * lineHeight;
611: if (backgroundColor == null && frameWidth <= 0)
612: stringHeight = stringHeight - fm.getLeading()
613: - fm.getDescent();
614: int xInt = 0, yInt = 0;
615:
616: int hPos = position % 4;
617: int vPos = position / 4;
618: if (position < 0 || hPos > 2 || vPos > 2) { // Use TOP_LEFT as default, if position is not a legal value.
619: hPos = 0;
620: vPos = 0;
621: }
622:
623: if (xPos == null) {
624: if (hPos == 0)
625: xInt = xmin + trueOffset;
626: else if (hPos == 1)
627: xInt = (xmin + xmax - stringWidth) / 2;
628: else
629: xInt = xmax - stringWidth - trueOffset;
630: } else {
631: if (hPos == 0)
632: xInt = coords.xToPixel(xRef) + trueOffset;
633: else if (hPos == 1)
634: xInt = coords.xToPixel(xRef) - stringWidth / 2;
635: else
636: xInt = coords.xToPixel(xRef) - stringWidth - trueOffset;
637: }
638:
639: if (yPos == null) {
640: if (vPos == 0)
641: yInt = ymin + trueOffset;
642: else if (vPos == 1)
643: yInt = (ymin + ymax - stringHeight) / 2;
644: else
645: yInt = ymax - stringHeight - trueOffset;
646: } else {
647: if (vPos == 0)
648: yInt = coords.yToPixel(yRef) + trueOffset;
649: else if (vPos == 1)
650: yInt = coords.yToPixel(yRef) - stringHeight / 2;
651: else
652: yInt = coords.yToPixel(yRef) - stringHeight
653: - trueOffset;
654: }
655:
656: if (clamp) {
657: if (xInt + stringWidth > xmax)
658: xInt = xmax - stringWidth;
659: if (xInt < xmin)
660: xInt = xmin;
661: if (yInt + stringHeight > ymax)
662: yInt = ymax - stringHeight;
663: if (yInt < ymin)
664: yInt = ymin;
665: }
666:
667: if (backgroundColor != null) {
668: g.setColor(backgroundColor);
669: g.fillRect(xInt - 3, yInt - 3, stringWidth + 6,
670: stringHeight + 6);
671: }
672:
673: if (frameWidth > 0) {
674: if (frameColor != null)
675: g.setColor(frameColor);
676: else if (color != null)
677: g.setColor(color);
678: else
679: g.setColor(Color.black);
680: for (int k = 1; k <= frameWidth; k++)
681: g.drawRect(xInt - 3 - k, yInt - 3 - k, stringWidth + 5
682: + 2 * k, stringHeight + 5 + 2 * k);
683: }
684:
685: if (color != null)
686: g.setColor(color);
687: else
688: g.setColor(Color.black);
689:
690: yInt += fm.getAscent();
691: for (int i = 0; i < strings.length; i++) {
692: int x = xInt;
693: if (justification == CENTER)
694: x = x + (stringWidth - fm.stringWidth(strings[i])) / 2;
695: else if (justification == RIGHT)
696: x = x + stringWidth - fm.stringWidth(strings[i]);
697: g.drawString(strings[i], x, yInt);
698: yInt += lineHeight;
699: }
700:
701: if (saveFont != null)
702: g.setFont(saveFont);
703:
704: }
705:
706: } // end class DrawString
|