001: /**
002: * Chart2D, a java library for drawing two dimensional charts.
003: * Copyright (C) 2001 Jason J. Simas
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: *
018: * The author of this library may be contacted at:
019: * E-mail: jjsimas@users.sourceforge.net
020: * Street Address: J J Simas, 887 Tico Road, Ojai, CA 93023-3555 USA
021: */package net.sourceforge.chart2d;
022:
023: import java.awt.*;
024: import java.util.*;
025: import java.awt.font.*;
026: import java.awt.geom.*;
027: import java.text.*;
028:
029: /**
030: * A customizable text label. This label has built-int bordering, spacing
031: * between borders and text, text rotation, line breaking, auto justification
032: * within the borders, growing and shrinking, and auto locating. Much of this
033: * functionality is provided by its super classes.<br>
034: * Note: Does not support null values. Pass empty strings instead.
035: */
036: final class TextArea extends FontArea {
037:
038: private String text;
039: private Vector textLayouts;
040: private boolean rotateLeft;
041: private int textJustification;
042: private boolean needsUpdate;
043:
044: /**
045: * Creates a new text area with the following default values:
046: * setText ("");<br>
047: * setRotateLeft (false);<br>
048: * setTextJustification (CENTER);<br>
049: * resetTextAreaModel (true);<br>
050: */
051: TextArea() {
052:
053: setText("");
054: setRotateLeft(false);
055: setTextJustification(CENTER);
056: resetTextAreaModel(true);
057: needsUpdate = true;
058: }
059:
060: /**
061: * Changes the text of this label.
062: * @param t The new text.
063: */
064: final void setText(String t) {
065:
066: needsUpdate = true;
067: text = t;
068: }
069:
070: /**
071: * Adjusts the rotation of the text within the label. If not rotated, text
072: * runs from left to right, top to bottom of the are. If rotate, text runs
073: * from bottom to top, left to right. The text is rotated -90 degree. Even
074: * when rotate, the location or origin of this label is always the top left
075: * corner of it, however, the text's actual origin is near the bottom left
076: * corner.
077: * @param r If true, then adjusts settings so that text is rotated.
078: */
079: final void setRotateLeft(boolean r) {
080:
081: needsUpdate = true;
082: rotateLeft = r;
083: }
084:
085: /**
086: * Specifies whether text will be (in the case text is not rotated) left,
087: * right, or center justified respective to the space within the bordered
088: * area. In the case text is rotated, then bottom, top or center
089: * justification is available. This only adjusts the horizontal
090: * justification, or in the case of rotated text, the vertical justification.
091: * @param which Which justification for the text. Possible values, if not
092: * rotated: LEFT, RIGHT, CENTER; if rotated: BOTTOM, TOP, CENTER. Also,
093: * if you prefer and when rotated, LEFT and RIGHT may be used to mean BOTTOM
094: * and TOP respectively; this program translates them into BOTTOM and TOP for
095: * you.
096: */
097: final void setTextJustification(int which) {
098:
099: needsUpdate = true;
100: if (which == TOP)
101: textJustification = RIGHT;
102: else if (which == BOTTOM)
103: textJustification = LEFT;
104: else
105: textJustification = which;
106: }
107:
108: /**
109: * Returns the text of this label.
110: * @return The label's text.
111: */
112: final String getText() {
113:
114: return text;
115: }
116:
117: /**
118: * Returns whether this text is rotated left 90 degrees.
119: * @return If rotated, then true.
120: */
121: final boolean getRotateLeft() {
122:
123: return rotateLeft;
124: }
125:
126: /**
127: * Indicates whether some property of this class has changed.
128: * @return True if some property has changed.
129: */
130: final boolean getTextAreaNeedsUpdate() {
131:
132: return (needsUpdate || getFontAreaNeedsUpdate());
133: }
134:
135: /**
136: * Updates all the variables of all this parent's classes, then all the
137: * variables of this class.
138: * @param g2D The graphics context under which to make calculations.
139: */
140: final void updateTextArea(Graphics2D g2D) {
141:
142: if (getTextAreaNeedsUpdate()) {
143: updateFontArea();
144: update(g2D);
145: }
146: needsUpdate = false;
147: }
148:
149: /**
150: * Resets the model for this class. The model is used for shrinking and
151: * growing of its components based on the maximum size of this class. If this
152: * method is called, then the next time the maximum size is set, this classes
153: * model maximum size will be made equal to the new maximum size. Effectively
154: * what this does is ensure that whenever this objects maximum size is equal
155: * to the one given, then all of the components will take on their default
156: * model sizes. Note: This is only useful when auto model max sizing is
157: * disabled.
158: * @param reset True causes the max model size to be set upon the next max
159: * sizing.
160: */
161: final void resetTextAreaModel(boolean reset) {
162:
163: needsUpdate = true;
164: resetFontAreaModel(reset);
165: }
166:
167: /**
168: * Paints all of the components of this class. First all the variables are
169: * updated. Then all the components are painted.
170: * @param g2D The graphics context for calculations and painting.
171: */
172: final void paintComponent(Graphics2D g2D) {
173:
174: updateTextArea(g2D);
175: super .paintComponent(g2D);
176:
177: Color oldColor = g2D.getColor();
178: g2D.setColor(getFontColor());
179:
180: int delta = 0;
181: int count = textLayouts.size();
182: for (int i = 0; i < count; ++i) {
183:
184: TextLayout layout = (TextLayout) textLayouts.get(i);
185: Shape shape = layout.getOutline(new AffineTransform());
186: int ascent = (int) Math.abs(shape.getBounds().y);
187: int descent = shape.getBounds().height - ascent;
188: int height = ascent + descent;
189: int leading = (int) layout.getLeading();
190:
191: if (!rotateLeft) {
192:
193: int clipHeight = delta + height > getSpaceSize(MIN).height ? getSpaceSize(MIN).height
194: - delta
195: : height;
196: Rectangle rect = new Rectangle(
197: getSpaceSizeLocation(MIN).x,
198: getSpaceSizeLocation(MIN).y + delta,
199: getSpaceSize(MIN).width, clipHeight);
200: g2D.clip(rect);
201: int translateX;
202: if (textJustification == LEFT) {
203: translateX = getSpaceSizeLocation(MIN).x
204: - shape.getBounds().x;
205: } else if (textJustification == RIGHT) {
206: translateX = getSpaceSizeLocation(MIN).x
207: + getSpaceSize(MIN).width
208: - shape.getBounds().width
209: - shape.getBounds().x;
210: } else {
211: translateX = getSpaceSizeLocation(MIN).x
212: + (getSpaceSize(MIN).width - shape
213: .getBounds().width) / 2
214: - shape.getBounds().x;
215: }
216: int translateY = getSpaceSizeLocation(MIN).y + delta
217: + ascent;
218: g2D.translate(translateX, translateY);
219: g2D.fill(shape);
220:
221: g2D.setClip(null);
222: g2D.translate(-translateX, -translateY);
223: delta = delta + height + leading;
224: } else {
225:
226: int clipHeight = delta + height > getSpaceSize(MIN).width ? getSpaceSize(MIN).width
227: - delta
228: : height;
229: Rectangle rect = new Rectangle(
230: getSpaceSizeLocation(MIN).x + delta,
231: getSpaceSizeLocation(MIN).y, clipHeight,
232: getSpaceSize(MIN).height);
233: g2D.clip(rect);
234: int translateX = getSpaceSizeLocation(MIN).x + delta
235: + ascent;
236: int translateY;
237: if (textJustification == LEFT) {
238: translateY = getSpaceSizeLocation(MIN).y
239: + getSpaceSize(MIN).height
240: + shape.getBounds().x;
241: } else if (textJustification == RIGHT) {
242: translateY = getSpaceSizeLocation(MIN).y
243: + shape.getBounds().width
244: + shape.getBounds().x;
245: } else {
246: translateY = getSpaceSizeLocation(MIN).y
247: + (getSpaceSize(MIN).height + shape
248: .getBounds().width) / 2
249: + shape.getBounds().x;
250: }
251: g2D.translate(translateX, translateY);
252: g2D.rotate(Math.toRadians(-90d));
253: g2D.fill(shape);
254:
255: g2D.setClip(null);
256: g2D.rotate(Math.toRadians(90d));
257: g2D.translate(-translateX, -translateY);
258: delta = delta + height + leading;
259: }
260: }
261:
262: g2D.setColor(oldColor);
263: }
264:
265: private void update(Graphics2D g2D) {
266:
267: int greatestAdvance = 0;
268: int lineBreaksMeasurement = 0;
269: int leading = 0;
270: textLayouts = new Vector(0, 1);
271: if (text.length() > 0) {
272: int wrapping = !rotateLeft ? getSpaceSize(MAX).width
273: : getSpaceSize(MAX).height;
274: int mockWrapping = wrapping;
275: boolean fits = true;
276: AttributedString attributedString = new AttributedString(
277: text, getFont().getAttributes());
278: AttributedCharacterIterator attributedCharacterIterator = attributedString
279: .getIterator();
280: FontRenderContext fontRenderContext = g2D
281: .getFontRenderContext();
282: for (;;) {
283: LineBreakMeasurer measurer = new LineBreakMeasurer(
284: attributedCharacterIterator, fontRenderContext);
285: textLayouts = new Vector(0, 1);
286: greatestAdvance = 0;
287: lineBreaksMeasurement = 0;
288: for (TextLayout layout = measurer
289: .nextLayout(mockWrapping); layout != null; layout = measurer
290: .nextLayout(mockWrapping)) {
291: textLayouts.add(layout);
292: Shape shape = layout
293: .getOutline(new AffineTransform());
294: int width = shape.getBounds().width;
295: greatestAdvance = greatestAdvance < width ? width
296: : greatestAdvance;
297: int ascent = (int) Math.abs(shape.getBounds().y);
298: int descent = shape.getBounds().height - ascent;
299: int height = ascent + descent;
300: leading = (int) layout.getLeading();
301: lineBreaksMeasurement = lineBreaksMeasurement
302: + height + leading;
303:
304: }
305: lineBreaksMeasurement -= leading;
306:
307: if (lineBreaksMeasurement > (!rotateLeft ? getSpaceSize(MAX).height
308: : getSpaceSize(MAX).width)) {
309: fits = false;
310: greatestAdvance = 0;
311: lineBreaksMeasurement = 0;
312: textLayouts = new Vector(0, 1);
313: break;
314: } else if (greatestAdvance > wrapping) {
315: mockWrapping = mockWrapping
316: - (greatestAdvance - mockWrapping);
317: if (mockWrapping <= 0) {
318: fits = false;
319: greatestAdvance = 0;
320: lineBreaksMeasurement = 0;
321: textLayouts = new Vector(0, 1);
322: break;
323: }
324: //else loop again
325: } else
326: break; //it fits
327: }
328: }
329:
330: if (!getAutoSize(MIN)) {
331: if (!rotateLeft) {
332: setSpaceSize(MIN, new Dimension(greatestAdvance,
333: lineBreaksMeasurement));
334: } else {
335: setSpaceSize(MIN, new Dimension(lineBreaksMeasurement,
336: greatestAdvance));
337: }
338: updateFontArea();
339: }
340: }
341: }
|